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 "context" 10 "encoding/json" 11 "fmt" 12 "go/types" 13 "log" 14 "os" 15 "os/exec" 16 "path" 17 "path/filepath" 18 "reflect" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "time" 24 "unicode" 25 26 "golang.org/x/tools/go/internal/packagesdriver" 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 45func newDeduper() *responseDeduper { 46 return &responseDeduper{ 47 dr: &driverResponse{}, 48 seenRoots: map[string]bool{}, 49 seenPackages: map[string]*Package{}, 50 } 51} 52 53// addAll fills in r with a driverResponse. 54func (r *responseDeduper) addAll(dr *driverResponse) { 55 for _, pkg := range dr.Packages { 56 r.addPackage(pkg) 57 } 58 for _, root := range dr.Roots { 59 r.addRoot(root) 60 } 61} 62 63func (r *responseDeduper) addPackage(p *Package) { 64 if r.seenPackages[p.ID] != nil { 65 return 66 } 67 r.seenPackages[p.ID] = p 68 r.dr.Packages = append(r.dr.Packages, p) 69} 70 71func (r *responseDeduper) addRoot(id string) { 72 if r.seenRoots[id] { 73 return 74 } 75 r.seenRoots[id] = true 76 r.dr.Roots = append(r.dr.Roots, id) 77} 78 79type golistState struct { 80 cfg *Config 81 ctx context.Context 82 83 envOnce sync.Once 84 goEnvError error 85 goEnv map[string]string 86 87 rootsOnce sync.Once 88 rootDirsError error 89 rootDirs map[string]string 90 91 // vendorDirs caches the (non)existence of vendor directories. 92 vendorDirs map[string]bool 93} 94 95// getEnv returns Go environment variables. Only specific variables are 96// populated -- computing all of them is slow. 97func (state *golistState) getEnv() (map[string]string, error) { 98 state.envOnce.Do(func() { 99 var b *bytes.Buffer 100 b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH") 101 if state.goEnvError != nil { 102 return 103 } 104 105 state.goEnv = make(map[string]string) 106 decoder := json.NewDecoder(b) 107 if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil { 108 return 109 } 110 }) 111 return state.goEnv, state.goEnvError 112} 113 114// mustGetEnv is a convenience function that can be used if getEnv has already succeeded. 115func (state *golistState) mustGetEnv() map[string]string { 116 env, err := state.getEnv() 117 if err != nil { 118 panic(fmt.Sprintf("mustGetEnv: %v", err)) 119 } 120 return env 121} 122 123// goListDriver uses the go list command to interpret the patterns and produce 124// the build system package structure. 125// See driver for more details. 126func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { 127 // Make sure that any asynchronous go commands are killed when we return. 128 parentCtx := cfg.Context 129 if parentCtx == nil { 130 parentCtx = context.Background() 131 } 132 ctx, cancel := context.WithCancel(parentCtx) 133 defer cancel() 134 135 response := newDeduper() 136 137 // Fill in response.Sizes asynchronously if necessary. 138 var sizeserr error 139 var sizeswg sync.WaitGroup 140 if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { 141 sizeswg.Add(1) 142 go func() { 143 var sizes types.Sizes 144 sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) 145 // types.SizesFor always returns nil or a *types.StdSizes. 146 response.dr.Sizes, _ = sizes.(*types.StdSizes) 147 sizeswg.Done() 148 }() 149 } 150 151 state := &golistState{ 152 cfg: cfg, 153 ctx: ctx, 154 vendorDirs: map[string]bool{}, 155 } 156 157 // Determine files requested in contains patterns 158 var containFiles []string 159 restPatterns := make([]string, 0, len(patterns)) 160 // Extract file= and other [querytype]= patterns. Report an error if querytype 161 // doesn't exist. 162extractQueries: 163 for _, pattern := range patterns { 164 eqidx := strings.Index(pattern, "=") 165 if eqidx < 0 { 166 restPatterns = append(restPatterns, pattern) 167 } else { 168 query, value := pattern[:eqidx], pattern[eqidx+len("="):] 169 switch query { 170 case "file": 171 containFiles = append(containFiles, value) 172 case "pattern": 173 restPatterns = append(restPatterns, value) 174 case "": // not a reserved query 175 restPatterns = append(restPatterns, pattern) 176 default: 177 for _, rune := range query { 178 if rune < 'a' || rune > 'z' { // not a reserved query 179 restPatterns = append(restPatterns, pattern) 180 continue extractQueries 181 } 182 } 183 // Reject all other patterns containing "=" 184 return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) 185 } 186 } 187 } 188 189 // See if we have any patterns to pass through to go list. Zero initial 190 // patterns also requires a go list call, since it's the equivalent of 191 // ".". 192 if len(restPatterns) > 0 || len(patterns) == 0 { 193 dr, err := state.createDriverResponse(restPatterns...) 194 if err != nil { 195 return nil, err 196 } 197 response.addAll(dr) 198 } 199 200 if len(containFiles) != 0 { 201 if err := state.runContainsQueries(response, containFiles); err != nil { 202 return nil, err 203 } 204 } 205 206 modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) 207 if err != nil { 208 return nil, err 209 } 210 211 var containsCandidates []string 212 if len(containFiles) > 0 { 213 containsCandidates = append(containsCandidates, modifiedPkgs...) 214 containsCandidates = append(containsCandidates, needPkgs...) 215 } 216 if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { 217 return nil, err 218 } 219 // Check candidate packages for containFiles. 220 if len(containFiles) > 0 { 221 for _, id := range containsCandidates { 222 pkg, ok := response.seenPackages[id] 223 if !ok { 224 response.addPackage(&Package{ 225 ID: id, 226 Errors: []Error{ 227 { 228 Kind: ListError, 229 Msg: fmt.Sprintf("package %s expected but not seen", id), 230 }, 231 }, 232 }) 233 continue 234 } 235 for _, f := range containFiles { 236 for _, g := range pkg.GoFiles { 237 if sameFile(f, g) { 238 response.addRoot(id) 239 } 240 } 241 } 242 } 243 } 244 245 sizeswg.Wait() 246 if sizeserr != nil { 247 return nil, sizeserr 248 } 249 return response.dr, nil 250} 251 252func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { 253 if len(pkgs) == 0 { 254 return nil 255 } 256 dr, err := state.createDriverResponse(pkgs...) 257 if err != nil { 258 return err 259 } 260 for _, pkg := range dr.Packages { 261 response.addPackage(pkg) 262 } 263 _, needPkgs, err := state.processGolistOverlay(response) 264 if err != nil { 265 return err 266 } 267 return state.addNeededOverlayPackages(response, needPkgs) 268} 269 270func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { 271 for _, query := range queries { 272 // TODO(matloob): Do only one query per directory. 273 fdir := filepath.Dir(query) 274 // Pass absolute path of directory to go list so that it knows to treat it as a directory, 275 // not a package path. 276 pattern, err := filepath.Abs(fdir) 277 if err != nil { 278 return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) 279 } 280 dirResponse, err := state.createDriverResponse(pattern) 281 282 // If there was an error loading the package, or the package is returned 283 // with errors, try to load the file as an ad-hoc package. 284 // Usually the error will appear in a returned package, but may not if we're 285 // in module mode and the ad-hoc is located outside a module. 286 if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && 287 len(dirResponse.Packages[0].Errors) == 1 { 288 var queryErr error 289 if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { 290 return err // return the original error 291 } 292 } 293 isRoot := make(map[string]bool, len(dirResponse.Roots)) 294 for _, root := range dirResponse.Roots { 295 isRoot[root] = true 296 } 297 for _, pkg := range dirResponse.Packages { 298 // Add any new packages to the main set 299 // We don't bother to filter packages that will be dropped by the changes of roots, 300 // that will happen anyway during graph construction outside this function. 301 // Over-reporting packages is not a problem. 302 response.addPackage(pkg) 303 // if the package was not a root one, it cannot have the file 304 if !isRoot[pkg.ID] { 305 continue 306 } 307 for _, pkgFile := range pkg.GoFiles { 308 if filepath.Base(query) == filepath.Base(pkgFile) { 309 response.addRoot(pkg.ID) 310 break 311 } 312 } 313 } 314 } 315 return nil 316} 317 318// adhocPackage attempts to load or construct an ad-hoc package for a given 319// query, if the original call to the driver produced inadequate results. 320func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { 321 response, err := state.createDriverResponse(query) 322 if err != nil { 323 return nil, err 324 } 325 // If we get nothing back from `go list`, 326 // try to make this file into its own ad-hoc package. 327 // TODO(rstambler): Should this check against the original response? 328 if len(response.Packages) == 0 { 329 response.Packages = append(response.Packages, &Package{ 330 ID: "command-line-arguments", 331 PkgPath: query, 332 GoFiles: []string{query}, 333 CompiledGoFiles: []string{query}, 334 Imports: make(map[string]*Package), 335 }) 336 response.Roots = append(response.Roots, "command-line-arguments") 337 } 338 // Handle special cases. 339 if len(response.Packages) == 1 { 340 // golang/go#33482: If this is a file= query for ad-hoc packages where 341 // the file only exists on an overlay, and exists outside of a module, 342 // add the file to the package and remove the errors. 343 if response.Packages[0].ID == "command-line-arguments" || 344 filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) { 345 if len(response.Packages[0].GoFiles) == 0 { 346 filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath 347 // TODO(matloob): check if the file is outside of a root dir? 348 for path := range state.cfg.Overlay { 349 if path == filename { 350 response.Packages[0].Errors = nil 351 response.Packages[0].GoFiles = []string{path} 352 response.Packages[0].CompiledGoFiles = []string{path} 353 } 354 } 355 } 356 } 357 } 358 return response, nil 359} 360 361// Fields must match go list; 362// see $GOROOT/src/cmd/go/internal/load/pkg.go. 363type jsonPackage struct { 364 ImportPath string 365 Dir string 366 Name string 367 Export string 368 GoFiles []string 369 CompiledGoFiles []string 370 CFiles []string 371 CgoFiles []string 372 CXXFiles []string 373 MFiles []string 374 HFiles []string 375 FFiles []string 376 SFiles []string 377 SwigFiles []string 378 SwigCXXFiles []string 379 SysoFiles []string 380 Imports []string 381 ImportMap map[string]string 382 Deps []string 383 TestGoFiles []string 384 TestImports []string 385 XTestGoFiles []string 386 XTestImports []string 387 ForTest string // q in a "p [q.test]" package, else "" 388 DepOnly bool 389 390 Error *jsonPackageError 391} 392 393type jsonPackageError struct { 394 ImportStack []string 395 Pos string 396 Err string 397} 398 399func otherFiles(p *jsonPackage) [][]string { 400 return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} 401} 402 403// createDriverResponse uses the "go list" command to expand the pattern 404// words and return a response for the specified packages. 405func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { 406 // go list uses the following identifiers in ImportPath and Imports: 407 // 408 // "p" -- importable package or main (command) 409 // "q.test" -- q's test executable 410 // "p [q.test]" -- variant of p as built for q's test executable 411 // "q_test [q.test]" -- q's external test package 412 // 413 // The packages p that are built differently for a test q.test 414 // are q itself, plus any helpers used by the external test q_test, 415 // typically including "testing" and all its dependencies. 416 417 // Run "go list" for complete 418 // information on the specified packages. 419 buf, err := state.invokeGo("list", golistargs(state.cfg, words)...) 420 if err != nil { 421 return nil, err 422 } 423 seen := make(map[string]*jsonPackage) 424 pkgs := make(map[string]*Package) 425 additionalErrors := make(map[string][]Error) 426 // Decode the JSON and convert it to Package form. 427 var response driverResponse 428 for dec := json.NewDecoder(buf); dec.More(); { 429 p := new(jsonPackage) 430 if err := dec.Decode(p); err != nil { 431 return nil, fmt.Errorf("JSON decoding failed: %v", err) 432 } 433 434 if p.ImportPath == "" { 435 // The documentation for go list says that “[e]rroneous packages will have 436 // a non-empty ImportPath”. If for some reason it comes back empty, we 437 // prefer to error out rather than silently discarding data or handing 438 // back a package without any way to refer to it. 439 if p.Error != nil { 440 return nil, Error{ 441 Pos: p.Error.Pos, 442 Msg: p.Error.Err, 443 } 444 } 445 return nil, fmt.Errorf("package missing import path: %+v", p) 446 } 447 448 // Work around https://golang.org/issue/33157: 449 // go list -e, when given an absolute path, will find the package contained at 450 // that directory. But when no package exists there, it will return a fake package 451 // with an error and the ImportPath set to the absolute path provided to go list. 452 // Try to convert that absolute path to what its package path would be if it's 453 // contained in a known module or GOPATH entry. This will allow the package to be 454 // properly "reclaimed" when overlays are processed. 455 if filepath.IsAbs(p.ImportPath) && p.Error != nil { 456 pkgPath, ok, err := state.getPkgPath(p.ImportPath) 457 if err != nil { 458 return nil, err 459 } 460 if ok { 461 p.ImportPath = pkgPath 462 } 463 } 464 465 if old, found := seen[p.ImportPath]; found { 466 // If one version of the package has an error, and the other doesn't, assume 467 // that this is a case where go list is reporting a fake dependency variant 468 // of the imported package: When a package tries to invalidly import another 469 // package, go list emits a variant of the imported package (with the same 470 // import path, but with an error on it, and the package will have a 471 // DepError set on it). An example of when this can happen is for imports of 472 // main packages: main packages can not be imported, but they may be 473 // separately matched and listed by another pattern. 474 // See golang.org/issue/36188 for more details. 475 476 // The plan is that eventually, hopefully in Go 1.15, the error will be 477 // reported on the importing package rather than the duplicate "fake" 478 // version of the imported package. Once all supported versions of Go 479 // have the new behavior this logic can be deleted. 480 // TODO(matloob): delete the workaround logic once all supported versions of 481 // Go return the errors on the proper package. 482 483 // There should be exactly one version of a package that doesn't have an 484 // error. 485 if old.Error == nil && p.Error == nil { 486 if !reflect.DeepEqual(p, old) { 487 return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) 488 } 489 continue 490 } 491 492 // Determine if this package's error needs to be bubbled up. 493 // This is a hack, and we expect for go list to eventually set the error 494 // on the package. 495 if old.Error != nil { 496 var errkind string 497 if strings.Contains(old.Error.Err, "not an importable package") { 498 errkind = "not an importable package" 499 } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") { 500 errkind = "use of internal package not allowed" 501 } 502 if errkind != "" { 503 if len(old.Error.ImportStack) < 2 { 504 return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack with fewer than two elements`, errkind) 505 } 506 importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-2] 507 additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ 508 Pos: old.Error.Pos, 509 Msg: old.Error.Err, 510 Kind: ListError, 511 }) 512 } 513 } 514 515 // Make sure that if there's a version of the package without an error, 516 // that's the one reported to the user. 517 if old.Error == nil { 518 continue 519 } 520 521 // This package will replace the old one at the end of the loop. 522 } 523 seen[p.ImportPath] = p 524 525 pkg := &Package{ 526 Name: p.Name, 527 ID: p.ImportPath, 528 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), 529 CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), 530 OtherFiles: absJoin(p.Dir, otherFiles(p)...), 531 forTest: p.ForTest, 532 } 533 534 // Work around https://golang.org/issue/28749: 535 // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. 536 // Filter out any elements of CompiledGoFiles that are also in OtherFiles. 537 // We have to keep this workaround in place until go1.12 is a distant memory. 538 if len(pkg.OtherFiles) > 0 { 539 other := make(map[string]bool, len(pkg.OtherFiles)) 540 for _, f := range pkg.OtherFiles { 541 other[f] = true 542 } 543 544 out := pkg.CompiledGoFiles[:0] 545 for _, f := range pkg.CompiledGoFiles { 546 if other[f] { 547 continue 548 } 549 out = append(out, f) 550 } 551 pkg.CompiledGoFiles = out 552 } 553 554 // Extract the PkgPath from the package's ID. 555 if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { 556 pkg.PkgPath = pkg.ID[:i] 557 } else { 558 pkg.PkgPath = pkg.ID 559 } 560 561 if pkg.PkgPath == "unsafe" { 562 pkg.GoFiles = nil // ignore fake unsafe.go file 563 } 564 565 // Assume go list emits only absolute paths for Dir. 566 if p.Dir != "" && !filepath.IsAbs(p.Dir) { 567 log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir) 568 } 569 570 if p.Export != "" && !filepath.IsAbs(p.Export) { 571 pkg.ExportFile = filepath.Join(p.Dir, p.Export) 572 } else { 573 pkg.ExportFile = p.Export 574 } 575 576 // imports 577 // 578 // Imports contains the IDs of all imported packages. 579 // ImportsMap records (path, ID) only where they differ. 580 ids := make(map[string]bool) 581 for _, id := range p.Imports { 582 ids[id] = true 583 } 584 pkg.Imports = make(map[string]*Package) 585 for path, id := range p.ImportMap { 586 pkg.Imports[path] = &Package{ID: id} // non-identity import 587 delete(ids, id) 588 } 589 for id := range ids { 590 if id == "C" { 591 continue 592 } 593 594 pkg.Imports[id] = &Package{ID: id} // identity import 595 } 596 if !p.DepOnly { 597 response.Roots = append(response.Roots, pkg.ID) 598 } 599 600 // Work around for pre-go.1.11 versions of go list. 601 // TODO(matloob): they should be handled by the fallback. 602 // Can we delete this? 603 if len(pkg.CompiledGoFiles) == 0 { 604 pkg.CompiledGoFiles = pkg.GoFiles 605 } 606 607 if p.Error != nil { 608 msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. 609 // Address golang.org/issue/35964 by appending import stack to error message. 610 if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 { 611 msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack) 612 } 613 pkg.Errors = append(pkg.Errors, Error{ 614 Pos: p.Error.Pos, 615 Msg: msg, 616 Kind: ListError, 617 }) 618 } 619 620 pkgs[pkg.ID] = pkg 621 } 622 623 for id, errs := range additionalErrors { 624 if p, ok := pkgs[id]; ok { 625 p.Errors = append(p.Errors, errs...) 626 } 627 } 628 for _, pkg := range pkgs { 629 response.Packages = append(response.Packages, pkg) 630 } 631 sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) 632 633 return &response, nil 634} 635 636// getPkgPath finds the package path of a directory if it's relative to a root directory. 637func (state *golistState) getPkgPath(dir string) (string, bool, error) { 638 absDir, err := filepath.Abs(dir) 639 if err != nil { 640 return "", false, err 641 } 642 roots, err := state.determineRootDirs() 643 if err != nil { 644 return "", false, err 645 } 646 647 for rdir, rpath := range roots { 648 // Make sure that the directory is in the module, 649 // to avoid creating a path relative to another module. 650 if !strings.HasPrefix(absDir, rdir) { 651 continue 652 } 653 // TODO(matloob): This doesn't properly handle symlinks. 654 r, err := filepath.Rel(rdir, dir) 655 if err != nil { 656 continue 657 } 658 if rpath != "" { 659 // We choose only one root even though the directory even it can belong in multiple modules 660 // or GOPATH entries. This is okay because we only need to work with absolute dirs when a 661 // file is missing from disk, for instance when gopls calls go/packages in an overlay. 662 // Once the file is saved, gopls, or the next invocation of the tool will get the correct 663 // result straight from golist. 664 // TODO(matloob): Implement module tiebreaking? 665 return path.Join(rpath, filepath.ToSlash(r)), true, nil 666 } 667 return filepath.ToSlash(r), true, nil 668 } 669 return "", false, nil 670} 671 672// absJoin absolutizes and flattens the lists of files. 673func absJoin(dir string, fileses ...[]string) (res []string) { 674 for _, files := range fileses { 675 for _, file := range files { 676 if !filepath.IsAbs(file) { 677 file = filepath.Join(dir, file) 678 } 679 res = append(res, file) 680 } 681 } 682 return res 683} 684 685func golistargs(cfg *Config, words []string) []string { 686 const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo 687 fullargs := []string{ 688 "-e", "-json", 689 fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0), 690 fmt.Sprintf("-test=%t", cfg.Tests), 691 fmt.Sprintf("-export=%t", usesExportData(cfg)), 692 fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), 693 // go list doesn't let you pass -test and -find together, 694 // probably because you'd just get the TestMain. 695 fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0), 696 } 697 fullargs = append(fullargs, cfg.BuildFlags...) 698 fullargs = append(fullargs, "--") 699 fullargs = append(fullargs, words...) 700 return fullargs 701} 702 703// invokeGo returns the stdout of a go command invocation. 704func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { 705 cfg := state.cfg 706 707 stdout := new(bytes.Buffer) 708 stderr := new(bytes.Buffer) 709 goArgs := []string{verb} 710 if verb != "env" { 711 goArgs = append(goArgs, cfg.BuildFlags...) 712 } 713 goArgs = append(goArgs, args...) 714 cmd := exec.CommandContext(state.ctx, "go", goArgs...) 715 // On darwin the cwd gets resolved to the real path, which breaks anything that 716 // expects the working directory to keep the original path, including the 717 // go command when dealing with modules. 718 // The Go stdlib has a special feature where if the cwd and the PWD are the 719 // same node then it trusts the PWD, so by setting it in the env for the child 720 // process we fix up all the paths returned by the go command. 721 cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) 722 cmd.Dir = cfg.Dir 723 cmd.Stdout = stdout 724 cmd.Stderr = stderr 725 defer func(start time.Time) { 726 cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, goArgs...), stderr, stdout) 727 }(time.Now()) 728 729 if err := cmd.Run(); err != nil { 730 // Check for 'go' executable not being found. 731 if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 732 return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) 733 } 734 735 exitErr, ok := err.(*exec.ExitError) 736 if !ok { 737 // Catastrophic error: 738 // - context cancellation 739 return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) 740 } 741 742 // Old go version? 743 if strings.Contains(stderr.String(), "flag provided but not defined") { 744 return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} 745 } 746 747 // Related to #24854 748 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { 749 return nil, fmt.Errorf("%s", stderr.String()) 750 } 751 752 // Is there an error running the C compiler in cgo? This will be reported in the "Error" field 753 // and should be suppressed by go list -e. 754 // 755 // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. 756 isPkgPathRune := func(r rune) bool { 757 // From https://golang.org/ref/spec#Import_declarations: 758 // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings 759 // using only characters belonging to Unicode's L, M, N, P, and S general categories 760 // (the Graphic characters without spaces) and may also exclude the 761 // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. 762 return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && 763 !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) 764 } 765 if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { 766 msg := stderr.String()[len("# "):] 767 if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") { 768 return stdout, nil 769 } 770 // Treat pkg-config errors as a special case (golang.org/issue/36770). 771 if strings.HasPrefix(msg, "pkg-config") { 772 return stdout, nil 773 } 774 } 775 776 // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show 777 // the error in the Err section of stdout in case -e option is provided. 778 // This fix is provided for backwards compatibility. 779 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") { 780 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 781 strings.Trim(stderr.String(), "\n")) 782 return bytes.NewBufferString(output), nil 783 } 784 785 // Similar to the previous error, but currently lacks a fix in Go. 786 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") { 787 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 788 strings.Trim(stderr.String(), "\n")) 789 return bytes.NewBufferString(output), nil 790 } 791 792 // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. 793 // If the package doesn't exist, put the absolute path of the directory into the error message, 794 // as Go 1.13 list does. 795 const noSuchDirectory = "no such directory" 796 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) { 797 errstr := stderr.String() 798 abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):]) 799 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 800 abspath, strings.Trim(stderr.String(), "\n")) 801 return bytes.NewBufferString(output), nil 802 } 803 804 // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. 805 // Note that the error message we look for in this case is different that the one looked for above. 806 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") { 807 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 808 strings.Trim(stderr.String(), "\n")) 809 return bytes.NewBufferString(output), nil 810 } 811 812 // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a 813 // directory outside any module. 814 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") { 815 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 816 // TODO(matloob): command-line-arguments isn't correct here. 817 "command-line-arguments", strings.Trim(stderr.String(), "\n")) 818 return bytes.NewBufferString(output), nil 819 } 820 821 // Another variation of the previous error 822 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") { 823 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 824 // TODO(matloob): command-line-arguments isn't correct here. 825 "command-line-arguments", strings.Trim(stderr.String(), "\n")) 826 return bytes.NewBufferString(output), nil 827 } 828 829 // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit 830 // status if there's a dependency on a package that doesn't exist. But it should return 831 // a zero exit status and set an error on that package. 832 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { 833 // Don't clobber stdout if `go list` actually returned something. 834 if len(stdout.String()) > 0 { 835 return stdout, nil 836 } 837 // try to extract package name from string 838 stderrStr := stderr.String() 839 var importPath string 840 colon := strings.Index(stderrStr, ":") 841 if colon > 0 && strings.HasPrefix(stderrStr, "go build ") { 842 importPath = stderrStr[len("go build "):colon] 843 } 844 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 845 importPath, strings.Trim(stderrStr, "\n")) 846 return bytes.NewBufferString(output), nil 847 } 848 849 // Export mode entails a build. 850 // If that build fails, errors appear on stderr 851 // (despite the -e flag) and the Export field is blank. 852 // Do not fail in that case. 853 // The same is true if an ad-hoc package given to go list doesn't exist. 854 // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when 855 // packages don't exist or a build fails. 856 if !usesExportData(cfg) && !containsGoFile(args) { 857 return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) 858 } 859 } 860 861 // As of writing, go list -export prints some non-fatal compilation 862 // errors to stderr, even with -e set. We would prefer that it put 863 // them in the Package.Error JSON (see https://golang.org/issue/26319). 864 // In the meantime, there's nowhere good to put them, but they can 865 // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS 866 // is set. 867 if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { 868 fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) 869 } 870 return stdout, nil 871} 872 873func containsGoFile(s []string) bool { 874 for _, f := range s { 875 if strings.HasSuffix(f, ".go") { 876 return true 877 } 878 } 879 return false 880} 881 882func cmdDebugStr(cmd *exec.Cmd, args ...string) string { 883 env := make(map[string]string) 884 for _, kv := range cmd.Env { 885 split := strings.Split(kv, "=") 886 k, v := split[0], split[1] 887 env[k] = v 888 } 889 var quotedArgs []string 890 for _, arg := range args { 891 quotedArgs = append(quotedArgs, strconv.Quote(arg)) 892 } 893 894 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " ")) 895} 896