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