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