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