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 5package cache 6 7import ( 8 "bytes" 9 "context" 10 "fmt" 11 "go/ast" 12 "go/token" 13 "go/types" 14 "io" 15 "os" 16 "path/filepath" 17 "sort" 18 "strconv" 19 "strings" 20 "sync" 21 22 "golang.org/x/mod/modfile" 23 "golang.org/x/mod/module" 24 "golang.org/x/tools/go/analysis" 25 "golang.org/x/tools/go/packages" 26 "golang.org/x/tools/internal/event" 27 "golang.org/x/tools/internal/gocommand" 28 "golang.org/x/tools/internal/lsp/debug/tag" 29 "golang.org/x/tools/internal/lsp/source" 30 "golang.org/x/tools/internal/memoize" 31 "golang.org/x/tools/internal/packagesinternal" 32 "golang.org/x/tools/internal/span" 33 "golang.org/x/tools/internal/typesinternal" 34 errors "golang.org/x/xerrors" 35) 36 37type snapshot struct { 38 memoize.Arg // allow as a memoize.Function arg 39 40 id uint64 41 view *View 42 43 // the cache generation that contains the data for this snapshot. 44 generation *memoize.Generation 45 46 // builtin pins the AST and package for builtin.go in memory. 47 builtin *builtinPackageHandle 48 49 // The snapshot's initialization state is controlled by the fields below. 50 // 51 // initializeOnce guards snapshot initialization. Each snapshot is 52 // initialized at most once: reinitialization is triggered on later snapshots 53 // by invalidating this field. 54 initializeOnce *sync.Once 55 // initializedErr holds the last error resulting from initialization. If 56 // initialization fails, we only retry when the the workspace modules change, 57 // to avoid too many go/packages calls. 58 initializedErr error 59 60 // mu guards all of the maps in the snapshot. 61 mu sync.Mutex 62 63 // ids maps file URIs to package IDs. 64 // It may be invalidated on calls to go/packages. 65 ids map[span.URI][]packageID 66 67 // metadata maps file IDs to their associated metadata. 68 // It may invalidated on calls to go/packages. 69 metadata map[packageID]*metadata 70 71 // importedBy maps package IDs to the list of packages that import them. 72 importedBy map[packageID][]packageID 73 74 // files maps file URIs to their corresponding FileHandles. 75 // It may invalidated when a file's content changes. 76 files map[span.URI]source.VersionedFileHandle 77 78 // goFiles maps a parseKey to its parseGoHandle. 79 goFiles map[parseKey]*parseGoHandle 80 81 // packages maps a packageKey to a set of packageHandles to which that file belongs. 82 // It may be invalidated when a file's content changes. 83 packages map[packageKey]*packageHandle 84 85 // actions maps an actionkey to its actionHandle. 86 actions map[actionKey]*actionHandle 87 88 // workspacePackages contains the workspace's packages, which are loaded 89 // when the view is created. 90 workspacePackages map[packageID]packagePath 91 92 // unloadableFiles keeps track of files that we've failed to load. 93 unloadableFiles map[span.URI]struct{} 94 95 // parseModHandles keeps track of any ParseModHandles for the snapshot. 96 // The handles need not refer to only the view's go.mod file. 97 parseModHandles map[span.URI]*parseModHandle 98 99 // Preserve go.mod-related handles to avoid garbage-collecting the results 100 // of various calls to the go command. The handles need not refer to only 101 // the view's go.mod file. 102 modTidyHandles map[span.URI]*modTidyHandle 103 modUpgradeHandles map[span.URI]*modUpgradeHandle 104 modWhyHandles map[span.URI]*modWhyHandle 105 106 workspace *workspace 107 workspaceDirHandle *memoize.Handle 108} 109 110type packageKey struct { 111 mode source.ParseMode 112 id packageID 113} 114 115type actionKey struct { 116 pkg packageKey 117 analyzer *analysis.Analyzer 118} 119 120func (s *snapshot) ID() uint64 { 121 return s.id 122} 123 124func (s *snapshot) View() source.View { 125 return s.view 126} 127 128func (s *snapshot) FileSet() *token.FileSet { 129 return s.view.session.cache.fset 130} 131 132func (s *snapshot) ModFiles() []span.URI { 133 var uris []span.URI 134 for modURI := range s.workspace.getActiveModFiles() { 135 uris = append(uris, modURI) 136 } 137 return uris 138} 139 140func (s *snapshot) ValidBuildConfiguration() bool { 141 return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles()) 142} 143 144// workspaceMode describes the way in which the snapshot's workspace should 145// be loaded. 146func (s *snapshot) workspaceMode() workspaceMode { 147 var mode workspaceMode 148 149 // If the view has an invalid configuration, don't build the workspace 150 // module. 151 validBuildConfiguration := s.ValidBuildConfiguration() 152 if !validBuildConfiguration { 153 return mode 154 } 155 // If the view is not in a module and contains no modules, but still has a 156 // valid workspace configuration, do not create the workspace module. 157 // It could be using GOPATH or a different build system entirely. 158 if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration { 159 return mode 160 } 161 mode |= moduleMode 162 options := s.view.Options() 163 // The -modfile flag is available for Go versions >= 1.14. 164 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 { 165 mode |= tempModfile 166 } 167 // If the user is intentionally limiting their workspace scope, don't 168 // enable multi-module workspace mode. 169 // TODO(rstambler): This should only change the calculation of the root, 170 // not the mode. 171 if !options.ExpandWorkspaceToModule { 172 return mode 173 } 174 // The workspace module has been disabled by the user. 175 if !options.ExperimentalWorkspaceModule { 176 return mode 177 } 178 mode |= usesWorkspaceModule 179 return mode 180} 181 182// config returns the configuration used for the snapshot's interaction with 183// the go/packages API. It uses the given working directory. 184// 185// TODO(rstambler): go/packages requires that we do not provide overlays for 186// multiple modules in on config, so buildOverlay needs to filter overlays by 187// module. 188func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config { 189 s.view.optionsMu.Lock() 190 verboseOutput := s.view.options.VerboseOutput 191 s.view.optionsMu.Unlock() 192 193 // Forcibly disable GOPACKAGESDRIVER. It's incompatible with the 194 // packagesinternal APIs we use, and we really only support the go command 195 // anyway. 196 env := append(append([]string{}, inv.Env...), "GOPACKAGESDRIVER=off") 197 cfg := &packages.Config{ 198 Context: ctx, 199 Dir: inv.WorkingDir, 200 Env: env, 201 BuildFlags: inv.BuildFlags, 202 Mode: packages.NeedName | 203 packages.NeedFiles | 204 packages.NeedCompiledGoFiles | 205 packages.NeedImports | 206 packages.NeedDeps | 207 packages.NeedTypesSizes | 208 packages.NeedModule, 209 Fset: s.view.session.cache.fset, 210 Overlay: s.buildOverlay(), 211 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) { 212 panic("go/packages must not be used to parse files") 213 }, 214 Logf: func(format string, args ...interface{}) { 215 if verboseOutput { 216 event.Log(ctx, fmt.Sprintf(format, args...)) 217 } 218 }, 219 Tests: true, 220 } 221 packagesinternal.SetModFile(cfg, inv.ModFile) 222 packagesinternal.SetModFlag(cfg, inv.ModFlag) 223 // We want to type check cgo code if go/types supports it. 224 if typesinternal.SetUsesCgo(&types.Config{}) { 225 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo) 226 } 227 packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner) 228 return cfg 229} 230 231func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) { 232 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 233 if err != nil { 234 return nil, err 235 } 236 defer cleanup() 237 238 return s.view.session.gocmdRunner.Run(ctx, *inv) 239} 240 241func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error { 242 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 243 if err != nil { 244 return err 245 } 246 defer cleanup() 247 return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr) 248} 249 250func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) { 251 s.view.optionsMu.Lock() 252 allowModfileModificationOption := s.view.options.AllowModfileModifications 253 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess 254 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.go111module) 255 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...) 256 s.view.optionsMu.Unlock() 257 cleanup = func() {} // fallback 258 259 // All logic below is for module mode. 260 if s.workspaceMode()&moduleMode == 0 { 261 return "", inv, cleanup, nil 262 } 263 264 mode, allowNetwork := flags.Mode(), flags.AllowNetwork() 265 if !allowNetwork && !allowNetworkOption { 266 inv.Env = append(inv.Env, "GOPROXY=off") 267 } 268 269 var modURI span.URI 270 // Select the module context to use. 271 // If we're type checking, we need to use the workspace context, meaning 272 // the main (workspace) module. Otherwise, we should use the module for 273 // the passed-in working dir. 274 if mode == source.LoadWorkspace { 275 if s.workspaceMode()&usesWorkspaceModule == 0 { 276 for m := range s.workspace.getActiveModFiles() { // range to access the only element 277 modURI = m 278 } 279 } else { 280 var tmpDir span.URI 281 var err error 282 tmpDir, err = s.getWorkspaceDir(ctx) 283 if err != nil { 284 return "", nil, cleanup, err 285 } 286 inv.WorkingDir = tmpDir.Filename() 287 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod")) 288 } 289 } else { 290 modURI = s.GoModForFile(ctx, span.URIFromPath(inv.WorkingDir)) 291 } 292 293 var modContent []byte 294 if modURI != "" { 295 modFH, err := s.GetFile(ctx, modURI) 296 if err != nil { 297 return "", nil, cleanup, err 298 } 299 modContent, err = modFH.Read() 300 if err != nil { 301 return "", nil, cleanup, err 302 } 303 } 304 305 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent) 306 if err != nil { 307 return "", nil, cleanup, err 308 } 309 310 mutableModFlag := "" 311 if s.view.goversion >= 16 { 312 mutableModFlag = "mod" 313 } 314 315 switch mode { 316 case source.LoadWorkspace, source.Normal: 317 if vendorEnabled { 318 inv.ModFlag = "vendor" 319 } else if s.workspaceMode()&usesWorkspaceModule == 0 && !allowModfileModificationOption { 320 inv.ModFlag = "readonly" 321 } else { 322 // Temporarily allow updates for multi-module workspace mode: 323 // it doesn't create a go.sum at all. golang/go#42509. 324 inv.ModFlag = mutableModFlag 325 } 326 case source.UpdateUserModFile, source.WriteTemporaryModFile: 327 inv.ModFlag = mutableModFlag 328 } 329 330 wantTempMod := mode != source.UpdateUserModFile 331 needTempMod := mode == source.WriteTemporaryModFile 332 tempMod := wantTempMod && s.workspaceMode()&tempModfile != 0 333 if needTempMod && !tempMod { 334 return "", nil, cleanup, source.ErrTmpModfileUnsupported 335 } 336 337 if tempMod { 338 if modURI == "" { 339 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir) 340 } 341 modFH, err := s.GetFile(ctx, modURI) 342 if err != nil { 343 return "", nil, cleanup, err 344 } 345 // Use the go.sum if it happens to be available. 346 gosum := s.goSum(ctx, modURI) 347 tmpURI, cleanup, err = tempModFile(modFH, gosum) 348 if err != nil { 349 return "", nil, cleanup, err 350 } 351 inv.ModFile = tmpURI.Filename() 352 } 353 354 return tmpURI, inv, cleanup, nil 355} 356 357func (s *snapshot) buildOverlay() map[string][]byte { 358 s.mu.Lock() 359 defer s.mu.Unlock() 360 361 overlays := make(map[string][]byte) 362 for uri, fh := range s.files { 363 overlay, ok := fh.(*overlay) 364 if !ok { 365 continue 366 } 367 if overlay.saved { 368 continue 369 } 370 // TODO(rstambler): Make sure not to send overlays outside of the current view. 371 overlays[uri.Filename()] = overlay.text 372 } 373 return overlays 374} 375 376func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string { 377 var unsaved []string 378 for uri, fh := range files { 379 if overlay, ok := fh.(*overlay); ok && !overlay.saved { 380 unsaved = append(unsaved, uri.Filename()) 381 } 382 } 383 sort.Strings(unsaved) 384 return hashContents([]byte(strings.Join(unsaved, ""))) 385} 386 387func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]source.Package, error) { 388 ctx = event.Label(ctx, tag.URI.Of(uri)) 389 390 phs, err := s.packageHandlesForFile(ctx, uri, mode) 391 if err != nil { 392 return nil, err 393 } 394 var pkgs []source.Package 395 for _, ph := range phs { 396 pkg, err := ph.check(ctx, s) 397 if err != nil { 398 return nil, err 399 } 400 pkgs = append(pkgs, pkg) 401 } 402 return pkgs, nil 403} 404 405func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) { 406 ctx = event.Label(ctx, tag.URI.Of(uri)) 407 408 phs, err := s.packageHandlesForFile(ctx, uri, mode) 409 if err != nil { 410 return nil, err 411 } 412 413 if len(phs) < 1 { 414 return nil, errors.Errorf("no packages") 415 } 416 417 ph := phs[0] 418 for _, handle := range phs[1:] { 419 switch pkgPolicy { 420 case source.WidestPackage: 421 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) { 422 ph = handle 423 } 424 case source.NarrowestPackage: 425 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) { 426 ph = handle 427 } 428 } 429 } 430 if ph == nil { 431 return nil, errors.Errorf("no packages in input") 432 } 433 434 return ph.check(ctx, s) 435} 436 437func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]*packageHandle, error) { 438 // Check if we should reload metadata for the file. We don't invalidate IDs 439 // (though we should), so the IDs will be a better source of truth than the 440 // metadata. If there are no IDs for the file, then we should also reload. 441 fh, err := s.GetFile(ctx, uri) 442 if err != nil { 443 return nil, err 444 } 445 if fh.Kind() != source.Go { 446 return nil, fmt.Errorf("no packages for non-Go file %s", uri) 447 } 448 ids := s.getIDsForURI(uri) 449 reload := len(ids) == 0 450 for _, id := range ids { 451 // Reload package metadata if any of the metadata has missing 452 // dependencies, in case something has changed since the last time we 453 // reloaded it. 454 if m := s.getMetadata(id); m == nil { 455 reload = true 456 break 457 } 458 // TODO(golang/go#36918): Previously, we would reload any package with 459 // missing dependencies. This is expensive and results in too many 460 // calls to packages.Load. Determine what we should do instead. 461 } 462 if reload { 463 if err := s.load(ctx, false, fileURI(uri)); err != nil { 464 return nil, err 465 } 466 } 467 // Get the list of IDs from the snapshot again, in case it has changed. 468 var phs []*packageHandle 469 for _, id := range s.getIDsForURI(uri) { 470 var parseModes []source.ParseMode 471 switch mode { 472 case source.TypecheckAll: 473 if s.workspaceParseMode(id) == source.ParseFull { 474 parseModes = []source.ParseMode{source.ParseFull} 475 } else { 476 parseModes = []source.ParseMode{source.ParseExported, source.ParseFull} 477 } 478 case source.TypecheckFull: 479 parseModes = []source.ParseMode{source.ParseFull} 480 case source.TypecheckWorkspace: 481 parseModes = []source.ParseMode{s.workspaceParseMode(id)} 482 } 483 484 for _, parseMode := range parseModes { 485 ph, err := s.buildPackageHandle(ctx, id, parseMode) 486 if err != nil { 487 return nil, err 488 } 489 phs = append(phs, ph) 490 } 491 } 492 493 return phs, nil 494} 495 496func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) { 497 if err := s.awaitLoaded(ctx); err != nil { 498 return nil, err 499 } 500 ids := make(map[packageID]struct{}) 501 s.transitiveReverseDependencies(packageID(id), ids) 502 503 // Make sure to delete the original package ID from the map. 504 delete(ids, packageID(id)) 505 506 var pkgs []source.Package 507 for id := range ids { 508 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 509 if err != nil { 510 return nil, err 511 } 512 pkgs = append(pkgs, pkg) 513 } 514 return pkgs, nil 515} 516 517func (s *snapshot) checkedPackage(ctx context.Context, id packageID, mode source.ParseMode) (*pkg, error) { 518 ph, err := s.buildPackageHandle(ctx, id, mode) 519 if err != nil { 520 return nil, err 521 } 522 return ph.check(ctx, s) 523} 524 525// transitiveReverseDependencies populates the uris map with file URIs 526// belonging to the provided package and its transitive reverse dependencies. 527func (s *snapshot) transitiveReverseDependencies(id packageID, ids map[packageID]struct{}) { 528 if _, ok := ids[id]; ok { 529 return 530 } 531 if s.getMetadata(id) == nil { 532 return 533 } 534 ids[id] = struct{}{} 535 importedBy := s.getImportedBy(id) 536 for _, parentID := range importedBy { 537 s.transitiveReverseDependencies(parentID, ids) 538 } 539} 540 541func (s *snapshot) getGoFile(key parseKey) *parseGoHandle { 542 s.mu.Lock() 543 defer s.mu.Unlock() 544 return s.goFiles[key] 545} 546 547func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle { 548 s.mu.Lock() 549 defer s.mu.Unlock() 550 if existing, ok := s.goFiles[key]; ok { 551 return existing 552 } 553 s.goFiles[key] = pgh 554 return pgh 555} 556 557func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle { 558 s.mu.Lock() 559 defer s.mu.Unlock() 560 return s.parseModHandles[uri] 561} 562 563func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle { 564 s.mu.Lock() 565 defer s.mu.Unlock() 566 return s.modWhyHandles[uri] 567} 568 569func (s *snapshot) getModUpgradeHandle(uri span.URI) *modUpgradeHandle { 570 s.mu.Lock() 571 defer s.mu.Unlock() 572 return s.modUpgradeHandles[uri] 573} 574 575func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle { 576 s.mu.Lock() 577 defer s.mu.Unlock() 578 return s.modTidyHandles[uri] 579} 580 581func (s *snapshot) getImportedBy(id packageID) []packageID { 582 s.mu.Lock() 583 defer s.mu.Unlock() 584 return s.getImportedByLocked(id) 585} 586 587func (s *snapshot) getImportedByLocked(id packageID) []packageID { 588 // If we haven't rebuilt the import graph since creating the snapshot. 589 if len(s.importedBy) == 0 { 590 s.rebuildImportGraph() 591 } 592 return s.importedBy[id] 593} 594 595func (s *snapshot) clearAndRebuildImportGraph() { 596 s.mu.Lock() 597 defer s.mu.Unlock() 598 599 // Completely invalidate the original map. 600 s.importedBy = make(map[packageID][]packageID) 601 s.rebuildImportGraph() 602} 603 604func (s *snapshot) rebuildImportGraph() { 605 for id, m := range s.metadata { 606 for _, importID := range m.deps { 607 s.importedBy[importID] = append(s.importedBy[importID], id) 608 } 609 } 610} 611 612func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle { 613 s.mu.Lock() 614 defer s.mu.Unlock() 615 616 // If the package handle has already been cached, 617 // return the cached handle instead of overriding it. 618 if ph, ok := s.packages[ph.packageKey()]; ok { 619 return ph 620 } 621 s.packages[ph.packageKey()] = ph 622 return ph 623} 624 625func (s *snapshot) workspacePackageIDs() (ids []packageID) { 626 s.mu.Lock() 627 defer s.mu.Unlock() 628 629 for id := range s.workspacePackages { 630 ids = append(ids, id) 631 } 632 return ids 633} 634 635func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { 636 // Work-around microsoft/vscode#100870 by making sure that we are, 637 // at least, watching the user's entire workspace. This will still be 638 // applied to every folder in the workspace. 639 patterns := map[string]struct{}{ 640 "**/*.{go,mod,sum}": {}, 641 } 642 dirs := s.workspace.dirs(ctx, s) 643 for _, dir := range dirs { 644 dirName := dir.Filename() 645 646 // If the directory is within the view's folder, we're already watching 647 // it with the pattern above. 648 if source.InDir(s.view.folder.Filename(), dirName) { 649 continue 650 } 651 // TODO(rstambler): If microsoft/vscode#3025 is resolved before 652 // microsoft/vscode#101042, we will need a work-around for Windows 653 // drive letter casing. 654 patterns[fmt.Sprintf("%s/**/*.{go,mod,sum}", dirName)] = struct{}{} 655 } 656 657 // Some clients do not send notifications for changes to directories that 658 // contain Go code (golang/go#42348). To handle this, explicitly watch all 659 // of the directories in the workspace. We find them by adding the 660 // directories of every file in the snapshot's workspace directories. 661 var dirNames []string 662 for uri := range s.allKnownSubdirs(ctx) { 663 dirNames = append(dirNames, uri.Filename()) 664 } 665 sort.Strings(dirNames) 666 if len(dirNames) > 0 { 667 patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{} 668 } 669 return patterns 670} 671 672// allKnownSubdirs returns all of the subdirectories within the snapshot's 673// workspace directories. None of the workspace directories are included. 674func (s *snapshot) allKnownSubdirs(ctx context.Context) map[span.URI]struct{} { 675 dirs := s.workspace.dirs(ctx, s) 676 677 s.mu.Lock() 678 defer s.mu.Unlock() 679 seen := make(map[span.URI]struct{}) 680 for uri := range s.files { 681 dir := filepath.Dir(uri.Filename()) 682 var matched span.URI 683 for _, wsDir := range dirs { 684 if source.InDir(wsDir.Filename(), dir) { 685 matched = wsDir 686 break 687 } 688 } 689 // Don't watch any directory outside of the workspace directories. 690 if matched == "" { 691 continue 692 } 693 for { 694 if dir == "" || dir == matched.Filename() { 695 break 696 } 697 uri := span.URIFromPath(dir) 698 if _, ok := seen[uri]; ok { 699 break 700 } 701 seen[uri] = struct{}{} 702 dir = filepath.Dir(dir) 703 } 704 } 705 return seen 706} 707 708// knownFilesInDir returns the files known to the given snapshot that are in 709// the given directory. It does not respect symlinks. 710func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI { 711 var files []span.URI 712 for uri := range s.files { 713 if source.InDir(dir.Filename(), uri.Filename()) { 714 files = append(files, uri) 715 } 716 } 717 return files 718} 719 720func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.Package, error) { 721 if err := s.awaitLoaded(ctx); err != nil { 722 return nil, err 723 } 724 var pkgs []source.Package 725 for _, pkgID := range s.workspacePackageIDs() { 726 pkg, err := s.checkedPackage(ctx, pkgID, s.workspaceParseMode(pkgID)) 727 if err != nil { 728 return nil, err 729 } 730 pkgs = append(pkgs, pkg) 731 } 732 return pkgs, nil 733} 734 735func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) { 736 if err := s.awaitLoaded(ctx); err != nil { 737 return nil, err 738 } 739 740 // The WorkspaceSymbols implementation relies on this function returning 741 // workspace packages first. 742 ids := s.workspacePackageIDs() 743 s.mu.Lock() 744 for id := range s.metadata { 745 if _, ok := s.workspacePackages[id]; ok { 746 continue 747 } 748 ids = append(ids, id) 749 } 750 s.mu.Unlock() 751 752 var pkgs []source.Package 753 for _, id := range ids { 754 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 755 if err != nil { 756 return nil, err 757 } 758 pkgs = append(pkgs, pkg) 759 } 760 return pkgs, nil 761} 762 763func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) { 764 // Don't reload workspace package metadata. 765 // This function is meant to only return currently cached information. 766 s.AwaitInitialized(ctx) 767 768 s.mu.Lock() 769 defer s.mu.Unlock() 770 771 results := map[string]source.Package{} 772 for _, ph := range s.packages { 773 cachedPkg, err := ph.cached(s.generation) 774 if err != nil { 775 continue 776 } 777 for importPath, newPkg := range cachedPkg.imports { 778 if oldPkg, ok := results[string(importPath)]; ok { 779 // Using the same trick as NarrowestPackage, prefer non-variants. 780 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) { 781 results[string(importPath)] = newPkg 782 } 783 } else { 784 results[string(importPath)] = newPkg 785 } 786 } 787 } 788 return results, nil 789} 790 791func (s *snapshot) GoModForFile(ctx context.Context, uri span.URI) span.URI { 792 var match span.URI 793 for modURI := range s.workspace.getActiveModFiles() { 794 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) { 795 continue 796 } 797 if len(modURI) > len(match) { 798 match = modURI 799 } 800 } 801 return match 802} 803 804func (s *snapshot) getPackage(id packageID, mode source.ParseMode) *packageHandle { 805 s.mu.Lock() 806 defer s.mu.Unlock() 807 808 key := packageKey{ 809 id: id, 810 mode: mode, 811 } 812 return s.packages[key] 813} 814 815func (s *snapshot) getActionHandle(id packageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle { 816 s.mu.Lock() 817 defer s.mu.Unlock() 818 819 key := actionKey{ 820 pkg: packageKey{ 821 id: id, 822 mode: m, 823 }, 824 analyzer: a, 825 } 826 return s.actions[key] 827} 828 829func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle { 830 s.mu.Lock() 831 defer s.mu.Unlock() 832 833 key := actionKey{ 834 analyzer: ah.analyzer, 835 pkg: packageKey{ 836 id: ah.pkg.m.id, 837 mode: ah.pkg.mode, 838 }, 839 } 840 if ah, ok := s.actions[key]; ok { 841 return ah 842 } 843 s.actions[key] = ah 844 return ah 845} 846 847func (s *snapshot) getIDsForURI(uri span.URI) []packageID { 848 s.mu.Lock() 849 defer s.mu.Unlock() 850 851 return s.ids[uri] 852} 853 854func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) { 855 // TODO(matloob): uri can be a file or directory. Should we update the mappings 856 // to map directories to their contained packages? 857 858 for _, id := range s.ids[uri] { 859 if m, ok := s.metadata[id]; ok { 860 metadata = append(metadata, m) 861 } 862 } 863 return metadata 864} 865 866func (s *snapshot) getMetadata(id packageID) *metadata { 867 s.mu.Lock() 868 defer s.mu.Unlock() 869 870 return s.metadata[id] 871} 872 873func (s *snapshot) addID(uri span.URI, id packageID) { 874 s.mu.Lock() 875 defer s.mu.Unlock() 876 877 for i, existingID := range s.ids[uri] { 878 // TODO: We should make sure not to set duplicate IDs, 879 // and instead panic here. This can be done by making sure not to 880 // reset metadata information for packages we've already seen. 881 if existingID == id { 882 return 883 } 884 // If we are setting a real ID, when the package had only previously 885 // had a command-line-arguments ID, we should just replace it. 886 if existingID == "command-line-arguments" { 887 s.ids[uri][i] = id 888 // Delete command-line-arguments if it was a workspace package. 889 delete(s.workspacePackages, existingID) 890 return 891 } 892 } 893 s.ids[uri] = append(s.ids[uri], id) 894} 895 896func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) { 897 s.mu.Lock() 898 defer s.mu.Unlock() 899 900 scope, ok := s.workspacePackages[id] 901 return scope, ok 902} 903 904func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle { 905 f, err := s.view.getFile(uri) 906 if err != nil { 907 return nil 908 } 909 910 s.mu.Lock() 911 defer s.mu.Unlock() 912 913 return s.files[f.URI()] 914} 915 916// GetVersionedFile returns a File for the given URI. If the file is unknown it 917// is added to the managed set. 918// 919// GetVersionedFile succeeds even if the file does not exist. A non-nil error return 920// indicates some type of internal error, for example if ctx is cancelled. 921func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) { 922 f, err := s.view.getFile(uri) 923 if err != nil { 924 return nil, err 925 } 926 927 s.mu.Lock() 928 defer s.mu.Unlock() 929 return s.getFileLocked(ctx, f) 930} 931 932// GetFile implements the fileSource interface by wrapping GetVersionedFile. 933func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 934 return s.GetVersionedFile(ctx, uri) 935} 936 937func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) { 938 if fh, ok := s.files[f.URI()]; ok { 939 return fh, nil 940 } 941 942 fh, err := s.view.session.cache.getFile(ctx, f.URI()) 943 if err != nil { 944 return nil, err 945 } 946 closed := &closedFile{fh} 947 s.files[f.URI()] = closed 948 return closed, nil 949} 950 951func (s *snapshot) IsOpen(uri span.URI) bool { 952 s.mu.Lock() 953 defer s.mu.Unlock() 954 return s.isOpenLocked(uri) 955 956} 957 958func (s *snapshot) isOpenLocked(uri span.URI) bool { 959 _, open := s.files[uri].(*overlay) 960 return open 961} 962 963func (s *snapshot) awaitLoaded(ctx context.Context) error { 964 // Do not return results until the snapshot's view has been initialized. 965 s.AwaitInitialized(ctx) 966 967 if err := s.reloadWorkspace(ctx); err != nil { 968 return err 969 } 970 if err := s.reloadOrphanedFiles(ctx); err != nil { 971 return err 972 } 973 // If we still have absolutely no metadata, check if the view failed to 974 // initialize and return any errors. 975 // TODO(rstambler): Should we clear the error after we return it? 976 s.mu.Lock() 977 defer s.mu.Unlock() 978 if len(s.metadata) == 0 { 979 return s.initializedErr 980 } 981 return nil 982} 983 984func (s *snapshot) AwaitInitialized(ctx context.Context) { 985 select { 986 case <-ctx.Done(): 987 return 988 case <-s.view.initialWorkspaceLoad: 989 } 990 // We typically prefer to run something as intensive as the IWL without 991 // blocking. I'm not sure if there is a way to do that here. 992 s.initialize(ctx, false) 993} 994 995// reloadWorkspace reloads the metadata for all invalidated workspace packages. 996func (s *snapshot) reloadWorkspace(ctx context.Context) error { 997 // See which of the workspace packages are missing metadata. 998 s.mu.Lock() 999 missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0 1000 pkgPathSet := map[packagePath]struct{}{} 1001 for id, pkgPath := range s.workspacePackages { 1002 if s.metadata[id] != nil { 1003 continue 1004 } 1005 missingMetadata = true 1006 1007 // Don't try to reload "command-line-arguments" directly. 1008 if pkgPath == "command-line-arguments" { 1009 continue 1010 } 1011 pkgPathSet[pkgPath] = struct{}{} 1012 } 1013 s.mu.Unlock() 1014 1015 // If the view's build configuration is invalid, we cannot reload by 1016 // package path. Just reload the directory instead. 1017 if missingMetadata && !s.ValidBuildConfiguration() { 1018 return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW")) 1019 } 1020 1021 if len(pkgPathSet) == 0 { 1022 return nil 1023 } 1024 1025 var pkgPaths []interface{} 1026 for pkgPath := range pkgPathSet { 1027 pkgPaths = append(pkgPaths, pkgPath) 1028 } 1029 return s.load(ctx, false, pkgPaths...) 1030} 1031 1032func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error { 1033 // When we load ./... or a package path directly, we may not get packages 1034 // that exist only in overlays. As a workaround, we search all of the files 1035 // available in the snapshot and reload their metadata individually using a 1036 // file= query if the metadata is unavailable. 1037 scopes := s.orphanedFileScopes() 1038 if len(scopes) == 0 { 1039 return nil 1040 } 1041 1042 err := s.load(ctx, false, scopes...) 1043 1044 // If we failed to load some files, i.e. they have no metadata, 1045 // mark the failures so we don't bother retrying until the file's 1046 // content changes. 1047 // 1048 // TODO(rstambler): This may be an overestimate if the load stopped 1049 // early for an unrelated errors. Add a fallback? 1050 // 1051 // Check for context cancellation so that we don't incorrectly mark files 1052 // as unloadable, but don't return before setting all workspace packages. 1053 if ctx.Err() == nil && err != nil { 1054 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes)) 1055 s.mu.Lock() 1056 for _, scope := range scopes { 1057 uri := span.URI(scope.(fileURI)) 1058 if s.getMetadataForURILocked(uri) == nil { 1059 s.unloadableFiles[uri] = struct{}{} 1060 } 1061 } 1062 s.mu.Unlock() 1063 } 1064 return nil 1065} 1066 1067func (s *snapshot) orphanedFileScopes() []interface{} { 1068 s.mu.Lock() 1069 defer s.mu.Unlock() 1070 1071 scopeSet := make(map[span.URI]struct{}) 1072 for uri, fh := range s.files { 1073 // Don't try to reload metadata for go.mod files. 1074 if fh.Kind() != source.Go { 1075 continue 1076 } 1077 // If the URI doesn't belong to this view, then it's not in a workspace 1078 // package and should not be reloaded directly. 1079 if !contains(s.view.session.viewsOf(uri), s.view) { 1080 continue 1081 } 1082 // If the file is not open and is in a vendor directory, don't treat it 1083 // like a workspace package. 1084 if _, ok := fh.(*overlay); !ok && inVendor(uri) { 1085 continue 1086 } 1087 // Don't reload metadata for files we've already deemed unloadable. 1088 if _, ok := s.unloadableFiles[uri]; ok { 1089 continue 1090 } 1091 if s.getMetadataForURILocked(uri) == nil { 1092 scopeSet[uri] = struct{}{} 1093 } 1094 } 1095 var scopes []interface{} 1096 for uri := range scopeSet { 1097 scopes = append(scopes, fileURI(uri)) 1098 } 1099 return scopes 1100} 1101 1102func contains(views []*View, view *View) bool { 1103 for _, v := range views { 1104 if v == view { 1105 return true 1106 } 1107 } 1108 return false 1109} 1110 1111func inVendor(uri span.URI) bool { 1112 toSlash := filepath.ToSlash(uri.Filename()) 1113 if !strings.Contains(toSlash, "/vendor/") { 1114 return false 1115 } 1116 // Only packages in _subdirectories_ of /vendor/ are considered vendored 1117 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). 1118 split := strings.Split(toSlash, "/vendor/") 1119 if len(split) < 2 { 1120 return false 1121 } 1122 return strings.Contains(split[1], "/") 1123} 1124 1125func generationName(v *View, snapshotID uint64) string { 1126 return fmt.Sprintf("v%v/%v", v.id, snapshotID) 1127} 1128 1129func (s *snapshot) clone(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, bool) { 1130 // Track some important types of changes. 1131 var ( 1132 vendorChanged bool 1133 modulesChanged bool 1134 ) 1135 newWorkspace, workspaceChanged := s.workspace.invalidate(ctx, changes) 1136 1137 s.mu.Lock() 1138 defer s.mu.Unlock() 1139 1140 newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1)) 1141 result := &snapshot{ 1142 id: s.id + 1, 1143 generation: newGen, 1144 view: s.view, 1145 builtin: s.builtin, 1146 initializeOnce: s.initializeOnce, 1147 initializedErr: s.initializedErr, 1148 ids: make(map[span.URI][]packageID), 1149 importedBy: make(map[packageID][]packageID), 1150 metadata: make(map[packageID]*metadata), 1151 packages: make(map[packageKey]*packageHandle), 1152 actions: make(map[actionKey]*actionHandle), 1153 files: make(map[span.URI]source.VersionedFileHandle), 1154 goFiles: make(map[parseKey]*parseGoHandle), 1155 workspacePackages: make(map[packageID]packagePath), 1156 unloadableFiles: make(map[span.URI]struct{}), 1157 parseModHandles: make(map[span.URI]*parseModHandle), 1158 modTidyHandles: make(map[span.URI]*modTidyHandle), 1159 modUpgradeHandles: make(map[span.URI]*modUpgradeHandle), 1160 modWhyHandles: make(map[span.URI]*modWhyHandle), 1161 workspace: newWorkspace, 1162 } 1163 1164 if !workspaceChanged && s.workspaceDirHandle != nil { 1165 result.workspaceDirHandle = s.workspaceDirHandle 1166 newGen.Inherit(s.workspaceDirHandle) 1167 } 1168 1169 if s.builtin != nil { 1170 newGen.Inherit(s.builtin.handle) 1171 } 1172 1173 // Copy all of the FileHandles. 1174 for k, v := range s.files { 1175 result.files[k] = v 1176 } 1177 1178 // Copy the set of unloadable files. 1179 for k, v := range s.unloadableFiles { 1180 result.unloadableFiles[k] = v 1181 } 1182 // Copy all of the modHandles. 1183 for k, v := range s.parseModHandles { 1184 result.parseModHandles[k] = v 1185 } 1186 1187 for k, v := range s.goFiles { 1188 if _, ok := changes[k.file.URI]; ok { 1189 continue 1190 } 1191 newGen.Inherit(v.handle) 1192 newGen.Inherit(v.astCacheHandle) 1193 result.goFiles[k] = v 1194 } 1195 1196 // Copy all of the go.mod-related handles. They may be invalidated later, 1197 // so we inherit them at the end of the function. 1198 for k, v := range s.modTidyHandles { 1199 if _, ok := changes[k]; ok { 1200 continue 1201 } 1202 result.modTidyHandles[k] = v 1203 } 1204 for k, v := range s.modUpgradeHandles { 1205 if _, ok := changes[k]; ok { 1206 continue 1207 } 1208 result.modUpgradeHandles[k] = v 1209 } 1210 for k, v := range s.modWhyHandles { 1211 if _, ok := changes[k]; ok { 1212 continue 1213 } 1214 result.modWhyHandles[k] = v 1215 } 1216 1217 // directIDs keeps track of package IDs that have directly changed. 1218 // It maps id->invalidateMetadata. 1219 directIDs := map[packageID]bool{} 1220 // Invalidate all package metadata if the workspace module has changed. 1221 if workspaceChanged { 1222 for k := range s.metadata { 1223 directIDs[k] = true 1224 } 1225 } 1226 1227 changedPkgNames := map[packageID][]span.URI{} 1228 for uri, change := range changes { 1229 // Maybe reinitialize the view if we see a change in the vendor 1230 // directory. 1231 if inVendor(uri) { 1232 vendorChanged = true 1233 } 1234 1235 // The original FileHandle for this URI is cached on the snapshot. 1236 originalFH := s.files[uri] 1237 1238 // Check if the file's package name or imports have changed, 1239 // and if so, invalidate this file's packages' metadata. 1240 shouldInvalidateMetadata, pkgNameChanged := s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle) 1241 invalidateMetadata := forceReloadMetadata || shouldInvalidateMetadata 1242 1243 // Mark all of the package IDs containing the given file. 1244 // TODO: if the file has moved into a new package, we should invalidate that too. 1245 filePackageIDs := guessPackageIDsForURI(uri, s.ids) 1246 if pkgNameChanged { 1247 for _, id := range filePackageIDs { 1248 changedPkgNames[id] = append(changedPkgNames[id], uri) 1249 } 1250 } 1251 for _, id := range filePackageIDs { 1252 directIDs[id] = directIDs[id] || invalidateMetadata 1253 } 1254 1255 // Invalidate the previous modTidyHandle if any of the files have been 1256 // saved or if any of the metadata has been invalidated. 1257 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) { 1258 // TODO(rstambler): Only delete mod handles for which the 1259 // withoutURI is relevant. 1260 for k := range s.modTidyHandles { 1261 delete(result.modTidyHandles, k) 1262 } 1263 for k := range s.modUpgradeHandles { 1264 delete(result.modUpgradeHandles, k) 1265 } 1266 for k := range s.modWhyHandles { 1267 delete(result.modWhyHandles, k) 1268 } 1269 } 1270 if isGoMod(uri) { 1271 // If the view's go.mod file's contents have changed, invalidate 1272 // the metadata for every known package in the snapshot. 1273 delete(result.parseModHandles, uri) 1274 if _, ok := result.workspace.getActiveModFiles()[uri]; ok { 1275 modulesChanged = true 1276 } 1277 } 1278 // Handle the invalidated file; it may have new contents or not exist. 1279 if !change.exists { 1280 delete(result.files, uri) 1281 } else { 1282 result.files[uri] = change.fileHandle 1283 } 1284 // Make sure to remove the changed file from the unloadable set. 1285 delete(result.unloadableFiles, uri) 1286 } 1287 1288 // Invalidate reverse dependencies too. 1289 // TODO(heschi): figure out the locking model and use transitiveReverseDeps? 1290 // transitiveIDs keeps track of transitive reverse dependencies. 1291 // If an ID is present in the map, invalidate its types. 1292 // If an ID's value is true, invalidate its metadata too. 1293 transitiveIDs := make(map[packageID]bool) 1294 var addRevDeps func(packageID, bool) 1295 addRevDeps = func(id packageID, invalidateMetadata bool) { 1296 current, seen := transitiveIDs[id] 1297 newInvalidateMetadata := current || invalidateMetadata 1298 1299 // If we've already seen this ID, and the value of invalidate 1300 // metadata has not changed, we can return early. 1301 if seen && current == newInvalidateMetadata { 1302 return 1303 } 1304 transitiveIDs[id] = newInvalidateMetadata 1305 for _, rid := range s.getImportedByLocked(id) { 1306 addRevDeps(rid, invalidateMetadata) 1307 } 1308 } 1309 for id, invalidateMetadata := range directIDs { 1310 addRevDeps(id, invalidateMetadata) 1311 } 1312 1313 // Copy the package type information. 1314 for k, v := range s.packages { 1315 if _, ok := transitiveIDs[k.id]; ok { 1316 continue 1317 } 1318 newGen.Inherit(v.handle) 1319 result.packages[k] = v 1320 } 1321 // Copy the package analysis information. 1322 for k, v := range s.actions { 1323 if _, ok := transitiveIDs[k.pkg.id]; ok { 1324 continue 1325 } 1326 newGen.Inherit(v.handle) 1327 result.actions[k] = v 1328 } 1329 // Copy the package metadata. We only need to invalidate packages directly 1330 // containing the affected file, and only if it changed in a relevant way. 1331 for k, v := range s.metadata { 1332 if invalidateMetadata, ok := transitiveIDs[k]; invalidateMetadata && ok { 1333 continue 1334 } 1335 result.metadata[k] = v 1336 } 1337 // Copy the URI to package ID mappings, skipping only those URIs whose 1338 // metadata will be reloaded in future calls to load. 1339copyIDs: 1340 for k, ids := range s.ids { 1341 for _, id := range ids { 1342 if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok { 1343 continue copyIDs 1344 } 1345 } 1346 result.ids[k] = ids 1347 } 1348 // Copy the set of initially loaded packages. 1349 for id, pkgPath := range s.workspacePackages { 1350 // Packages with the id "command-line-arguments" are generated by the 1351 // go command when the user is outside of GOPATH and outside of a 1352 // module. Do not cache them as workspace packages for longer than 1353 // necessary. 1354 if id == "command-line-arguments" { 1355 if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok { 1356 continue 1357 } 1358 } 1359 1360 // If all the files we know about in a package have been deleted, 1361 // the package is gone and we should no longer try to load it. 1362 if m := s.metadata[id]; m != nil { 1363 hasFiles := false 1364 for _, uri := range s.metadata[id].goFiles { 1365 if _, ok := result.files[uri]; ok { 1366 hasFiles = true 1367 break 1368 } 1369 } 1370 if !hasFiles { 1371 continue 1372 } 1373 } 1374 1375 // If the package name of a file in the package has changed, it's 1376 // possible that the package ID may no longer exist. 1377 if uris, ok := changedPkgNames[id]; ok && s.shouldDeleteWorkspacePackageID(id, uris) { 1378 continue 1379 } 1380 1381 result.workspacePackages[id] = pkgPath 1382 } 1383 1384 // Inherit all of the go.mod-related handles. 1385 for _, v := range result.modTidyHandles { 1386 newGen.Inherit(v.handle) 1387 } 1388 for _, v := range result.modUpgradeHandles { 1389 newGen.Inherit(v.handle) 1390 } 1391 for _, v := range result.modWhyHandles { 1392 newGen.Inherit(v.handle) 1393 } 1394 for _, v := range result.parseModHandles { 1395 newGen.Inherit(v.handle) 1396 } 1397 // Don't bother copying the importedBy graph, 1398 // as it changes each time we update metadata. 1399 1400 // If the snapshot's workspace mode has changed, the packages loaded using 1401 // the previous mode are no longer relevant, so clear them out. 1402 if s.workspaceMode() != result.workspaceMode() { 1403 result.workspacePackages = map[packageID]packagePath{} 1404 } 1405 1406 // The snapshot may need to be reinitialized. 1407 if modulesChanged || workspaceChanged || vendorChanged { 1408 if workspaceChanged || result.initializedErr != nil { 1409 result.initializeOnce = &sync.Once{} 1410 } 1411 } 1412 return result, workspaceChanged 1413} 1414 1415// shouldDeleteWorkspacePackageID reports whether the given package ID should 1416// be removed from the set of workspace packages. If one of the files in the 1417// package has changed package names, we check if it is the only file that 1418// *only* belongs to this package. For example, in the case of a test variant, 1419// confirm that it is the sole file constituting the test variant. 1420func (s *snapshot) shouldDeleteWorkspacePackageID(id packageID, changedPkgNames []span.URI) bool { 1421 m, ok := s.metadata[id] 1422 if !ok { 1423 return false 1424 } 1425 changedPkgName := func(uri span.URI) bool { 1426 for _, changed := range changedPkgNames { 1427 if uri == changed { 1428 return true 1429 } 1430 } 1431 return false 1432 } 1433 for _, uri := range m.compiledGoFiles { 1434 if changedPkgName(uri) { 1435 continue 1436 } 1437 // If there is at least one file remaining that belongs only to this 1438 // package, and its package name has not changed, we shouldn't delete 1439 // its package ID from the set of workspace packages. 1440 if ids := guessPackageIDsForURI(uri, s.ids); len(ids) == 1 && ids[0] == id { 1441 return false 1442 } 1443 } 1444 return true 1445} 1446 1447// guessPackageIDsForURI returns all packages related to uri. If we haven't 1448// seen this URI before, we guess based on files in the same directory. This 1449// is of course incorrect in build systems where packages are not organized by 1450// directory. 1451func guessPackageIDsForURI(uri span.URI, known map[span.URI][]packageID) []packageID { 1452 packages := known[uri] 1453 if len(packages) > 0 { 1454 // We've seen this file before. 1455 return packages 1456 } 1457 // This is a file we don't yet know about. Guess relevant packages by 1458 // considering files in the same directory. 1459 1460 // Cache of FileInfo to avoid unnecessary stats for multiple files in the 1461 // same directory. 1462 stats := make(map[string]struct { 1463 os.FileInfo 1464 error 1465 }) 1466 getInfo := func(dir string) (os.FileInfo, error) { 1467 if res, ok := stats[dir]; ok { 1468 return res.FileInfo, res.error 1469 } 1470 fi, err := os.Stat(dir) 1471 stats[dir] = struct { 1472 os.FileInfo 1473 error 1474 }{fi, err} 1475 return fi, err 1476 } 1477 dir := filepath.Dir(uri.Filename()) 1478 fi, err := getInfo(dir) 1479 if err != nil { 1480 return nil 1481 } 1482 1483 // Aggregate all possibly relevant package IDs. 1484 var found []packageID 1485 for knownURI, ids := range known { 1486 knownDir := filepath.Dir(knownURI.Filename()) 1487 knownFI, err := getInfo(knownDir) 1488 if err != nil { 1489 continue 1490 } 1491 if os.SameFile(fi, knownFI) { 1492 found = append(found, ids...) 1493 } 1494 } 1495 return found 1496} 1497 1498// fileWasSaved reports whether the FileHandle passed in has been saved. It 1499// accomplishes this by checking to see if the original and current FileHandles 1500// are both overlays, and if the current FileHandle is saved while the original 1501// FileHandle was not saved. 1502func fileWasSaved(originalFH, currentFH source.FileHandle) bool { 1503 c, ok := currentFH.(*overlay) 1504 if !ok || c == nil { 1505 return true 1506 } 1507 o, ok := originalFH.(*overlay) 1508 if !ok || o == nil { 1509 return c.saved 1510 } 1511 return !o.saved && c.saved 1512} 1513 1514// shouldInvalidateMetadata reparses a file's package and import declarations to 1515// determine if the file requires a metadata reload. 1516func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged bool) { 1517 if originalFH == nil { 1518 return true, false 1519 } 1520 // If the file hasn't changed, there's no need to reload. 1521 if originalFH.FileIdentity() == currentFH.FileIdentity() { 1522 return false, false 1523 } 1524 // If a go.mod in the workspace has been changed, invalidate metadata. 1525 if kind := originalFH.Kind(); kind == source.Mod { 1526 if !source.InDir(filepath.Dir(s.view.rootURI.Filename()), originalFH.URI().Filename()) { 1527 return false, false 1528 } 1529 return currentFH.Saved(), false 1530 } 1531 // Get the original and current parsed files in order to check package name 1532 // and imports. Use the new snapshot to parse to avoid modifying the 1533 // current snapshot. 1534 original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseHeader) 1535 current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseHeader) 1536 if originalErr != nil || currentErr != nil { 1537 return (originalErr == nil) != (currentErr == nil), false 1538 } 1539 // Check if the package's metadata has changed. The cases handled are: 1540 // 1. A package's name has changed 1541 // 2. A file's imports have changed 1542 if original.File.Name.Name != current.File.Name.Name { 1543 return true, true 1544 } 1545 importSet := make(map[string]struct{}) 1546 for _, importSpec := range original.File.Imports { 1547 importSet[importSpec.Path.Value] = struct{}{} 1548 } 1549 // If any of the current imports were not in the original imports. 1550 for _, importSpec := range current.File.Imports { 1551 if _, ok := importSet[importSpec.Path.Value]; ok { 1552 continue 1553 } 1554 // If the import path is obviously not valid, we can skip reloading 1555 // metadata. For now, valid means properly quoted and without a 1556 // terminal slash. 1557 path, err := strconv.Unquote(importSpec.Path.Value) 1558 if err != nil { 1559 continue 1560 } 1561 if path == "" { 1562 continue 1563 } 1564 if path[len(path)-1] == '/' { 1565 continue 1566 } 1567 return true, false 1568 } 1569 return false, false 1570} 1571 1572func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) { 1573 s.AwaitInitialized(ctx) 1574 1575 if s.builtin == nil { 1576 return nil, errors.Errorf("no builtin package for view %s", s.view.name) 1577 } 1578 d, err := s.builtin.handle.Get(ctx, s.generation, s) 1579 if err != nil { 1580 return nil, err 1581 } 1582 data := d.(*builtinPackageData) 1583 return data.parsed, data.err 1584} 1585 1586func (s *snapshot) buildBuiltinPackage(ctx context.Context, goFiles []string) error { 1587 if len(goFiles) != 1 { 1588 return errors.Errorf("only expected 1 file, got %v", len(goFiles)) 1589 } 1590 uri := span.URIFromPath(goFiles[0]) 1591 1592 // Get the FileHandle through the cache to avoid adding it to the snapshot 1593 // and to get the file content from disk. 1594 fh, err := s.view.session.cache.getFile(ctx, uri) 1595 if err != nil { 1596 return err 1597 } 1598 h := s.generation.Bind(fh.FileIdentity(), func(ctx context.Context, arg memoize.Arg) interface{} { 1599 snapshot := arg.(*snapshot) 1600 1601 pgh := snapshot.parseGoHandle(ctx, fh, source.ParseFull) 1602 pgf, _, err := snapshot.parseGo(ctx, pgh) 1603 if err != nil { 1604 return &builtinPackageData{err: err} 1605 } 1606 pkg, err := ast.NewPackage(snapshot.view.session.cache.fset, map[string]*ast.File{ 1607 pgf.URI.Filename(): pgf.File, 1608 }, nil, nil) 1609 if err != nil { 1610 return &builtinPackageData{err: err} 1611 } 1612 return &builtinPackageData{ 1613 parsed: &source.BuiltinPackage{ 1614 ParsedFile: pgf, 1615 Package: pkg, 1616 }, 1617 } 1618 }, nil) 1619 s.builtin = &builtinPackageHandle{handle: h} 1620 return nil 1621} 1622 1623// BuildGoplsMod generates a go.mod file for all modules in the workspace. It 1624// bypasses any existing gopls.mod. 1625func BuildGoplsMod(ctx context.Context, root span.URI, fs source.FileSource) (*modfile.File, error) { 1626 allModules, err := findModules(ctx, root, 0) 1627 if err != nil { 1628 return nil, err 1629 } 1630 return buildWorkspaceModFile(ctx, allModules, fs) 1631} 1632 1633// TODO(rfindley): move this to workspacemodule.go 1634func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) { 1635 file := &modfile.File{} 1636 file.AddModuleStmt("gopls-workspace") 1637 1638 paths := make(map[string]span.URI) 1639 for modURI := range modFiles { 1640 fh, err := fs.GetFile(ctx, modURI) 1641 if err != nil { 1642 return nil, err 1643 } 1644 content, err := fh.Read() 1645 if err != nil { 1646 return nil, err 1647 } 1648 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 1649 if err != nil { 1650 return nil, err 1651 } 1652 if file == nil || parsed.Module == nil { 1653 return nil, fmt.Errorf("no module declaration for %s", modURI) 1654 } 1655 path := parsed.Module.Mod.Path 1656 paths[path] = modURI 1657 // If the module's path includes a major version, we expect it to have 1658 // a matching major version. 1659 _, majorVersion, _ := module.SplitPathVersion(path) 1660 if majorVersion == "" { 1661 majorVersion = "/v0" 1662 } 1663 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions 1664 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false) 1665 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil { 1666 return nil, err 1667 } 1668 } 1669 // Go back through all of the modules to handle any of their replace 1670 // statements. 1671 for modURI := range modFiles { 1672 fh, err := fs.GetFile(ctx, modURI) 1673 if err != nil { 1674 return nil, err 1675 } 1676 content, err := fh.Read() 1677 if err != nil { 1678 return nil, err 1679 } 1680 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 1681 if err != nil { 1682 return nil, err 1683 } 1684 // If any of the workspace modules have replace directives, they need 1685 // to be reflected in the workspace module. 1686 for _, rep := range parsed.Replace { 1687 // Don't replace any modules that are in our workspace--we should 1688 // always use the version in the workspace. 1689 if _, ok := paths[rep.Old.Path]; ok { 1690 continue 1691 } 1692 newPath := rep.New.Path 1693 newVersion := rep.New.Version 1694 // If a replace points to a module in the workspace, make sure we 1695 // direct it to version of the module in the workspace. 1696 if m, ok := paths[rep.New.Path]; ok { 1697 newPath = dirURI(m).Filename() 1698 newVersion = "" 1699 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) { 1700 // Make any relative paths absolute. 1701 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path) 1702 } 1703 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil { 1704 return nil, err 1705 } 1706 } 1707 } 1708 return file, nil 1709} 1710