github.com/ajguerrer/rules_go@v0.20.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 := readParamsFiles(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 compileArchiveMultiFlag
    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")
    66  	fs.StringVar(&outFactsPath, "x", "", "The nogo facts file to write")
    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  	outFactsPath 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  		const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata"
   298  		var coverdata *archive
   299  		for i := range deps {
   300  			if deps[i].importPath == coverdataPath {
   301  				coverdata = &deps[i]
   302  				break
   303  			}
   304  		}
   305  		if coverdata == nil {
   306  			return errors.New("coverage requested but coverdata dependency not provided")
   307  		}
   308  		imports[coverdataPath] = coverdata
   309  	}
   310  
   311  	// Build an importcfg file for the compiler.
   312  	importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outPath))
   313  	if err != nil {
   314  		return err
   315  	}
   316  	defer os.Remove(importcfgPath)
   317  
   318  	// Run nogo concurrently.
   319  	var nogoChan chan error
   320  	if nogoPath != "" {
   321  		ctx, cancel := context.WithCancel(context.Background())
   322  		nogoChan = make(chan error)
   323  		go func() {
   324  			nogoChan <- runNogo(ctx, nogoPath, goSrcs, deps, packagePath, importcfgPath, outFactsPath)
   325  		}()
   326  		defer func() {
   327  			if nogoChan != nil {
   328  				cancel()
   329  				<-nogoChan
   330  			}
   331  		}()
   332  	}
   333  
   334  	// If there are assembly files, and this is go1.12+, generate symbol ABIs.
   335  	asmHdrPath := ""
   336  	if len(srcs.sSrcs) > 0 {
   337  		asmHdrPath = filepath.Join(workDir, "go_asm.h")
   338  	}
   339  	symabisPath, err := buildSymabisFile(goenv, srcs.sSrcs, srcs.hSrcs, asmHdrPath)
   340  	if symabisPath != "" {
   341  		defer os.Remove(symabisPath)
   342  	}
   343  	if err != nil {
   344  		return err
   345  	}
   346  
   347  	// Compile the filtered .go files.
   348  	if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, asmHdrPath, symabisPath, gcFlags, outPath); err != nil {
   349  		return err
   350  	}
   351  
   352  	// Compile the .s files.
   353  	if len(srcs.sSrcs) > 0 {
   354  		includeSet := map[string]struct{}{
   355  			filepath.Join(os.Getenv("GOROOT"), "pkg", "include"): struct{}{},
   356  			workDir: struct{}{},
   357  		}
   358  		for _, hdr := range srcs.hSrcs {
   359  			includeSet[filepath.Dir(hdr.filename)] = struct{}{}
   360  		}
   361  		includes := make([]string, len(includeSet))
   362  		for inc := range includeSet {
   363  			includes = append(includes, inc)
   364  		}
   365  		sort.Strings(includes)
   366  		for _, inc := range includes {
   367  			asmFlags = append(asmFlags, "-I", inc)
   368  		}
   369  		for i, sSrc := range srcs.sSrcs {
   370  			obj := filepath.Join(workDir, fmt.Sprintf("s%d.o", i))
   371  			if err := asmFile(goenv, sSrc.filename, asmFlags, obj); err != nil {
   372  				return err
   373  			}
   374  			objFiles = append(objFiles, obj)
   375  		}
   376  	}
   377  
   378  	// Pack .o files into the archive. These may come from cgo generated code,
   379  	// cgo dependencies (cdeps), or assembly.
   380  	if len(objFiles) > 0 {
   381  		if err := appendFiles(goenv, outPath, objFiles); err != nil {
   382  			return err
   383  		}
   384  	}
   385  
   386  	// Check results from nogo.
   387  	if nogoChan != nil {
   388  		err := <-nogoChan
   389  		nogoChan = nil // no cancellation needed
   390  		if err != nil {
   391  			return err
   392  		}
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, asmHdrPath, symabisPath string, gcFlags []string, outPath string) error {
   399  	args := goenv.goTool("compile")
   400  	args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack")
   401  	if asmHdrPath != "" {
   402  		args = append(args, "-asmhdr", asmHdrPath)
   403  	}
   404  	if symabisPath != "" {
   405  		args = append(args, "-symabis", symabisPath)
   406  	}
   407  	args = append(args, gcFlags...)
   408  	args = append(args, "-o", outPath)
   409  	args = append(args, "--")
   410  	args = append(args, srcs...)
   411  	absArgs(args, []string{"-I", "-o", "-trimpath", "-importcfg"})
   412  	return goenv.runCommand(args)
   413  }
   414  
   415  func runNogo(ctx context.Context, nogoPath string, srcs []string, deps []archive, packagePath, importcfgPath, outFactsPath string) error {
   416  	args := []string{nogoPath}
   417  	args = append(args, "-p", packagePath)
   418  	args = append(args, "-importcfg", importcfgPath)
   419  	for _, dep := range deps {
   420  		if dep.xFile != "" {
   421  			args = append(args, "-fact", fmt.Sprintf("%s=%s", dep.importPath, dep.xFile))
   422  		}
   423  	}
   424  	args = append(args, "-x", outFactsPath)
   425  	args = append(args, srcs...)
   426  
   427  	cmd := exec.CommandContext(ctx, args[0], args[1:]...)
   428  	out := &bytes.Buffer{}
   429  	cmd.Stdout, cmd.Stderr = out, out
   430  	if err := cmd.Run(); err != nil {
   431  		if _, ok := err.(*exec.ExitError); ok {
   432  			return errors.New(out.String())
   433  		} else {
   434  			if out.Len() != 0 {
   435  				fmt.Fprintln(os.Stderr, out.String())
   436  			}
   437  			return fmt.Errorf("error running nogo: %v", err)
   438  		}
   439  	}
   440  	return nil
   441  }
   442  
   443  func sanitizePathForIdentifier(path string) string {
   444  	return strings.Map(func(r rune) rune {
   445  		if 'A' <= r && r <= 'Z' ||
   446  			'a' <= r && r <= 'z' ||
   447  			'0' <= r && r <= '9' ||
   448  			r == '_' {
   449  			return r
   450  		}
   451  		return '_'
   452  	}, path)
   453  }