github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/compilepkg.go (about)

     1  // Copyright 2019 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  // compilepkg compiles a complete Go package from Go, C, and assembly files.  It
    16  // supports cgo, coverage, and nogo. It is invoked by the Go rules as an action.
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"os/exec"
    28  	"path"
    29  	"path/filepath"
    30  	"sort"
    31  	"strings"
    32  )
    33  
    34  func compilePkg(args []string) error {
    35  	// Parse arguments.
    36  	args, err := expandParamsFiles(args)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	fs := flag.NewFlagSet("GoCompilePkg", flag.ExitOnError)
    42  	goenv := envFlags(fs)
    43  	var unfilteredSrcs, coverSrcs multiFlag
    44  	var deps archiveMultiFlag
    45  	var importPath, packagePath, nogoPath, packageListPath, coverMode string
    46  	var outPath, outFactsPath, cgoExportHPath string
    47  	var testFilter string
    48  	var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
    49  	fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
    50  	fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
    51  	fs.Var(&deps, "arc", "Import path, package path, and file name of a direct dependency, separated by '='")
    52  	fs.StringVar(&importPath, "importpath", "", "The import path of the package being compiled. Not passed to the compiler, but may be displayed in debug data.")
    53  	fs.StringVar(&packagePath, "p", "", "The package path (importmap) of the package being compiled")
    54  	fs.Var(&gcFlags, "gcflags", "Go compiler flags")
    55  	fs.Var(&asmFlags, "asmflags", "Go assembler flags")
    56  	fs.Var(&cppFlags, "cppflags", "C preprocessor flags")
    57  	fs.Var(&cFlags, "cflags", "C compiler flags")
    58  	fs.Var(&cxxFlags, "cxxflags", "C++ compiler flags")
    59  	fs.Var(&objcFlags, "objcflags", "Objective-C compiler flags")
    60  	fs.Var(&objcxxFlags, "objcxxflags", "Objective-C++ compiler flags")
    61  	fs.Var(&ldFlags, "ldflags", "C linker flags")
    62  	fs.StringVar(&nogoPath, "nogo", "", "The nogo binary. If unset, nogo will not be run.")
    63  	fs.StringVar(&packageListPath, "package_list", "", "The file containing the list of standard library packages")
    64  	fs.StringVar(&coverMode, "cover_mode", "", "The coverage mode to use. Empty if coverage instrumentation should not be added.")
    65  	fs.StringVar(&outPath, "o", "", "The output archive file to write compiled code")
    66  	fs.StringVar(&outFactsPath, "x", "", "The output archive file to write export data and nogo facts")
    67  	fs.StringVar(&cgoExportHPath, "cgoexport", "", "The _cgo_exports.h file to write")
    68  	fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering")
    69  	if err := fs.Parse(args); err != nil {
    70  		return err
    71  	}
    72  	if err := goenv.checkFlags(); err != nil {
    73  		return err
    74  	}
    75  	if importPath == "" {
    76  		importPath = packagePath
    77  	}
    78  	cgoEnabled := os.Getenv("CGO_ENABLED") == "1"
    79  	cc := os.Getenv("CC")
    80  	outPath = abs(outPath)
    81  	for i := range unfilteredSrcs {
    82  		unfilteredSrcs[i] = abs(unfilteredSrcs[i])
    83  	}
    84  	for i := range coverSrcs {
    85  		coverSrcs[i] = abs(coverSrcs[i])
    86  	}
    87  
    88  	// Filter sources.
    89  	srcs, err := filterAndSplitFiles(unfilteredSrcs)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	// TODO(jayconrod): remove -testfilter flag. The test action should compile
    95  	// the main, internal, and external packages by calling compileArchive
    96  	// with the correct sources for each.
    97  	switch testFilter {
    98  	case "off":
    99  	case "only":
   100  		testSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
   101  		for _, f := range srcs.goSrcs {
   102  			if strings.HasSuffix(f.pkg, "_test") {
   103  				testSrcs = append(testSrcs, f)
   104  			}
   105  		}
   106  		srcs.goSrcs = testSrcs
   107  	case "exclude":
   108  		libSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
   109  		for _, f := range srcs.goSrcs {
   110  			if !strings.HasSuffix(f.pkg, "_test") {
   111  				libSrcs = append(libSrcs, f)
   112  			}
   113  		}
   114  		srcs.goSrcs = libSrcs
   115  	default:
   116  		return fmt.Errorf("invalid test filter %q", testFilter)
   117  	}
   118  
   119  	return compileArchive(
   120  		goenv,
   121  		importPath,
   122  		packagePath,
   123  		srcs,
   124  		deps,
   125  		coverMode,
   126  		coverSrcs,
   127  		cgoEnabled,
   128  		cc,
   129  		gcFlags,
   130  		asmFlags,
   131  		cppFlags,
   132  		cFlags,
   133  		cxxFlags,
   134  		objcFlags,
   135  		objcxxFlags,
   136  		ldFlags,
   137  		nogoPath,
   138  		packageListPath,
   139  		outPath,
   140  		outFactsPath,
   141  		cgoExportHPath)
   142  }
   143  
   144  func compileArchive(
   145  	goenv *env,
   146  	importPath string,
   147  	packagePath string,
   148  	srcs archiveSrcs,
   149  	deps []archive,
   150  	coverMode string,
   151  	coverSrcs []string,
   152  	cgoEnabled bool,
   153  	cc string,
   154  	gcFlags []string,
   155  	asmFlags []string,
   156  	cppFlags []string,
   157  	cFlags []string,
   158  	cxxFlags []string,
   159  	objcFlags []string,
   160  	objcxxFlags []string,
   161  	ldFlags []string,
   162  	nogoPath string,
   163  	packageListPath string,
   164  	outPath string,
   165  	outXPath string,
   166  	cgoExportHPath string) error {
   167  
   168  	workDir, cleanup, err := goenv.workDir()
   169  	if err != nil {
   170  		return err
   171  	}
   172  	defer cleanup()
   173  
   174  	if len(srcs.goSrcs) == 0 {
   175  		emptyPath := filepath.Join(workDir, "_empty.go")
   176  		if err := ioutil.WriteFile(emptyPath, []byte("package empty\n"), 0666); err != nil {
   177  			return err
   178  		}
   179  		srcs.goSrcs = append(srcs.goSrcs, fileInfo{
   180  			filename: emptyPath,
   181  			ext:      goExt,
   182  			matched:  true,
   183  			pkg:      "empty",
   184  		})
   185  		defer os.Remove(emptyPath)
   186  	}
   187  	packageName := srcs.goSrcs[0].pkg
   188  	var goSrcs, cgoSrcs []string
   189  	for _, src := range srcs.goSrcs {
   190  		if src.isCgo {
   191  			cgoSrcs = append(cgoSrcs, src.filename)
   192  		} else {
   193  			goSrcs = append(goSrcs, src.filename)
   194  		}
   195  	}
   196  	cSrcs := make([]string, len(srcs.cSrcs))
   197  	for i, src := range srcs.cSrcs {
   198  		cSrcs[i] = src.filename
   199  	}
   200  	cxxSrcs := make([]string, len(srcs.cxxSrcs))
   201  	for i, src := range srcs.cxxSrcs {
   202  		cxxSrcs[i] = src.filename
   203  	}
   204  	objcSrcs := make([]string, len(srcs.objcSrcs))
   205  	for i, src := range srcs.objcSrcs {
   206  		objcSrcs[i] = src.filename
   207  	}
   208  	objcxxSrcs := make([]string, len(srcs.objcxxSrcs))
   209  	for i, src := range srcs.objcxxSrcs {
   210  		objcxxSrcs[i] = src.filename
   211  	}
   212  	sSrcs := make([]string, len(srcs.sSrcs))
   213  	for i, src := range srcs.sSrcs {
   214  		sSrcs[i] = src.filename
   215  	}
   216  	hSrcs := make([]string, len(srcs.hSrcs))
   217  	for i, src := range srcs.hSrcs {
   218  		hSrcs[i] = src.filename
   219  	}
   220  	haveCgo := len(cgoSrcs)+len(cSrcs)+len(cxxSrcs)+len(objcSrcs)+len(objcxxSrcs) > 0
   221  
   222  	// Instrument source files for coverage.
   223  	if coverMode != "" {
   224  		shouldCover := make(map[string]bool)
   225  		for _, s := range coverSrcs {
   226  			shouldCover[s] = true
   227  		}
   228  
   229  		combined := append([]string{}, goSrcs...)
   230  		if cgoEnabled {
   231  			combined = append(combined, cgoSrcs...)
   232  		}
   233  		for i, origSrc := range combined {
   234  			if !shouldCover[origSrc] {
   235  				continue
   236  			}
   237  
   238  			srcName := origSrc
   239  			if importPath != "" {
   240  				srcName = path.Join(importPath, filepath.Base(origSrc))
   241  			}
   242  
   243  			stem := filepath.Base(origSrc)
   244  			if ext := filepath.Ext(stem); ext != "" {
   245  				stem = stem[:len(stem)-len(ext)]
   246  			}
   247  			coverVar := fmt.Sprintf("Cover_%s_%d_%s", sanitizePathForIdentifier(importPath), i, sanitizePathForIdentifier(stem))
   248  			coverSrc := filepath.Join(workDir, fmt.Sprintf("cover_%d.go", i))
   249  			if err := instrumentForCoverage(goenv, origSrc, srcName, coverVar, coverMode, coverSrc); err != nil {
   250  				return err
   251  			}
   252  
   253  			if i < len(goSrcs) {
   254  				goSrcs[i] = coverSrc
   255  			} else {
   256  				cgoSrcs[i-len(goSrcs)] = coverSrc
   257  			}
   258  		}
   259  	}
   260  
   261  	// If we have cgo, generate separate C and go files, and compile the
   262  	// C files.
   263  	var objFiles []string
   264  	if cgoEnabled && haveCgo {
   265  		// TODO(#2006): Compile .s and .S files with cgo2, not the Go assembler.
   266  		// If cgo is not enabled or we don't have other cgo sources, don't
   267  		// compile .S files.
   268  		var srcDir string
   269  		srcDir, goSrcs, objFiles, err = cgo2(goenv, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, nil, hSrcs, packagePath, packageName, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags, cgoExportHPath)
   270  		if err != nil {
   271  			return err
   272  		}
   273  
   274  		gcFlags = append(gcFlags, "-trimpath="+srcDir)
   275  	} else {
   276  		if cgoExportHPath != "" {
   277  			if err := ioutil.WriteFile(cgoExportHPath, nil, 0666); err != nil {
   278  				return err
   279  			}
   280  		}
   281  		gcFlags = append(gcFlags, "-trimpath=.")
   282  	}
   283  
   284  	// Check that the filtered sources don't import anything outside of
   285  	// the standard library and the direct dependencies.
   286  	imports, err := checkImports(srcs.goSrcs, deps, packageListPath)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	if cgoEnabled && len(cgoSrcs) != 0 {
   291  		// cgo generated code imports some extra packages.
   292  		imports["runtime/cgo"] = nil
   293  		imports["syscall"] = nil
   294  		imports["unsafe"] = nil
   295  	}
   296  	if coverMode != "" {
   297  		if coverMode == "atomic" {
   298  			imports["sync/atomic"] = nil
   299  		}
   300  		const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata"
   301  		var coverdata *archive
   302  		for i := range deps {
   303  			if deps[i].importPath == coverdataPath {
   304  				coverdata = &deps[i]
   305  				break
   306  			}
   307  		}
   308  		if coverdata == nil {
   309  			return errors.New("coverage requested but coverdata dependency not provided")
   310  		}
   311  		imports[coverdataPath] = coverdata
   312  	}
   313  
   314  	// Build an importcfg file for the compiler.
   315  	importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outPath))
   316  	if err != nil {
   317  		return err
   318  	}
   319  	defer os.Remove(importcfgPath)
   320  
   321  	// Run nogo concurrently.
   322  	var nogoChan chan error
   323  	outFactsPath := filepath.Join(workDir, nogoFact)
   324  	if nogoPath != "" {
   325  		ctx, cancel := context.WithCancel(context.Background())
   326  		nogoChan = make(chan error)
   327  		go func() {
   328  			nogoChan <- runNogo(ctx, workDir, nogoPath, goSrcs, deps, packagePath, importcfgPath, outFactsPath)
   329  		}()
   330  		defer func() {
   331  			if nogoChan != nil {
   332  				cancel()
   333  				<-nogoChan
   334  			}
   335  		}()
   336  	}
   337  
   338  	// If there are assembly files, and this is go1.12+, generate symbol ABIs.
   339  	asmHdrPath := ""
   340  	if len(srcs.sSrcs) > 0 {
   341  		asmHdrPath = filepath.Join(workDir, "go_asm.h")
   342  	}
   343  	symabisPath, err := buildSymabisFile(goenv, srcs.sSrcs, srcs.hSrcs, asmHdrPath)
   344  	if symabisPath != "" {
   345  		defer os.Remove(symabisPath)
   346  	}
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	// Compile the filtered .go files.
   352  	if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, asmHdrPath, symabisPath, gcFlags, outPath); err != nil {
   353  		return err
   354  	}
   355  
   356  	// Compile the .s files.
   357  	if len(srcs.sSrcs) > 0 {
   358  		includeSet := map[string]struct{}{
   359  			filepath.Join(os.Getenv("GOROOT"), "pkg", "include"): struct{}{},
   360  			workDir: struct{}{},
   361  		}
   362  		for _, hdr := range srcs.hSrcs {
   363  			includeSet[filepath.Dir(hdr.filename)] = struct{}{}
   364  		}
   365  		includes := make([]string, len(includeSet))
   366  		for inc := range includeSet {
   367  			includes = append(includes, inc)
   368  		}
   369  		sort.Strings(includes)
   370  		for _, inc := range includes {
   371  			asmFlags = append(asmFlags, "-I", inc)
   372  		}
   373  		for i, sSrc := range srcs.sSrcs {
   374  			obj := filepath.Join(workDir, fmt.Sprintf("s%d.o", i))
   375  			if err := asmFile(goenv, sSrc.filename, asmFlags, obj); err != nil {
   376  				return err
   377  			}
   378  			objFiles = append(objFiles, obj)
   379  		}
   380  	}
   381  
   382  	// Pack .o files into the archive. These may come from cgo generated code,
   383  	// cgo dependencies (cdeps), or assembly.
   384  	if len(objFiles) > 0 {
   385  		if err := appendFiles(goenv, outPath, objFiles); err != nil {
   386  			return err
   387  		}
   388  	}
   389  
   390  	// Check results from nogo.
   391  	nogoStatus := nogoNotRun
   392  	if nogoChan != nil {
   393  		err := <-nogoChan
   394  		nogoChan = nil // no cancellation needed
   395  		if err != nil {
   396  			nogoStatus = nogoFailed
   397  			// TODO: should we still create the .x file without nogo facts in this case?
   398  			return err
   399  		}
   400  		nogoStatus = nogoSucceeded
   401  	}
   402  
   403  	// Extract the export data file and pack it in an .x archive together with the
   404  	// nogo facts file (if there is one). This allows compile actions to depend
   405  	// on .x files only, so we don't need to recompile a package when one of its
   406  	// imports changes in a way that doesn't affect export data.
   407  	// TODO(golang/go#33820): Ideally, we would use -linkobj to tell the compiler
   408  	// to create separate .a and .x files for compiled code and export data, then
   409  	// copy the nogo facts into the .x file. Unfortunately, when building a plugin,
   410  	// the linker needs export data in the .a file. To work around this, we copy
   411  	// the export data into the .x file ourselves.
   412  	if err = extractFileFromArchive(outPath, workDir, pkgDef); err != nil {
   413  		return err
   414  	}
   415  	pkgDefPath := filepath.Join(workDir, pkgDef)
   416  	if nogoStatus == nogoSucceeded {
   417  		return appendFiles(goenv, outXPath, []string{pkgDefPath, outFactsPath})
   418  	}
   419  	return appendFiles(goenv, outXPath, []string{pkgDefPath})
   420  }
   421  
   422  func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, asmHdrPath, symabisPath string, gcFlags []string, outPath string) error {
   423  	args := goenv.goTool("compile")
   424  	args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack")
   425  	if asmHdrPath != "" {
   426  		args = append(args, "-asmhdr", asmHdrPath)
   427  	}
   428  	if symabisPath != "" {
   429  		args = append(args, "-symabis", symabisPath)
   430  	}
   431  	args = append(args, gcFlags...)
   432  	args = append(args, "-o", outPath)
   433  	args = append(args, "--")
   434  	args = append(args, srcs...)
   435  	absArgs(args, []string{"-I", "-o", "-trimpath", "-importcfg"})
   436  	return goenv.runCommand(args)
   437  }
   438  
   439  func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string, deps []archive, packagePath, importcfgPath, outFactsPath string) error {
   440  	args := []string{nogoPath}
   441  	args = append(args, "-p", packagePath)
   442  	args = append(args, "-importcfg", importcfgPath)
   443  	for _, dep := range deps {
   444  		args = append(args, "-fact", fmt.Sprintf("%s=%s", dep.importPath, dep.file))
   445  	}
   446  	args = append(args, "-x", outFactsPath)
   447  	args = append(args, srcs...)
   448  
   449  	paramsFile := filepath.Join(workDir, "nogo.param")
   450  	if err := writeParamsFile(paramsFile, args[1:]); err != nil {
   451  		return fmt.Errorf("error writing nogo params file: %v", err)
   452  	}
   453  
   454  	cmd := exec.CommandContext(ctx, args[0], "-param="+paramsFile)
   455  	out := &bytes.Buffer{}
   456  	cmd.Stdout, cmd.Stderr = out, out
   457  	if err := cmd.Run(); err != nil {
   458  		if exitErr, ok := err.(*exec.ExitError); ok {
   459  			if !exitErr.Exited() {
   460  				cmdLine := strings.Join(args, " ")
   461  				return fmt.Errorf("nogo command '%s' exited unexpectedly: %s", cmdLine, exitErr.String())
   462  			}
   463  			return errors.New(out.String())
   464  		} else {
   465  			if out.Len() != 0 {
   466  				fmt.Fprintln(os.Stderr, out.String())
   467  			}
   468  			return fmt.Errorf("error running nogo: %v", err)
   469  		}
   470  	}
   471  	return nil
   472  }
   473  
   474  func sanitizePathForIdentifier(path string) string {
   475  	return strings.Map(func(r rune) rune {
   476  		if 'A' <= r && r <= 'Z' ||
   477  			'a' <= r && r <= 'z' ||
   478  			'0' <= r && r <= '9' ||
   479  			r == '_' {
   480  			return r
   481  		}
   482  		return '_'
   483  	}, path)
   484  }