xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/compilepkg.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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.
17package main
18
19import (
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
34type nogoResult int
35
36const (
37	nogoNotRun nogoResult = iota
38	nogoError
39	nogoFailed
40	nogoSucceeded
41)
42
43func compilePkg(args []string) error {
44	// Parse arguments.
45	args, _, err := expandParamsFiles(args)
46	if err != nil {
47		return err
48	}
49
50	fs := flag.NewFlagSet("GoCompilePkg", flag.ExitOnError)
51	goenv := envFlags(fs)
52	var unfilteredSrcs, coverSrcs, embedSrcs, embedLookupDirs, embedRoots, recompileInternalDeps multiFlag
53	var deps archiveMultiFlag
54	var importPath, packagePath, nogoPath, packageListPath, coverMode string
55	var outPath, outFactsPath, cgoExportHPath string
56	var testFilter string
57	var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
58	var coverFormat string
59	fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
60	fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
61	fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive")
62	fs.Var(&embedLookupDirs, "embedlookupdir", "Root-relative paths to directories relative to which //go:embed directives are resolved")
63	fs.Var(&embedRoots, "embedroot", "Bazel output root under which a file passed via -embedsrc resides")
64	fs.Var(&deps, "arc", "Import path, package path, and file name of a direct dependency, separated by '='")
65	fs.StringVar(&importPath, "importpath", "", "The import path of the package being compiled. Not passed to the compiler, but may be displayed in debug data.")
66	fs.StringVar(&packagePath, "p", "", "The package path (importmap) of the package being compiled")
67	fs.Var(&gcFlags, "gcflags", "Go compiler flags")
68	fs.Var(&asmFlags, "asmflags", "Go assembler flags")
69	fs.Var(&cppFlags, "cppflags", "C preprocessor flags")
70	fs.Var(&cFlags, "cflags", "C compiler flags")
71	fs.Var(&cxxFlags, "cxxflags", "C++ compiler flags")
72	fs.Var(&objcFlags, "objcflags", "Objective-C compiler flags")
73	fs.Var(&objcxxFlags, "objcxxflags", "Objective-C++ compiler flags")
74	fs.Var(&ldFlags, "ldflags", "C linker flags")
75	fs.StringVar(&nogoPath, "nogo", "", "The nogo binary. If unset, nogo will not be run.")
76	fs.StringVar(&packageListPath, "package_list", "", "The file containing the list of standard library packages")
77	fs.StringVar(&coverMode, "cover_mode", "", "The coverage mode to use. Empty if coverage instrumentation should not be added.")
78	fs.StringVar(&outPath, "o", "", "The output archive file to write compiled code")
79	fs.StringVar(&outFactsPath, "x", "", "The output archive file to write export data and nogo facts")
80	fs.StringVar(&cgoExportHPath, "cgoexport", "", "The _cgo_exports.h file to write")
81	fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering")
82	fs.StringVar(&coverFormat, "cover_format", "", "Emit source file paths in coverage instrumentation suitable for the specified coverage format")
83	fs.Var(&recompileInternalDeps, "recompile_internal_deps", "The import path of the direct dependencies that needs to be recompiled.")
84	if err := fs.Parse(args); err != nil {
85		return err
86	}
87	if err := goenv.checkFlags(); err != nil {
88		return err
89	}
90	if importPath == "" {
91		importPath = packagePath
92	}
93	cgoEnabled := os.Getenv("CGO_ENABLED") == "1"
94	cc := os.Getenv("CC")
95	outPath = abs(outPath)
96	for i := range unfilteredSrcs {
97		unfilteredSrcs[i] = abs(unfilteredSrcs[i])
98	}
99	for i := range embedSrcs {
100		embedSrcs[i] = abs(embedSrcs[i])
101	}
102
103	// Filter sources.
104	srcs, err := filterAndSplitFiles(unfilteredSrcs)
105	if err != nil {
106		return err
107	}
108
109	// TODO(jayconrod): remove -testfilter flag. The test action should compile
110	// the main, internal, and external packages by calling compileArchive
111	// with the correct sources for each.
112	switch testFilter {
113	case "off":
114	case "only":
115		testSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
116		for _, f := range srcs.goSrcs {
117			if strings.HasSuffix(f.pkg, "_test") {
118				testSrcs = append(testSrcs, f)
119			}
120		}
121		srcs.goSrcs = testSrcs
122	case "exclude":
123		libSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
124		for _, f := range srcs.goSrcs {
125			if !strings.HasSuffix(f.pkg, "_test") {
126				libSrcs = append(libSrcs, f)
127			}
128		}
129		srcs.goSrcs = libSrcs
130	default:
131		return fmt.Errorf("invalid test filter %q", testFilter)
132	}
133
134	return compileArchive(
135		goenv,
136		importPath,
137		packagePath,
138		srcs,
139		deps,
140		coverMode,
141		coverSrcs,
142		embedSrcs,
143		embedLookupDirs,
144		embedRoots,
145		cgoEnabled,
146		cc,
147		gcFlags,
148		asmFlags,
149		cppFlags,
150		cFlags,
151		cxxFlags,
152		objcFlags,
153		objcxxFlags,
154		ldFlags,
155		nogoPath,
156		packageListPath,
157		outPath,
158		outFactsPath,
159		cgoExportHPath,
160		coverFormat,
161		recompileInternalDeps)
162}
163
164func compileArchive(
165	goenv *env,
166	importPath string,
167	packagePath string,
168	srcs archiveSrcs,
169	deps []archive,
170	coverMode string,
171	coverSrcs []string,
172	embedSrcs []string,
173	embedLookupDirs []string,
174	embedRoots []string,
175	cgoEnabled bool,
176	cc string,
177	gcFlags []string,
178	asmFlags []string,
179	cppFlags []string,
180	cFlags []string,
181	cxxFlags []string,
182	objcFlags []string,
183	objcxxFlags []string,
184	ldFlags []string,
185	nogoPath string,
186	packageListPath string,
187	outPath string,
188	outXPath string,
189	cgoExportHPath string,
190	coverFormat string,
191	recompileInternalDeps []string,
192) error {
193	workDir, cleanup, err := goenv.workDir()
194	if err != nil {
195		return err
196	}
197	defer cleanup()
198
199	// As part of compilation process, rules_go does generate and/or rewrite code
200	// based on the original source files.  We should only run static analysis
201	// over original source files and not the generated source as end users have
202	// little control over the generated source.
203	//
204	// nogoSrcsOrigin maps generated/rewritten source files back to original source.
205	// If the original source path is an empty string, exclude generated source from nogo run.
206	nogoSrcsOrigin := make(map[string]string)
207
208	if len(srcs.goSrcs) == 0 {
209		// We need to run the compiler to create a valid archive, even if there's nothing in it.
210		// Otherwise, GoPack will complain if we try to add assembly or cgo objects.
211		// A truly empty archive does not include any references to source file paths, which
212		// ensures hermeticity even though the temp file path is random.
213		emptyGoFile, err := os.CreateTemp(filepath.Dir(outPath), "*.go")
214		if err != nil {
215			return err
216		}
217		defer os.Remove(emptyGoFile.Name())
218		defer emptyGoFile.Close()
219		if _, err := emptyGoFile.WriteString("package empty\n"); err != nil {
220			return err
221		}
222		if err := emptyGoFile.Close(); err != nil {
223			return err
224		}
225
226		srcs.goSrcs = append(srcs.goSrcs, fileInfo{
227			filename: emptyGoFile.Name(),
228			ext:      goExt,
229			matched:  true,
230			pkg:      "empty",
231		})
232
233		nogoSrcsOrigin[emptyGoFile.Name()] = ""
234	}
235	packageName := srcs.goSrcs[0].pkg
236	var goSrcs, cgoSrcs []string
237	for _, src := range srcs.goSrcs {
238		if src.isCgo {
239			cgoSrcs = append(cgoSrcs, src.filename)
240		} else {
241			goSrcs = append(goSrcs, src.filename)
242		}
243	}
244	cSrcs := make([]string, len(srcs.cSrcs))
245	for i, src := range srcs.cSrcs {
246		cSrcs[i] = src.filename
247	}
248	cxxSrcs := make([]string, len(srcs.cxxSrcs))
249	for i, src := range srcs.cxxSrcs {
250		cxxSrcs[i] = src.filename
251	}
252	objcSrcs := make([]string, len(srcs.objcSrcs))
253	for i, src := range srcs.objcSrcs {
254		objcSrcs[i] = src.filename
255	}
256	objcxxSrcs := make([]string, len(srcs.objcxxSrcs))
257	for i, src := range srcs.objcxxSrcs {
258		objcxxSrcs[i] = src.filename
259	}
260	sSrcs := make([]string, len(srcs.sSrcs))
261	for i, src := range srcs.sSrcs {
262		sSrcs[i] = src.filename
263	}
264	hSrcs := make([]string, len(srcs.hSrcs))
265	for i, src := range srcs.hSrcs {
266		hSrcs[i] = src.filename
267	}
268	haveCgo := len(cgoSrcs)+len(cSrcs)+len(cxxSrcs)+len(objcSrcs)+len(objcxxSrcs) > 0
269
270	// Instrument source files for coverage.
271	if coverMode != "" {
272		relCoverPath := make(map[string]string)
273		for _, s := range coverSrcs {
274			relCoverPath[abs(s)] = s
275		}
276
277		combined := append([]string{}, goSrcs...)
278		if cgoEnabled {
279			combined = append(combined, cgoSrcs...)
280		}
281		for i, origSrc := range combined {
282			if _, ok := relCoverPath[origSrc]; !ok {
283				continue
284			}
285
286			var srcName string
287			switch coverFormat {
288			case "go_cover":
289				srcName = origSrc
290				if importPath != "" {
291					srcName = path.Join(importPath, filepath.Base(origSrc))
292				}
293			case "lcov":
294				// Bazel merges lcov reports across languages and thus assumes
295				// that the source file paths are relative to the exec root.
296				srcName = relCoverPath[origSrc]
297			default:
298				return fmt.Errorf("invalid value for -cover_format: %q", coverFormat)
299			}
300
301			stem := filepath.Base(origSrc)
302			if ext := filepath.Ext(stem); ext != "" {
303				stem = stem[:len(stem)-len(ext)]
304			}
305			coverVar := fmt.Sprintf("Cover_%s_%d_%s", sanitizePathForIdentifier(importPath), i, sanitizePathForIdentifier(stem))
306			coverVar = strings.ReplaceAll(coverVar, "_", "Z")
307			coverSrc := filepath.Join(workDir, fmt.Sprintf("cover_%d.go", i))
308			if err := instrumentForCoverage(goenv, origSrc, srcName, coverVar, coverMode, coverSrc); err != nil {
309				return err
310			}
311
312			if i < len(goSrcs) {
313				goSrcs[i] = coverSrc
314				nogoSrcsOrigin[coverSrc] = origSrc
315				continue
316			}
317
318			cgoSrcs[i-len(goSrcs)] = coverSrc
319		}
320	}
321
322	// If we have cgo, generate separate C and go files, and compile the
323	// C files.
324	var objFiles []string
325	if cgoEnabled && haveCgo {
326		// TODO(#2006): Compile .s and .S files with cgo2, not the Go assembler.
327		// If cgo is not enabled or we don't have other cgo sources, don't
328		// compile .S files.
329		var srcDir string
330		srcDir, goSrcs, objFiles, err = cgo2(goenv, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, nil, hSrcs, packagePath, packageName, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags, cgoExportHPath)
331		if err != nil {
332			return err
333		}
334
335		gcFlags = append(gcFlags, createTrimPath(gcFlags, srcDir))
336	} else {
337		if cgoExportHPath != "" {
338			if err := ioutil.WriteFile(cgoExportHPath, nil, 0o666); err != nil {
339				return err
340			}
341		}
342		gcFlags = append(gcFlags, createTrimPath(gcFlags, "."))
343	}
344
345	// Check that the filtered sources don't import anything outside of
346	// the standard library and the direct dependencies.
347	imports, err := checkImports(srcs.goSrcs, deps, packageListPath, importPath, recompileInternalDeps)
348	if err != nil {
349		return err
350	}
351	if cgoEnabled && len(cgoSrcs) != 0 {
352		// cgo generated code imports some extra packages.
353		imports["runtime/cgo"] = nil
354		imports["syscall"] = nil
355		imports["unsafe"] = nil
356	}
357	if coverMode != "" {
358		if coverMode == "atomic" {
359			imports["sync/atomic"] = nil
360		}
361		const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata"
362		var coverdata *archive
363		for i := range deps {
364			if deps[i].importPath == coverdataPath {
365				coverdata = &deps[i]
366				break
367			}
368		}
369		if coverdata == nil {
370			return errors.New("coverage requested but coverdata dependency not provided")
371		}
372		imports[coverdataPath] = coverdata
373	}
374
375	// Build an importcfg file for the compiler.
376	importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outPath))
377	if err != nil {
378		return err
379	}
380	if !goenv.shouldPreserveWorkDir {
381		defer os.Remove(importcfgPath)
382	}
383
384	// Build an embedcfg file mapping embed patterns to filenames.
385	// Embed patterns are relative to any one of a list of root directories
386	// that may contain embeddable files. Source files containing embed patterns
387	// must be in one of these root directories so the pattern appears to be
388	// relative to the source file. Due to transitions, source files can reside
389	// under Bazel roots different from both those of the go srcs and those of
390	// the compilation output. Thus, we have to consider all combinations of
391	// Bazel roots embedsrcs and root-relative paths of source files and the
392	// output binary.
393	var embedRootDirs []string
394	for _, root := range embedRoots {
395		for _, lookupDir := range embedLookupDirs {
396			embedRootDir := abs(filepath.Join(root, lookupDir))
397			// Since we are iterating over all combinations of roots and
398			// root-relative paths, some resulting paths may not exist and
399			// should be filtered out before being passed to buildEmbedcfgFile.
400			// Since Bazel uniquified both the roots and the root-relative
401			// paths, the combinations are automatically unique.
402			if _, err := os.Stat(embedRootDir); err == nil {
403				embedRootDirs = append(embedRootDirs, embedRootDir)
404			}
405		}
406	}
407	embedcfgPath, err := buildEmbedcfgFile(srcs.goSrcs, embedSrcs, embedRootDirs, workDir)
408	if err != nil {
409		return err
410	}
411	if embedcfgPath != "" {
412		if !goenv.shouldPreserveWorkDir {
413			defer os.Remove(embedcfgPath)
414		}
415	}
416
417	// Run nogo concurrently.
418	var nogoChan chan error
419	outFactsPath := filepath.Join(workDir, nogoFact)
420	nogoSrcs := make([]string, 0, len(goSrcs))
421	for _, goSrc := range goSrcs {
422		// If source is found in the origin map, that means it's likely to be a generated source file
423		// so feed the original source file to static analyzers instead of the generated one.
424		//
425		// If origin is empty, that means the generated source file is not based on a user-provided source file
426		// thus ignore that entry entirely.
427		if originSrc, ok := nogoSrcsOrigin[goSrc]; ok {
428			if originSrc != "" {
429				nogoSrcs = append(nogoSrcs, originSrc)
430			}
431			continue
432		}
433
434		// TODO(sluongng): most likely what remains here are CGO-generated source files as the result of calling cgo2()
435		// Need to determine whether we want to feed these CGO-generated files into static analyzers.
436		//
437		// Add unknown origin source files into the mix.
438		nogoSrcs = append(nogoSrcs, goSrc)
439	}
440	if nogoPath != "" && len(nogoSrcs) > 0 {
441		ctx, cancel := context.WithCancel(context.Background())
442		nogoChan = make(chan error)
443		go func() {
444			nogoChan <- runNogo(ctx, workDir, nogoPath, nogoSrcs, deps, packagePath, importcfgPath, outFactsPath)
445		}()
446		defer func() {
447			if nogoChan != nil {
448				cancel()
449				<-nogoChan
450			}
451		}()
452	}
453
454	// If there are assembly files, and this is go1.12+, generate symbol ABIs.
455	asmHdrPath := ""
456	if len(srcs.sSrcs) > 0 {
457		asmHdrPath = filepath.Join(workDir, "go_asm.h")
458	}
459	symabisPath, err := buildSymabisFile(goenv, srcs.sSrcs, srcs.hSrcs, asmHdrPath)
460	if symabisPath != "" {
461		if !goenv.shouldPreserveWorkDir {
462			defer os.Remove(symabisPath)
463		}
464	}
465	if err != nil {
466		return err
467	}
468
469	// Compile the filtered .go files.
470	if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, outPath); err != nil {
471		return err
472	}
473
474	// Compile the .s files.
475	if len(srcs.sSrcs) > 0 {
476		includeSet := map[string]struct{}{
477			filepath.Join(os.Getenv("GOROOT"), "pkg", "include"): {},
478			workDir: {},
479		}
480		for _, hdr := range srcs.hSrcs {
481			includeSet[filepath.Dir(hdr.filename)] = struct{}{}
482		}
483		includes := make([]string, len(includeSet))
484		for inc := range includeSet {
485			includes = append(includes, inc)
486		}
487		sort.Strings(includes)
488		for _, inc := range includes {
489			asmFlags = append(asmFlags, "-I", inc)
490		}
491		for i, sSrc := range srcs.sSrcs {
492			obj := filepath.Join(workDir, fmt.Sprintf("s%d.o", i))
493			if err := asmFile(goenv, sSrc.filename, packagePath, asmFlags, obj); err != nil {
494				return err
495			}
496			objFiles = append(objFiles, obj)
497		}
498	}
499
500	// Pack .o files into the archive. These may come from cgo generated code,
501	// cgo dependencies (cdeps), or assembly.
502	if len(objFiles) > 0 {
503		if err := appendFiles(goenv, outPath, objFiles); err != nil {
504			return err
505		}
506	}
507
508	// Check results from nogo.
509	nogoStatus := nogoNotRun
510	if nogoChan != nil {
511		err := <-nogoChan
512		nogoChan = nil // no cancellation needed
513		if err != nil {
514			nogoStatus = nogoFailed
515			// TODO: should we still create the .x file without nogo facts in this case?
516			return err
517		}
518		nogoStatus = nogoSucceeded
519	}
520
521	// Extract the export data file and pack it in an .x archive together with the
522	// nogo facts file (if there is one). This allows compile actions to depend
523	// on .x files only, so we don't need to recompile a package when one of its
524	// imports changes in a way that doesn't affect export data.
525	// TODO(golang/go#33820): After Go 1.16 is the minimum supported version,
526	// use -linkobj to tell the compiler to create separate .a and .x files for
527	// compiled code and export data. Before that version, the linker needed
528	// export data in the .a file when building a plugin. To work around that,
529	// we copy the export data into .x ourselves.
530	if err = extractFileFromArchive(outPath, workDir, pkgDef); err != nil {
531		return err
532	}
533	pkgDefPath := filepath.Join(workDir, pkgDef)
534	if nogoStatus == nogoSucceeded {
535		return appendFiles(goenv, outXPath, []string{pkgDefPath, outFactsPath})
536	}
537	return appendFiles(goenv, outXPath, []string{pkgDefPath})
538}
539
540func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, outPath string) error {
541	args := goenv.goTool("compile")
542	args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack")
543	if embedcfgPath != "" {
544		args = append(args, "-embedcfg", embedcfgPath)
545	}
546	if asmHdrPath != "" {
547		args = append(args, "-asmhdr", asmHdrPath)
548	}
549	if symabisPath != "" {
550		args = append(args, "-symabis", symabisPath)
551	}
552	args = append(args, gcFlags...)
553	args = append(args, "-o", outPath)
554	args = append(args, "--")
555	args = append(args, srcs...)
556	absArgs(args, []string{"-I", "-o", "-trimpath", "-importcfg"})
557	return goenv.runCommand(args)
558}
559
560func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string, deps []archive, packagePath, importcfgPath, outFactsPath string) error {
561	args := []string{nogoPath}
562	args = append(args, "-p", packagePath)
563	args = append(args, "-importcfg", importcfgPath)
564	for _, dep := range deps {
565		args = append(args, "-fact", fmt.Sprintf("%s=%s", dep.importPath, dep.file))
566	}
567	args = append(args, "-x", outFactsPath)
568	args = append(args, srcs...)
569
570	paramsFile := filepath.Join(workDir, "nogo.param")
571	if err := writeParamsFile(paramsFile, args[1:]); err != nil {
572		return fmt.Errorf("error writing nogo params file: %v", err)
573	}
574
575	cmd := exec.CommandContext(ctx, args[0], "-param="+paramsFile)
576	out := &bytes.Buffer{}
577	cmd.Stdout, cmd.Stderr = out, out
578	if err := cmd.Run(); err != nil {
579		if exitErr, ok := err.(*exec.ExitError); ok {
580			if !exitErr.Exited() {
581				cmdLine := strings.Join(args, " ")
582				return fmt.Errorf("nogo command '%s' exited unexpectedly: %s", cmdLine, exitErr.String())
583			}
584			return errors.New(string(relativizePaths(out.Bytes())))
585		} else {
586			if out.Len() != 0 {
587				fmt.Fprintln(os.Stderr, out.String())
588			}
589			return fmt.Errorf("error running nogo: %v", err)
590		}
591	}
592	return nil
593}
594
595func createTrimPath(gcFlags []string, path string) string {
596	for _, flag := range gcFlags {
597		if strings.HasPrefix(flag, "-trimpath=") {
598			return flag + ":" + path
599		}
600	}
601
602	return "-trimpath=" + path
603}
604
605func sanitizePathForIdentifier(path string) string {
606	return strings.Map(func(r rune) rune {
607		if 'A' <= r && r <= 'Z' ||
608			'a' <= r && r <= 'z' ||
609			'0' <= r && r <= '9' ||
610			r == '_' {
611			return r
612		}
613		return '_'
614	}, path)
615}
616