github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/packages/golist.go (about) 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 5 package packages 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "fmt" 12 "go/types" 13 "io/ioutil" 14 "log" 15 "os" 16 "path" 17 "path/filepath" 18 "reflect" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "unicode" 24 25 exec "golang.org/x/sys/execabs" 26 "golang.org/x/tools/go/internal/packagesdriver" 27 "golang.org/x/tools/internal/gocommand" 28 "golang.org/x/tools/internal/packagesinternal" 29 ) 30 31 // debug controls verbose logging. 32 var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG")) 33 34 // A goTooOldError reports that the go command 35 // found by exec.LookPath is too old to use the new go list behavior. 36 type goTooOldError struct { 37 error 38 } 39 40 // responseDeduper wraps a driverResponse, deduplicating its contents. 41 type responseDeduper struct { 42 seenRoots map[string]bool 43 seenPackages map[string]*Package 44 dr *driverResponse 45 } 46 47 func newDeduper() *responseDeduper { 48 return &responseDeduper{ 49 dr: &driverResponse{}, 50 seenRoots: map[string]bool{}, 51 seenPackages: map[string]*Package{}, 52 } 53 } 54 55 // addAll fills in r with a driverResponse. 56 func (r *responseDeduper) addAll(dr *driverResponse) { 57 for _, pkg := range dr.Packages { 58 r.addPackage(pkg) 59 } 60 for _, root := range dr.Roots { 61 r.addRoot(root) 62 } 63 r.dr.GoVersion = dr.GoVersion 64 } 65 66 func (r *responseDeduper) addPackage(p *Package) { 67 if r.seenPackages[p.ID] != nil { 68 return 69 } 70 r.seenPackages[p.ID] = p 71 r.dr.Packages = append(r.dr.Packages, p) 72 } 73 74 func (r *responseDeduper) addRoot(id string) { 75 if r.seenRoots[id] { 76 return 77 } 78 r.seenRoots[id] = true 79 r.dr.Roots = append(r.dr.Roots, id) 80 } 81 82 type golistState struct { 83 cfg *Config 84 ctx context.Context 85 86 envOnce sync.Once 87 goEnvError error 88 goEnv map[string]string 89 90 rootsOnce sync.Once 91 rootDirsError error 92 rootDirs map[string]string 93 94 goVersionOnce sync.Once 95 goVersionError error 96 goVersion int // The X in Go 1.X. 97 98 // vendorDirs caches the (non)existence of vendor directories. 99 vendorDirs map[string]bool 100 } 101 102 // getEnv returns Go environment variables. Only specific variables are 103 // populated -- computing all of them is slow. 104 func (state *golistState) getEnv() (map[string]string, error) { 105 state.envOnce.Do(func() { 106 var b *bytes.Buffer 107 b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH") 108 if state.goEnvError != nil { 109 return 110 } 111 112 state.goEnv = make(map[string]string) 113 decoder := json.NewDecoder(b) 114 if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil { 115 return 116 } 117 }) 118 return state.goEnv, state.goEnvError 119 } 120 121 // mustGetEnv is a convenience function that can be used if getEnv has already succeeded. 122 func (state *golistState) mustGetEnv() map[string]string { 123 env, err := state.getEnv() 124 if err != nil { 125 panic(fmt.Sprintf("mustGetEnv: %v", err)) 126 } 127 return env 128 } 129 130 // goListDriver uses the go list command to interpret the patterns and produce 131 // the build system package structure. 132 // See driver for more details. 133 func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { 134 // Make sure that any asynchronous go commands are killed when we return. 135 parentCtx := cfg.Context 136 if parentCtx == nil { 137 parentCtx = context.Background() 138 } 139 ctx, cancel := context.WithCancel(parentCtx) 140 defer cancel() 141 142 response := newDeduper() 143 144 state := &golistState{ 145 cfg: cfg, 146 ctx: ctx, 147 vendorDirs: map[string]bool{}, 148 } 149 150 // Fill in response.Sizes asynchronously if necessary. 151 var sizeserr error 152 var sizeswg sync.WaitGroup 153 if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { 154 sizeswg.Add(1) 155 go func() { 156 var sizes types.Sizes 157 sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner) 158 // types.SizesFor always returns nil or a *types.StdSizes. 159 response.dr.Sizes, _ = sizes.(*types.StdSizes) 160 sizeswg.Done() 161 }() 162 } 163 164 // Determine files requested in contains patterns 165 var containFiles []string 166 restPatterns := make([]string, 0, len(patterns)) 167 // Extract file= and other [querytype]= patterns. Report an error if querytype 168 // doesn't exist. 169 extractQueries: 170 for _, pattern := range patterns { 171 eqidx := strings.Index(pattern, "=") 172 if eqidx < 0 { 173 restPatterns = append(restPatterns, pattern) 174 } else { 175 query, value := pattern[:eqidx], pattern[eqidx+len("="):] 176 switch query { 177 case "file": 178 containFiles = append(containFiles, value) 179 case "pattern": 180 restPatterns = append(restPatterns, value) 181 case "": // not a reserved query 182 restPatterns = append(restPatterns, pattern) 183 default: 184 for _, rune := range query { 185 if rune < 'a' || rune > 'z' { // not a reserved query 186 restPatterns = append(restPatterns, pattern) 187 continue extractQueries 188 } 189 } 190 // Reject all other patterns containing "=" 191 return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) 192 } 193 } 194 } 195 196 // See if we have any patterns to pass through to go list. Zero initial 197 // patterns also requires a go list call, since it's the equivalent of 198 // ".". 199 if len(restPatterns) > 0 || len(patterns) == 0 { 200 dr, err := state.createDriverResponse(restPatterns...) 201 if err != nil { 202 return nil, err 203 } 204 response.addAll(dr) 205 } 206 207 if len(containFiles) != 0 { 208 if err := state.runContainsQueries(response, containFiles); err != nil { 209 return nil, err 210 } 211 } 212 213 // Only use go/packages' overlay processing if we're using a Go version 214 // below 1.16. Otherwise, go list handles it. 215 if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 { 216 modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) 217 if err != nil { 218 return nil, err 219 } 220 221 var containsCandidates []string 222 if len(containFiles) > 0 { 223 containsCandidates = append(containsCandidates, modifiedPkgs...) 224 containsCandidates = append(containsCandidates, needPkgs...) 225 } 226 if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { 227 return nil, err 228 } 229 // Check candidate packages for containFiles. 230 if len(containFiles) > 0 { 231 for _, id := range containsCandidates { 232 pkg, ok := response.seenPackages[id] 233 if !ok { 234 response.addPackage(&Package{ 235 ID: id, 236 Errors: []Error{{ 237 Kind: ListError, 238 Msg: fmt.Sprintf("package %s expected but not seen", id), 239 }}, 240 }) 241 continue 242 } 243 for _, f := range containFiles { 244 for _, g := range pkg.GoFiles { 245 if sameFile(f, g) { 246 response.addRoot(id) 247 } 248 } 249 } 250 } 251 } 252 // Add root for any package that matches a pattern. This applies only to 253 // packages that are modified by overlays, since they are not added as 254 // roots automatically. 255 for _, pattern := range restPatterns { 256 match := matchPattern(pattern) 257 for _, pkgID := range modifiedPkgs { 258 pkg, ok := response.seenPackages[pkgID] 259 if !ok { 260 continue 261 } 262 if match(pkg.PkgPath) { 263 response.addRoot(pkg.ID) 264 } 265 } 266 } 267 } 268 269 sizeswg.Wait() 270 if sizeserr != nil { 271 return nil, sizeserr 272 } 273 return response.dr, nil 274 } 275 276 func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { 277 if len(pkgs) == 0 { 278 return nil 279 } 280 dr, err := state.createDriverResponse(pkgs...) 281 if err != nil { 282 return err 283 } 284 for _, pkg := range dr.Packages { 285 response.addPackage(pkg) 286 } 287 _, needPkgs, err := state.processGolistOverlay(response) 288 if err != nil { 289 return err 290 } 291 return state.addNeededOverlayPackages(response, needPkgs) 292 } 293 294 func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { 295 for _, query := range queries { 296 // TODO(matloob): Do only one query per directory. 297 fdir := filepath.Dir(query) 298 // Pass absolute path of directory to go list so that it knows to treat it as a directory, 299 // not a package path. 300 pattern, err := filepath.Abs(fdir) 301 if err != nil { 302 return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) 303 } 304 dirResponse, err := state.createDriverResponse(pattern) 305 306 // If there was an error loading the package, or no packages are returned, 307 // or the package is returned with errors, try to load the file as an 308 // ad-hoc package. 309 // Usually the error will appear in a returned package, but may not if we're 310 // in module mode and the ad-hoc is located outside a module. 311 if err != nil || len(dirResponse.Packages) == 0 || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && 312 len(dirResponse.Packages[0].Errors) == 1 { 313 var queryErr error 314 if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { 315 return err // return the original error 316 } 317 } 318 isRoot := make(map[string]bool, len(dirResponse.Roots)) 319 for _, root := range dirResponse.Roots { 320 isRoot[root] = true 321 } 322 for _, pkg := range dirResponse.Packages { 323 // Add any new packages to the main set 324 // We don't bother to filter packages that will be dropped by the changes of roots, 325 // that will happen anyway during graph construction outside this function. 326 // Over-reporting packages is not a problem. 327 response.addPackage(pkg) 328 // if the package was not a root one, it cannot have the file 329 if !isRoot[pkg.ID] { 330 continue 331 } 332 for _, pkgFile := range pkg.GoFiles { 333 if filepath.Base(query) == filepath.Base(pkgFile) { 334 response.addRoot(pkg.ID) 335 break 336 } 337 } 338 } 339 } 340 return nil 341 } 342 343 // adhocPackage attempts to load or construct an ad-hoc package for a given 344 // query, if the original call to the driver produced inadequate results. 345 func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { 346 response, err := state.createDriverResponse(query) 347 if err != nil { 348 return nil, err 349 } 350 // If we get nothing back from `go list`, 351 // try to make this file into its own ad-hoc package. 352 // TODO(rstambler): Should this check against the original response? 353 if len(response.Packages) == 0 { 354 response.Packages = append(response.Packages, &Package{ 355 ID: "command-line-arguments", 356 PkgPath: query, 357 GoFiles: []string{query}, 358 CompiledGoFiles: []string{query}, 359 Imports: make(map[string]*Package), 360 }) 361 response.Roots = append(response.Roots, "command-line-arguments") 362 } 363 // Handle special cases. 364 if len(response.Packages) == 1 { 365 // golang/go#33482: If this is a file= query for ad-hoc packages where 366 // the file only exists on an overlay, and exists outside of a module, 367 // add the file to the package and remove the errors. 368 if response.Packages[0].ID == "command-line-arguments" || 369 filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) { 370 if len(response.Packages[0].GoFiles) == 0 { 371 filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath 372 // TODO(matloob): check if the file is outside of a root dir? 373 for path := range state.cfg.Overlay { 374 if path == filename { 375 response.Packages[0].Errors = nil 376 response.Packages[0].GoFiles = []string{path} 377 response.Packages[0].CompiledGoFiles = []string{path} 378 } 379 } 380 } 381 } 382 } 383 return response, nil 384 } 385 386 // Fields must match go list; 387 // see $GOROOT/src/cmd/go/internal/load/pkg.go. 388 type jsonPackage struct { 389 ImportPath string 390 Dir string 391 Name string 392 Export string 393 GoFiles []string 394 CompiledGoFiles []string 395 IgnoredGoFiles []string 396 IgnoredOtherFiles []string 397 EmbedPatterns []string 398 EmbedFiles []string 399 CFiles []string 400 CgoFiles []string 401 CXXFiles []string 402 MFiles []string 403 HFiles []string 404 FFiles []string 405 SFiles []string 406 SwigFiles []string 407 SwigCXXFiles []string 408 SysoFiles []string 409 Imports []string 410 ImportMap map[string]string 411 Deps []string 412 Module *Module 413 TestGoFiles []string 414 TestImports []string 415 XTestGoFiles []string 416 XTestImports []string 417 ForTest string // q in a "p [q.test]" package, else "" 418 DepOnly bool 419 420 Error *packagesinternal.PackageError 421 DepsErrors []*packagesinternal.PackageError 422 } 423 424 type jsonPackageError struct { 425 ImportStack []string 426 Pos string 427 Err string 428 } 429 430 func otherFiles(p *jsonPackage) [][]string { 431 return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} 432 } 433 434 // createDriverResponse uses the "go list" command to expand the pattern 435 // words and return a response for the specified packages. 436 func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { 437 // go list uses the following identifiers in ImportPath and Imports: 438 // 439 // "p" -- importable package or main (command) 440 // "q.test" -- q's test executable 441 // "p [q.test]" -- variant of p as built for q's test executable 442 // "q_test [q.test]" -- q's external test package 443 // 444 // The packages p that are built differently for a test q.test 445 // are q itself, plus any helpers used by the external test q_test, 446 // typically including "testing" and all its dependencies. 447 448 // Run "go list" for complete 449 // information on the specified packages. 450 goVersion, err := state.getGoVersion() 451 if err != nil { 452 return nil, err 453 } 454 buf, err := state.invokeGo("list", golistargs(state.cfg, words, goVersion)...) 455 if err != nil { 456 return nil, err 457 } 458 459 seen := make(map[string]*jsonPackage) 460 pkgs := make(map[string]*Package) 461 additionalErrors := make(map[string][]Error) 462 // Decode the JSON and convert it to Package form. 463 response := &driverResponse{ 464 GoVersion: goVersion, 465 } 466 for dec := json.NewDecoder(buf); dec.More(); { 467 p := new(jsonPackage) 468 if err := dec.Decode(p); err != nil { 469 return nil, fmt.Errorf("JSON decoding failed: %v", err) 470 } 471 472 if p.ImportPath == "" { 473 // The documentation for go list says that “[e]rroneous packages will have 474 // a non-empty ImportPath”. If for some reason it comes back empty, we 475 // prefer to error out rather than silently discarding data or handing 476 // back a package without any way to refer to it. 477 if p.Error != nil { 478 return nil, Error{ 479 Pos: p.Error.Pos, 480 Msg: p.Error.Err, 481 } 482 } 483 return nil, fmt.Errorf("package missing import path: %+v", p) 484 } 485 486 // Work around https://golang.org/issue/33157: 487 // go list -e, when given an absolute path, will find the package contained at 488 // that directory. But when no package exists there, it will return a fake package 489 // with an error and the ImportPath set to the absolute path provided to go list. 490 // Try to convert that absolute path to what its package path would be if it's 491 // contained in a known module or GOPATH entry. This will allow the package to be 492 // properly "reclaimed" when overlays are processed. 493 if filepath.IsAbs(p.ImportPath) && p.Error != nil { 494 pkgPath, ok, err := state.getPkgPath(p.ImportPath) 495 if err != nil { 496 return nil, err 497 } 498 if ok { 499 p.ImportPath = pkgPath 500 } 501 } 502 503 if old, found := seen[p.ImportPath]; found { 504 // If one version of the package has an error, and the other doesn't, assume 505 // that this is a case where go list is reporting a fake dependency variant 506 // of the imported package: When a package tries to invalidly import another 507 // package, go list emits a variant of the imported package (with the same 508 // import path, but with an error on it, and the package will have a 509 // DepError set on it). An example of when this can happen is for imports of 510 // main packages: main packages can not be imported, but they may be 511 // separately matched and listed by another pattern. 512 // See golang.org/issue/36188 for more details. 513 514 // The plan is that eventually, hopefully in Go 1.15, the error will be 515 // reported on the importing package rather than the duplicate "fake" 516 // version of the imported package. Once all supported versions of Go 517 // have the new behavior this logic can be deleted. 518 // TODO(matloob): delete the workaround logic once all supported versions of 519 // Go return the errors on the proper package. 520 521 // There should be exactly one version of a package that doesn't have an 522 // error. 523 if old.Error == nil && p.Error == nil { 524 if !reflect.DeepEqual(p, old) { 525 return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) 526 } 527 continue 528 } 529 530 // Determine if this package's error needs to be bubbled up. 531 // This is a hack, and we expect for go list to eventually set the error 532 // on the package. 533 if old.Error != nil { 534 var errkind string 535 if strings.Contains(old.Error.Err, "not an importable package") { 536 errkind = "not an importable package" 537 } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") { 538 errkind = "use of internal package not allowed" 539 } 540 if errkind != "" { 541 if len(old.Error.ImportStack) < 1 { 542 return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) 543 } 544 importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] 545 if importingPkg == old.ImportPath { 546 // Using an older version of Go which put this package itself on top of import 547 // stack, instead of the importer. Look for importer in second from top 548 // position. 549 if len(old.Error.ImportStack) < 2 { 550 return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) 551 } 552 importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] 553 } 554 additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ 555 Pos: old.Error.Pos, 556 Msg: old.Error.Err, 557 Kind: ListError, 558 }) 559 } 560 } 561 562 // Make sure that if there's a version of the package without an error, 563 // that's the one reported to the user. 564 if old.Error == nil { 565 continue 566 } 567 568 // This package will replace the old one at the end of the loop. 569 } 570 seen[p.ImportPath] = p 571 572 pkg := &Package{ 573 Name: p.Name, 574 ID: p.ImportPath, 575 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), 576 CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), 577 OtherFiles: absJoin(p.Dir, otherFiles(p)...), 578 EmbedFiles: absJoin(p.Dir, p.EmbedFiles), 579 EmbedPatterns: absJoin(p.Dir, p.EmbedPatterns), 580 IgnoredFiles: absJoin(p.Dir, p.IgnoredGoFiles, p.IgnoredOtherFiles), 581 forTest: p.ForTest, 582 depsErrors: p.DepsErrors, 583 Module: p.Module, 584 } 585 586 if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 { 587 if len(p.CompiledGoFiles) > len(p.GoFiles) { 588 // We need the cgo definitions, which are in the first 589 // CompiledGoFile after the non-cgo ones. This is a hack but there 590 // isn't currently a better way to find it. We also need the pure 591 // Go files and unprocessed cgo files, all of which are already 592 // in pkg.GoFiles. 593 cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] 594 pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) 595 } else { 596 // golang/go#38990: go list silently fails to do cgo processing 597 pkg.CompiledGoFiles = nil 598 pkg.Errors = append(pkg.Errors, Error{ 599 Msg: "go list failed to return CompiledGoFiles. This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.", 600 Kind: ListError, 601 }) 602 } 603 } 604 605 // Work around https://golang.org/issue/28749: 606 // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. 607 // Remove files from CompiledGoFiles that are non-go files 608 // (or are not files that look like they are from the cache). 609 if len(pkg.CompiledGoFiles) > 0 { 610 out := pkg.CompiledGoFiles[:0] 611 for _, f := range pkg.CompiledGoFiles { 612 if ext := filepath.Ext(f); ext != ".go" && ext != "" { // ext == "" means the file is from the cache, so probably cgo-processed file 613 continue 614 } 615 out = append(out, f) 616 } 617 pkg.CompiledGoFiles = out 618 } 619 620 // Extract the PkgPath from the package's ID. 621 if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { 622 pkg.PkgPath = pkg.ID[:i] 623 } else { 624 pkg.PkgPath = pkg.ID 625 } 626 627 if pkg.PkgPath == "unsafe" { 628 pkg.GoFiles = nil // ignore fake unsafe.go file 629 } 630 631 // Assume go list emits only absolute paths for Dir. 632 if p.Dir != "" && !filepath.IsAbs(p.Dir) { 633 log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir) 634 } 635 636 if p.Export != "" && !filepath.IsAbs(p.Export) { 637 pkg.ExportFile = filepath.Join(p.Dir, p.Export) 638 } else { 639 pkg.ExportFile = p.Export 640 } 641 642 // imports 643 // 644 // Imports contains the IDs of all imported packages. 645 // ImportsMap records (path, ID) only where they differ. 646 ids := make(map[string]bool) 647 for _, id := range p.Imports { 648 ids[id] = true 649 } 650 pkg.Imports = make(map[string]*Package) 651 for path, id := range p.ImportMap { 652 pkg.Imports[path] = &Package{ID: id} // non-identity import 653 delete(ids, id) 654 } 655 for id := range ids { 656 if id == "C" { 657 continue 658 } 659 660 pkg.Imports[id] = &Package{ID: id} // identity import 661 } 662 if !p.DepOnly { 663 response.Roots = append(response.Roots, pkg.ID) 664 } 665 666 // Work around for pre-go.1.11 versions of go list. 667 // TODO(matloob): they should be handled by the fallback. 668 // Can we delete this? 669 if len(pkg.CompiledGoFiles) == 0 { 670 pkg.CompiledGoFiles = pkg.GoFiles 671 } 672 673 // Temporary work-around for golang/go#39986. Parse filenames out of 674 // error messages. This happens if there are unrecoverable syntax 675 // errors in the source, so we can't match on a specific error message. 676 if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) { 677 addFilenameFromPos := func(pos string) bool { 678 split := strings.Split(pos, ":") 679 if len(split) < 1 { 680 return false 681 } 682 filename := strings.TrimSpace(split[0]) 683 if filename == "" { 684 return false 685 } 686 if !filepath.IsAbs(filename) { 687 filename = filepath.Join(state.cfg.Dir, filename) 688 } 689 info, _ := os.Stat(filename) 690 if info == nil { 691 return false 692 } 693 pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) 694 pkg.GoFiles = append(pkg.GoFiles, filename) 695 return true 696 } 697 found := addFilenameFromPos(err.Pos) 698 // In some cases, go list only reports the error position in the 699 // error text, not the error position. One such case is when the 700 // file's package name is a keyword (see golang.org/issue/39763). 701 if !found { 702 addFilenameFromPos(err.Err) 703 } 704 } 705 706 if p.Error != nil { 707 msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. 708 // Address golang.org/issue/35964 by appending import stack to error message. 709 if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 { 710 msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack) 711 } 712 pkg.Errors = append(pkg.Errors, Error{ 713 Pos: p.Error.Pos, 714 Msg: msg, 715 Kind: ListError, 716 }) 717 } 718 719 pkgs[pkg.ID] = pkg 720 } 721 722 for id, errs := range additionalErrors { 723 if p, ok := pkgs[id]; ok { 724 p.Errors = append(p.Errors, errs...) 725 } 726 } 727 for _, pkg := range pkgs { 728 response.Packages = append(response.Packages, pkg) 729 } 730 sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) 731 732 return response, nil 733 } 734 735 func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { 736 if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 { 737 return false 738 } 739 740 goV, err := state.getGoVersion() 741 if err != nil { 742 return false 743 } 744 745 // On Go 1.14 and earlier, only add filenames from errors if the import stack is empty. 746 // The import stack behaves differently for these versions than newer Go versions. 747 if goV < 15 { 748 return len(p.Error.ImportStack) == 0 749 } 750 751 // On Go 1.15 and later, only parse filenames out of error if there's no import stack, 752 // or the current package is at the top of the import stack. This is not guaranteed 753 // to work perfectly, but should avoid some cases where files in errors don't belong to this 754 // package. 755 return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath 756 } 757 758 // getGoVersion returns the effective minor version of the go command. 759 func (state *golistState) getGoVersion() (int, error) { 760 state.goVersionOnce.Do(func() { 761 state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner) 762 }) 763 return state.goVersion, state.goVersionError 764 } 765 766 // getPkgPath finds the package path of a directory if it's relative to a root 767 // directory. 768 func (state *golistState) getPkgPath(dir string) (string, bool, error) { 769 absDir, err := filepath.Abs(dir) 770 if err != nil { 771 return "", false, err 772 } 773 roots, err := state.determineRootDirs() 774 if err != nil { 775 return "", false, err 776 } 777 778 for rdir, rpath := range roots { 779 // Make sure that the directory is in the module, 780 // to avoid creating a path relative to another module. 781 if !strings.HasPrefix(absDir, rdir) { 782 continue 783 } 784 // TODO(matloob): This doesn't properly handle symlinks. 785 r, err := filepath.Rel(rdir, dir) 786 if err != nil { 787 continue 788 } 789 if rpath != "" { 790 // We choose only one root even though the directory even it can belong in multiple modules 791 // or GOPATH entries. This is okay because we only need to work with absolute dirs when a 792 // file is missing from disk, for instance when gopls calls go/packages in an overlay. 793 // Once the file is saved, gopls, or the next invocation of the tool will get the correct 794 // result straight from golist. 795 // TODO(matloob): Implement module tiebreaking? 796 return path.Join(rpath, filepath.ToSlash(r)), true, nil 797 } 798 return filepath.ToSlash(r), true, nil 799 } 800 return "", false, nil 801 } 802 803 // absJoin absolutizes and flattens the lists of files. 804 func absJoin(dir string, fileses ...[]string) (res []string) { 805 for _, files := range fileses { 806 for _, file := range files { 807 if !filepath.IsAbs(file) { 808 file = filepath.Join(dir, file) 809 } 810 res = append(res, file) 811 } 812 } 813 return res 814 } 815 816 func jsonFlag(cfg *Config, goVersion int) string { 817 if goVersion < 19 { 818 return "-json" 819 } 820 var fields []string 821 added := make(map[string]bool) 822 addFields := func(fs ...string) { 823 for _, f := range fs { 824 if !added[f] { 825 added[f] = true 826 fields = append(fields, f) 827 } 828 } 829 } 830 addFields("Name", "ImportPath", "Error") // These fields are always needed 831 if cfg.Mode&NeedFiles != 0 || cfg.Mode&NeedTypes != 0 { 832 addFields("Dir", "GoFiles", "IgnoredGoFiles", "IgnoredOtherFiles", "CFiles", 833 "CgoFiles", "CXXFiles", "MFiles", "HFiles", "FFiles", "SFiles", 834 "SwigFiles", "SwigCXXFiles", "SysoFiles") 835 if cfg.Tests { 836 addFields("TestGoFiles", "XTestGoFiles") 837 } 838 } 839 if cfg.Mode&NeedTypes != 0 { 840 // CompiledGoFiles seems to be required for the test case TestCgoNoSyntax, 841 // even when -compiled isn't passed in. 842 // TODO(#52435): Should we make the test ask for -compiled, or automatically 843 // request CompiledGoFiles in certain circumstances? 844 addFields("Dir", "CompiledGoFiles") 845 } 846 if cfg.Mode&NeedCompiledGoFiles != 0 { 847 addFields("Dir", "CompiledGoFiles", "Export") 848 } 849 if cfg.Mode&NeedImports != 0 { 850 // When imports are requested, DepOnly is used to distinguish between packages 851 // explicitly requested and transitive imports of those packages. 852 addFields("DepOnly", "Imports", "ImportMap") 853 if cfg.Tests { 854 addFields("TestImports", "XTestImports") 855 } 856 } 857 if cfg.Mode&NeedDeps != 0 { 858 addFields("DepOnly") 859 } 860 if usesExportData(cfg) { 861 // Request Dir in the unlikely case Export is not absolute. 862 addFields("Dir", "Export") 863 } 864 if cfg.Mode&needInternalForTest != 0 { 865 addFields("ForTest") 866 } 867 if cfg.Mode&needInternalDepsErrors != 0 { 868 addFields("DepsErrors") 869 } 870 if cfg.Mode&NeedModule != 0 { 871 addFields("Module") 872 } 873 if cfg.Mode&NeedEmbedFiles != 0 { 874 addFields("EmbedFiles") 875 } 876 if cfg.Mode&NeedEmbedPatterns != 0 { 877 addFields("EmbedPatterns") 878 } 879 return "-json=" + strings.Join(fields, ",") 880 } 881 882 func golistargs(cfg *Config, words []string, goVersion int) []string { 883 const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo 884 fullargs := []string{ 885 "-e", jsonFlag(cfg, goVersion), 886 fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0), 887 fmt.Sprintf("-test=%t", cfg.Tests), 888 fmt.Sprintf("-export=%t", usesExportData(cfg)), 889 fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), 890 // go list doesn't let you pass -test and -find together, 891 // probably because you'd just get the TestMain. 892 fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)), 893 } 894 fullargs = append(fullargs, cfg.BuildFlags...) 895 fullargs = append(fullargs, "--") 896 fullargs = append(fullargs, words...) 897 return fullargs 898 } 899 900 // cfgInvocation returns an Invocation that reflects cfg's settings. 901 func (state *golistState) cfgInvocation() gocommand.Invocation { 902 cfg := state.cfg 903 return gocommand.Invocation{ 904 BuildFlags: cfg.BuildFlags, 905 ModFile: cfg.modFile, 906 ModFlag: cfg.modFlag, 907 CleanEnv: cfg.Env != nil, 908 Env: cfg.Env, 909 Logf: cfg.Logf, 910 WorkingDir: cfg.Dir, 911 } 912 } 913 914 // invokeGo returns the stdout of a go command invocation. 915 func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { 916 cfg := state.cfg 917 918 inv := state.cfgInvocation() 919 920 // For Go versions 1.16 and above, `go list` accepts overlays directly via 921 // the -overlay flag. Set it, if it's available. 922 // 923 // The check for "list" is not necessarily required, but we should avoid 924 // getting the go version if possible. 925 if verb == "list" { 926 goVersion, err := state.getGoVersion() 927 if err != nil { 928 return nil, err 929 } 930 if goVersion >= 16 { 931 filename, cleanup, err := state.writeOverlays() 932 if err != nil { 933 return nil, err 934 } 935 defer cleanup() 936 inv.Overlay = filename 937 } 938 } 939 inv.Verb = verb 940 inv.Args = args 941 gocmdRunner := cfg.gocmdRunner 942 if gocmdRunner == nil { 943 gocmdRunner = &gocommand.Runner{} 944 } 945 stdout, stderr, friendlyErr, err := gocmdRunner.RunRaw(cfg.Context, inv) 946 if err != nil { 947 // Check for 'go' executable not being found. 948 if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 949 return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) 950 } 951 952 exitErr, ok := err.(*exec.ExitError) 953 if !ok { 954 // Catastrophic error: 955 // - context cancellation 956 return nil, fmt.Errorf("couldn't run 'go': %w", err) 957 } 958 959 // Old go version? 960 if strings.Contains(stderr.String(), "flag provided but not defined") { 961 return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} 962 } 963 964 // Related to #24854 965 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { 966 return nil, friendlyErr 967 } 968 969 // Is there an error running the C compiler in cgo? This will be reported in the "Error" field 970 // and should be suppressed by go list -e. 971 // 972 // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. 973 isPkgPathRune := func(r rune) bool { 974 // From https://golang.org/ref/spec#Import_declarations: 975 // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings 976 // using only characters belonging to Unicode's L, M, N, P, and S general categories 977 // (the Graphic characters without spaces) and may also exclude the 978 // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. 979 return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && 980 !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) 981 } 982 // golang/go#36770: Handle case where cmd/go prints module download messages before the error. 983 msg := stderr.String() 984 for strings.HasPrefix(msg, "go: downloading") { 985 msg = msg[strings.IndexRune(msg, '\n')+1:] 986 } 987 if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { 988 msg := msg[len("# "):] 989 if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") { 990 return stdout, nil 991 } 992 // Treat pkg-config errors as a special case (golang.org/issue/36770). 993 if strings.HasPrefix(msg, "pkg-config") { 994 return stdout, nil 995 } 996 } 997 998 // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show 999 // the error in the Err section of stdout in case -e option is provided. 1000 // This fix is provided for backwards compatibility. 1001 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") { 1002 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1003 strings.Trim(stderr.String(), "\n")) 1004 return bytes.NewBufferString(output), nil 1005 } 1006 1007 // Similar to the previous error, but currently lacks a fix in Go. 1008 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") { 1009 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1010 strings.Trim(stderr.String(), "\n")) 1011 return bytes.NewBufferString(output), nil 1012 } 1013 1014 // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. 1015 // If the package doesn't exist, put the absolute path of the directory into the error message, 1016 // as Go 1.13 list does. 1017 const noSuchDirectory = "no such directory" 1018 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) { 1019 errstr := stderr.String() 1020 abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):]) 1021 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1022 abspath, strings.Trim(stderr.String(), "\n")) 1023 return bytes.NewBufferString(output), nil 1024 } 1025 1026 // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. 1027 // Note that the error message we look for in this case is different that the one looked for above. 1028 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") { 1029 output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1030 strings.Trim(stderr.String(), "\n")) 1031 return bytes.NewBufferString(output), nil 1032 } 1033 1034 // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a 1035 // directory outside any module. 1036 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") { 1037 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1038 // TODO(matloob): command-line-arguments isn't correct here. 1039 "command-line-arguments", strings.Trim(stderr.String(), "\n")) 1040 return bytes.NewBufferString(output), nil 1041 } 1042 1043 // Another variation of the previous error 1044 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") { 1045 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1046 // TODO(matloob): command-line-arguments isn't correct here. 1047 "command-line-arguments", strings.Trim(stderr.String(), "\n")) 1048 return bytes.NewBufferString(output), nil 1049 } 1050 1051 // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit 1052 // status if there's a dependency on a package that doesn't exist. But it should return 1053 // a zero exit status and set an error on that package. 1054 if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { 1055 // Don't clobber stdout if `go list` actually returned something. 1056 if len(stdout.String()) > 0 { 1057 return stdout, nil 1058 } 1059 // try to extract package name from string 1060 stderrStr := stderr.String() 1061 var importPath string 1062 colon := strings.Index(stderrStr, ":") 1063 if colon > 0 && strings.HasPrefix(stderrStr, "go build ") { 1064 importPath = stderrStr[len("go build "):colon] 1065 } 1066 output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, 1067 importPath, strings.Trim(stderrStr, "\n")) 1068 return bytes.NewBufferString(output), nil 1069 } 1070 1071 // Export mode entails a build. 1072 // If that build fails, errors appear on stderr 1073 // (despite the -e flag) and the Export field is blank. 1074 // Do not fail in that case. 1075 // The same is true if an ad-hoc package given to go list doesn't exist. 1076 // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when 1077 // packages don't exist or a build fails. 1078 if !usesExportData(cfg) && !containsGoFile(args) { 1079 return nil, friendlyErr 1080 } 1081 } 1082 return stdout, nil 1083 } 1084 1085 // OverlayJSON is the format overlay files are expected to be in. 1086 // The Replace map maps from overlaid paths to replacement paths: 1087 // the Go command will forward all reads trying to open 1088 // each overlaid path to its replacement path, or consider the overlaid 1089 // path not to exist if the replacement path is empty. 1090 // 1091 // From golang/go#39958. 1092 type OverlayJSON struct { 1093 Replace map[string]string `json:"replace,omitempty"` 1094 } 1095 1096 // writeOverlays writes out files for go list's -overlay flag, as described 1097 // above. 1098 func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) { 1099 // Do nothing if there are no overlays in the config. 1100 if len(state.cfg.Overlay) == 0 { 1101 return "", func() {}, nil 1102 } 1103 dir, err := ioutil.TempDir("", "gopackages-*") 1104 if err != nil { 1105 return "", nil, err 1106 } 1107 // The caller must clean up this directory, unless this function returns an 1108 // error. 1109 cleanup = func() { 1110 os.RemoveAll(dir) 1111 } 1112 defer func() { 1113 if err != nil { 1114 cleanup() 1115 } 1116 }() 1117 overlays := map[string]string{} 1118 for k, v := range state.cfg.Overlay { 1119 // Create a unique filename for the overlaid files, to avoid 1120 // creating nested directories. 1121 noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "") 1122 f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator)) 1123 if err != nil { 1124 return "", func() {}, err 1125 } 1126 if _, err := f.Write(v); err != nil { 1127 return "", func() {}, err 1128 } 1129 if err := f.Close(); err != nil { 1130 return "", func() {}, err 1131 } 1132 overlays[k] = f.Name() 1133 } 1134 b, err := json.Marshal(OverlayJSON{Replace: overlays}) 1135 if err != nil { 1136 return "", func() {}, err 1137 } 1138 // Write out the overlay file that contains the filepath mappings. 1139 filename = filepath.Join(dir, "overlay.json") 1140 if err := ioutil.WriteFile(filename, b, 0665); err != nil { 1141 return "", func() {}, err 1142 } 1143 return filename, cleanup, nil 1144 } 1145 1146 func containsGoFile(s []string) bool { 1147 for _, f := range s { 1148 if strings.HasSuffix(f, ".go") { 1149 return true 1150 } 1151 } 1152 return false 1153 } 1154 1155 func cmdDebugStr(cmd *exec.Cmd) string { 1156 env := make(map[string]string) 1157 for _, kv := range cmd.Env { 1158 split := strings.SplitN(kv, "=", 2) 1159 k, v := split[0], split[1] 1160 env[k] = v 1161 } 1162 1163 var args []string 1164 for _, arg := range cmd.Args { 1165 quoted := strconv.Quote(arg) 1166 if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") { 1167 args = append(args, quoted) 1168 } else { 1169 args = append(args, arg) 1170 } 1171 } 1172 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " ")) 1173 }