github.com/ajguerrer/rules_go@v0.20.3/go/tools/builders/compile.go (about)

     1  // Copyright 2017 The Bazel Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // compile compiles .go files with "go tool compile". It is invoked by the
    16  // Go rules as an action.
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"flag"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strings"
    28  )
    29  
    30  func compile(args []string) error {
    31  	// Parse arguments.
    32  	args, err := readParamsFiles(args)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	builderArgs, toolArgs := splitArgs(args)
    37  	flags := flag.NewFlagSet("GoCompile", flag.ExitOnError)
    38  	unfiltered := multiFlag{}
    39  	archives := compileArchiveMultiFlag{}
    40  	goenv := envFlags(flags)
    41  	packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled")
    42  	flags.Var(&unfiltered, "src", "A source file to be filtered and compiled")
    43  	flags.Var(&archives, "arc", "Import path, package path, and file name of a direct dependency, separated by '='")
    44  	nogo := flags.String("nogo", "", "The nogo binary")
    45  	outExport := flags.String("x", "", "Path to nogo that should be written")
    46  	output := flags.String("o", "", "The output object file to write")
    47  	asmhdr := flags.String("asmhdr", "", "Path to assembly header file to write")
    48  	packageList := flags.String("package_list", "", "The file containing the list of standard library packages")
    49  	testfilter := flags.String("testfilter", "off", "Controls test package filtering")
    50  	if err := flags.Parse(builderArgs); err != nil {
    51  		return err
    52  	}
    53  	if err := goenv.checkFlags(); err != nil {
    54  		return err
    55  	}
    56  	*output = abs(*output)
    57  	if *asmhdr != "" {
    58  		*asmhdr = abs(*asmhdr)
    59  	}
    60  
    61  	// Filter sources using build constraints.
    62  	all, err := filterAndSplitFiles(unfiltered)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	goFiles, sFiles, hFiles := all.goSrcs, all.sSrcs, all.hSrcs
    67  	if len(all.cSrcs) > 0 {
    68  		return fmt.Errorf("unexpected C file: %s", all.cSrcs[0].filename)
    69  	}
    70  	if len(all.cxxSrcs) > 0 {
    71  		return fmt.Errorf("unexpected C++ file: %s", all.cxxSrcs[0].filename)
    72  	}
    73  	switch *testfilter {
    74  	case "off":
    75  	case "only":
    76  		testFiles := make([]fileInfo, 0, len(goFiles))
    77  		for _, f := range goFiles {
    78  			if strings.HasSuffix(f.pkg, "_test") {
    79  				testFiles = append(testFiles, f)
    80  			}
    81  		}
    82  		goFiles = testFiles
    83  	case "exclude":
    84  		libFiles := make([]fileInfo, 0, len(goFiles))
    85  		for _, f := range goFiles {
    86  			if !strings.HasSuffix(f.pkg, "_test") {
    87  				libFiles = append(libFiles, f)
    88  			}
    89  		}
    90  		goFiles = libFiles
    91  	default:
    92  		return fmt.Errorf("invalid test filter %q", *testfilter)
    93  	}
    94  	if len(goFiles) == 0 {
    95  		// We need to run the compiler to create a valid archive, even if there's
    96  		// nothing in it. GoPack will complain if we try to add assembly or cgo
    97  		// objects.
    98  		emptyPath := filepath.Join(filepath.Dir(*output), "_empty.go")
    99  		if err := ioutil.WriteFile(emptyPath, []byte("package empty\n"), 0666); err != nil {
   100  			return err
   101  		}
   102  		goFiles = append(goFiles, fileInfo{filename: emptyPath, pkg: "empty"})
   103  	}
   104  
   105  	if *packagePath == "" {
   106  		*packagePath = goFiles[0].pkg
   107  	}
   108  
   109  	// Check that the filtered sources don't import anything outside of
   110  	// the standard library and the direct dependencies.
   111  	imports, err := checkImports(goFiles, archives, *packageList)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	// Build an importcfg file for the compiler.
   117  	importcfgName, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(*output))
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer os.Remove(importcfgName)
   122  
   123  	// If there are assembly files, and this is go1.12+, generate symbol ABIs.
   124  	symabisName, err := buildSymabisFile(goenv, sFiles, hFiles, *asmhdr)
   125  	if symabisName != "" {
   126  		defer os.Remove(symabisName)
   127  	}
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// Compile the filtered files.
   133  	goargs := goenv.goTool("compile")
   134  	goargs = append(goargs, "-p", *packagePath)
   135  	goargs = append(goargs, "-importcfg", importcfgName)
   136  	goargs = append(goargs, "-pack", "-o", *output)
   137  	if symabisName != "" {
   138  		goargs = append(goargs, "-symabis", symabisName)
   139  	}
   140  	if *asmhdr != "" {
   141  		goargs = append(goargs, "-asmhdr", *asmhdr)
   142  	}
   143  	goargs = append(goargs, toolArgs...)
   144  	goargs = append(goargs, "--")
   145  	filenames := make([]string, 0, len(goFiles))
   146  	for _, f := range goFiles {
   147  		filenames = append(filenames, f.filename)
   148  	}
   149  	goargs = append(goargs, filenames...)
   150  	absArgs(goargs, []string{"-I", "-o", "-trimpath", "-importcfg"})
   151  	cmd := exec.Command(goargs[0], goargs[1:]...)
   152  	cmd.Stdout = os.Stdout
   153  	cmd.Stderr = os.Stderr
   154  	if err := cmd.Start(); err != nil {
   155  		return fmt.Errorf("error starting compiler: %v", err)
   156  	}
   157  
   158  	// Run nogo concurrently.
   159  	var nogoOutput bytes.Buffer
   160  	nogoFailed := false
   161  	if *nogo != "" {
   162  		var nogoargs []string
   163  		nogoargs = append(nogoargs, "-p", *packagePath)
   164  		nogoargs = append(nogoargs, "-importcfg", importcfgName)
   165  		for _, arc := range archives {
   166  			if arc.xFile != "" {
   167  				nogoargs = append(nogoargs, "-fact", fmt.Sprintf("%s=%s", arc.importPath, arc.xFile))
   168  			}
   169  		}
   170  		nogoargs = append(nogoargs, "-x", *outExport)
   171  		nogoargs = append(nogoargs, filenames...)
   172  		nogoCmd := exec.Command(*nogo, nogoargs...)
   173  		nogoCmd.Stdout, nogoCmd.Stderr = &nogoOutput, &nogoOutput
   174  		if err := nogoCmd.Run(); err != nil {
   175  			if _, ok := err.(*exec.ExitError); ok {
   176  				// Only fail the build if nogo runs and finds errors in source code.
   177  				nogoFailed = true
   178  			} else {
   179  				// All errors related to running nogo will merely be printed.
   180  				nogoOutput.WriteString(fmt.Sprintf("error running nogo: %v\n", err))
   181  			}
   182  		}
   183  	}
   184  	if err := cmd.Wait(); err != nil {
   185  		return fmt.Errorf("error running compiler: %v", err)
   186  	}
   187  	// Only print the output of nogo if compilation succeeds.
   188  	if nogoFailed {
   189  		return fmt.Errorf("%s", nogoOutput.String())
   190  	}
   191  	if nogoOutput.Len() != 0 {
   192  		fmt.Fprintln(os.Stderr, nogoOutput.String())
   193  	}
   194  	return nil
   195  }