1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 type nogoResult int
35
36 const (
37 nogoNotRun nogoResult = iota
38 nogoError
39 nogoFailed
40 nogoSucceeded
41 )
42
43 func compilePkg(args []string) error {
44
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, facts archiveMultiFlag
54 var importPath, packagePath, nogoPath, packageListPath, coverMode string
55 var outLinkobjPath, outInterfacePath, outFactsPath, cgoExportHPath string
56 var testFilter string
57 var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
58 var coverFormat string
59 var pgoprofile string
60 fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
61 fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
62 fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive")
63 fs.Var(&embedLookupDirs, "embedlookupdir", "Root-relative paths to directories relative to which //go:embed directives are resolved")
64 fs.Var(&embedRoots, "embedroot", "Bazel output root under which a file passed via -embedsrc resides")
65 fs.Var(&deps, "arc", "Import path, package path, and file name of a direct dependency, separated by '='")
66 fs.Var(&facts, "facts", "Import path, package path, and file name of a direct dependency's nogo facts file, separated by '='")
67 fs.StringVar(&importPath, "importpath", "", "The import path of the package being compiled. Not passed to the compiler, but may be displayed in debug data.")
68 fs.StringVar(&packagePath, "p", "", "The package path (importmap) of the package being compiled")
69 fs.Var(&gcFlags, "gcflags", "Go compiler flags")
70 fs.Var(&asmFlags, "asmflags", "Go assembler flags")
71 fs.Var(&cppFlags, "cppflags", "C preprocessor flags")
72 fs.Var(&cFlags, "cflags", "C compiler flags")
73 fs.Var(&cxxFlags, "cxxflags", "C++ compiler flags")
74 fs.Var(&objcFlags, "objcflags", "Objective-C compiler flags")
75 fs.Var(&objcxxFlags, "objcxxflags", "Objective-C++ compiler flags")
76 fs.Var(&ldFlags, "ldflags", "C linker flags")
77 fs.StringVar(&nogoPath, "nogo", "", "The nogo binary. If unset, nogo will not be run.")
78 fs.StringVar(&packageListPath, "package_list", "", "The file containing the list of standard library packages")
79 fs.StringVar(&coverMode, "cover_mode", "", "The coverage mode to use. Empty if coverage instrumentation should not be added.")
80 fs.StringVar(&outLinkobjPath, "lo", "", "The full output archive file required by the linker")
81 fs.StringVar(&outInterfacePath, "o", "", "The export-only output archive required to compile dependent packages")
82 fs.StringVar(&outFactsPath, "out_facts", "", "The file to emit serialized nogo facts to (must be set if -nogo is set")
83 fs.StringVar(&cgoExportHPath, "cgoexport", "", "The _cgo_exports.h file to write")
84 fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering")
85 fs.StringVar(&coverFormat, "cover_format", "", "Emit source file paths in coverage instrumentation suitable for the specified coverage format")
86 fs.Var(&recompileInternalDeps, "recompile_internal_deps", "The import path of the direct dependencies that needs to be recompiled.")
87 fs.StringVar(&pgoprofile, "pgoprofile", "", "The pprof profile to consider for profile guided optimization.")
88 if err := fs.Parse(args); err != nil {
89 return err
90 }
91 if err := goenv.checkFlags(); err != nil {
92 return err
93 }
94 if importPath == "" {
95 importPath = packagePath
96 }
97 cgoEnabled := os.Getenv("CGO_ENABLED") == "1"
98 cc := os.Getenv("CC")
99 outLinkobjPath = abs(outLinkobjPath)
100 for i := range unfilteredSrcs {
101 unfilteredSrcs[i] = abs(unfilteredSrcs[i])
102 }
103 for i := range embedSrcs {
104 embedSrcs[i] = abs(embedSrcs[i])
105 }
106 if pgoprofile != "" {
107 pgoprofile = abs(pgoprofile)
108 }
109
110
111 srcs, err := filterAndSplitFiles(unfilteredSrcs)
112 if err != nil {
113 return err
114 }
115
116
117
118
119 switch testFilter {
120 case "off":
121 case "only":
122 testSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
123 for _, f := range srcs.goSrcs {
124 if strings.HasSuffix(f.pkg, "_test") {
125 testSrcs = append(testSrcs, f)
126 }
127 }
128 srcs.goSrcs = testSrcs
129 case "exclude":
130 libSrcs := make([]fileInfo, 0, len(srcs.goSrcs))
131 for _, f := range srcs.goSrcs {
132 if !strings.HasSuffix(f.pkg, "_test") {
133 libSrcs = append(libSrcs, f)
134 }
135 }
136 srcs.goSrcs = libSrcs
137 default:
138 return fmt.Errorf("invalid test filter %q", testFilter)
139 }
140
141 return compileArchive(
142 goenv,
143 importPath,
144 packagePath,
145 srcs,
146 deps,
147 facts,
148 coverMode,
149 coverSrcs,
150 embedSrcs,
151 embedLookupDirs,
152 embedRoots,
153 cgoEnabled,
154 cc,
155 gcFlags,
156 asmFlags,
157 cppFlags,
158 cFlags,
159 cxxFlags,
160 objcFlags,
161 objcxxFlags,
162 ldFlags,
163 nogoPath,
164 packageListPath,
165 outLinkobjPath,
166 outInterfacePath,
167 outFactsPath,
168 cgoExportHPath,
169 coverFormat,
170 recompileInternalDeps,
171 pgoprofile)
172 }
173
174 func compileArchive(
175 goenv *env,
176 importPath string,
177 packagePath string,
178 srcs archiveSrcs,
179 deps []archive,
180 facts []archive,
181 coverMode string,
182 coverSrcs []string,
183 embedSrcs []string,
184 embedLookupDirs []string,
185 embedRoots []string,
186 cgoEnabled bool,
187 cc string,
188 gcFlags []string,
189 asmFlags []string,
190 cppFlags []string,
191 cFlags []string,
192 cxxFlags []string,
193 objcFlags []string,
194 objcxxFlags []string,
195 ldFlags []string,
196 nogoPath string,
197 packageListPath string,
198 outLinkObj string,
199 outInterfacePath string,
200 outFactsPath string,
201 cgoExportHPath string,
202 coverFormat string,
203 recompileInternalDeps []string,
204 pgoprofile string,
205 ) error {
206 workDir, cleanup, err := goenv.workDir()
207 if err != nil {
208 return err
209 }
210 defer cleanup()
211
212 emptyGoFilePath := ""
213 if len(srcs.goSrcs) == 0 {
214
215
216
217
218 emptyGoFile, err := os.CreateTemp(filepath.Dir(outLinkObj), "*.go")
219 if err != nil {
220 return err
221 }
222 defer os.Remove(emptyGoFile.Name())
223 defer emptyGoFile.Close()
224 if _, err := emptyGoFile.WriteString("package empty\n"); err != nil {
225 return err
226 }
227 if err := emptyGoFile.Close(); err != nil {
228 return err
229 }
230
231 srcs.goSrcs = append(srcs.goSrcs, fileInfo{
232 filename: emptyGoFile.Name(),
233 ext: goExt,
234 matched: true,
235 pkg: "empty",
236 })
237 emptyGoFilePath = emptyGoFile.Name()
238 }
239 packageName := srcs.goSrcs[0].pkg
240 var goSrcs, cgoSrcs []string
241 for _, src := range srcs.goSrcs {
242 if src.isCgo {
243 cgoSrcs = append(cgoSrcs, src.filename)
244 } else {
245 goSrcs = append(goSrcs, src.filename)
246 }
247 }
248 cSrcs := make([]string, len(srcs.cSrcs))
249 for i, src := range srcs.cSrcs {
250 cSrcs[i] = src.filename
251 }
252 cxxSrcs := make([]string, len(srcs.cxxSrcs))
253 for i, src := range srcs.cxxSrcs {
254 cxxSrcs[i] = src.filename
255 }
256 objcSrcs := make([]string, len(srcs.objcSrcs))
257 for i, src := range srcs.objcSrcs {
258 objcSrcs[i] = src.filename
259 }
260 objcxxSrcs := make([]string, len(srcs.objcxxSrcs))
261 for i, src := range srcs.objcxxSrcs {
262 objcxxSrcs[i] = src.filename
263 }
264 sSrcs := make([]string, len(srcs.sSrcs))
265 for i, src := range srcs.sSrcs {
266 sSrcs[i] = src.filename
267 }
268 hSrcs := make([]string, len(srcs.hSrcs))
269 for i, src := range srcs.hSrcs {
270 hSrcs[i] = src.filename
271 }
272
273
274 haveCgo := len(cgoSrcs)+len(cSrcs)+len(cxxSrcs)+len(objcSrcs)+len(objcxxSrcs) > 0
275
276
277
278 compilingWithCgo := haveCgo && cgoEnabled
279
280 filterForNogo := func(slice []string) []string {
281 filtered := make([]string, 0, len(slice))
282 for _, s := range slice {
283
284 if s != emptyGoFilePath {
285 filtered = append(filtered, s)
286 }
287 }
288 return filtered
289 }
290
291
292
293 goSrcsNogo := filterForNogo(goSrcs)
294 cgoSrcsNogo := append([]string{}, cgoSrcs...)
295
296
297 if coverMode != "" {
298 relCoverPath := make(map[string]string)
299 for _, s := range coverSrcs {
300 relCoverPath[abs(s)] = s
301 }
302
303 combined := append([]string{}, goSrcs...)
304 if cgoEnabled {
305 combined = append(combined, cgoSrcs...)
306 }
307 for i, origSrc := range combined {
308 if _, ok := relCoverPath[origSrc]; !ok {
309 continue
310 }
311
312 var srcName string
313 switch coverFormat {
314 case "go_cover":
315 srcName = origSrc
316 if importPath != "" {
317 srcName = path.Join(importPath, filepath.Base(origSrc))
318 }
319 case "lcov":
320
321
322 srcName = relCoverPath[origSrc]
323 default:
324 return fmt.Errorf("invalid value for -cover_format: %q", coverFormat)
325 }
326
327 stem := filepath.Base(origSrc)
328 if ext := filepath.Ext(stem); ext != "" {
329 stem = stem[:len(stem)-len(ext)]
330 }
331 coverVar := fmt.Sprintf("Cover_%s_%d_%s", sanitizePathForIdentifier(importPath), i, sanitizePathForIdentifier(stem))
332 coverVar = strings.ReplaceAll(coverVar, "_", "Z")
333 coverSrc := filepath.Join(workDir, fmt.Sprintf("cover_%d.go", i))
334 if err := instrumentForCoverage(goenv, origSrc, srcName, coverVar, coverMode, coverSrc); err != nil {
335 return err
336 }
337
338 if i < len(goSrcs) {
339 goSrcs[i] = coverSrc
340 continue
341 }
342
343 cgoSrcs[i-len(goSrcs)] = coverSrc
344 }
345 }
346
347
348
349 var objFiles []string
350 if compilingWithCgo {
351
352
353 var srcDir string
354 srcDir, goSrcs, objFiles, err = cgo2(goenv, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, packagePath, packageName, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags, cgoExportHPath)
355 if err != nil {
356 return err
357 }
358 if coverMode != "" && nogoPath != "" {
359
360 _, goSrcsNogo, _, err = cgo2(goenv, goSrcsNogo, cgoSrcsNogo, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, packagePath, packageName, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags, cgoExportHPath)
361 if err != nil {
362 return err
363 }
364 } else {
365 goSrcsNogo = goSrcs
366 }
367
368 gcFlags = append(gcFlags, createTrimPath(gcFlags, srcDir))
369 } else {
370 if cgoExportHPath != "" {
371 if err := ioutil.WriteFile(cgoExportHPath, nil, 0o666); err != nil {
372 return err
373 }
374 }
375 gcFlags = append(gcFlags, createTrimPath(gcFlags, "."))
376 }
377
378
379
380 imports, err := checkImports(srcs.goSrcs, deps, packageListPath, importPath, recompileInternalDeps)
381 if err != nil {
382 return err
383 }
384 if compilingWithCgo {
385
386 imports["runtime/cgo"] = nil
387 imports["syscall"] = nil
388 imports["unsafe"] = nil
389 }
390 if coverMode != "" {
391 if coverMode == "atomic" {
392 imports["sync/atomic"] = nil
393 }
394 const coverdataPath = "github.com/bazelbuild/rules_go/go/tools/coverdata"
395 var coverdata *archive
396 for i := range deps {
397 if deps[i].importPath == coverdataPath {
398 coverdata = &deps[i]
399 break
400 }
401 }
402 if coverdata == nil {
403 return errors.New("coverage requested but coverdata dependency not provided")
404 }
405 imports[coverdataPath] = coverdata
406 }
407
408
409 importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outLinkObj))
410 if err != nil {
411 return err
412 }
413 if !goenv.shouldPreserveWorkDir {
414 defer os.Remove(importcfgPath)
415 }
416
417
418
419
420
421
422
423
424
425
426 var embedRootDirs []string
427 for _, root := range embedRoots {
428 for _, lookupDir := range embedLookupDirs {
429 embedRootDir := abs(filepath.Join(root, lookupDir))
430
431
432
433
434
435 if _, err := os.Stat(embedRootDir); err == nil {
436 embedRootDirs = append(embedRootDirs, embedRootDir)
437 }
438 }
439 }
440 embedcfgPath, err := buildEmbedcfgFile(srcs.goSrcs, embedSrcs, embedRootDirs, workDir)
441 if err != nil {
442 return err
443 }
444 if embedcfgPath != "" {
445 if !goenv.shouldPreserveWorkDir {
446 defer os.Remove(embedcfgPath)
447 }
448 }
449
450
451 var nogoChan chan error
452 if nogoPath != "" {
453 ctx, cancel := context.WithCancel(context.Background())
454 nogoChan = make(chan error)
455 go func() {
456 nogoChan <- runNogo(ctx, workDir, nogoPath, goSrcsNogo, facts, packagePath, importcfgPath, outFactsPath)
457 }()
458 defer func() {
459 if nogoChan != nil {
460 cancel()
461 <-nogoChan
462 }
463 }()
464 }
465
466
467
468 asmHdrPath := ""
469 if len(srcs.sSrcs) > 0 {
470 asmHdrPath = filepath.Join(workDir, "go_asm.h")
471 }
472 var symabisPath string
473 if !haveCgo {
474 symabisPath, err = buildSymabisFile(goenv, packagePath, srcs.sSrcs, srcs.hSrcs, asmHdrPath)
475 if symabisPath != "" {
476 if !goenv.shouldPreserveWorkDir {
477 defer os.Remove(symabisPath)
478 }
479 }
480 if err != nil {
481 return err
482 }
483 }
484
485
486 if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, pgoprofile, outLinkObj, outInterfacePath); err != nil {
487 return err
488 }
489
490
491
492 if len(srcs.sSrcs) > 0 && !haveCgo {
493 includeSet := map[string]struct{}{
494 filepath.Join(os.Getenv("GOROOT"), "pkg", "include"): {},
495 workDir: {},
496 }
497 for _, hdr := range srcs.hSrcs {
498 includeSet[filepath.Dir(hdr.filename)] = struct{}{}
499 }
500 includes := make([]string, len(includeSet))
501 for inc := range includeSet {
502 includes = append(includes, inc)
503 }
504 sort.Strings(includes)
505 for _, inc := range includes {
506 asmFlags = append(asmFlags, "-I", inc)
507 }
508 for i, sSrc := range srcs.sSrcs {
509 obj := filepath.Join(workDir, fmt.Sprintf("s%d.o", i))
510 if err := asmFile(goenv, sSrc.filename, packagePath, asmFlags, obj); err != nil {
511 return err
512 }
513 objFiles = append(objFiles, obj)
514 }
515 }
516
517
518
519 if len(objFiles) > 0 {
520 if err := appendToArchive(goenv, outLinkObj, objFiles); err != nil {
521 return err
522 }
523 }
524
525
526 if nogoChan != nil {
527 err := <-nogoChan
528 nogoChan = nil
529 if err != nil {
530
531 return err
532 }
533 }
534
535 return nil
536 }
537
538 func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, pgoprofile, outLinkobjPath, outInterfacePath string) error {
539 args := goenv.goTool("compile")
540 args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack")
541 if embedcfgPath != "" {
542 args = append(args, "-embedcfg", embedcfgPath)
543 }
544 if asmHdrPath != "" {
545 args = append(args, "-asmhdr", asmHdrPath)
546 }
547 if symabisPath != "" {
548 args = append(args, "-symabis", symabisPath)
549 }
550 if pgoprofile != "" {
551 args = append(args, "-pgoprofile", pgoprofile)
552 }
553 args = append(args, gcFlags...)
554 args = append(args, "-o", outInterfacePath)
555 args = append(args, "-linkobj", outLinkobjPath)
556 args = append(args, "--")
557 args = append(args, srcs...)
558 absArgs(args, []string{"-I", "-o", "-trimpath", "-importcfg"})
559 return goenv.runCommand(args)
560 }
561
562 func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string, facts []archive, packagePath, importcfgPath, outFactsPath string) error {
563 if len(srcs) == 0 {
564
565 return os.WriteFile(outFactsPath, nil, 0o666)
566 }
567 args := []string{nogoPath}
568 args = append(args, "-p", packagePath)
569 args = append(args, "-importcfg", importcfgPath)
570 for _, fact := range facts {
571 args = append(args, "-fact", fmt.Sprintf("%s=%s", fact.importPath, fact.file))
572 }
573 args = append(args, "-x", outFactsPath)
574 args = append(args, srcs...)
575
576 paramsFile := filepath.Join(workDir, "nogo.param")
577 if err := writeParamsFile(paramsFile, args[1:]); err != nil {
578 return fmt.Errorf("error writing nogo params file: %v", err)
579 }
580
581 cmd := exec.CommandContext(ctx, args[0], "-param="+paramsFile)
582 out := &bytes.Buffer{}
583 cmd.Stdout, cmd.Stderr = out, out
584 if err := cmd.Run(); err != nil {
585 if exitErr, ok := err.(*exec.ExitError); ok {
586 if !exitErr.Exited() {
587 cmdLine := strings.Join(args, " ")
588 return fmt.Errorf("nogo command '%s' exited unexpectedly: %s", cmdLine, exitErr.String())
589 }
590 return errors.New(string(relativizePaths(out.Bytes())))
591 } else {
592 if out.Len() != 0 {
593 fmt.Fprintln(os.Stderr, out.String())
594 }
595 return fmt.Errorf("error running nogo: %v", err)
596 }
597 }
598 return nil
599 }
600
601 func appendToArchive(goenv *env, outPath string, objFiles []string) error {
602
603 args := goenv.goTool("pack", "r", abs(outPath))
604 args = append(args, objFiles...)
605 return goenv.runCommand(args)
606 }
607
608 func createTrimPath(gcFlags []string, path string) string {
609 for _, flag := range gcFlags {
610 if strings.HasPrefix(flag, "-trimpath=") {
611 return flag + ":" + path
612 }
613 }
614
615 return "-trimpath=" + path
616 }
617
618 func sanitizePathForIdentifier(path string) string {
619 return strings.Map(func(r rune) rune {
620 if 'A' <= r && r <= 'Z' ||
621 'a' <= r && r <= 'z' ||
622 '0' <= r && r <= '9' ||
623 r == '_' {
624 return r
625 }
626 return '_'
627 }, path)
628 }
629
View as plain text