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 }