github.com/0xKiwi/rules_go@v0.24.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 := expandParamsFiles(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 := archiveMultiFlag{}
    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", "", "The output archive file to write export data and nogo facts")
    46  	output := flags.String("o", "", "The output archive file to write compiled code")
    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  	// tempdir to store nogo facts and pkgdef for packaging later
   159  	xTempDir, err := ioutil.TempDir(filepath.Dir(*outExport), "x_files")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	defer os.RemoveAll(xTempDir)
   164  	// Run nogo concurrently.
   165  	var nogoOutput bytes.Buffer
   166  	nogoStatus := nogoNotRun
   167  	outFact := filepath.Join(xTempDir, nogoFact)
   168  	if *nogo != "" {
   169  		var nogoargs []string
   170  		nogoargs = append(nogoargs, "-p", *packagePath)
   171  		nogoargs = append(nogoargs, "-importcfg", importcfgName)
   172  		for _, arc := range archives {
   173  			nogoargs = append(nogoargs, "-fact", fmt.Sprintf("%s=%s", arc.importPath, arc.file))
   174  		}
   175  		nogoargs = append(nogoargs, "-x", outFact)
   176  		nogoargs = append(nogoargs, filenames...)
   177  		nogoCmd := exec.Command(*nogo, nogoargs...)
   178  		nogoCmd.Stdout, nogoCmd.Stderr = &nogoOutput, &nogoOutput
   179  		if err := nogoCmd.Run(); err != nil {
   180  			if _, ok := err.(*exec.ExitError); ok {
   181  				// Only fail the build if nogo runs and finds errors in source code.
   182  				nogoStatus = nogoFailed
   183  			} else {
   184  				// All errors related to running nogo will merely be printed.
   185  				nogoOutput.WriteString(fmt.Sprintf("error running nogo: %v\n", err))
   186  				nogoStatus = nogoError
   187  			}
   188  		} else {
   189  			nogoStatus = nogoSucceeded
   190  		}
   191  	}
   192  	if err := cmd.Wait(); err != nil {
   193  		return fmt.Errorf("error running compiler: %v", err)
   194  	}
   195  
   196  	// Only print the output of nogo if compilation succeeds.
   197  	if nogoStatus == nogoFailed {
   198  		return fmt.Errorf("%s", nogoOutput.String())
   199  	}
   200  	if nogoOutput.Len() != 0 {
   201  		fmt.Fprintln(os.Stderr, nogoOutput.String())
   202  	}
   203  
   204  	// Extract the export data file and pack it in an .x archive together with the
   205  	// nogo facts file (if there is one). This allows compile actions to depend
   206  	// on .x files only, so we don't need to recompile a package when one of its
   207  	// imports changes in a way that doesn't affect export data.
   208  	// TODO(golang/go#33820): Ideally, we would use -linkobj to tell the compiler
   209  	// to create separate .a and .x files for compiled code and export data, then
   210  	// copy the nogo facts into the .x file. Unfortunately, when building a plugin,
   211  	// the linker needs export data in the .a file. To work around this, we copy
   212  	// the export data into the .x file ourselves.
   213  	if err = extractFileFromArchive(*output, xTempDir, pkgDef); err != nil {
   214  		return err
   215  	}
   216  	pkgDefPath := filepath.Join(xTempDir, pkgDef)
   217  	if nogoStatus == nogoSucceeded {
   218  		return appendFiles(goenv, *outExport, []string{pkgDefPath, outFact})
   219  	}
   220  	return appendFiles(goenv, *outExport, []string{pkgDefPath})
   221  }
   222  
   223  type nogoResult int
   224  
   225  const (
   226  	nogoNotRun nogoResult = iota
   227  	nogoError
   228  	nogoFailed
   229  	nogoSucceeded
   230  )