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