1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package packages 6 7import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "go/types" 12 "io/ioutil" 13 "log" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "reflect" 18 "regexp" 19 "strconv" 20 "strings" 21 "sync" 22 "time" 23 24 "golang.org/x/tools/go/internal/packagesdriver" 25 "golang.org/x/tools/internal/gopathwalk" 26 "golang.org/x/tools/internal/semver" 27) 28 29// debug controls verbose logging. 30var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG")) 31 32// A goTooOldError reports that the go command 33// found by exec.LookPath is too old to use the new go list behavior. 34type goTooOldError struct { 35 error 36} 37 38// responseDeduper wraps a driverResponse, deduplicating its contents. 39type responseDeduper struct { 40 seenRoots map[string]bool 41 seenPackages map[string]*Package 42 dr *driverResponse 43} 44 45// init fills in r with a driverResponse. 46func (r *responseDeduper) init(dr *driverResponse) { 47 r.dr = dr 48 r.seenRoots = map[string]bool{} 49 r.seenPackages = map[string]*Package{} 50 for _, pkg := range dr.Packages { 51 r.seenPackages[pkg.ID] = pkg 52 } 53 for _, root := range dr.Roots { 54 r.seenRoots[root] = true 55 } 56} 57 58func (r *responseDeduper) addPackage(p *Package) { 59 if r.seenPackages[p.ID] != nil { 60 return 61 } 62 r.seenPackages[p.ID] = p 63 r.dr.Packages = append(r.dr.Packages, p) 64} 65 66func (r *responseDeduper) addRoot(id string) { 67 if r.seenRoots[id] { 68 return 69 } 70 r.seenRoots[id] = true 71 r.dr.Roots = append(r.dr.Roots, id) 72} 73 74// goListDriver uses the go list command to interpret the patterns and produce 75// the build system package structure. 76// See driver for more details. 77func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { 78 var sizes types.Sizes 79 var sizeserr error 80 var sizeswg sync.WaitGroup 81 if cfg.Mode >= LoadTypes { 82 sizeswg.Add(1) 83 go func() { 84 sizes, sizeserr = getSizes(cfg) 85 sizeswg.Done() 86 }() 87 } 88 89 // Determine files requested in contains patterns 90 var containFiles []string 91 var packagesNamed []string 92 restPatterns := make([]string, 0, len(patterns)) 93 // Extract file= and other [querytype]= patterns. Report an error if querytype 94 // doesn't exist. 95extractQueries: 96 for _, pattern := range patterns { 97 eqidx := strings.Index(pattern, "=") 98 if eqidx < 0 { 99 restPatterns = append(restPatterns, pattern) 100 } else { 101 query, value := pattern[:eqidx], pattern[eqidx+len("="):] 102 switch query { 103 case "file": 104 containFiles = append(containFiles, value) 105 case "pattern": 106 restPatterns = append(restPatterns, value) 107 case "iamashamedtousethedisabledqueryname": 108 packagesNamed = append(packagesNamed, value) 109 case "": // not a reserved query 110 restPatterns = append(restPatterns, pattern) 111 default: 112 for _, rune := range query { 113 if rune < 'a' || rune > 'z' { // not a reserved query 114 restPatterns = append(restPatterns, pattern) 115 continue extractQueries 116 } 117 } 118 // Reject all other patterns containing "=" 119 return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) 120 } 121 } 122 } 123 124 // TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released. 125 var listfunc driver 126 var isFallback bool 127 listfunc = func(cfg *Config, words ...string) (*driverResponse, error) { 128 response, err := golistDriverCurrent(cfg, words...) 129 if _, ok := err.(goTooOldError); ok { 130 isFallback = true 131 listfunc = golistDriverFallback 132 return listfunc(cfg, words...) 133 } 134 listfunc = golistDriverCurrent 135 return response, err 136 } 137 138 response := &responseDeduper{} 139 var err error 140 141 // See if we have any patterns to pass through to go list. Zero initial 142 // patterns also requires a go list call, since it's the equivalent of 143 // ".". 144 if len(restPatterns) > 0 || len(patterns) == 0 { 145 dr, err := listfunc(cfg, restPatterns...) 146 if err != nil { 147 return nil, err 148 } 149 response.init(dr) 150 } else { 151 response.init(&driverResponse{}) 152 } 153 154 sizeswg.Wait() 155 if sizeserr != nil { 156 return nil, sizeserr 157 } 158 // types.SizesFor always returns nil or a *types.StdSizes 159 response.dr.Sizes, _ = sizes.(*types.StdSizes) 160 161 var containsCandidates []string 162 163 if len(containFiles) != 0 { 164 if err := runContainsQueries(cfg, listfunc, isFallback, response, containFiles); err != nil { 165 return nil, err 166 } 167 } 168 169 if len(packagesNamed) != 0 { 170 if err := runNamedQueries(cfg, listfunc, response, packagesNamed); err != nil { 171 return nil, err 172 } 173 } 174 175 modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response.dr) 176 if err != nil { 177 return nil, err 178 } 179 if len(containFiles) > 0 { 180 containsCandidates = append(containsCandidates, modifiedPkgs...) 181 containsCandidates = append(containsCandidates, needPkgs...) 182 } 183 184 if len(needPkgs) > 0 { 185 addNeededOverlayPackages(cfg, listfunc, response, needPkgs) 186 if err != nil { 187 return nil, err 188 } 189 } 190 // Check candidate packages for containFiles. 191 if len(containFiles) > 0 { 192 for _, id := range containsCandidates { 193 pkg := response.seenPackages[id] 194 for _, f := range containFiles { 195 for _, g := range pkg.GoFiles { 196 if sameFile(f, g) { 197 response.addRoot(id) 198 } 199 } 200 } 201 } 202 } 203 204 return response.dr, nil 205} 206 207func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error { 208 dr, err := driver(cfg, pkgs...) 209 if err != nil { 210 return err 211 } 212 for _, pkg := range dr.Packages { 213 response.addPackage(pkg) 214 } 215 return nil 216} 217 218func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *responseDeduper, queries []string) error { 219 for _, query := range queries { 220 // TODO(matloob): Do only one query per directory. 221 fdir := filepath.Dir(query) 222 // Pass absolute path of directory to go list so that it knows to treat it as a directory, 223 // not a package path. 224 pattern, err := filepath.Abs(fdir) 225 if err != nil { 226 return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) 227 } 228 if isFallback { 229 pattern = "." 230 cfg.Dir = fdir 231 } 232 233 dirResponse, err := driver(cfg, pattern) 234 if err != nil { 235 return err 236 } 237 isRoot := make(map[string]bool, len(dirResponse.Roots)) 238 for _, root := range dirResponse.Roots { 239 isRoot[root] = true 240 } 241 for _, pkg := range dirResponse.Packages { 242 // Add any new packages to the main set 243 // We don't bother to filter packages that will be dropped by the changes of roots, 244 // that will happen anyway during graph construction outside this function. 245 // Over-reporting packages is not a problem. 246 response.addPackage(pkg) 247 // if the package was not a root one, it cannot have the file 248 if !isRoot[pkg.ID] { 249 continue 250 } 251 for _, pkgFile := range pkg.GoFiles { 252 if filepath.Base(query) == filepath.Base(pkgFile) { 253 response.addRoot(pkg.ID) 254 break 255 } 256 } 257 } 258 } 259 return nil 260} 261 262// modCacheRegexp splits a path in a module cache into module, module version, and package. 263var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) 264 265func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { 266 // calling `go env` isn't free; bail out if there's nothing to do. 267 if len(queries) == 0 { 268 return nil 269 } 270 // Determine which directories are relevant to scan. 271 roots, modRoot, err := roots(cfg) 272 if err != nil { 273 return err 274 } 275 276 // Scan the selected directories. Simple matches, from GOPATH/GOROOT 277 // or the local module, can simply be "go list"ed. Matches from the 278 // module cache need special treatment. 279 var matchesMu sync.Mutex 280 var simpleMatches, modCacheMatches []string 281 add := func(root gopathwalk.Root, dir string) { 282 // Walk calls this concurrently; protect the result slices. 283 matchesMu.Lock() 284 defer matchesMu.Unlock() 285 286 path := dir 287 if dir != root.Path { 288 path = dir[len(root.Path)+1:] 289 } 290 if pathMatchesQueries(path, queries) { 291 switch root.Type { 292 case gopathwalk.RootModuleCache: 293 modCacheMatches = append(modCacheMatches, path) 294 case gopathwalk.RootCurrentModule: 295 // We'd need to read go.mod to find the full 296 // import path. Relative's easier. 297 rel, err := filepath.Rel(cfg.Dir, dir) 298 if err != nil { 299 // This ought to be impossible, since 300 // we found dir in the current module. 301 panic(err) 302 } 303 simpleMatches = append(simpleMatches, "./"+rel) 304 case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT: 305 simpleMatches = append(simpleMatches, path) 306 } 307 } 308 } 309 310 startWalk := time.Now() 311 gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug}) 312 if debug { 313 log.Printf("%v for walk", time.Since(startWalk)) 314 } 315 316 // Weird special case: the top-level package in a module will be in 317 // whatever directory the user checked the repository out into. It's 318 // more reasonable for that to not match the package name. So, if there 319 // are any Go files in the mod root, query it just to be safe. 320 if modRoot != "" { 321 rel, err := filepath.Rel(cfg.Dir, modRoot) 322 if err != nil { 323 panic(err) // See above. 324 } 325 326 files, err := ioutil.ReadDir(modRoot) 327 for _, f := range files { 328 if strings.HasSuffix(f.Name(), ".go") { 329 simpleMatches = append(simpleMatches, rel) 330 break 331 } 332 } 333 } 334 335 addResponse := func(r *driverResponse) { 336 for _, pkg := range r.Packages { 337 response.addPackage(pkg) 338 for _, name := range queries { 339 if pkg.Name == name { 340 response.addRoot(pkg.ID) 341 break 342 } 343 } 344 } 345 } 346 347 if len(simpleMatches) != 0 { 348 resp, err := driver(cfg, simpleMatches...) 349 if err != nil { 350 return err 351 } 352 addResponse(resp) 353 } 354 355 // Module cache matches are tricky. We want to avoid downloading new 356 // versions of things, so we need to use the ones present in the cache. 357 // go list doesn't accept version specifiers, so we have to write out a 358 // temporary module, and do the list in that module. 359 if len(modCacheMatches) != 0 { 360 // Collect all the matches, deduplicating by major version 361 // and preferring the newest. 362 type modInfo struct { 363 mod string 364 major string 365 } 366 mods := make(map[modInfo]string) 367 var imports []string 368 for _, modPath := range modCacheMatches { 369 matches := modCacheRegexp.FindStringSubmatch(modPath) 370 mod, ver := filepath.ToSlash(matches[1]), matches[2] 371 importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3])) 372 373 major := semver.Major(ver) 374 if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 { 375 mods[modInfo{mod, major}] = ver 376 } 377 378 imports = append(imports, importPath) 379 } 380 381 // Build the temporary module. 382 var gomod bytes.Buffer 383 gomod.WriteString("module modquery\nrequire (\n") 384 for mod, version := range mods { 385 gomod.WriteString("\t" + mod.mod + " " + version + "\n") 386 } 387 gomod.WriteString(")\n") 388 389 tmpCfg := *cfg 390 391 // We're only trying to look at stuff in the module cache, so 392 // disable the network. This should speed things up, and has 393 // prevented errors in at least one case, #28518. 394 tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...)) 395 396 var err error 397 tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") 398 if err != nil { 399 return err 400 } 401 defer os.RemoveAll(tmpCfg.Dir) 402 403 if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil { 404 return fmt.Errorf("writing go.mod for module cache query: %v", err) 405 } 406 407 // Run the query, using the import paths calculated from the matches above. 408 resp, err := driver(&tmpCfg, imports...) 409 if err != nil { 410 return fmt.Errorf("querying module cache matches: %v", err) 411 } 412 addResponse(resp) 413 } 414 415 return nil 416} 417 418func getSizes(cfg *Config) (types.Sizes, error) { 419 return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) 420} 421 422// roots selects the appropriate paths to walk based on the passed-in configuration, 423// particularly the environment and the presence of a go.mod in cfg.Dir's parents. 424func roots(cfg *Config) ([]gopathwalk.Root, string, error) { 425 stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD") 426 if err != nil { 427 return nil, "", err 428 } 429 430 fields := strings.Split(stdout.String(), "\n") 431 if len(fields) != 4 || len(fields[3]) != 0 { 432 return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String()) 433 } 434 goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2] 435 var modDir string 436 if gomod != "" { 437 modDir = filepath.Dir(gomod) 438 } 439 440 var roots []gopathwalk.Root 441 // Always add GOROOT. 442 roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT}) 443 // If modules are enabled, scan the module dir. 444 if modDir != "" { 445 roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule}) 446 } 447 // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. 448 for _, p := range gopath { 449 if modDir != "" { 450 roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache}) 451 } else { 452 roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH}) 453 } 454 } 455 456 return roots, modDir, nil 457} 458 459// These functions were copied from goimports. See further documentation there. 460 461// pathMatchesQueries is adapted from pkgIsCandidate. 462// TODO: is it reasonable to do Contains here, rather than an exact match on a path component? 463func pathMatchesQueries(path string, queries []string) bool { 464 lastTwo := lastTwoComponents(path) 465 for _, query := range queries { 466 if strings.Contains(lastTwo, query) { 467 return true 468 } 469 if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) { 470 lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) 471 if strings.Contains(lastTwo, query) { 472 return true 473 } 474 } 475 } 476 return false 477} 478 479// lastTwoComponents returns at most the last two path components 480// of v, using either / or \ as the path separator. 481func lastTwoComponents(v string) string { 482 nslash := 0 483 for i := len(v) - 1; i >= 0; i-- { 484 if v[i] == '/' || v[i] == '\\' { 485 nslash++ 486 if nslash == 2 { 487 return v[i:] 488 } 489 } 490 } 491 return v 492} 493 494func hasHyphenOrUpperASCII(s string) bool { 495 for i := 0; i < len(s); i++ { 496 b := s[i] 497 if b == '-' || ('A' <= b && b <= 'Z') { 498 return true 499 } 500 } 501 return false 502} 503 504func lowerASCIIAndRemoveHyphen(s string) (ret string) { 505 buf := make([]byte, 0, len(s)) 506 for i := 0; i < len(s); i++ { 507 b := s[i] 508 switch { 509 case b == '-': 510 continue 511 case 'A' <= b && b <= 'Z': 512 buf = append(buf, b+('a'-'A')) 513 default: 514 buf = append(buf, b) 515 } 516 } 517 return string(buf) 518} 519 520// Fields must match go list; 521// see $GOROOT/src/cmd/go/internal/load/pkg.go. 522type jsonPackage struct { 523 ImportPath string 524 Dir string 525 Name string 526 Export string 527 GoFiles []string 528 CompiledGoFiles []string 529 CFiles []string 530 CgoFiles []string 531 CXXFiles []string 532 MFiles []string 533 HFiles []string 534 FFiles []string 535 SFiles []string 536 SwigFiles []string 537 SwigCXXFiles []string 538 SysoFiles []string 539 Imports []string 540 ImportMap map[string]string 541 Deps []string 542 TestGoFiles []string 543 TestImports []string 544 XTestGoFiles []string 545 XTestImports []string 546 ForTest string // q in a "p [q.test]" package, else "" 547 DepOnly bool 548 549 Error *jsonPackageError 550} 551 552type jsonPackageError struct { 553 ImportStack []string 554 Pos string 555 Err string 556} 557 558func otherFiles(p *jsonPackage) [][]string { 559 return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} 560} 561 562// golistDriverCurrent uses the "go list" command to expand the 563// pattern words and return metadata for the specified packages. 564// dir may be "" and env may be nil, as per os/exec.Command. 565func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) { 566 // go list uses the following identifiers in ImportPath and Imports: 567 // 568 // "p" -- importable package or main (command) 569 // "q.test" -- q's test executable 570 // "p [q.test]" -- variant of p as built for q's test executable 571 // "q_test [q.test]" -- q's external test package 572 // 573 // The packages p that are built differently for a test q.test 574 // are q itself, plus any helpers used by the external test q_test, 575 // typically including "testing" and all its dependencies. 576 577 // Run "go list" for complete 578 // information on the specified packages. 579 buf, err := invokeGo(cfg, golistargs(cfg, words)...) 580 if err != nil { 581 return nil, err 582 } 583 seen := make(map[string]*jsonPackage) 584 // Decode the JSON and convert it to Package form. 585 var response driverResponse 586 for dec := json.NewDecoder(buf); dec.More(); { 587 p := new(jsonPackage) 588 if err := dec.Decode(p); err != nil { 589 return nil, fmt.Errorf("JSON decoding failed: %v", err) 590 } 591 592 if p.ImportPath == "" { 593 // The documentation for go list says that “[e]rroneous packages will have 594 // a non-empty ImportPath”. If for some reason it comes back empty, we 595 // prefer to error out rather than silently discarding data or handing 596 // back a package without any way to refer to it. 597 if p.Error != nil { 598 return nil, Error{ 599 Pos: p.Error.Pos, 600 Msg: p.Error.Err, 601 } 602 } 603 return nil, fmt.Errorf("package missing import path: %+v", p) 604 } 605 606 if old, found := seen[p.ImportPath]; found { 607 if !reflect.DeepEqual(p, old) { 608 return nil, fmt.Errorf("go list repeated package %v with different values", p.ImportPath) 609 } 610 // skip the duplicate 611 continue 612 } 613 seen[p.ImportPath] = p 614 615 pkg := &Package{ 616 Name: p.Name, 617 ID: p.ImportPath, 618 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), 619 CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), 620 OtherFiles: absJoin(p.Dir, otherFiles(p)...), 621 } 622 623 // Work around https://golang.org/issue/28749: 624 // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. 625 // Filter out any elements of CompiledGoFiles that are also in OtherFiles. 626 // We have to keep this workaround in place until go1.12 is a distant memory. 627 if len(pkg.OtherFiles) > 0 { 628 other := make(map[string]bool, len(pkg.OtherFiles)) 629 for _, f := range pkg.OtherFiles { 630 other[f] = true 631 } 632 633 out := pkg.CompiledGoFiles[:0] 634 for _, f := range pkg.CompiledGoFiles { 635 if other[f] { 636 continue 637 } 638 out = append(out, f) 639 } 640 pkg.CompiledGoFiles = out 641 } 642 643 // Extract the PkgPath from the package's ID. 644 if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { 645 pkg.PkgPath = pkg.ID[:i] 646 } else { 647 pkg.PkgPath = pkg.ID 648 } 649 650 if pkg.PkgPath == "unsafe" { 651 pkg.GoFiles = nil // ignore fake unsafe.go file 652 } 653 654 // Assume go list emits only absolute paths for Dir. 655 if p.Dir != "" && !filepath.IsAbs(p.Dir) { 656 log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir) 657 } 658 659 if p.Export != "" && !filepath.IsAbs(p.Export) { 660 pkg.ExportFile = filepath.Join(p.Dir, p.Export) 661 } else { 662 pkg.ExportFile = p.Export 663 } 664 665 // imports 666 // 667 // Imports contains the IDs of all imported packages. 668 // ImportsMap records (path, ID) only where they differ. 669 ids := make(map[string]bool) 670 for _, id := range p.Imports { 671 ids[id] = true 672 } 673 pkg.Imports = make(map[string]*Package) 674 for path, id := range p.ImportMap { 675 pkg.Imports[path] = &Package{ID: id} // non-identity import 676 delete(ids, id) 677 } 678 for id := range ids { 679 if id == "C" { 680 continue 681 } 682 683 pkg.Imports[id] = &Package{ID: id} // identity import 684 } 685 if !p.DepOnly { 686 response.Roots = append(response.Roots, pkg.ID) 687 } 688 689 // Work around for pre-go.1.11 versions of go list. 690 // TODO(matloob): they should be handled by the fallback. 691 // Can we delete this? 692 if len(pkg.CompiledGoFiles) == 0 { 693 pkg.CompiledGoFiles = pkg.GoFiles 694 } 695 696 if p.Error != nil { 697 pkg.Errors = append(pkg.Errors, Error{ 698 Pos: p.Error.Pos, 699 Msg: p.Error.Err, 700 }) 701 } 702 703 response.Packages = append(response.Packages, pkg) 704 } 705 706 return &response, nil 707} 708 709// absJoin absolutizes and flattens the lists of files. 710func absJoin(dir string, fileses ...[]string) (res []string) { 711 for _, files := range fileses { 712 for _, file := range files { 713 if !filepath.IsAbs(file) { 714 file = filepath.Join(dir, file) 715 } 716 res = append(res, file) 717 } 718 } 719 return res 720} 721 722func golistargs(cfg *Config, words []string) []string { 723 fullargs := []string{ 724 "list", "-e", "-json", "-compiled", 725 fmt.Sprintf("-test=%t", cfg.Tests), 726 fmt.Sprintf("-export=%t", usesExportData(cfg)), 727 fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports), 728 // go list doesn't let you pass -test and -find together, 729 // probably because you'd just get the TestMain. 730 fmt.Sprintf("-find=%t", cfg.Mode < LoadImports && !cfg.Tests), 731 } 732 fullargs = append(fullargs, cfg.BuildFlags...) 733 fullargs = append(fullargs, "--") 734 fullargs = append(fullargs, words...) 735 return fullargs 736} 737 738// invokeGo returns the stdout of a go command invocation. 739func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { 740 stdout := new(bytes.Buffer) 741 stderr := new(bytes.Buffer) 742 cmd := exec.CommandContext(cfg.Context, "go", args...) 743 // On darwin the cwd gets resolved to the real path, which breaks anything that 744 // expects the working directory to keep the original path, including the 745 // go command when dealing with modules. 746 // The Go stdlib has a special feature where if the cwd and the PWD are the 747 // same node then it trusts the PWD, so by setting it in the env for the child 748 // process we fix up all the paths returned by the go command. 749 cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) 750 cmd.Dir = cfg.Dir 751 cmd.Stdout = stdout 752 cmd.Stderr = stderr 753 if debug { 754 defer func(start time.Time) { 755 log.Printf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr) 756 }(time.Now()) 757 } 758 759 if err := cmd.Run(); err != nil { 760 exitErr, ok := err.(*exec.ExitError) 761 if !ok { 762 // Catastrophic error: 763 // - executable not found 764 // - context cancellation 765 return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) 766 } 767 768 // Old go version? 769 if strings.Contains(stderr.String(), "flag provided but not defined") { 770 return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} 771 } 772 773 // Export mode entails a build. 774 // If that build fails, errors appear on stderr 775 // (despite the -e flag) and the Export field is blank. 776 // Do not fail in that case. 777 // The same is true if an ad-hoc package given to go list doesn't exist. 778 // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when 779 // packages don't exist or a build fails. 780 if !usesExportData(cfg) && !containsGoFile(args) { 781 return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) 782 } 783 } 784 785 // As of writing, go list -export prints some non-fatal compilation 786 // errors to stderr, even with -e set. We would prefer that it put 787 // them in the Package.Error JSON (see https://golang.org/issue/26319). 788 // In the meantime, there's nowhere good to put them, but they can 789 // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS 790 // is set. 791 if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { 792 fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) 793 } 794 795 // debugging 796 if false { 797 fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout) 798 } 799 800 return stdout, nil 801} 802 803func containsGoFile(s []string) bool { 804 for _, f := range s { 805 if strings.HasSuffix(f, ".go") { 806 return true 807 } 808 } 809 return false 810} 811 812func cmdDebugStr(cmd *exec.Cmd, args ...string) string { 813 env := make(map[string]string) 814 for _, kv := range cmd.Env { 815 split := strings.Split(kv, "=") 816 k, v := split[0], split[1] 817 env[k] = v 818 } 819 var quotedArgs []string 820 for _, arg := range args { 821 quotedArgs = append(quotedArgs, strconv.Quote(arg)) 822 } 823 824 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " ")) 825} 826