github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/cache/snapshot.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 "fmt" 11 "go/ast" 12 "go/token" 13 "go/types" 14 "io" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 24 "golang.org/x/mod/modfile" 25 "golang.org/x/mod/module" 26 "golang.org/x/mod/semver" 27 "github.com/powerman/golang-tools/go/analysis" 28 "github.com/powerman/golang-tools/go/packages" 29 "github.com/powerman/golang-tools/internal/event" 30 "github.com/powerman/golang-tools/internal/gocommand" 31 "github.com/powerman/golang-tools/internal/lsp/debug/log" 32 "github.com/powerman/golang-tools/internal/lsp/debug/tag" 33 "github.com/powerman/golang-tools/internal/lsp/source" 34 "github.com/powerman/golang-tools/internal/memoize" 35 "github.com/powerman/golang-tools/internal/packagesinternal" 36 "github.com/powerman/golang-tools/internal/span" 37 "github.com/powerman/golang-tools/internal/typesinternal" 38 errors "golang.org/x/xerrors" 39 ) 40 41 type snapshot struct { 42 memoize.Arg // allow as a memoize.Function arg 43 44 id uint64 45 view *View 46 47 cancel func() 48 backgroundCtx context.Context 49 50 // the cache generation that contains the data for this snapshot. 51 generation *memoize.Generation 52 53 // The snapshot's initialization state is controlled by the fields below. 54 // 55 // initializeOnce guards snapshot initialization. Each snapshot is 56 // initialized at most once: reinitialization is triggered on later snapshots 57 // by invalidating this field. 58 initializeOnce *sync.Once 59 // initializedErr holds the last error resulting from initialization. If 60 // initialization fails, we only retry when the the workspace modules change, 61 // to avoid too many go/packages calls. 62 initializedErr *source.CriticalError 63 64 // mu guards all of the maps in the snapshot, as well as the builtin URI. 65 mu sync.Mutex 66 67 // builtin pins the AST and package for builtin.go in memory. 68 builtin span.URI 69 70 // ids maps file URIs to package IDs. 71 // It may be invalidated on calls to go/packages. 72 ids map[span.URI][]PackageID 73 74 // metadata maps file IDs to their associated metadata. 75 // It may invalidated on calls to go/packages. 76 metadata map[PackageID]*KnownMetadata 77 78 // importedBy maps package IDs to the list of packages that import them. 79 importedBy map[PackageID][]PackageID 80 81 // files maps file URIs to their corresponding FileHandles. 82 // It may invalidated when a file's content changes. 83 files map[span.URI]source.VersionedFileHandle 84 85 // goFiles maps a parseKey to its parseGoHandle. 86 goFiles map[parseKey]*parseGoHandle 87 88 // TODO(rfindley): consider merging this with files to reduce burden on clone. 89 symbols map[span.URI]*symbolHandle 90 91 // packages maps a packageKey to a set of packageHandles to which that file belongs. 92 // It may be invalidated when a file's content changes. 93 packages map[packageKey]*packageHandle 94 95 // actions maps an actionkey to its actionHandle. 96 actions map[actionKey]*actionHandle 97 98 // workspacePackages contains the workspace's packages, which are loaded 99 // when the view is created. 100 workspacePackages map[PackageID]PackagePath 101 102 // unloadableFiles keeps track of files that we've failed to load. 103 unloadableFiles map[span.URI]struct{} 104 105 // parseModHandles keeps track of any parseModHandles for the snapshot. 106 // The handles need not refer to only the view's go.mod file. 107 parseModHandles map[span.URI]*parseModHandle 108 109 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot. 110 // The handles need not refer to only the view's go.work file. 111 parseWorkHandles map[span.URI]*parseWorkHandle 112 113 // Preserve go.mod-related handles to avoid garbage-collecting the results 114 // of various calls to the go command. The handles need not refer to only 115 // the view's go.mod file. 116 modTidyHandles map[span.URI]*modTidyHandle 117 modWhyHandles map[span.URI]*modWhyHandle 118 119 workspace *workspace 120 workspaceDirHandle *memoize.Handle 121 122 // knownSubdirs is the set of subdirectories in the workspace, used to 123 // create glob patterns for file watching. 124 knownSubdirs map[span.URI]struct{} 125 // unprocessedSubdirChanges are any changes that might affect the set of 126 // subdirectories in the workspace. They are not reflected to knownSubdirs 127 // during the snapshot cloning step as it can slow down cloning. 128 unprocessedSubdirChanges []*fileChange 129 } 130 131 type packageKey struct { 132 mode source.ParseMode 133 id PackageID 134 } 135 136 type actionKey struct { 137 pkg packageKey 138 analyzer *analysis.Analyzer 139 } 140 141 func (s *snapshot) ID() uint64 { 142 return s.id 143 } 144 145 func (s *snapshot) View() source.View { 146 return s.view 147 } 148 149 func (s *snapshot) BackgroundContext() context.Context { 150 return s.backgroundCtx 151 } 152 153 func (s *snapshot) FileSet() *token.FileSet { 154 return s.view.session.cache.fset 155 } 156 157 func (s *snapshot) ModFiles() []span.URI { 158 var uris []span.URI 159 for modURI := range s.workspace.getActiveModFiles() { 160 uris = append(uris, modURI) 161 } 162 return uris 163 } 164 165 func (s *snapshot) WorkFile() span.URI { 166 return s.workspace.workFile 167 } 168 169 func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle { 170 s.mu.Lock() 171 defer s.mu.Unlock() 172 173 tmpls := map[span.URI]source.VersionedFileHandle{} 174 for k, fh := range s.files { 175 if s.view.FileKind(fh) == source.Tmpl { 176 tmpls[k] = fh 177 } 178 } 179 return tmpls 180 } 181 182 func (s *snapshot) ValidBuildConfiguration() bool { 183 return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles()) 184 } 185 186 // workspaceMode describes the way in which the snapshot's workspace should 187 // be loaded. 188 func (s *snapshot) workspaceMode() workspaceMode { 189 var mode workspaceMode 190 191 // If the view has an invalid configuration, don't build the workspace 192 // module. 193 validBuildConfiguration := s.ValidBuildConfiguration() 194 if !validBuildConfiguration { 195 return mode 196 } 197 // If the view is not in a module and contains no modules, but still has a 198 // valid workspace configuration, do not create the workspace module. 199 // It could be using GOPATH or a different build system entirely. 200 if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration { 201 return mode 202 } 203 mode |= moduleMode 204 options := s.view.Options() 205 // The -modfile flag is available for Go versions >= 1.14. 206 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 { 207 mode |= tempModfile 208 } 209 return mode 210 } 211 212 // config returns the configuration used for the snapshot's interaction with 213 // the go/packages API. It uses the given working directory. 214 // 215 // TODO(rstambler): go/packages requires that we do not provide overlays for 216 // multiple modules in on config, so buildOverlay needs to filter overlays by 217 // module. 218 func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config { 219 s.view.optionsMu.Lock() 220 verboseOutput := s.view.options.VerboseOutput 221 s.view.optionsMu.Unlock() 222 223 cfg := &packages.Config{ 224 Context: ctx, 225 Dir: inv.WorkingDir, 226 Env: inv.Env, 227 BuildFlags: inv.BuildFlags, 228 Mode: packages.NeedName | 229 packages.NeedFiles | 230 packages.NeedCompiledGoFiles | 231 packages.NeedImports | 232 packages.NeedDeps | 233 packages.NeedTypesSizes | 234 packages.NeedModule, 235 Fset: s.FileSet(), 236 Overlay: s.buildOverlay(), 237 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) { 238 panic("go/packages must not be used to parse files") 239 }, 240 Logf: func(format string, args ...interface{}) { 241 if verboseOutput { 242 event.Log(ctx, fmt.Sprintf(format, args...)) 243 } 244 }, 245 Tests: true, 246 } 247 packagesinternal.SetModFile(cfg, inv.ModFile) 248 packagesinternal.SetModFlag(cfg, inv.ModFlag) 249 // We want to type check cgo code if go/types supports it. 250 if typesinternal.SetUsesCgo(&types.Config{}) { 251 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo) 252 } 253 packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner) 254 return cfg 255 } 256 257 func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) { 258 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 259 if err != nil { 260 return nil, err 261 } 262 defer cleanup() 263 264 return s.view.session.gocmdRunner.Run(ctx, *inv) 265 } 266 267 func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error { 268 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 269 if err != nil { 270 return err 271 } 272 defer cleanup() 273 return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr) 274 } 275 276 func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) { 277 var flags source.InvocationFlags 278 if s.workspaceMode()&tempModfile != 0 { 279 flags = source.WriteTemporaryModFile 280 } else { 281 flags = source.Normal 282 } 283 if allowNetwork { 284 flags |= source.AllowNetwork 285 } 286 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd}) 287 if err != nil { 288 return false, nil, nil, err 289 } 290 defer cleanup() 291 invoke := func(args ...string) (*bytes.Buffer, error) { 292 inv.Verb = args[0] 293 inv.Args = args[1:] 294 return s.view.session.gocmdRunner.Run(ctx, *inv) 295 } 296 if err := run(invoke); err != nil { 297 return false, nil, nil, err 298 } 299 if flags.Mode() != source.WriteTemporaryModFile { 300 return false, nil, nil, nil 301 } 302 var modBytes, sumBytes []byte 303 modBytes, err = ioutil.ReadFile(tmpURI.Filename()) 304 if err != nil && !os.IsNotExist(err) { 305 return false, nil, nil, err 306 } 307 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum") 308 if err != nil && !os.IsNotExist(err) { 309 return false, nil, nil, err 310 } 311 return true, modBytes, sumBytes, nil 312 } 313 314 func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) { 315 s.view.optionsMu.Lock() 316 allowModfileModificationOption := s.view.options.AllowModfileModifications 317 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess 318 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.effectiveGo111Module) 319 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...) 320 s.view.optionsMu.Unlock() 321 cleanup = func() {} // fallback 322 323 // All logic below is for module mode. 324 if s.workspaceMode()&moduleMode == 0 { 325 return "", inv, cleanup, nil 326 } 327 328 mode, allowNetwork := flags.Mode(), flags.AllowNetwork() 329 if !allowNetwork && !allowNetworkOption { 330 inv.Env = append(inv.Env, "GOPROXY=off") 331 } 332 333 // What follows is rather complicated logic for how to actually run the go 334 // command. A word of warning: this is the result of various incremental 335 // features added to gopls, and varying behavior of the Go command across Go 336 // versions. It can surely be cleaned up significantly, but tread carefully. 337 // 338 // Roughly speaking we need to resolve four things: 339 // - the working directory. 340 // - the -mod flag 341 // - the -modfile flag 342 // - the -workfile flag 343 // 344 // These are dependent on a number of factors: whether we need to run in a 345 // synthetic workspace, whether flags are supported at the current go 346 // version, and what we're actually trying to achieve (the 347 // source.InvocationFlags). 348 349 var modURI span.URI 350 // Select the module context to use. 351 // If we're type checking, we need to use the workspace context, meaning 352 // the main (workspace) module. Otherwise, we should use the module for 353 // the passed-in working dir. 354 if mode == source.LoadWorkspace { 355 switch s.workspace.moduleSource { 356 case legacyWorkspace: 357 for m := range s.workspace.getActiveModFiles() { // range to access the only element 358 modURI = m 359 } 360 case goWorkWorkspace: 361 if s.view.goversion >= 18 { 362 break 363 } 364 // Before go 1.18, the Go command did not natively support go.work files, 365 // so we 'fake' them with a workspace module. 366 fallthrough 367 case fileSystemWorkspace, goplsModWorkspace: 368 var tmpDir span.URI 369 var err error 370 tmpDir, err = s.getWorkspaceDir(ctx) 371 if err != nil { 372 return "", nil, cleanup, err 373 } 374 inv.WorkingDir = tmpDir.Filename() 375 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod")) 376 } 377 } else { 378 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir)) 379 } 380 381 var modContent []byte 382 if modURI != "" { 383 modFH, err := s.GetFile(ctx, modURI) 384 if err != nil { 385 return "", nil, cleanup, err 386 } 387 modContent, err = modFH.Read() 388 if err != nil { 389 return "", nil, cleanup, err 390 } 391 } 392 393 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent) 394 if err != nil { 395 return "", nil, cleanup, err 396 } 397 398 mutableModFlag := "" 399 // If the mod flag isn't set, populate it based on the mode and workspace. 400 if inv.ModFlag == "" { 401 if s.view.goversion >= 16 { 402 mutableModFlag = "mod" 403 } 404 405 switch mode { 406 case source.LoadWorkspace, source.Normal: 407 if vendorEnabled { 408 inv.ModFlag = "vendor" 409 } else if !allowModfileModificationOption { 410 inv.ModFlag = "readonly" 411 } else { 412 inv.ModFlag = mutableModFlag 413 } 414 case source.WriteTemporaryModFile: 415 inv.ModFlag = mutableModFlag 416 // -mod must be readonly when using go.work files - see issue #48941 417 inv.Env = append(inv.Env, "GOWORK=off") 418 } 419 } 420 421 // Only use a temp mod file if the modfile can actually be mutated. 422 needTempMod := inv.ModFlag == mutableModFlag 423 useTempMod := s.workspaceMode()&tempModfile != 0 424 if needTempMod && !useTempMod { 425 return "", nil, cleanup, source.ErrTmpModfileUnsupported 426 } 427 428 // We should use -workfile if: 429 // 1. We're not actively trying to mutate a modfile. 430 // 2. We have an active go.work file. 431 // 3. We're using at least Go 1.18. 432 useWorkFile := !needTempMod && s.workspace.moduleSource == goWorkWorkspace && s.view.goversion >= 18 433 if useWorkFile { 434 // TODO(#51215): build a temp workfile and set GOWORK in the environment. 435 } else if useTempMod { 436 if modURI == "" { 437 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir) 438 } 439 modFH, err := s.GetFile(ctx, modURI) 440 if err != nil { 441 return "", nil, cleanup, err 442 } 443 // Use the go.sum if it happens to be available. 444 gosum := s.goSum(ctx, modURI) 445 tmpURI, cleanup, err = tempModFile(modFH, gosum) 446 if err != nil { 447 return "", nil, cleanup, err 448 } 449 inv.ModFile = tmpURI.Filename() 450 } 451 452 return tmpURI, inv, cleanup, nil 453 } 454 455 func (s *snapshot) buildOverlay() map[string][]byte { 456 s.mu.Lock() 457 defer s.mu.Unlock() 458 459 overlays := make(map[string][]byte) 460 for uri, fh := range s.files { 461 overlay, ok := fh.(*overlay) 462 if !ok { 463 continue 464 } 465 if overlay.saved { 466 continue 467 } 468 // TODO(rstambler): Make sure not to send overlays outside of the current view. 469 overlays[uri.Filename()] = overlay.text 470 } 471 return overlays 472 } 473 474 func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string { 475 var unsaved []string 476 for uri, fh := range files { 477 if overlay, ok := fh.(*overlay); ok && !overlay.saved { 478 unsaved = append(unsaved, uri.Filename()) 479 } 480 } 481 sort.Strings(unsaved) 482 return hashContents([]byte(strings.Join(unsaved, ""))) 483 } 484 485 func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]source.Package, error) { 486 ctx = event.Label(ctx, tag.URI.Of(uri)) 487 488 phs, err := s.packageHandlesForFile(ctx, uri, mode, includeTestVariants) 489 if err != nil { 490 return nil, err 491 } 492 var pkgs []source.Package 493 for _, ph := range phs { 494 pkg, err := ph.check(ctx, s) 495 if err != nil { 496 return nil, err 497 } 498 pkgs = append(pkgs, pkg) 499 } 500 return pkgs, nil 501 } 502 503 func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) { 504 ctx = event.Label(ctx, tag.URI.Of(uri)) 505 506 phs, err := s.packageHandlesForFile(ctx, uri, mode, false) 507 if err != nil { 508 return nil, err 509 } 510 511 if len(phs) < 1 { 512 return nil, errors.Errorf("no packages") 513 } 514 515 ph := phs[0] 516 for _, handle := range phs[1:] { 517 switch pkgPolicy { 518 case source.WidestPackage: 519 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) { 520 ph = handle 521 } 522 case source.NarrowestPackage: 523 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) { 524 ph = handle 525 } 526 } 527 } 528 if ph == nil { 529 return nil, errors.Errorf("no packages in input") 530 } 531 532 return ph.check(ctx, s) 533 } 534 535 func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]*packageHandle, error) { 536 // Check if we should reload metadata for the file. We don't invalidate IDs 537 // (though we should), so the IDs will be a better source of truth than the 538 // metadata. If there are no IDs for the file, then we should also reload. 539 fh, err := s.GetFile(ctx, uri) 540 if err != nil { 541 return nil, err 542 } 543 if kind := s.view.FileKind(fh); kind != source.Go { 544 return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind) 545 } 546 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri) 547 if err != nil { 548 return nil, err 549 } 550 551 var phs []*packageHandle 552 for _, id := range knownIDs { 553 // Filter out any intermediate test variants. We typically aren't 554 // interested in these packages for file= style queries. 555 if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant && !includeTestVariants { 556 continue 557 } 558 var parseModes []source.ParseMode 559 switch mode { 560 case source.TypecheckAll: 561 if s.workspaceParseMode(id) == source.ParseFull { 562 parseModes = []source.ParseMode{source.ParseFull} 563 } else { 564 parseModes = []source.ParseMode{source.ParseExported, source.ParseFull} 565 } 566 case source.TypecheckFull: 567 parseModes = []source.ParseMode{source.ParseFull} 568 case source.TypecheckWorkspace: 569 parseModes = []source.ParseMode{s.workspaceParseMode(id)} 570 } 571 572 for _, parseMode := range parseModes { 573 ph, err := s.buildPackageHandle(ctx, id, parseMode) 574 if err != nil { 575 return nil, err 576 } 577 phs = append(phs, ph) 578 } 579 } 580 return phs, nil 581 } 582 583 func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) { 584 knownIDs := s.getIDsForURI(uri) 585 reload := len(knownIDs) == 0 586 for _, id := range knownIDs { 587 // Reload package metadata if any of the metadata has missing 588 // dependencies, in case something has changed since the last time we 589 // reloaded it. 590 if s.noValidMetadataForID(id) { 591 reload = true 592 break 593 } 594 // TODO(golang/go#36918): Previously, we would reload any package with 595 // missing dependencies. This is expensive and results in too many 596 // calls to packages.Load. Determine what we should do instead. 597 } 598 if reload { 599 err := s.load(ctx, false, fileURI(uri)) 600 601 if !s.useInvalidMetadata() && err != nil { 602 return nil, err 603 } 604 // We've tried to reload and there are still no known IDs for the URI. 605 // Return the load error, if there was one. 606 knownIDs = s.getIDsForURI(uri) 607 if len(knownIDs) == 0 { 608 return nil, err 609 } 610 } 611 return knownIDs, nil 612 } 613 614 // Only use invalid metadata for Go versions >= 1.13. Go 1.12 and below has 615 // issues with overlays that will cause confusing error messages if we reuse 616 // old metadata. 617 func (s *snapshot) useInvalidMetadata() bool { 618 return s.view.goversion >= 13 && s.view.Options().ExperimentalUseInvalidMetadata 619 } 620 621 func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) { 622 if err := s.awaitLoaded(ctx); err != nil { 623 return nil, err 624 } 625 ids := make(map[PackageID]struct{}) 626 s.transitiveReverseDependencies(PackageID(id), ids) 627 628 // Make sure to delete the original package ID from the map. 629 delete(ids, PackageID(id)) 630 631 var pkgs []source.Package 632 for id := range ids { 633 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 634 if err != nil { 635 return nil, err 636 } 637 pkgs = append(pkgs, pkg) 638 } 639 return pkgs, nil 640 } 641 642 func (s *snapshot) checkedPackage(ctx context.Context, id PackageID, mode source.ParseMode) (*pkg, error) { 643 ph, err := s.buildPackageHandle(ctx, id, mode) 644 if err != nil { 645 return nil, err 646 } 647 return ph.check(ctx, s) 648 } 649 650 // transitiveReverseDependencies populates the ids map with package IDs 651 // belonging to the provided package and its transitive reverse dependencies. 652 func (s *snapshot) transitiveReverseDependencies(id PackageID, ids map[PackageID]struct{}) { 653 if _, ok := ids[id]; ok { 654 return 655 } 656 m := s.getMetadata(id) 657 // Only use invalid metadata if we support it. 658 if m == nil || !(m.Valid || s.useInvalidMetadata()) { 659 return 660 } 661 ids[id] = struct{}{} 662 importedBy := s.getImportedBy(id) 663 for _, parentID := range importedBy { 664 s.transitiveReverseDependencies(parentID, ids) 665 } 666 } 667 668 func (s *snapshot) getGoFile(key parseKey) *parseGoHandle { 669 s.mu.Lock() 670 defer s.mu.Unlock() 671 return s.goFiles[key] 672 } 673 674 func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle { 675 s.mu.Lock() 676 defer s.mu.Unlock() 677 if existing, ok := s.goFiles[key]; ok { 678 return existing 679 } 680 s.goFiles[key] = pgh 681 return pgh 682 } 683 684 func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle { 685 s.mu.Lock() 686 defer s.mu.Unlock() 687 return s.parseModHandles[uri] 688 } 689 690 func (s *snapshot) getParseWorkHandle(uri span.URI) *parseWorkHandle { 691 s.mu.Lock() 692 defer s.mu.Unlock() 693 return s.parseWorkHandles[uri] 694 } 695 696 func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle { 697 s.mu.Lock() 698 defer s.mu.Unlock() 699 return s.modWhyHandles[uri] 700 } 701 702 func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle { 703 s.mu.Lock() 704 defer s.mu.Unlock() 705 return s.modTidyHandles[uri] 706 } 707 708 func (s *snapshot) getImportedBy(id PackageID) []PackageID { 709 s.mu.Lock() 710 defer s.mu.Unlock() 711 return s.getImportedByLocked(id) 712 } 713 714 func (s *snapshot) getImportedByLocked(id PackageID) []PackageID { 715 // If we haven't rebuilt the import graph since creating the snapshot. 716 if len(s.importedBy) == 0 { 717 s.rebuildImportGraph() 718 } 719 return s.importedBy[id] 720 } 721 722 func (s *snapshot) clearAndRebuildImportGraph() { 723 s.mu.Lock() 724 defer s.mu.Unlock() 725 726 // Completely invalidate the original map. 727 s.importedBy = make(map[PackageID][]PackageID) 728 s.rebuildImportGraph() 729 } 730 731 func (s *snapshot) rebuildImportGraph() { 732 for id, m := range s.metadata { 733 for _, importID := range m.Deps { 734 s.importedBy[importID] = append(s.importedBy[importID], id) 735 } 736 } 737 } 738 739 func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle { 740 s.mu.Lock() 741 defer s.mu.Unlock() 742 743 // If the package handle has already been cached, 744 // return the cached handle instead of overriding it. 745 if ph, ok := s.packages[ph.packageKey()]; ok { 746 return ph 747 } 748 s.packages[ph.packageKey()] = ph 749 return ph 750 } 751 752 func (s *snapshot) workspacePackageIDs() (ids []PackageID) { 753 s.mu.Lock() 754 defer s.mu.Unlock() 755 756 for id := range s.workspacePackages { 757 ids = append(ids, id) 758 } 759 return ids 760 } 761 762 func (s *snapshot) activePackageIDs() (ids []PackageID) { 763 if s.view.Options().MemoryMode == source.ModeNormal { 764 return s.workspacePackageIDs() 765 } 766 767 s.mu.Lock() 768 defer s.mu.Unlock() 769 770 seen := make(map[PackageID]bool) 771 for id := range s.workspacePackages { 772 if s.isActiveLocked(id, seen) { 773 ids = append(ids, id) 774 } 775 } 776 return ids 777 } 778 779 func (s *snapshot) isActiveLocked(id PackageID, seen map[PackageID]bool) (active bool) { 780 if seen == nil { 781 seen = make(map[PackageID]bool) 782 } 783 if seen, ok := seen[id]; ok { 784 return seen 785 } 786 defer func() { 787 seen[id] = active 788 }() 789 m, ok := s.metadata[id] 790 if !ok { 791 return false 792 } 793 for _, cgf := range m.CompiledGoFiles { 794 if s.isOpenLocked(cgf) { 795 return true 796 } 797 } 798 for _, dep := range m.Deps { 799 if s.isActiveLocked(dep, seen) { 800 return true 801 } 802 } 803 return false 804 } 805 806 const fileExtensions = "go,mod,sum,work" 807 808 func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { 809 extensions := fileExtensions 810 for _, ext := range s.View().Options().TemplateExtensions { 811 extensions += "," + ext 812 } 813 // Work-around microsoft/vscode#100870 by making sure that we are, 814 // at least, watching the user's entire workspace. This will still be 815 // applied to every folder in the workspace. 816 patterns := map[string]struct{}{ 817 fmt.Sprintf("**/*.{%s}", extensions): {}, 818 } 819 dirs := s.workspace.dirs(ctx, s) 820 for _, dir := range dirs { 821 dirName := dir.Filename() 822 823 // If the directory is within the view's folder, we're already watching 824 // it with the pattern above. 825 if source.InDir(s.view.folder.Filename(), dirName) { 826 continue 827 } 828 // TODO(rstambler): If microsoft/vscode#3025 is resolved before 829 // microsoft/vscode#101042, we will need a work-around for Windows 830 // drive letter casing. 831 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{} 832 } 833 834 // Some clients do not send notifications for changes to directories that 835 // contain Go code (golang/go#42348). To handle this, explicitly watch all 836 // of the directories in the workspace. We find them by adding the 837 // directories of every file in the snapshot's workspace directories. 838 var dirNames []string 839 for _, uri := range s.getKnownSubdirs(dirs) { 840 dirNames = append(dirNames, uri.Filename()) 841 } 842 sort.Strings(dirNames) 843 if len(dirNames) > 0 { 844 patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{} 845 } 846 return patterns 847 } 848 849 // collectAllKnownSubdirs collects all of the subdirectories within the 850 // snapshot's workspace directories. None of the workspace directories are 851 // included. 852 func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) { 853 dirs := s.workspace.dirs(ctx, s) 854 855 s.mu.Lock() 856 defer s.mu.Unlock() 857 858 s.knownSubdirs = map[span.URI]struct{}{} 859 for uri := range s.files { 860 s.addKnownSubdirLocked(uri, dirs) 861 } 862 } 863 864 func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) []span.URI { 865 s.mu.Lock() 866 defer s.mu.Unlock() 867 868 // First, process any pending changes and update the set of known 869 // subdirectories. 870 for _, c := range s.unprocessedSubdirChanges { 871 if c.isUnchanged { 872 continue 873 } 874 if !c.exists { 875 s.removeKnownSubdirLocked(c.fileHandle.URI()) 876 } else { 877 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs) 878 } 879 } 880 s.unprocessedSubdirChanges = nil 881 882 var result []span.URI 883 for uri := range s.knownSubdirs { 884 result = append(result, uri) 885 } 886 return result 887 } 888 889 func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) { 890 dir := filepath.Dir(uri.Filename()) 891 // First check if the directory is already known, because then we can 892 // return early. 893 if _, ok := s.knownSubdirs[span.URIFromPath(dir)]; ok { 894 return 895 } 896 var matched span.URI 897 for _, wsDir := range dirs { 898 if source.InDir(wsDir.Filename(), dir) { 899 matched = wsDir 900 break 901 } 902 } 903 // Don't watch any directory outside of the workspace directories. 904 if matched == "" { 905 return 906 } 907 for { 908 if dir == "" || dir == matched.Filename() { 909 break 910 } 911 uri := span.URIFromPath(dir) 912 if _, ok := s.knownSubdirs[uri]; ok { 913 break 914 } 915 s.knownSubdirs[uri] = struct{}{} 916 dir = filepath.Dir(dir) 917 } 918 } 919 920 func (s *snapshot) removeKnownSubdirLocked(uri span.URI) { 921 dir := filepath.Dir(uri.Filename()) 922 for dir != "" { 923 uri := span.URIFromPath(dir) 924 if _, ok := s.knownSubdirs[uri]; !ok { 925 break 926 } 927 if info, _ := os.Stat(dir); info == nil { 928 delete(s.knownSubdirs, uri) 929 } 930 dir = filepath.Dir(dir) 931 } 932 } 933 934 // knownFilesInDir returns the files known to the given snapshot that are in 935 // the given directory. It does not respect symlinks. 936 func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI { 937 var files []span.URI 938 s.mu.Lock() 939 defer s.mu.Unlock() 940 941 for uri := range s.files { 942 if source.InDir(dir.Filename(), uri.Filename()) { 943 files = append(files, uri) 944 } 945 } 946 return files 947 } 948 949 func (s *snapshot) workspacePackageHandles(ctx context.Context) ([]*packageHandle, error) { 950 if err := s.awaitLoaded(ctx); err != nil { 951 return nil, err 952 } 953 var phs []*packageHandle 954 for _, pkgID := range s.workspacePackageIDs() { 955 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID)) 956 if err != nil { 957 return nil, err 958 } 959 phs = append(phs, ph) 960 } 961 return phs, nil 962 } 963 964 func (s *snapshot) ActivePackages(ctx context.Context) ([]source.Package, error) { 965 phs, err := s.activePackageHandles(ctx) 966 if err != nil { 967 return nil, err 968 } 969 var pkgs []source.Package 970 for _, ph := range phs { 971 pkg, err := ph.check(ctx, s) 972 if err != nil { 973 return nil, err 974 } 975 pkgs = append(pkgs, pkg) 976 } 977 return pkgs, nil 978 } 979 980 func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle, error) { 981 if err := s.awaitLoaded(ctx); err != nil { 982 return nil, err 983 } 984 var phs []*packageHandle 985 for _, pkgID := range s.activePackageIDs() { 986 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID)) 987 if err != nil { 988 return nil, err 989 } 990 phs = append(phs, ph) 991 } 992 return phs, nil 993 } 994 995 func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, error) { 996 result := make(map[span.URI][]source.Symbol) 997 998 // Keep going on errors, but log the first failure. Partial symbol results 999 // are better than no symbol results. 1000 var firstErr error 1001 for uri, f := range s.files { 1002 sh := s.buildSymbolHandle(ctx, f) 1003 v, err := sh.handle.Get(ctx, s.generation, s) 1004 if err != nil { 1005 if firstErr == nil { 1006 firstErr = err 1007 } 1008 continue 1009 } 1010 data := v.(*symbolData) 1011 result[uri] = data.symbols 1012 } 1013 if firstErr != nil { 1014 event.Error(ctx, "getting snapshot symbols", firstErr) 1015 } 1016 return result, nil 1017 } 1018 1019 func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]source.Metadata, error) { 1020 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri) 1021 if err != nil { 1022 return nil, err 1023 } 1024 var mds []source.Metadata 1025 for _, id := range knownIDs { 1026 md := s.getMetadata(id) 1027 // TODO(rfindley): knownIDs and metadata should be in sync, but existing 1028 // code is defensive of nil metadata. 1029 if md != nil { 1030 mds = append(mds, md) 1031 } 1032 } 1033 return mds, nil 1034 } 1035 1036 func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) { 1037 if err := s.awaitLoaded(ctx); err != nil { 1038 return nil, err 1039 } 1040 1041 // The WorkspaceSymbols implementation relies on this function returning 1042 // workspace packages first. 1043 ids := s.workspacePackageIDs() 1044 s.mu.Lock() 1045 for id := range s.metadata { 1046 if _, ok := s.workspacePackages[id]; ok { 1047 continue 1048 } 1049 ids = append(ids, id) 1050 } 1051 s.mu.Unlock() 1052 1053 var pkgs []source.Package 1054 for _, id := range ids { 1055 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 1056 if err != nil { 1057 return nil, err 1058 } 1059 pkgs = append(pkgs, pkg) 1060 } 1061 return pkgs, nil 1062 } 1063 1064 func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) { 1065 // Don't reload workspace package metadata. 1066 // This function is meant to only return currently cached information. 1067 s.AwaitInitialized(ctx) 1068 1069 s.mu.Lock() 1070 defer s.mu.Unlock() 1071 1072 results := map[string]source.Package{} 1073 for _, ph := range s.packages { 1074 cachedPkg, err := ph.cached(s.generation) 1075 if err != nil { 1076 continue 1077 } 1078 for importPath, newPkg := range cachedPkg.imports { 1079 if oldPkg, ok := results[string(importPath)]; ok { 1080 // Using the same trick as NarrowestPackage, prefer non-variants. 1081 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) { 1082 results[string(importPath)] = newPkg 1083 } 1084 } else { 1085 results[string(importPath)] = newPkg 1086 } 1087 } 1088 } 1089 return results, nil 1090 } 1091 1092 func (s *snapshot) GoModForFile(uri span.URI) span.URI { 1093 return moduleForURI(s.workspace.activeModFiles, uri) 1094 } 1095 1096 func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI { 1097 var match span.URI 1098 for modURI := range modFiles { 1099 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) { 1100 continue 1101 } 1102 if len(modURI) > len(match) { 1103 match = modURI 1104 } 1105 } 1106 return match 1107 } 1108 1109 func (s *snapshot) getPackage(id PackageID, mode source.ParseMode) *packageHandle { 1110 s.mu.Lock() 1111 defer s.mu.Unlock() 1112 1113 key := packageKey{ 1114 id: id, 1115 mode: mode, 1116 } 1117 return s.packages[key] 1118 } 1119 1120 func (s *snapshot) getSymbolHandle(uri span.URI) *symbolHandle { 1121 s.mu.Lock() 1122 defer s.mu.Unlock() 1123 1124 return s.symbols[uri] 1125 } 1126 1127 func (s *snapshot) addSymbolHandle(sh *symbolHandle) *symbolHandle { 1128 s.mu.Lock() 1129 defer s.mu.Unlock() 1130 1131 uri := sh.fh.URI() 1132 // If the package handle has already been cached, 1133 // return the cached handle instead of overriding it. 1134 if sh, ok := s.symbols[uri]; ok { 1135 return sh 1136 } 1137 s.symbols[uri] = sh 1138 return sh 1139 } 1140 1141 func (s *snapshot) getActionHandle(id PackageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle { 1142 s.mu.Lock() 1143 defer s.mu.Unlock() 1144 1145 key := actionKey{ 1146 pkg: packageKey{ 1147 id: id, 1148 mode: m, 1149 }, 1150 analyzer: a, 1151 } 1152 return s.actions[key] 1153 } 1154 1155 func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle { 1156 s.mu.Lock() 1157 defer s.mu.Unlock() 1158 1159 key := actionKey{ 1160 analyzer: ah.analyzer, 1161 pkg: packageKey{ 1162 id: ah.pkg.m.ID, 1163 mode: ah.pkg.mode, 1164 }, 1165 } 1166 if ah, ok := s.actions[key]; ok { 1167 return ah 1168 } 1169 s.actions[key] = ah 1170 return ah 1171 } 1172 1173 func (s *snapshot) getIDsForURI(uri span.URI) []PackageID { 1174 s.mu.Lock() 1175 defer s.mu.Unlock() 1176 1177 return s.ids[uri] 1178 } 1179 1180 func (s *snapshot) getMetadata(id PackageID) *KnownMetadata { 1181 s.mu.Lock() 1182 defer s.mu.Unlock() 1183 1184 return s.metadata[id] 1185 } 1186 1187 func (s *snapshot) shouldLoad(scope interface{}) bool { 1188 s.mu.Lock() 1189 defer s.mu.Unlock() 1190 1191 switch scope := scope.(type) { 1192 case PackagePath: 1193 var meta *KnownMetadata 1194 for _, m := range s.metadata { 1195 if m.PkgPath != scope { 1196 continue 1197 } 1198 meta = m 1199 } 1200 if meta == nil || meta.ShouldLoad { 1201 return true 1202 } 1203 return false 1204 case fileURI: 1205 uri := span.URI(scope) 1206 ids := s.ids[uri] 1207 if len(ids) == 0 { 1208 return true 1209 } 1210 for _, id := range ids { 1211 m, ok := s.metadata[id] 1212 if !ok || m.ShouldLoad { 1213 return true 1214 } 1215 } 1216 return false 1217 default: 1218 return true 1219 } 1220 } 1221 1222 func (s *snapshot) clearShouldLoad(scope interface{}) { 1223 s.mu.Lock() 1224 defer s.mu.Unlock() 1225 1226 switch scope := scope.(type) { 1227 case PackagePath: 1228 var meta *KnownMetadata 1229 for _, m := range s.metadata { 1230 if m.PkgPath == scope { 1231 meta = m 1232 } 1233 } 1234 if meta == nil { 1235 return 1236 } 1237 meta.ShouldLoad = false 1238 case fileURI: 1239 uri := span.URI(scope) 1240 ids := s.ids[uri] 1241 if len(ids) == 0 { 1242 return 1243 } 1244 for _, id := range ids { 1245 if m, ok := s.metadata[id]; ok { 1246 m.ShouldLoad = false 1247 } 1248 } 1249 } 1250 } 1251 1252 // noValidMetadataForURILocked reports whether there is any valid metadata for 1253 // the given URI. 1254 func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool { 1255 ids, ok := s.ids[uri] 1256 if !ok { 1257 return true 1258 } 1259 for _, id := range ids { 1260 if m, ok := s.metadata[id]; ok && m.Valid { 1261 return false 1262 } 1263 } 1264 return true 1265 } 1266 1267 // noValidMetadataForID reports whether there is no valid metadata for the 1268 // given ID. 1269 func (s *snapshot) noValidMetadataForID(id PackageID) bool { 1270 s.mu.Lock() 1271 defer s.mu.Unlock() 1272 return s.noValidMetadataForIDLocked(id) 1273 } 1274 1275 func (s *snapshot) noValidMetadataForIDLocked(id PackageID) bool { 1276 m := s.metadata[id] 1277 return m == nil || !m.Valid 1278 } 1279 1280 // updateIDForURIsLocked adds the given ID to the set of known IDs for the given URI. 1281 // Any existing invalid IDs are removed from the set of known IDs. IDs that are 1282 // not "command-line-arguments" are preferred, so if a new ID comes in for a 1283 // URI that previously only had "command-line-arguments", the new ID will 1284 // replace the "command-line-arguments" ID. 1285 func (s *snapshot) updateIDForURIsLocked(id PackageID, uris map[span.URI]struct{}) { 1286 for uri := range uris { 1287 // Collect the new set of IDs, preserving any valid existing IDs. 1288 newIDs := []PackageID{id} 1289 for _, existingID := range s.ids[uri] { 1290 // Don't set duplicates of the same ID. 1291 if existingID == id { 1292 continue 1293 } 1294 // If the package previously only had a command-line-arguments ID, 1295 // delete the command-line-arguments workspace package. 1296 if source.IsCommandLineArguments(string(existingID)) { 1297 delete(s.workspacePackages, existingID) 1298 continue 1299 } 1300 // If the metadata for an existing ID is invalid, and we are 1301 // setting metadata for a new, valid ID--don't preserve the old ID. 1302 if m, ok := s.metadata[existingID]; !ok || !m.Valid { 1303 continue 1304 } 1305 newIDs = append(newIDs, existingID) 1306 } 1307 sort.Slice(newIDs, func(i, j int) bool { 1308 return newIDs[i] < newIDs[j] 1309 }) 1310 s.ids[uri] = newIDs 1311 } 1312 } 1313 1314 func (s *snapshot) isWorkspacePackage(id PackageID) bool { 1315 s.mu.Lock() 1316 defer s.mu.Unlock() 1317 1318 _, ok := s.workspacePackages[id] 1319 return ok 1320 } 1321 1322 func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle { 1323 f := s.view.getFile(uri) 1324 1325 s.mu.Lock() 1326 defer s.mu.Unlock() 1327 1328 return s.files[f.URI()] 1329 } 1330 1331 // GetVersionedFile returns a File for the given URI. If the file is unknown it 1332 // is added to the managed set. 1333 // 1334 // GetVersionedFile succeeds even if the file does not exist. A non-nil error return 1335 // indicates some type of internal error, for example if ctx is cancelled. 1336 func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) { 1337 f := s.view.getFile(uri) 1338 1339 s.mu.Lock() 1340 defer s.mu.Unlock() 1341 return s.getFileLocked(ctx, f) 1342 } 1343 1344 // GetFile implements the fileSource interface by wrapping GetVersionedFile. 1345 func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 1346 return s.GetVersionedFile(ctx, uri) 1347 } 1348 1349 func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) { 1350 if fh, ok := s.files[f.URI()]; ok { 1351 return fh, nil 1352 } 1353 1354 fh, err := s.view.session.cache.getFile(ctx, f.URI()) 1355 if err != nil { 1356 return nil, err 1357 } 1358 closed := &closedFile{fh} 1359 s.files[f.URI()] = closed 1360 return closed, nil 1361 } 1362 1363 func (s *snapshot) IsOpen(uri span.URI) bool { 1364 s.mu.Lock() 1365 defer s.mu.Unlock() 1366 return s.isOpenLocked(uri) 1367 1368 } 1369 1370 func (s *snapshot) openFiles() []source.VersionedFileHandle { 1371 s.mu.Lock() 1372 defer s.mu.Unlock() 1373 1374 var open []source.VersionedFileHandle 1375 for _, fh := range s.files { 1376 if s.isOpenLocked(fh.URI()) { 1377 open = append(open, fh) 1378 } 1379 } 1380 return open 1381 } 1382 1383 func (s *snapshot) isOpenLocked(uri span.URI) bool { 1384 _, open := s.files[uri].(*overlay) 1385 return open 1386 } 1387 1388 func (s *snapshot) awaitLoaded(ctx context.Context) error { 1389 loadErr := s.awaitLoadedAllErrors(ctx) 1390 1391 s.mu.Lock() 1392 defer s.mu.Unlock() 1393 1394 // If we still have absolutely no metadata, check if the view failed to 1395 // initialize and return any errors. 1396 if s.useInvalidMetadata() && len(s.metadata) > 0 { 1397 return nil 1398 } 1399 for _, m := range s.metadata { 1400 if m.Valid { 1401 return nil 1402 } 1403 } 1404 if loadErr != nil { 1405 return loadErr.MainError 1406 } 1407 return nil 1408 } 1409 1410 func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError { 1411 loadErr := s.awaitLoadedAllErrors(ctx) 1412 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) { 1413 return nil 1414 } 1415 1416 // Even if packages didn't fail to load, we still may want to show 1417 // additional warnings. 1418 if loadErr == nil { 1419 wsPkgs, _ := s.ActivePackages(ctx) 1420 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" { 1421 return &source.CriticalError{ 1422 MainError: errors.New(msg), 1423 } 1424 } 1425 // Even if workspace packages were returned, there still may be an error 1426 // with the user's workspace layout. Workspace packages that only have the 1427 // ID "command-line-arguments" are usually a symptom of a bad workspace 1428 // configuration. 1429 if containsCommandLineArguments(wsPkgs) { 1430 return s.workspaceLayoutError(ctx) 1431 } 1432 return nil 1433 } 1434 1435 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") { 1436 return s.workspaceLayoutError(ctx) 1437 } 1438 return loadErr 1439 } 1440 1441 const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src. 1442 If you are using modules, please open your editor to a directory in your module. 1443 If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.` 1444 1445 func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string { 1446 if snapshot.ValidBuildConfiguration() { 1447 return "" 1448 } 1449 for _, pkg := range pkgs { 1450 if len(pkg.MissingDependencies()) > 0 { 1451 return adHocPackagesWarning 1452 } 1453 } 1454 return "" 1455 } 1456 1457 func containsCommandLineArguments(pkgs []source.Package) bool { 1458 for _, pkg := range pkgs { 1459 if source.IsCommandLineArguments(pkg.ID()) { 1460 return true 1461 } 1462 } 1463 return false 1464 } 1465 1466 func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError { 1467 // Do not return results until the snapshot's view has been initialized. 1468 s.AwaitInitialized(ctx) 1469 1470 // TODO(rstambler): Should we be more careful about returning the 1471 // initialization error? Is it possible for the initialization error to be 1472 // corrected without a successful reinitialization? 1473 s.mu.Lock() 1474 initializedErr := s.initializedErr 1475 s.mu.Unlock() 1476 if initializedErr != nil { 1477 return initializedErr 1478 } 1479 1480 if ctx.Err() != nil { 1481 return &source.CriticalError{MainError: ctx.Err()} 1482 } 1483 1484 if err := s.reloadWorkspace(ctx); err != nil { 1485 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1486 return &source.CriticalError{ 1487 MainError: err, 1488 DiagList: diags, 1489 } 1490 } 1491 if err := s.reloadOrphanedFiles(ctx); err != nil { 1492 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1493 return &source.CriticalError{ 1494 MainError: err, 1495 DiagList: diags, 1496 } 1497 } 1498 return nil 1499 } 1500 1501 func (s *snapshot) getInitializationError(ctx context.Context) *source.CriticalError { 1502 s.mu.Lock() 1503 defer s.mu.Unlock() 1504 1505 return s.initializedErr 1506 } 1507 1508 func (s *snapshot) AwaitInitialized(ctx context.Context) { 1509 select { 1510 case <-ctx.Done(): 1511 return 1512 case <-s.view.initialWorkspaceLoad: 1513 } 1514 // We typically prefer to run something as intensive as the IWL without 1515 // blocking. I'm not sure if there is a way to do that here. 1516 s.initialize(ctx, false) 1517 } 1518 1519 // reloadWorkspace reloads the metadata for all invalidated workspace packages. 1520 func (s *snapshot) reloadWorkspace(ctx context.Context) error { 1521 // See which of the workspace packages are missing metadata. 1522 s.mu.Lock() 1523 missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0 1524 pkgPathSet := map[PackagePath]struct{}{} 1525 for id, pkgPath := range s.workspacePackages { 1526 if m, ok := s.metadata[id]; ok && m.Valid { 1527 continue 1528 } 1529 missingMetadata = true 1530 1531 // Don't try to reload "command-line-arguments" directly. 1532 if source.IsCommandLineArguments(string(pkgPath)) { 1533 continue 1534 } 1535 pkgPathSet[pkgPath] = struct{}{} 1536 } 1537 s.mu.Unlock() 1538 1539 // If the view's build configuration is invalid, we cannot reload by 1540 // package path. Just reload the directory instead. 1541 if missingMetadata && !s.ValidBuildConfiguration() { 1542 return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW")) 1543 } 1544 1545 if len(pkgPathSet) == 0 { 1546 return nil 1547 } 1548 1549 var pkgPaths []interface{} 1550 for pkgPath := range pkgPathSet { 1551 pkgPaths = append(pkgPaths, pkgPath) 1552 } 1553 return s.load(ctx, false, pkgPaths...) 1554 } 1555 1556 func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error { 1557 // When we load ./... or a package path directly, we may not get packages 1558 // that exist only in overlays. As a workaround, we search all of the files 1559 // available in the snapshot and reload their metadata individually using a 1560 // file= query if the metadata is unavailable. 1561 files := s.orphanedFiles() 1562 1563 // Files without a valid package declaration can't be loaded. Don't try. 1564 var scopes []interface{} 1565 for _, file := range files { 1566 pgf, err := s.ParseGo(ctx, file, source.ParseHeader) 1567 if err != nil { 1568 continue 1569 } 1570 if !pgf.File.Package.IsValid() { 1571 continue 1572 } 1573 scopes = append(scopes, fileURI(file.URI())) 1574 } 1575 1576 if len(scopes) == 0 { 1577 return nil 1578 } 1579 1580 // The regtests match this exact log message, keep them in sync. 1581 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes)) 1582 err := s.load(ctx, false, scopes...) 1583 1584 // If we failed to load some files, i.e. they have no metadata, 1585 // mark the failures so we don't bother retrying until the file's 1586 // content changes. 1587 // 1588 // TODO(rstambler): This may be an overestimate if the load stopped 1589 // early for an unrelated errors. Add a fallback? 1590 // 1591 // Check for context cancellation so that we don't incorrectly mark files 1592 // as unloadable, but don't return before setting all workspace packages. 1593 if ctx.Err() == nil && err != nil { 1594 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes)) 1595 s.mu.Lock() 1596 for _, scope := range scopes { 1597 uri := span.URI(scope.(fileURI)) 1598 if s.noValidMetadataForURILocked(uri) { 1599 s.unloadableFiles[uri] = struct{}{} 1600 } 1601 } 1602 s.mu.Unlock() 1603 } 1604 return nil 1605 } 1606 1607 func (s *snapshot) orphanedFiles() []source.VersionedFileHandle { 1608 s.mu.Lock() 1609 defer s.mu.Unlock() 1610 1611 var files []source.VersionedFileHandle 1612 for uri, fh := range s.files { 1613 // Don't try to reload metadata for go.mod files. 1614 if s.view.FileKind(fh) != source.Go { 1615 continue 1616 } 1617 // If the URI doesn't belong to this view, then it's not in a workspace 1618 // package and should not be reloaded directly. 1619 if !contains(s.view.session.viewsOf(uri), s.view) { 1620 continue 1621 } 1622 // If the file is not open and is in a vendor directory, don't treat it 1623 // like a workspace package. 1624 if _, ok := fh.(*overlay); !ok && inVendor(uri) { 1625 continue 1626 } 1627 // Don't reload metadata for files we've already deemed unloadable. 1628 if _, ok := s.unloadableFiles[uri]; ok { 1629 continue 1630 } 1631 if s.noValidMetadataForURILocked(uri) { 1632 files = append(files, fh) 1633 } 1634 } 1635 return files 1636 } 1637 1638 func contains(views []*View, view *View) bool { 1639 for _, v := range views { 1640 if v == view { 1641 return true 1642 } 1643 } 1644 return false 1645 } 1646 1647 func inVendor(uri span.URI) bool { 1648 if !strings.Contains(string(uri), "/vendor/") { 1649 return false 1650 } 1651 // Only packages in _subdirectories_ of /vendor/ are considered vendored 1652 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). 1653 split := strings.Split(string(uri), "/vendor/") 1654 if len(split) < 2 { 1655 return false 1656 } 1657 return strings.Contains(split[1], "/") 1658 } 1659 1660 func generationName(v *View, snapshotID uint64) string { 1661 return fmt.Sprintf("v%v/%v", v.id, snapshotID) 1662 } 1663 1664 // checkSnapshotLocked verifies that some invariants are preserved on the 1665 // snapshot. 1666 func checkSnapshotLocked(ctx context.Context, s *snapshot) { 1667 // Check that every go file for a workspace package is identified as 1668 // belonging to that workspace package. 1669 for wsID := range s.workspacePackages { 1670 if m, ok := s.metadata[wsID]; ok { 1671 for _, uri := range m.GoFiles { 1672 found := false 1673 for _, id := range s.ids[uri] { 1674 if id == wsID { 1675 found = true 1676 break 1677 } 1678 } 1679 if !found { 1680 log.Error.Logf(ctx, "workspace package %v not associated with %v", wsID, uri) 1681 } 1682 } 1683 } 1684 } 1685 } 1686 1687 // unappliedChanges is a file source that handles an uncloned snapshot. 1688 type unappliedChanges struct { 1689 originalSnapshot *snapshot 1690 changes map[span.URI]*fileChange 1691 } 1692 1693 func (ac *unappliedChanges) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 1694 if c, ok := ac.changes[uri]; ok { 1695 return c.fileHandle, nil 1696 } 1697 return ac.originalSnapshot.GetFile(ctx, uri) 1698 } 1699 1700 func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) *snapshot { 1701 var vendorChanged bool 1702 newWorkspace, workspaceChanged, workspaceReload := s.workspace.invalidate(ctx, changes, &unappliedChanges{ 1703 originalSnapshot: s, 1704 changes: changes, 1705 }) 1706 1707 s.mu.Lock() 1708 defer s.mu.Unlock() 1709 1710 checkSnapshotLocked(ctx, s) 1711 1712 newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1)) 1713 bgCtx, cancel := context.WithCancel(bgCtx) 1714 result := &snapshot{ 1715 id: s.id + 1, 1716 generation: newGen, 1717 view: s.view, 1718 backgroundCtx: bgCtx, 1719 cancel: cancel, 1720 builtin: s.builtin, 1721 initializeOnce: s.initializeOnce, 1722 initializedErr: s.initializedErr, 1723 ids: make(map[span.URI][]PackageID, len(s.ids)), 1724 importedBy: make(map[PackageID][]PackageID, len(s.importedBy)), 1725 metadata: make(map[PackageID]*KnownMetadata, len(s.metadata)), 1726 packages: make(map[packageKey]*packageHandle, len(s.packages)), 1727 actions: make(map[actionKey]*actionHandle, len(s.actions)), 1728 files: make(map[span.URI]source.VersionedFileHandle, len(s.files)), 1729 goFiles: make(map[parseKey]*parseGoHandle, len(s.goFiles)), 1730 symbols: make(map[span.URI]*symbolHandle, len(s.symbols)), 1731 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)), 1732 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)), 1733 parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)), 1734 parseWorkHandles: make(map[span.URI]*parseWorkHandle, len(s.parseWorkHandles)), 1735 modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)), 1736 modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)), 1737 knownSubdirs: make(map[span.URI]struct{}, len(s.knownSubdirs)), 1738 workspace: newWorkspace, 1739 } 1740 1741 if !workspaceChanged && s.workspaceDirHandle != nil { 1742 result.workspaceDirHandle = s.workspaceDirHandle 1743 newGen.Inherit(s.workspaceDirHandle) 1744 } 1745 1746 // Copy all of the FileHandles. 1747 for k, v := range s.files { 1748 result.files[k] = v 1749 } 1750 for k, v := range s.symbols { 1751 if change, ok := changes[k]; ok { 1752 if change.exists { 1753 result.symbols[k] = result.buildSymbolHandle(ctx, change.fileHandle) 1754 } 1755 continue 1756 } 1757 newGen.Inherit(v.handle) 1758 result.symbols[k] = v 1759 } 1760 1761 // Copy the set of unloadable files. 1762 for k, v := range s.unloadableFiles { 1763 result.unloadableFiles[k] = v 1764 } 1765 // Copy all of the modHandles. 1766 for k, v := range s.parseModHandles { 1767 result.parseModHandles[k] = v 1768 } 1769 // Copy all of the parseWorkHandles. 1770 for k, v := range s.parseWorkHandles { 1771 result.parseWorkHandles[k] = v 1772 } 1773 1774 for k, v := range s.goFiles { 1775 if _, ok := changes[k.file.URI]; ok { 1776 continue 1777 } 1778 newGen.Inherit(v.handle) 1779 result.goFiles[k] = v 1780 } 1781 1782 // Copy all of the go.mod-related handles. They may be invalidated later, 1783 // so we inherit them at the end of the function. 1784 for k, v := range s.modTidyHandles { 1785 if _, ok := changes[k]; ok { 1786 continue 1787 } 1788 result.modTidyHandles[k] = v 1789 } 1790 for k, v := range s.modWhyHandles { 1791 if _, ok := changes[k]; ok { 1792 continue 1793 } 1794 result.modWhyHandles[k] = v 1795 } 1796 1797 // Add all of the known subdirectories, but don't update them for the 1798 // changed files. We need to rebuild the workspace module to know the 1799 // true set of known subdirectories, but we don't want to do that in clone. 1800 for k, v := range s.knownSubdirs { 1801 result.knownSubdirs[k] = v 1802 } 1803 for _, c := range changes { 1804 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c) 1805 } 1806 1807 // directIDs keeps track of package IDs that have directly changed. 1808 // It maps id->invalidateMetadata. 1809 directIDs := map[PackageID]bool{} 1810 1811 // Invalidate all package metadata if the workspace module has changed. 1812 if workspaceReload { 1813 for k := range s.metadata { 1814 directIDs[k] = true 1815 } 1816 } 1817 1818 changedPkgNames := map[PackageID]struct{}{} 1819 anyImportDeleted := false 1820 for uri, change := range changes { 1821 // Maybe reinitialize the view if we see a change in the vendor 1822 // directory. 1823 if inVendor(uri) { 1824 vendorChanged = true 1825 } 1826 1827 // The original FileHandle for this URI is cached on the snapshot. 1828 originalFH := s.files[uri] 1829 1830 // Check if the file's package name or imports have changed, 1831 // and if so, invalidate this file's packages' metadata. 1832 var shouldInvalidateMetadata, pkgNameChanged, importDeleted bool 1833 if !isGoMod(uri) { 1834 shouldInvalidateMetadata, pkgNameChanged, importDeleted = s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle) 1835 } 1836 invalidateMetadata := forceReloadMetadata || workspaceReload || shouldInvalidateMetadata 1837 anyImportDeleted = anyImportDeleted || importDeleted 1838 1839 // Mark all of the package IDs containing the given file. 1840 // TODO: if the file has moved into a new package, we should invalidate that too. 1841 filePackageIDs := guessPackageIDsForURI(uri, s.ids) 1842 if pkgNameChanged { 1843 for _, id := range filePackageIDs { 1844 changedPkgNames[id] = struct{}{} 1845 } 1846 } 1847 for _, id := range filePackageIDs { 1848 directIDs[id] = directIDs[id] || invalidateMetadata 1849 } 1850 1851 // Invalidate the previous modTidyHandle if any of the files have been 1852 // saved or if any of the metadata has been invalidated. 1853 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) { 1854 // TODO(rstambler): Only delete mod handles for which the 1855 // withoutURI is relevant. 1856 for k := range s.modTidyHandles { 1857 delete(result.modTidyHandles, k) 1858 } 1859 for k := range s.modWhyHandles { 1860 delete(result.modWhyHandles, k) 1861 } 1862 } 1863 delete(result.parseModHandles, uri) 1864 delete(result.parseWorkHandles, uri) 1865 // Handle the invalidated file; it may have new contents or not exist. 1866 if !change.exists { 1867 delete(result.files, uri) 1868 } else { 1869 result.files[uri] = change.fileHandle 1870 } 1871 1872 // Make sure to remove the changed file from the unloadable set. 1873 delete(result.unloadableFiles, uri) 1874 } 1875 1876 // Deleting an import can cause list errors due to import cycles to be 1877 // resolved. The best we can do without parsing the list error message is to 1878 // hope that list errors may have been resolved by a deleted import. 1879 // 1880 // We could do better by parsing the list error message. We already do this 1881 // to assign a better range to the list error, but for such critical 1882 // functionality as metadata, it's better to be conservative until it proves 1883 // impractical. 1884 // 1885 // We could also do better by looking at which imports were deleted and 1886 // trying to find cycles they are involved in. This fails when the file goes 1887 // from an unparseable state to a parseable state, as we don't have a 1888 // starting point to compare with. 1889 if anyImportDeleted { 1890 for id, metadata := range s.metadata { 1891 if len(metadata.Errors) > 0 { 1892 directIDs[id] = true 1893 } 1894 } 1895 } 1896 1897 // Invalidate reverse dependencies too. 1898 // TODO(heschi): figure out the locking model and use transitiveReverseDeps? 1899 // idsToInvalidate keeps track of transitive reverse dependencies. 1900 // If an ID is present in the map, invalidate its types. 1901 // If an ID's value is true, invalidate its metadata too. 1902 idsToInvalidate := map[PackageID]bool{} 1903 var addRevDeps func(PackageID, bool) 1904 addRevDeps = func(id PackageID, invalidateMetadata bool) { 1905 current, seen := idsToInvalidate[id] 1906 newInvalidateMetadata := current || invalidateMetadata 1907 1908 // If we've already seen this ID, and the value of invalidate 1909 // metadata has not changed, we can return early. 1910 if seen && current == newInvalidateMetadata { 1911 return 1912 } 1913 idsToInvalidate[id] = newInvalidateMetadata 1914 for _, rid := range s.getImportedByLocked(id) { 1915 addRevDeps(rid, invalidateMetadata) 1916 } 1917 } 1918 for id, invalidateMetadata := range directIDs { 1919 addRevDeps(id, invalidateMetadata) 1920 } 1921 1922 // Copy the package type information. 1923 for k, v := range s.packages { 1924 if _, ok := idsToInvalidate[k.id]; ok { 1925 continue 1926 } 1927 newGen.Inherit(v.handle) 1928 result.packages[k] = v 1929 } 1930 // Copy the package analysis information. 1931 for k, v := range s.actions { 1932 if _, ok := idsToInvalidate[k.pkg.id]; ok { 1933 continue 1934 } 1935 newGen.Inherit(v.handle) 1936 result.actions[k] = v 1937 } 1938 1939 // If the workspace mode has changed, we must delete all metadata, as it 1940 // is unusable and may produce confusing or incorrect diagnostics. 1941 // If a file has been deleted, we must delete metadata all packages 1942 // containing that file. 1943 workspaceModeChanged := s.workspaceMode() != result.workspaceMode() 1944 skipID := map[PackageID]bool{} 1945 for _, c := range changes { 1946 if c.exists { 1947 continue 1948 } 1949 // The file has been deleted. 1950 if ids, ok := s.ids[c.fileHandle.URI()]; ok { 1951 for _, id := range ids { 1952 skipID[id] = true 1953 } 1954 } 1955 } 1956 1957 // Collect all of the IDs that are reachable from the workspace packages. 1958 // Any unreachable IDs will have their metadata deleted outright. 1959 reachableID := map[PackageID]bool{} 1960 var addForwardDeps func(PackageID) 1961 addForwardDeps = func(id PackageID) { 1962 if reachableID[id] { 1963 return 1964 } 1965 reachableID[id] = true 1966 m, ok := s.metadata[id] 1967 if !ok { 1968 return 1969 } 1970 for _, depID := range m.Deps { 1971 addForwardDeps(depID) 1972 } 1973 } 1974 for id := range s.workspacePackages { 1975 addForwardDeps(id) 1976 } 1977 1978 // Copy the URI to package ID mappings, skipping only those URIs whose 1979 // metadata will be reloaded in future calls to load. 1980 deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged 1981 idsInSnapshot := map[PackageID]bool{} // track all known IDs 1982 for uri, ids := range s.ids { 1983 for _, id := range ids { 1984 invalidateMetadata := idsToInvalidate[id] 1985 if skipID[id] || (invalidateMetadata && deleteInvalidMetadata) { 1986 continue 1987 } 1988 // The ID is not reachable from any workspace package, so it should 1989 // be deleted. 1990 if !reachableID[id] { 1991 continue 1992 } 1993 idsInSnapshot[id] = true 1994 result.ids[uri] = append(result.ids[uri], id) 1995 } 1996 } 1997 1998 // Copy the package metadata. We only need to invalidate packages directly 1999 // containing the affected file, and only if it changed in a relevant way. 2000 for k, v := range s.metadata { 2001 if !idsInSnapshot[k] { 2002 // Delete metadata for IDs that are no longer reachable from files 2003 // in the snapshot. 2004 continue 2005 } 2006 invalidateMetadata := idsToInvalidate[k] 2007 // Mark invalidated metadata rather than deleting it outright. 2008 result.metadata[k] = &KnownMetadata{ 2009 Metadata: v.Metadata, 2010 Valid: v.Valid && !invalidateMetadata, 2011 ShouldLoad: v.ShouldLoad || invalidateMetadata, 2012 } 2013 } 2014 2015 // Copy the set of initially loaded packages. 2016 for id, pkgPath := range s.workspacePackages { 2017 // Packages with the id "command-line-arguments" are generated by the 2018 // go command when the user is outside of GOPATH and outside of a 2019 // module. Do not cache them as workspace packages for longer than 2020 // necessary. 2021 if source.IsCommandLineArguments(string(id)) { 2022 if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok { 2023 continue 2024 } 2025 } 2026 2027 // If all the files we know about in a package have been deleted, 2028 // the package is gone and we should no longer try to load it. 2029 if m := s.metadata[id]; m != nil { 2030 hasFiles := false 2031 for _, uri := range s.metadata[id].GoFiles { 2032 // For internal tests, we need _test files, not just the normal 2033 // ones. External tests only have _test files, but we can check 2034 // them anyway. 2035 if m.ForTest != "" && !strings.HasSuffix(string(uri), "_test.go") { 2036 continue 2037 } 2038 if _, ok := result.files[uri]; ok { 2039 hasFiles = true 2040 break 2041 } 2042 } 2043 if !hasFiles { 2044 continue 2045 } 2046 } 2047 2048 // If the package name of a file in the package has changed, it's 2049 // possible that the package ID may no longer exist. Delete it from 2050 // the set of workspace packages, on the assumption that we will add it 2051 // back when the relevant files are reloaded. 2052 if _, ok := changedPkgNames[id]; ok { 2053 continue 2054 } 2055 2056 result.workspacePackages[id] = pkgPath 2057 } 2058 2059 // Inherit all of the go.mod-related handles. 2060 for _, v := range result.modTidyHandles { 2061 newGen.Inherit(v.handle) 2062 } 2063 for _, v := range result.modWhyHandles { 2064 newGen.Inherit(v.handle) 2065 } 2066 for _, v := range result.parseModHandles { 2067 newGen.Inherit(v.handle) 2068 } 2069 for _, v := range result.parseWorkHandles { 2070 newGen.Inherit(v.handle) 2071 } 2072 // Don't bother copying the importedBy graph, 2073 // as it changes each time we update metadata. 2074 2075 // If the snapshot's workspace mode has changed, the packages loaded using 2076 // the previous mode are no longer relevant, so clear them out. 2077 if workspaceModeChanged { 2078 result.workspacePackages = map[PackageID]PackagePath{} 2079 } 2080 2081 // The snapshot may need to be reinitialized. 2082 if workspaceReload || vendorChanged { 2083 if workspaceChanged || result.initializedErr != nil { 2084 result.initializeOnce = &sync.Once{} 2085 } 2086 } 2087 return result 2088 } 2089 2090 // guessPackageIDsForURI returns all packages related to uri. If we haven't 2091 // seen this URI before, we guess based on files in the same directory. This 2092 // is of course incorrect in build systems where packages are not organized by 2093 // directory. 2094 func guessPackageIDsForURI(uri span.URI, known map[span.URI][]PackageID) []PackageID { 2095 packages := known[uri] 2096 if len(packages) > 0 { 2097 // We've seen this file before. 2098 return packages 2099 } 2100 // This is a file we don't yet know about. Guess relevant packages by 2101 // considering files in the same directory. 2102 2103 // Cache of FileInfo to avoid unnecessary stats for multiple files in the 2104 // same directory. 2105 stats := make(map[string]struct { 2106 os.FileInfo 2107 error 2108 }) 2109 getInfo := func(dir string) (os.FileInfo, error) { 2110 if res, ok := stats[dir]; ok { 2111 return res.FileInfo, res.error 2112 } 2113 fi, err := os.Stat(dir) 2114 stats[dir] = struct { 2115 os.FileInfo 2116 error 2117 }{fi, err} 2118 return fi, err 2119 } 2120 dir := filepath.Dir(uri.Filename()) 2121 fi, err := getInfo(dir) 2122 if err != nil { 2123 return nil 2124 } 2125 2126 // Aggregate all possibly relevant package IDs. 2127 var found []PackageID 2128 for knownURI, ids := range known { 2129 knownDir := filepath.Dir(knownURI.Filename()) 2130 knownFI, err := getInfo(knownDir) 2131 if err != nil { 2132 continue 2133 } 2134 if os.SameFile(fi, knownFI) { 2135 found = append(found, ids...) 2136 } 2137 } 2138 return found 2139 } 2140 2141 // fileWasSaved reports whether the FileHandle passed in has been saved. It 2142 // accomplishes this by checking to see if the original and current FileHandles 2143 // are both overlays, and if the current FileHandle is saved while the original 2144 // FileHandle was not saved. 2145 func fileWasSaved(originalFH, currentFH source.FileHandle) bool { 2146 c, ok := currentFH.(*overlay) 2147 if !ok || c == nil { 2148 return true 2149 } 2150 o, ok := originalFH.(*overlay) 2151 if !ok || o == nil { 2152 return c.saved 2153 } 2154 return !o.saved && c.saved 2155 } 2156 2157 // shouldInvalidateMetadata reparses the full file's AST to determine 2158 // if the file requires a metadata reload. 2159 func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged, importDeleted bool) { 2160 if originalFH == nil { 2161 return true, false, false 2162 } 2163 // If the file hasn't changed, there's no need to reload. 2164 if originalFH.FileIdentity() == currentFH.FileIdentity() { 2165 return false, false, false 2166 } 2167 // Get the original and current parsed files in order to check package name 2168 // and imports. Use the new snapshot to parse to avoid modifying the 2169 // current snapshot. 2170 original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseFull) 2171 current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseFull) 2172 if originalErr != nil || currentErr != nil { 2173 return (originalErr == nil) != (currentErr == nil), false, (currentErr != nil) // we don't know if an import was deleted 2174 } 2175 // Check if the package's metadata has changed. The cases handled are: 2176 // 1. A package's name has changed 2177 // 2. A file's imports have changed 2178 if original.File.Name.Name != current.File.Name.Name { 2179 invalidate = true 2180 pkgNameChanged = true 2181 } 2182 origImportSet := make(map[string]struct{}) 2183 for _, importSpec := range original.File.Imports { 2184 origImportSet[importSpec.Path.Value] = struct{}{} 2185 } 2186 curImportSet := make(map[string]struct{}) 2187 for _, importSpec := range current.File.Imports { 2188 curImportSet[importSpec.Path.Value] = struct{}{} 2189 } 2190 // If any of the current imports were not in the original imports. 2191 for path := range curImportSet { 2192 if _, ok := origImportSet[path]; ok { 2193 delete(origImportSet, path) 2194 continue 2195 } 2196 // If the import path is obviously not valid, we can skip reloading 2197 // metadata. For now, valid means properly quoted and without a 2198 // terminal slash. 2199 if isBadImportPath(path) { 2200 continue 2201 } 2202 invalidate = true 2203 } 2204 2205 for path := range origImportSet { 2206 if !isBadImportPath(path) { 2207 invalidate = true 2208 importDeleted = true 2209 } 2210 } 2211 2212 if !invalidate { 2213 invalidate = magicCommentsChanged(original.File, current.File) 2214 } 2215 return invalidate, pkgNameChanged, importDeleted 2216 } 2217 2218 func magicCommentsChanged(original *ast.File, current *ast.File) bool { 2219 oldComments := extractMagicComments(original) 2220 newComments := extractMagicComments(current) 2221 if len(oldComments) != len(newComments) { 2222 return true 2223 } 2224 for i := range oldComments { 2225 if oldComments[i] != newComments[i] { 2226 return true 2227 } 2228 } 2229 return false 2230 } 2231 2232 func isBadImportPath(path string) bool { 2233 path, err := strconv.Unquote(path) 2234 if err != nil { 2235 return true 2236 } 2237 if path == "" { 2238 return true 2239 } 2240 if path[len(path)-1] == '/' { 2241 return true 2242 } 2243 return false 2244 } 2245 2246 var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`) 2247 2248 // extractMagicComments finds magic comments that affect metadata in f. 2249 func extractMagicComments(f *ast.File) []string { 2250 var results []string 2251 for _, cg := range f.Comments { 2252 for _, c := range cg.List { 2253 if buildConstraintOrEmbedRe.MatchString(c.Text) { 2254 results = append(results, c.Text) 2255 } 2256 } 2257 } 2258 return results 2259 } 2260 2261 func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) { 2262 s.AwaitInitialized(ctx) 2263 2264 s.mu.Lock() 2265 builtin := s.builtin 2266 s.mu.Unlock() 2267 2268 if builtin == "" { 2269 return nil, errors.Errorf("no builtin package for view %s", s.view.name) 2270 } 2271 2272 fh, err := s.GetFile(ctx, builtin) 2273 if err != nil { 2274 return nil, err 2275 } 2276 return s.ParseGo(ctx, fh, source.ParseFull) 2277 } 2278 2279 func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) bool { 2280 s.mu.Lock() 2281 defer s.mu.Unlock() 2282 // We should always get the builtin URI in a canonical form, so use simple 2283 // string comparison here. span.CompareURI is too expensive. 2284 return uri == s.builtin 2285 } 2286 2287 func (s *snapshot) setBuiltin(path string) { 2288 s.mu.Lock() 2289 defer s.mu.Unlock() 2290 2291 s.builtin = span.URIFromPath(path) 2292 } 2293 2294 // BuildGoplsMod generates a go.mod file for all modules in the workspace. It 2295 // bypasses any existing gopls.mod. 2296 func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) { 2297 allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0) 2298 if err != nil { 2299 return nil, err 2300 } 2301 return buildWorkspaceModFile(ctx, allModules, s) 2302 } 2303 2304 // TODO(rfindley): move this to workspacemodule.go 2305 func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) { 2306 file := &modfile.File{} 2307 file.AddModuleStmt("gopls-workspace") 2308 // Track the highest Go version, to be set on the workspace module. 2309 // Fall back to 1.12 -- old versions insist on having some version. 2310 goVersion := "1.12" 2311 2312 paths := map[string]span.URI{} 2313 excludes := map[string][]string{} 2314 var sortedModURIs []span.URI 2315 for uri := range modFiles { 2316 sortedModURIs = append(sortedModURIs, uri) 2317 } 2318 sort.Slice(sortedModURIs, func(i, j int) bool { 2319 return sortedModURIs[i] < sortedModURIs[j] 2320 }) 2321 for _, modURI := range sortedModURIs { 2322 fh, err := fs.GetFile(ctx, modURI) 2323 if err != nil { 2324 return nil, err 2325 } 2326 content, err := fh.Read() 2327 if err != nil { 2328 return nil, err 2329 } 2330 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2331 if err != nil { 2332 return nil, err 2333 } 2334 if file == nil || parsed.Module == nil { 2335 return nil, fmt.Errorf("no module declaration for %s", modURI) 2336 } 2337 // Prepend "v" to go versions to make them valid semver. 2338 if parsed.Go != nil && semver.Compare("v"+goVersion, "v"+parsed.Go.Version) < 0 { 2339 goVersion = parsed.Go.Version 2340 } 2341 path := parsed.Module.Mod.Path 2342 if _, ok := paths[path]; ok { 2343 return nil, fmt.Errorf("found module %q twice in the workspace", path) 2344 } 2345 paths[path] = modURI 2346 // If the module's path includes a major version, we expect it to have 2347 // a matching major version. 2348 _, majorVersion, _ := module.SplitPathVersion(path) 2349 if majorVersion == "" { 2350 majorVersion = "/v0" 2351 } 2352 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions 2353 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false) 2354 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil { 2355 return nil, err 2356 } 2357 for _, exclude := range parsed.Exclude { 2358 excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version) 2359 } 2360 } 2361 if goVersion != "" { 2362 file.AddGoStmt(goVersion) 2363 } 2364 // Go back through all of the modules to handle any of their replace 2365 // statements. 2366 for _, modURI := range sortedModURIs { 2367 fh, err := fs.GetFile(ctx, modURI) 2368 if err != nil { 2369 return nil, err 2370 } 2371 content, err := fh.Read() 2372 if err != nil { 2373 return nil, err 2374 } 2375 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2376 if err != nil { 2377 return nil, err 2378 } 2379 // If any of the workspace modules have replace directives, they need 2380 // to be reflected in the workspace module. 2381 for _, rep := range parsed.Replace { 2382 // Don't replace any modules that are in our workspace--we should 2383 // always use the version in the workspace. 2384 if _, ok := paths[rep.Old.Path]; ok { 2385 continue 2386 } 2387 newPath := rep.New.Path 2388 newVersion := rep.New.Version 2389 // If a replace points to a module in the workspace, make sure we 2390 // direct it to version of the module in the workspace. 2391 if m, ok := paths[rep.New.Path]; ok { 2392 newPath = dirURI(m).Filename() 2393 newVersion = "" 2394 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) { 2395 // Make any relative paths absolute. 2396 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path) 2397 } 2398 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil { 2399 return nil, err 2400 } 2401 } 2402 } 2403 for path, versions := range excludes { 2404 for _, version := range versions { 2405 file.AddExclude(path, version) 2406 } 2407 } 2408 file.SortBlocks() 2409 return file, nil 2410 } 2411 2412 func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) { 2413 allSums := map[module.Version][]string{} 2414 for modURI := range modFiles { 2415 // TODO(rfindley): factor out this pattern into a uripath package. 2416 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum")) 2417 fh, err := fs.GetFile(ctx, sumURI) 2418 if err != nil { 2419 continue 2420 } 2421 data, err := fh.Read() 2422 if os.IsNotExist(err) { 2423 continue 2424 } 2425 if err != nil { 2426 return nil, errors.Errorf("reading go sum: %w", err) 2427 } 2428 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil { 2429 return nil, err 2430 } 2431 } 2432 // This logic to write go.sum is copied (with minor modifications) from 2433 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2434 var mods []module.Version 2435 for m := range allSums { 2436 mods = append(mods, m) 2437 } 2438 module.Sort(mods) 2439 2440 var buf bytes.Buffer 2441 for _, m := range mods { 2442 list := allSums[m] 2443 sort.Strings(list) 2444 // Note (rfindley): here we add all sum lines without verification, because 2445 // the assumption is that if they come from a go.sum file, they are 2446 // trusted. 2447 for _, h := range list { 2448 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) 2449 } 2450 } 2451 return buf.Bytes(), nil 2452 } 2453 2454 // readGoSum is copied (with minor modifications) from 2455 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2456 func readGoSum(dst map[module.Version][]string, file string, data []byte) error { 2457 lineno := 0 2458 for len(data) > 0 { 2459 var line []byte 2460 lineno++ 2461 i := bytes.IndexByte(data, '\n') 2462 if i < 0 { 2463 line, data = data, nil 2464 } else { 2465 line, data = data[:i], data[i+1:] 2466 } 2467 f := strings.Fields(string(line)) 2468 if len(f) == 0 { 2469 // blank line; skip it 2470 continue 2471 } 2472 if len(f) != 3 { 2473 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) 2474 } 2475 mod := module.Version{Path: f[0], Version: f[1]} 2476 dst[mod] = append(dst[mod], f[2]) 2477 } 2478 return nil 2479 }