cuelang.org/go@v0.10.1/internal/golangorgx/gopls/cache/load.go (about) 1 // Copyright 2019 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 cache 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "path/filepath" 13 "sort" 14 "strings" 15 "sync/atomic" 16 "time" 17 18 "cuelang.org/go/internal/golangorgx/gopls/cache/metadata" 19 "cuelang.org/go/internal/golangorgx/gopls/file" 20 "cuelang.org/go/internal/golangorgx/gopls/protocol" 21 "cuelang.org/go/internal/golangorgx/gopls/util/bug" 22 "cuelang.org/go/internal/golangorgx/gopls/util/immutable" 23 "cuelang.org/go/internal/golangorgx/gopls/util/pathutil" 24 "cuelang.org/go/internal/golangorgx/gopls/util/slices" 25 "cuelang.org/go/internal/golangorgx/tools/event" 26 "cuelang.org/go/internal/golangorgx/tools/event/tag" 27 "cuelang.org/go/internal/golangorgx/tools/gocommand" 28 "cuelang.org/go/internal/golangorgx/tools/packagesinternal" 29 "golang.org/x/tools/go/packages" 30 ) 31 32 var loadID uint64 // atomic identifier for loads 33 34 // errNoPackages indicates that a load query matched no packages. 35 var errNoPackages = errors.New("no packages returned") 36 37 // load calls packages.Load for the given scopes, updating package metadata, 38 // import graph, and mapped files with the result. 39 // 40 // The resulting error may wrap the moduleErrorMap error type, representing 41 // errors associated with specific modules. 42 // 43 // If scopes contains a file scope there must be exactly one scope. 44 func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadScope) (err error) { 45 id := atomic.AddUint64(&loadID, 1) 46 eventName := fmt.Sprintf("go/packages.Load #%d", id) // unique name for logging 47 48 var query []string 49 var containsDir bool // for logging 50 var standalone bool // whether this is a load of a standalone file 51 52 // Keep track of module query -> module path so that we can later correlate query 53 // errors with errors. 54 moduleQueries := make(map[string]string) 55 for _, scope := range scopes { 56 switch scope := scope.(type) { 57 case packageLoadScope: 58 // The only time we pass package paths is when we're doing a 59 // partial workspace load. In those cases, the paths came back from 60 // go list and should already be GOPATH-vendorized when appropriate. 61 query = append(query, string(scope)) 62 63 case fileLoadScope: 64 // Given multiple scopes, the resulting load might contain inaccurate 65 // information. For example go/packages returns at most one command-line 66 // arguments package, and does not handle a combination of standalone 67 // files and packages. 68 uri := protocol.DocumentURI(scope) 69 if len(scopes) > 1 { 70 panic(fmt.Sprintf("internal error: load called with multiple scopes when a file scope is present (file: %s)", uri)) 71 } 72 fh := s.FindFile(uri) 73 if fh == nil || s.FileKind(fh) != file.Go { 74 // Don't try to load a file that doesn't exist, or isn't a go file. 75 continue 76 } 77 contents, err := fh.Content() 78 if err != nil { 79 continue 80 } 81 if isStandaloneFile(contents, s.Options().StandaloneTags) { 82 standalone = true 83 query = append(query, uri.Path()) 84 } else { 85 query = append(query, fmt.Sprintf("file=%s", uri.Path())) 86 } 87 88 case moduleLoadScope: 89 modQuery := fmt.Sprintf("%s%c...", scope.dir, filepath.Separator) 90 query = append(query, modQuery) 91 moduleQueries[modQuery] = scope.modulePath 92 93 case viewLoadScope: 94 // If we are outside of GOPATH, a module, or some other known 95 // build system, don't load subdirectories. 96 if s.view.typ == AdHocView { 97 query = append(query, "./") 98 } else { 99 query = append(query, "./...") 100 } 101 102 default: 103 panic(fmt.Sprintf("unknown scope type %T", scope)) 104 } 105 switch scope.(type) { 106 case viewLoadScope, moduleLoadScope: 107 containsDir = true 108 } 109 } 110 if len(query) == 0 { 111 return nil 112 } 113 sort.Strings(query) // for determinism 114 115 ctx, done := event.Start(ctx, "cache.snapshot.load", tag.Query.Of(query)) 116 defer done() 117 118 flags := LoadWorkspace 119 if allowNetwork { 120 flags |= AllowNetwork 121 } 122 _, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{ 123 WorkingDir: s.view.root.Path(), 124 }) 125 if err != nil { 126 return err 127 } 128 129 // Set a last resort deadline on packages.Load since it calls the go 130 // command, which may hang indefinitely if it has a bug. golang/go#42132 131 // and golang/go#42255 have more context. 132 ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) 133 defer cancel() 134 135 cfg := s.config(ctx, inv) 136 pkgs, err := packages.Load(cfg, query...) 137 cleanup() 138 139 // If the context was canceled, return early. Otherwise, we might be 140 // type-checking an incomplete result. Check the context directly, 141 // because go/packages adds extra information to the error. 142 if ctx.Err() != nil { 143 return ctx.Err() 144 } 145 146 // This log message is sought for by TestReloadOnlyOnce. 147 labels := append(s.Labels(), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs))) 148 if err != nil { 149 event.Error(ctx, eventName, err, labels...) 150 } else { 151 event.Log(ctx, eventName, labels...) 152 } 153 154 if standalone { 155 // Handle standalone package result. 156 // 157 // In general, this should just be a single "command-line-arguments" 158 // package containing the requested file. However, if the file is a test 159 // file, go/packages may return test variants of the command-line-arguments 160 // package. We don't support this; theoretically we could, but it seems 161 // unnecessarily complicated. 162 // 163 // Prior to golang/go#64233 we just assumed that we'd get exactly one 164 // package here. The categorization of bug reports below may be a bit 165 // verbose, but anticipates that perhaps we don't fully understand 166 // possible failure modes. 167 errorf := bug.Errorf 168 if s.view.typ == GoPackagesDriverView { 169 errorf = fmt.Errorf // all bets are off 170 } 171 172 var standalonePkg *packages.Package 173 for _, pkg := range pkgs { 174 if pkg.ID == "command-line-arguments" { 175 if standalonePkg != nil { 176 return errorf("internal error: go/packages returned multiple standalone packages") 177 } 178 standalonePkg = pkg 179 } else if packagesinternal.GetForTest(pkg) == "" && !strings.HasSuffix(pkg.ID, ".test") { 180 return errorf("internal error: go/packages returned unexpected package %q for standalone file", pkg.ID) 181 } 182 } 183 if standalonePkg == nil { 184 return errorf("internal error: go/packages failed to return non-test standalone package") 185 } 186 if len(standalonePkg.CompiledGoFiles) > 0 { 187 pkgs = []*packages.Package{standalonePkg} 188 } else { 189 pkgs = nil 190 } 191 } 192 193 if len(pkgs) == 0 { 194 if err == nil { 195 err = errNoPackages 196 } 197 return fmt.Errorf("packages.Load error: %w", err) 198 } 199 200 moduleErrs := make(map[string][]packages.Error) // module path -> errors 201 filterFunc := s.view.filterFunc() 202 newMetadata := make(map[PackageID]*metadata.Package) 203 for _, pkg := range pkgs { 204 // The Go command returns synthetic list results for module queries that 205 // encountered module errors. 206 // 207 // For example, given a module path a.mod, we'll query for "a.mod/..." and 208 // the go command will return a package named "a.mod/..." holding this 209 // error. Save it for later interpretation. 210 // 211 // See golang/go#50862 for more details. 212 if mod := moduleQueries[pkg.PkgPath]; mod != "" { // a synthetic result for the unloadable module 213 if len(pkg.Errors) > 0 { 214 moduleErrs[mod] = pkg.Errors 215 } 216 continue 217 } 218 219 if !containsDir || s.Options().VerboseOutput { 220 event.Log(ctx, eventName, append( 221 s.Labels(), 222 tag.Package.Of(pkg.ID), 223 tag.Files.Of(pkg.CompiledGoFiles))...) 224 } 225 226 // Ignore packages with no sources, since we will never be able to 227 // correctly invalidate that metadata. 228 if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 { 229 continue 230 } 231 // Special case for the builtin package, as it has no dependencies. 232 if pkg.PkgPath == "builtin" { 233 if len(pkg.GoFiles) != 1 { 234 return fmt.Errorf("only expected 1 file for builtin, got %v", len(pkg.GoFiles)) 235 } 236 s.setBuiltin(pkg.GoFiles[0]) 237 continue 238 } 239 // Skip test main packages. 240 if isTestMain(pkg, s.view.folder.Env.GOCACHE) { 241 continue 242 } 243 // Skip filtered packages. They may be added anyway if they're 244 // dependencies of non-filtered packages. 245 // 246 // TODO(rfindley): why exclude metadata arbitrarily here? It should be safe 247 // to capture all metadata. 248 // TODO(rfindley): what about compiled go files? 249 if allFilesExcluded(pkg.GoFiles, filterFunc) { 250 continue 251 } 252 buildMetadata(newMetadata, pkg, cfg.Dir, standalone) 253 } 254 255 s.mu.Lock() 256 257 // Assert the invariant s.packages.Get(id).m == s.meta.metadata[id]. 258 s.packages.Range(func(id PackageID, ph *packageHandle) { 259 if s.meta.Packages[id] != ph.mp { 260 panic("inconsistent metadata") 261 } 262 }) 263 264 // Compute the minimal metadata updates (for Clone) 265 // required to preserve the above invariant. 266 var files []protocol.DocumentURI // files to preload 267 seenFiles := make(map[protocol.DocumentURI]bool) 268 updates := make(map[PackageID]*metadata.Package) 269 for _, mp := range newMetadata { 270 if existing := s.meta.Packages[mp.ID]; existing == nil { 271 // Record any new files we should pre-load. 272 for _, uri := range mp.CompiledGoFiles { 273 if !seenFiles[uri] { 274 seenFiles[uri] = true 275 files = append(files, uri) 276 } 277 } 278 updates[mp.ID] = mp 279 s.shouldLoad.Delete(mp.ID) 280 } 281 } 282 283 event.Log(ctx, fmt.Sprintf("%s: updating metadata for %d packages", eventName, len(updates))) 284 285 meta := s.meta.Update(updates) 286 workspacePackages := computeWorkspacePackagesLocked(s, meta) 287 s.meta = meta 288 s.workspacePackages = workspacePackages 289 s.resetActivePackagesLocked() 290 291 s.mu.Unlock() 292 293 // Opt: preLoad files in parallel. 294 // 295 // Requesting files in batch optimizes the underlying filesystem reads. 296 // However, this is also currently necessary for correctness: populating all 297 // files in the snapshot is necessary for certain operations that rely on the 298 // completeness of the file map, e.g. computing the set of directories to 299 // watch. 300 // 301 // TODO(rfindley, golang/go#57558): determine the set of directories based on 302 // loaded packages, so that reading files here is not necessary for 303 // correctness. 304 s.preloadFiles(ctx, files) 305 306 if len(moduleErrs) > 0 { 307 return &moduleErrorMap{moduleErrs} 308 } 309 310 return nil 311 } 312 313 type moduleErrorMap struct { 314 errs map[string][]packages.Error // module path -> errors 315 } 316 317 func (m *moduleErrorMap) Error() string { 318 var paths []string // sort for stability 319 for path, errs := range m.errs { 320 if len(errs) > 0 { // should always be true, but be cautious 321 paths = append(paths, path) 322 } 323 } 324 sort.Strings(paths) 325 326 var buf bytes.Buffer 327 fmt.Fprintf(&buf, "%d modules have errors:\n", len(paths)) 328 for _, path := range paths { 329 fmt.Fprintf(&buf, "\t%s:%s\n", path, m.errs[path][0].Msg) 330 } 331 332 return buf.String() 333 } 334 335 // buildMetadata populates the updates map with metadata updates to 336 // apply, based on the given pkg. It recurs through pkg.Imports to ensure that 337 // metadata exists for all dependencies. 338 func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone bool) { 339 // Allow for multiple ad-hoc packages in the workspace (see #47584). 340 pkgPath := PackagePath(pkg.PkgPath) 341 id := PackageID(pkg.ID) 342 343 if metadata.IsCommandLineArguments(id) { 344 var f string // file to use as disambiguating suffix 345 if len(pkg.CompiledGoFiles) > 0 { 346 f = pkg.CompiledGoFiles[0] 347 348 // If there are multiple files, 349 // we can't use only the first. 350 // (Can this happen? #64557) 351 if len(pkg.CompiledGoFiles) > 1 { 352 bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles) 353 return 354 } 355 } else if len(pkg.IgnoredFiles) > 0 { 356 // A file=empty.go query results in IgnoredFiles=[empty.go]. 357 f = pkg.IgnoredFiles[0] 358 } else { 359 bug.Reportf("command-line-arguments package has neither CompiledGoFiles nor IgnoredFiles: %#v", "") //*pkg.Metadata) 360 return 361 } 362 id = PackageID(pkg.ID + f) 363 pkgPath = PackagePath(pkg.PkgPath + f) 364 } 365 366 // Duplicate? 367 if _, ok := updates[id]; ok { 368 // A package was encountered twice due to shared 369 // subgraphs (common) or cycles (rare). Although "go 370 // list" usually breaks cycles, we don't rely on it. 371 // breakImportCycles in metadataGraph.Clone takes care 372 // of it later. 373 return 374 } 375 376 if pkg.TypesSizes == nil { 377 panic(id + ".TypeSizes is nil") 378 } 379 380 // Recreate the metadata rather than reusing it to avoid locking. 381 mp := &metadata.Package{ 382 ID: id, 383 PkgPath: pkgPath, 384 Name: PackageName(pkg.Name), 385 ForTest: PackagePath(packagesinternal.GetForTest(pkg)), 386 TypesSizes: pkg.TypesSizes, 387 LoadDir: loadDir, 388 Module: pkg.Module, 389 Errors: pkg.Errors, 390 DepsErrors: packagesinternal.GetDepsErrors(pkg), 391 Standalone: standalone, 392 } 393 394 updates[id] = mp 395 396 for _, filename := range pkg.CompiledGoFiles { 397 uri := protocol.URIFromPath(filename) 398 mp.CompiledGoFiles = append(mp.CompiledGoFiles, uri) 399 } 400 for _, filename := range pkg.GoFiles { 401 uri := protocol.URIFromPath(filename) 402 mp.GoFiles = append(mp.GoFiles, uri) 403 } 404 for _, filename := range pkg.IgnoredFiles { 405 uri := protocol.URIFromPath(filename) 406 mp.IgnoredFiles = append(mp.IgnoredFiles, uri) 407 } 408 409 depsByImpPath := make(map[ImportPath]PackageID) 410 depsByPkgPath := make(map[PackagePath]PackageID) 411 for importPath, imported := range pkg.Imports { 412 importPath := ImportPath(importPath) 413 414 // It is not an invariant that importPath == imported.PkgPath. 415 // For example, package "net" imports "golang.org/x/net/dns/dnsmessage" 416 // which refers to the package whose ID and PkgPath are both 417 // "vendor/golang.org/x/net/dns/dnsmessage". Notice the ImportMap, 418 // which maps ImportPaths to PackagePaths: 419 // 420 // $ go list -json net vendor/golang.org/x/net/dns/dnsmessage 421 // { 422 // "ImportPath": "net", 423 // "Name": "net", 424 // "Imports": [ 425 // "C", 426 // "vendor/golang.org/x/net/dns/dnsmessage", 427 // "vendor/golang.org/x/net/route", 428 // ... 429 // ], 430 // "ImportMap": { 431 // "golang.org/x/net/dns/dnsmessage": "vendor/golang.org/x/net/dns/dnsmessage", 432 // "golang.org/x/net/route": "vendor/golang.org/x/net/route" 433 // }, 434 // ... 435 // } 436 // { 437 // "ImportPath": "vendor/golang.org/x/net/dns/dnsmessage", 438 // "Name": "dnsmessage", 439 // ... 440 // } 441 // 442 // (Beware that, for historical reasons, go list uses 443 // the JSON field "ImportPath" for the package's 444 // path--effectively the linker symbol prefix.) 445 // 446 // The example above is slightly special to go list 447 // because it's in the std module. Otherwise, 448 // vendored modules are simply modules whose directory 449 // is vendor/ instead of GOMODCACHE, and the 450 // import path equals the package path. 451 // 452 // But in GOPATH (non-module) mode, it's possible for 453 // package vendoring to cause a non-identity ImportMap, 454 // as in this example: 455 // 456 // $ cd $HOME/src 457 // $ find . -type f 458 // ./b/b.go 459 // ./vendor/example.com/a/a.go 460 // $ cat ./b/b.go 461 // package b 462 // import _ "example.com/a" 463 // $ cat ./vendor/example.com/a/a.go 464 // package a 465 // $ GOPATH=$HOME GO111MODULE=off go list -json ./b | grep -A2 ImportMap 466 // "ImportMap": { 467 // "example.com/a": "vendor/example.com/a" 468 // }, 469 470 // Don't remember any imports with significant errors. 471 // 472 // The len=0 condition is a heuristic check for imports of 473 // non-existent packages (for which go/packages will create 474 // an edge to a synthesized node). The heuristic is unsound 475 // because some valid packages have zero files, for example, 476 // a directory containing only the file p_test.go defines an 477 // empty package p. 478 // TODO(adonovan): clarify this. Perhaps go/packages should 479 // report which nodes were synthesized. 480 if importPath != "unsafe" && len(imported.CompiledGoFiles) == 0 { 481 depsByImpPath[importPath] = "" // missing 482 continue 483 } 484 485 // Don't record self-import edges. 486 // (This simplifies metadataGraph's cycle check.) 487 if PackageID(imported.ID) == id { 488 if len(pkg.Errors) == 0 { 489 bug.Reportf("self-import without error in package %s", id) 490 } 491 continue 492 } 493 494 buildMetadata(updates, imported, loadDir, false) // only top level packages can be standalone 495 496 // Don't record edges to packages with no name, as they cause trouble for 497 // the importer (golang/go#60952). 498 // 499 // However, we do want to insert these packages into the update map 500 // (buildMetadata above), so that we get type-checking diagnostics for the 501 // invalid packages. 502 if imported.Name == "" { 503 depsByImpPath[importPath] = "" // missing 504 continue 505 } 506 507 depsByImpPath[importPath] = PackageID(imported.ID) 508 depsByPkgPath[PackagePath(imported.PkgPath)] = PackageID(imported.ID) 509 } 510 mp.DepsByImpPath = depsByImpPath 511 mp.DepsByPkgPath = depsByPkgPath 512 513 // m.Diagnostics is set later in the loading pass, using 514 // computeLoadDiagnostics. 515 } 516 517 // computeLoadDiagnostics computes and sets m.Diagnostics for the given metadata m. 518 // 519 // It should only be called during package handle construction in buildPackageHandle. 520 func computeLoadDiagnostics(ctx context.Context, snapshot *Snapshot, mp *metadata.Package) []*Diagnostic { 521 var diags []*Diagnostic 522 for _, packagesErr := range mp.Errors { 523 // Filter out parse errors from go list. We'll get them when we 524 // actually parse, and buggy overlay support may generate spurious 525 // errors. (See TestNewModule_Issue38207.) 526 if strings.Contains(packagesErr.Msg, "expected '") { 527 continue 528 } 529 pkgDiags, err := goPackagesErrorDiagnostics(ctx, packagesErr, mp, snapshot) 530 if err != nil { 531 // There are certain cases where the go command returns invalid 532 // positions, so we cannot panic or even bug.Reportf here. 533 event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(mp.ID))) 534 continue 535 } 536 diags = append(diags, pkgDiags...) 537 } 538 539 // TODO(rfindley): this is buggy: an insignificant change to a modfile 540 // (or an unsaved modfile) could affect the position of deps errors, 541 // without invalidating the package. 542 depsDiags, err := depsErrors(ctx, snapshot, mp) 543 if err != nil { 544 if ctx.Err() == nil { 545 // TODO(rfindley): consider making this a bug.Reportf. depsErrors should 546 // not normally fail. 547 event.Error(ctx, "unable to compute deps errors", err, tag.Package.Of(string(mp.ID))) 548 } 549 } else { 550 diags = append(diags, depsDiags...) 551 } 552 return diags 553 } 554 555 // isWorkspacePackageLocked reports whether p is a workspace package for the 556 // snapshot s. 557 // 558 // Workspace packages are packages that we consider the user to be actively 559 // working on. As such, they are re-diagnosed on every keystroke, and searched 560 // for various workspace-wide queries such as references or workspace symbols. 561 // 562 // See the commentary inline for a description of the workspace package 563 // heuristics. 564 // 565 // s.mu must be held while calling this function. 566 func isWorkspacePackageLocked(s *Snapshot, meta *metadata.Graph, pkg *metadata.Package) bool { 567 if metadata.IsCommandLineArguments(pkg.ID) { 568 // Ad-hoc command-line-arguments packages aren't workspace packages. 569 // With zero-config gopls (golang/go#57979) they should be very rare, as 570 // they should only arise when the user opens a file outside the workspace 571 // which isn't present in the import graph of a workspace package. 572 // 573 // Considering them as workspace packages tends to be racy, as they don't 574 // deterministically belong to any view. 575 if !pkg.Standalone { 576 return false 577 } 578 579 // If all the files contained in pkg have a real package, we don't need to 580 // keep pkg as a workspace package. 581 if allFilesHaveRealPackages(meta, pkg) { 582 return false 583 } 584 585 // For now, allow open standalone packages (i.e. go:build ignore) to be 586 // workspace packages, but this means they could belong to multiple views. 587 return containsOpenFileLocked(s, pkg) 588 } 589 590 // Apply filtering logic. 591 // 592 // Workspace packages must contain at least one non-filtered file. 593 filterFunc := s.view.filterFunc() 594 uris := make(map[protocol.DocumentURI]unit) // filtered package URIs 595 for _, uri := range slices.Concat(pkg.CompiledGoFiles, pkg.GoFiles) { 596 if !strings.Contains(string(uri), "/vendor/") && !filterFunc(uri) { 597 uris[uri] = struct{}{} 598 } 599 } 600 if len(uris) == 0 { 601 return false // no non-filtered files 602 } 603 604 // For non-module views (of type GOPATH or AdHoc), or if 605 // expandWorkspaceToModule is unset, workspace packages must be contained in 606 // the workspace folder. 607 // 608 // For module views (of type GoMod or GoWork), packages must in any case be 609 // in a workspace module (enforced below). 610 if !s.view.moduleMode() || !s.Options().ExpandWorkspaceToModule { 611 folder := s.view.folder.Dir.Path() 612 inFolder := false 613 for uri := range uris { 614 if pathutil.InDir(folder, uri.Path()) { 615 inFolder = true 616 break 617 } 618 } 619 if !inFolder { 620 return false 621 } 622 } 623 624 // In module mode, a workspace package must be contained in a workspace 625 // module. 626 if s.view.moduleMode() { 627 if pkg.Module == nil { 628 return false 629 } 630 modURI := protocol.URIFromPath(pkg.Module.GoMod) 631 _, ok := s.view.workspaceModFiles[modURI] 632 return ok 633 } 634 635 return true // an ad-hoc package or GOPATH package 636 } 637 638 // containsOpenFileLocked reports whether any file referenced by m is open in 639 // the snapshot s. 640 // 641 // s.mu must be held while calling this function. 642 func containsOpenFileLocked(s *Snapshot, mp *metadata.Package) bool { 643 uris := map[protocol.DocumentURI]struct{}{} 644 for _, uri := range mp.CompiledGoFiles { 645 uris[uri] = struct{}{} 646 } 647 for _, uri := range mp.GoFiles { 648 uris[uri] = struct{}{} 649 } 650 651 for uri := range uris { 652 fh, _ := s.files.get(uri) 653 if _, open := fh.(*overlay); open { 654 return true 655 } 656 } 657 return false 658 } 659 660 // computeWorkspacePackagesLocked computes workspace packages in the 661 // snapshot s for the given metadata graph. The result does not 662 // contain intermediate test variants. 663 // 664 // s.mu must be held while calling this function. 665 func computeWorkspacePackagesLocked(s *Snapshot, meta *metadata.Graph) immutable.Map[PackageID, PackagePath] { 666 workspacePackages := make(map[PackageID]PackagePath) 667 for _, mp := range meta.Packages { 668 if !isWorkspacePackageLocked(s, meta, mp) { 669 continue 670 } 671 672 switch { 673 case mp.ForTest == "": 674 // A normal package. 675 workspacePackages[mp.ID] = mp.PkgPath 676 case mp.ForTest == mp.PkgPath, mp.ForTest+"_test" == mp.PkgPath: 677 // The test variant of some workspace package or its x_test. 678 // To load it, we need to load the non-test variant with -test. 679 // 680 // Notably, this excludes intermediate test variants from workspace 681 // packages. 682 assert(!mp.IsIntermediateTestVariant(), "unexpected ITV") 683 workspacePackages[mp.ID] = mp.ForTest 684 } 685 } 686 return immutable.MapOf(workspacePackages) 687 } 688 689 // allFilesHaveRealPackages reports whether all files referenced by m are 690 // contained in a "real" package (not command-line-arguments). 691 // 692 // If m is valid but all "real" packages containing any file are invalid, this 693 // function returns false. 694 // 695 // If m is not a command-line-arguments package, this is trivially true. 696 func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool { 697 n := len(mp.CompiledGoFiles) 698 checkURIs: 699 for _, uri := range append(mp.CompiledGoFiles[0:n:n], mp.GoFiles...) { 700 for _, id := range g.IDs[uri] { 701 if !metadata.IsCommandLineArguments(id) { 702 continue checkURIs 703 } 704 } 705 return false 706 } 707 return true 708 } 709 710 func isTestMain(pkg *packages.Package, gocache string) bool { 711 // Test mains must have an import path that ends with ".test". 712 if !strings.HasSuffix(pkg.PkgPath, ".test") { 713 return false 714 } 715 // Test main packages are always named "main". 716 if pkg.Name != "main" { 717 return false 718 } 719 // Test mains always have exactly one GoFile that is in the build cache. 720 if len(pkg.GoFiles) > 1 { 721 return false 722 } 723 if !pathutil.InDir(gocache, pkg.GoFiles[0]) { 724 return false 725 } 726 return true 727 }