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 }