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.view.session.cache.fset, 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 m := s.getMetadata(id) 1118 return m == nil || !m.valid 1119} 1120 1121// updateIDForURIs adds the given ID to the set of known IDs for the given URI. 1122// Any existing invalid IDs are removed from the set of known IDs. IDs that are 1123// not "command-line-arguments" are preferred, so if a new ID comes in for a 1124// URI that previously only had "command-line-arguments", the new ID will 1125// replace the "command-line-arguments" ID. 1126func (s *snapshot) updateIDForURIs(id packageID, uris map[span.URI]struct{}) { 1127 s.mu.Lock() 1128 defer s.mu.Unlock() 1129 1130 for uri := range uris { 1131 // Collect the new set of IDs, preserving any valid existing IDs. 1132 newIDs := []packageID{id} 1133 for _, existingID := range s.ids[uri] { 1134 // Don't set duplicates of the same ID. 1135 if existingID == id { 1136 continue 1137 } 1138 // If the package previously only had a command-line-arguments ID, 1139 // delete the command-line-arguments workspace package. 1140 if source.IsCommandLineArguments(string(existingID)) { 1141 delete(s.workspacePackages, existingID) 1142 continue 1143 } 1144 // If the metadata for an existing ID is invalid, and we are 1145 // setting metadata for a new, valid ID--don't preserve the old ID. 1146 if m, ok := s.metadata[existingID]; !ok || !m.valid { 1147 continue 1148 } 1149 newIDs = append(newIDs, existingID) 1150 } 1151 sort.Slice(newIDs, func(i, j int) bool { 1152 return newIDs[i] < newIDs[j] 1153 }) 1154 s.ids[uri] = newIDs 1155 } 1156} 1157 1158func (s *snapshot) isWorkspacePackage(id packageID) bool { 1159 s.mu.Lock() 1160 defer s.mu.Unlock() 1161 1162 _, ok := s.workspacePackages[id] 1163 return ok 1164} 1165 1166func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle { 1167 f := s.view.getFile(uri) 1168 1169 s.mu.Lock() 1170 defer s.mu.Unlock() 1171 1172 return s.files[f.URI()] 1173} 1174 1175// GetVersionedFile returns a File for the given URI. If the file is unknown it 1176// is added to the managed set. 1177// 1178// GetVersionedFile succeeds even if the file does not exist. A non-nil error return 1179// indicates some type of internal error, for example if ctx is cancelled. 1180func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) { 1181 f := s.view.getFile(uri) 1182 1183 s.mu.Lock() 1184 defer s.mu.Unlock() 1185 return s.getFileLocked(ctx, f) 1186} 1187 1188// GetFile implements the fileSource interface by wrapping GetVersionedFile. 1189func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 1190 return s.GetVersionedFile(ctx, uri) 1191} 1192 1193func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) { 1194 if fh, ok := s.files[f.URI()]; ok { 1195 return fh, nil 1196 } 1197 1198 fh, err := s.view.session.cache.getFile(ctx, f.URI()) 1199 if err != nil { 1200 return nil, err 1201 } 1202 closed := &closedFile{fh} 1203 s.files[f.URI()] = closed 1204 return closed, nil 1205} 1206 1207func (s *snapshot) IsOpen(uri span.URI) bool { 1208 s.mu.Lock() 1209 defer s.mu.Unlock() 1210 return s.isOpenLocked(uri) 1211 1212} 1213 1214func (s *snapshot) openFiles() []source.VersionedFileHandle { 1215 s.mu.Lock() 1216 defer s.mu.Unlock() 1217 1218 var open []source.VersionedFileHandle 1219 for _, fh := range s.files { 1220 if s.isOpenLocked(fh.URI()) { 1221 open = append(open, fh) 1222 } 1223 } 1224 return open 1225} 1226 1227func (s *snapshot) isOpenLocked(uri span.URI) bool { 1228 _, open := s.files[uri].(*overlay) 1229 return open 1230} 1231 1232func (s *snapshot) awaitLoaded(ctx context.Context) error { 1233 loadErr := s.awaitLoadedAllErrors(ctx) 1234 1235 s.mu.Lock() 1236 defer s.mu.Unlock() 1237 1238 // If we still have absolutely no metadata, check if the view failed to 1239 // initialize and return any errors. 1240 if s.useInvalidMetadata() && len(s.metadata) > 0 { 1241 return nil 1242 } 1243 for _, m := range s.metadata { 1244 if m.valid { 1245 return nil 1246 } 1247 } 1248 if loadErr != nil { 1249 return loadErr.MainError 1250 } 1251 return nil 1252} 1253 1254func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError { 1255 loadErr := s.awaitLoadedAllErrors(ctx) 1256 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) { 1257 return nil 1258 } 1259 1260 // Even if packages didn't fail to load, we still may want to show 1261 // additional warnings. 1262 if loadErr == nil { 1263 wsPkgs, _ := s.WorkspacePackages(ctx) 1264 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" { 1265 return &source.CriticalError{ 1266 MainError: errors.New(msg), 1267 } 1268 } 1269 // Even if workspace packages were returned, there still may be an error 1270 // with the user's workspace layout. Workspace packages that only have the 1271 // ID "command-line-arguments" are usually a symptom of a bad workspace 1272 // configuration. 1273 if containsCommandLineArguments(wsPkgs) { 1274 return s.workspaceLayoutError(ctx) 1275 } 1276 return nil 1277 } 1278 1279 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") { 1280 return s.workspaceLayoutError(ctx) 1281 } 1282 return loadErr 1283} 1284 1285const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src. 1286If you are using modules, please open your editor to a directory in your module. 1287If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.` 1288 1289func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string { 1290 if snapshot.ValidBuildConfiguration() { 1291 return "" 1292 } 1293 for _, pkg := range pkgs { 1294 if len(pkg.MissingDependencies()) > 0 { 1295 return adHocPackagesWarning 1296 } 1297 } 1298 return "" 1299} 1300 1301func containsCommandLineArguments(pkgs []source.Package) bool { 1302 for _, pkg := range pkgs { 1303 if source.IsCommandLineArguments(pkg.ID()) { 1304 return true 1305 } 1306 } 1307 return false 1308} 1309 1310func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError { 1311 // Do not return results until the snapshot's view has been initialized. 1312 s.AwaitInitialized(ctx) 1313 1314 // TODO(rstambler): Should we be more careful about returning the 1315 // initialization error? Is it possible for the initialization error to be 1316 // corrected without a successful reinitialization? 1317 s.mu.Lock() 1318 initializedErr := s.initializedErr 1319 s.mu.Unlock() 1320 if initializedErr != nil { 1321 return initializedErr 1322 } 1323 1324 if ctx.Err() != nil { 1325 return &source.CriticalError{MainError: ctx.Err()} 1326 } 1327 1328 if err := s.reloadWorkspace(ctx); err != nil { 1329 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1330 return &source.CriticalError{ 1331 MainError: err, 1332 DiagList: diags, 1333 } 1334 } 1335 if err := s.reloadOrphanedFiles(ctx); err != nil { 1336 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1337 return &source.CriticalError{ 1338 MainError: err, 1339 DiagList: diags, 1340 } 1341 } 1342 return nil 1343} 1344 1345func (s *snapshot) getInitializationError(ctx context.Context) *source.CriticalError { 1346 s.mu.Lock() 1347 defer s.mu.Unlock() 1348 1349 return s.initializedErr 1350} 1351 1352func (s *snapshot) AwaitInitialized(ctx context.Context) { 1353 select { 1354 case <-ctx.Done(): 1355 return 1356 case <-s.view.initialWorkspaceLoad: 1357 } 1358 // We typically prefer to run something as intensive as the IWL without 1359 // blocking. I'm not sure if there is a way to do that here. 1360 s.initialize(ctx, false) 1361} 1362 1363// reloadWorkspace reloads the metadata for all invalidated workspace packages. 1364func (s *snapshot) reloadWorkspace(ctx context.Context) error { 1365 // See which of the workspace packages are missing metadata. 1366 s.mu.Lock() 1367 missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0 1368 pkgPathSet := map[packagePath]struct{}{} 1369 for id, pkgPath := range s.workspacePackages { 1370 if m, ok := s.metadata[id]; ok && m.valid { 1371 continue 1372 } 1373 missingMetadata = true 1374 1375 // Don't try to reload "command-line-arguments" directly. 1376 if source.IsCommandLineArguments(string(pkgPath)) { 1377 continue 1378 } 1379 pkgPathSet[pkgPath] = struct{}{} 1380 } 1381 s.mu.Unlock() 1382 1383 // If the view's build configuration is invalid, we cannot reload by 1384 // package path. Just reload the directory instead. 1385 if missingMetadata && !s.ValidBuildConfiguration() { 1386 return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW")) 1387 } 1388 1389 if len(pkgPathSet) == 0 { 1390 return nil 1391 } 1392 1393 var pkgPaths []interface{} 1394 for pkgPath := range pkgPathSet { 1395 pkgPaths = append(pkgPaths, pkgPath) 1396 } 1397 return s.load(ctx, false, pkgPaths...) 1398} 1399 1400func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error { 1401 // When we load ./... or a package path directly, we may not get packages 1402 // that exist only in overlays. As a workaround, we search all of the files 1403 // available in the snapshot and reload their metadata individually using a 1404 // file= query if the metadata is unavailable. 1405 files := s.orphanedFiles() 1406 1407 // Files without a valid package declaration can't be loaded. Don't try. 1408 var scopes []interface{} 1409 for _, file := range files { 1410 pgf, err := s.ParseGo(ctx, file, source.ParseHeader) 1411 if err != nil { 1412 continue 1413 } 1414 if !pgf.File.Package.IsValid() { 1415 continue 1416 } 1417 scopes = append(scopes, fileURI(file.URI())) 1418 } 1419 1420 if len(scopes) == 0 { 1421 return nil 1422 } 1423 1424 // The regtests match this exact log message, keep them in sync. 1425 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes)) 1426 err := s.load(ctx, false, scopes...) 1427 1428 // If we failed to load some files, i.e. they have no metadata, 1429 // mark the failures so we don't bother retrying until the file's 1430 // content changes. 1431 // 1432 // TODO(rstambler): This may be an overestimate if the load stopped 1433 // early for an unrelated errors. Add a fallback? 1434 // 1435 // Check for context cancellation so that we don't incorrectly mark files 1436 // as unloadable, but don't return before setting all workspace packages. 1437 if ctx.Err() == nil && err != nil { 1438 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes)) 1439 s.mu.Lock() 1440 for _, scope := range scopes { 1441 uri := span.URI(scope.(fileURI)) 1442 if s.noValidMetadataForURILocked(uri) { 1443 s.unloadableFiles[uri] = struct{}{} 1444 } 1445 } 1446 s.mu.Unlock() 1447 } 1448 return nil 1449} 1450 1451func (s *snapshot) orphanedFiles() []source.VersionedFileHandle { 1452 s.mu.Lock() 1453 defer s.mu.Unlock() 1454 1455 var files []source.VersionedFileHandle 1456 for uri, fh := range s.files { 1457 // Don't try to reload metadata for go.mod files. 1458 if fh.Kind() != source.Go { 1459 continue 1460 } 1461 // If the URI doesn't belong to this view, then it's not in a workspace 1462 // package and should not be reloaded directly. 1463 if !contains(s.view.session.viewsOf(uri), s.view) { 1464 continue 1465 } 1466 // If the file is not open and is in a vendor directory, don't treat it 1467 // like a workspace package. 1468 if _, ok := fh.(*overlay); !ok && inVendor(uri) { 1469 continue 1470 } 1471 // Don't reload metadata for files we've already deemed unloadable. 1472 if _, ok := s.unloadableFiles[uri]; ok { 1473 continue 1474 } 1475 if s.noValidMetadataForURILocked(uri) { 1476 files = append(files, fh) 1477 } 1478 } 1479 return files 1480} 1481 1482func contains(views []*View, view *View) bool { 1483 for _, v := range views { 1484 if v == view { 1485 return true 1486 } 1487 } 1488 return false 1489} 1490 1491func inVendor(uri span.URI) bool { 1492 if !strings.Contains(string(uri), "/vendor/") { 1493 return false 1494 } 1495 // Only packages in _subdirectories_ of /vendor/ are considered vendored 1496 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). 1497 split := strings.Split(string(uri), "/vendor/") 1498 if len(split) < 2 { 1499 return false 1500 } 1501 return strings.Contains(split[1], "/") 1502} 1503 1504func generationName(v *View, snapshotID uint64) string { 1505 return fmt.Sprintf("v%v/%v", v.id, snapshotID) 1506} 1507 1508// checkSnapshotLocked verifies that some invariants are preserved on the 1509// snapshot. 1510func checkSnapshotLocked(ctx context.Context, s *snapshot) { 1511 // Check that every go file for a workspace package is identified as 1512 // belonging to that workspace package. 1513 for wsID := range s.workspacePackages { 1514 if m, ok := s.metadata[wsID]; ok { 1515 for _, uri := range m.goFiles { 1516 found := false 1517 for _, id := range s.ids[uri] { 1518 if id == wsID { 1519 found = true 1520 break 1521 } 1522 } 1523 if !found { 1524 log.Error.Logf(ctx, "workspace package %v not associated with %v", wsID, uri) 1525 } 1526 } 1527 } 1528 } 1529} 1530 1531func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, bool) { 1532 var vendorChanged bool 1533 newWorkspace, workspaceChanged, workspaceReload := s.workspace.invalidate(ctx, changes) 1534 1535 s.mu.Lock() 1536 defer s.mu.Unlock() 1537 1538 checkSnapshotLocked(ctx, s) 1539 1540 newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1)) 1541 bgCtx, cancel := context.WithCancel(bgCtx) 1542 result := &snapshot{ 1543 id: s.id + 1, 1544 generation: newGen, 1545 view: s.view, 1546 backgroundCtx: bgCtx, 1547 cancel: cancel, 1548 builtin: s.builtin, 1549 initializeOnce: s.initializeOnce, 1550 initializedErr: s.initializedErr, 1551 ids: make(map[span.URI][]packageID, len(s.ids)), 1552 importedBy: make(map[packageID][]packageID, len(s.importedBy)), 1553 metadata: make(map[packageID]*knownMetadata, len(s.metadata)), 1554 packages: make(map[packageKey]*packageHandle, len(s.packages)), 1555 actions: make(map[actionKey]*actionHandle, len(s.actions)), 1556 files: make(map[span.URI]source.VersionedFileHandle, len(s.files)), 1557 goFiles: make(map[parseKey]*parseGoHandle, len(s.goFiles)), 1558 workspacePackages: make(map[packageID]packagePath, len(s.workspacePackages)), 1559 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)), 1560 parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)), 1561 modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)), 1562 modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)), 1563 knownSubdirs: make(map[span.URI]struct{}, len(s.knownSubdirs)), 1564 workspace: newWorkspace, 1565 } 1566 1567 if !workspaceChanged && s.workspaceDirHandle != nil { 1568 result.workspaceDirHandle = s.workspaceDirHandle 1569 newGen.Inherit(s.workspaceDirHandle) 1570 } 1571 1572 // Copy all of the FileHandles. 1573 for k, v := range s.files { 1574 result.files[k] = v 1575 } 1576 1577 // Copy the set of unloadable files. 1578 for k, v := range s.unloadableFiles { 1579 result.unloadableFiles[k] = v 1580 } 1581 // Copy all of the modHandles. 1582 for k, v := range s.parseModHandles { 1583 result.parseModHandles[k] = v 1584 } 1585 1586 for k, v := range s.goFiles { 1587 if _, ok := changes[k.file.URI]; ok { 1588 continue 1589 } 1590 newGen.Inherit(v.handle) 1591 result.goFiles[k] = v 1592 } 1593 1594 // Copy all of the go.mod-related handles. They may be invalidated later, 1595 // so we inherit them at the end of the function. 1596 for k, v := range s.modTidyHandles { 1597 if _, ok := changes[k]; ok { 1598 continue 1599 } 1600 result.modTidyHandles[k] = v 1601 } 1602 for k, v := range s.modWhyHandles { 1603 if _, ok := changes[k]; ok { 1604 continue 1605 } 1606 result.modWhyHandles[k] = v 1607 } 1608 1609 // Add all of the known subdirectories, but don't update them for the 1610 // changed files. We need to rebuild the workspace module to know the 1611 // true set of known subdirectories, but we don't want to do that in clone. 1612 for k, v := range s.knownSubdirs { 1613 result.knownSubdirs[k] = v 1614 } 1615 for _, c := range changes { 1616 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c) 1617 } 1618 1619 // directIDs keeps track of package IDs that have directly changed. 1620 // It maps id->invalidateMetadata. 1621 directIDs := map[packageID]bool{} 1622 1623 // Invalidate all package metadata if the workspace module has changed. 1624 if workspaceReload { 1625 for k := range s.metadata { 1626 directIDs[k] = true 1627 } 1628 } 1629 1630 changedPkgNames := map[packageID]struct{}{} 1631 anyImportDeleted := false 1632 for uri, change := range changes { 1633 // Maybe reinitialize the view if we see a change in the vendor 1634 // directory. 1635 if inVendor(uri) { 1636 vendorChanged = true 1637 } 1638 1639 // The original FileHandle for this URI is cached on the snapshot. 1640 originalFH := s.files[uri] 1641 1642 // Check if the file's package name or imports have changed, 1643 // and if so, invalidate this file's packages' metadata. 1644 var shouldInvalidateMetadata, pkgNameChanged, importDeleted bool 1645 if !isGoMod(uri) { 1646 shouldInvalidateMetadata, pkgNameChanged, importDeleted = s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle) 1647 } 1648 invalidateMetadata := forceReloadMetadata || workspaceReload || shouldInvalidateMetadata 1649 anyImportDeleted = anyImportDeleted || importDeleted 1650 1651 // Mark all of the package IDs containing the given file. 1652 // TODO: if the file has moved into a new package, we should invalidate that too. 1653 filePackageIDs := guessPackageIDsForURI(uri, s.ids) 1654 if pkgNameChanged { 1655 for _, id := range filePackageIDs { 1656 changedPkgNames[id] = struct{}{} 1657 } 1658 } 1659 for _, id := range filePackageIDs { 1660 directIDs[id] = directIDs[id] || invalidateMetadata 1661 } 1662 1663 // Invalidate the previous modTidyHandle if any of the files have been 1664 // saved or if any of the metadata has been invalidated. 1665 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) { 1666 // TODO(rstambler): Only delete mod handles for which the 1667 // withoutURI is relevant. 1668 for k := range s.modTidyHandles { 1669 delete(result.modTidyHandles, k) 1670 } 1671 for k := range s.modWhyHandles { 1672 delete(result.modWhyHandles, k) 1673 } 1674 } 1675 if isGoMod(uri) { 1676 delete(result.parseModHandles, uri) 1677 } 1678 // Handle the invalidated file; it may have new contents or not exist. 1679 if !change.exists { 1680 delete(result.files, uri) 1681 } else { 1682 result.files[uri] = change.fileHandle 1683 } 1684 1685 // Make sure to remove the changed file from the unloadable set. 1686 delete(result.unloadableFiles, uri) 1687 } 1688 1689 // Deleting an import can cause list errors due to import cycles to be 1690 // resolved. The best we can do without parsing the list error message is to 1691 // hope that list errors may have been resolved by a deleted import. 1692 // 1693 // We could do better by parsing the list error message. We already do this 1694 // to assign a better range to the list error, but for such critical 1695 // functionality as metadata, it's better to be conservative until it proves 1696 // impractical. 1697 // 1698 // We could also do better by looking at which imports were deleted and 1699 // trying to find cycles they are involved in. This fails when the file goes 1700 // from an unparseable state to a parseable state, as we don't have a 1701 // starting point to compare with. 1702 if anyImportDeleted { 1703 for id, metadata := range s.metadata { 1704 if len(metadata.errors) > 0 { 1705 directIDs[id] = true 1706 } 1707 } 1708 } 1709 1710 // Invalidate reverse dependencies too. 1711 // TODO(heschi): figure out the locking model and use transitiveReverseDeps? 1712 // idsToInvalidate keeps track of transitive reverse dependencies. 1713 // If an ID is present in the map, invalidate its types. 1714 // If an ID's value is true, invalidate its metadata too. 1715 idsToInvalidate := map[packageID]bool{} 1716 var addRevDeps func(packageID, bool) 1717 addRevDeps = func(id packageID, invalidateMetadata bool) { 1718 current, seen := idsToInvalidate[id] 1719 newInvalidateMetadata := current || invalidateMetadata 1720 1721 // If we've already seen this ID, and the value of invalidate 1722 // metadata has not changed, we can return early. 1723 if seen && current == newInvalidateMetadata { 1724 return 1725 } 1726 idsToInvalidate[id] = newInvalidateMetadata 1727 for _, rid := range s.getImportedByLocked(id) { 1728 addRevDeps(rid, invalidateMetadata) 1729 } 1730 } 1731 for id, invalidateMetadata := range directIDs { 1732 addRevDeps(id, invalidateMetadata) 1733 } 1734 1735 // Copy the package type information. 1736 for k, v := range s.packages { 1737 if _, ok := idsToInvalidate[k.id]; ok { 1738 continue 1739 } 1740 newGen.Inherit(v.handle) 1741 result.packages[k] = v 1742 } 1743 // Copy the package analysis information. 1744 for k, v := range s.actions { 1745 if _, ok := idsToInvalidate[k.pkg.id]; ok { 1746 continue 1747 } 1748 newGen.Inherit(v.handle) 1749 result.actions[k] = v 1750 } 1751 1752 // If the workspace mode has changed, we must delete all metadata, as it 1753 // is unusable and may produce confusing or incorrect diagnostics. 1754 // If a file has been deleted, we must delete metadata all packages 1755 // containing that file. 1756 workspaceModeChanged := s.workspaceMode() != result.workspaceMode() 1757 skipID := map[packageID]bool{} 1758 for _, c := range changes { 1759 if c.exists { 1760 continue 1761 } 1762 // The file has been deleted. 1763 if ids, ok := s.ids[c.fileHandle.URI()]; ok { 1764 for _, id := range ids { 1765 skipID[id] = true 1766 } 1767 } 1768 } 1769 1770 // Collect all of the IDs that are reachable from the workspace packages. 1771 // Any unreachable IDs will have their metadata deleted outright. 1772 reachableID := map[packageID]bool{} 1773 var addForwardDeps func(packageID) 1774 addForwardDeps = func(id packageID) { 1775 if reachableID[id] { 1776 return 1777 } 1778 reachableID[id] = true 1779 m, ok := s.metadata[id] 1780 if !ok { 1781 return 1782 } 1783 for _, depID := range m.deps { 1784 addForwardDeps(depID) 1785 } 1786 } 1787 for id := range s.workspacePackages { 1788 addForwardDeps(id) 1789 } 1790 1791 // Copy the URI to package ID mappings, skipping only those URIs whose 1792 // metadata will be reloaded in future calls to load. 1793 deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged 1794 idsInSnapshot := map[packageID]bool{} // track all known IDs 1795 for uri, ids := range s.ids { 1796 for _, id := range ids { 1797 invalidateMetadata := idsToInvalidate[id] 1798 if skipID[id] || (invalidateMetadata && deleteInvalidMetadata) { 1799 continue 1800 } 1801 // The ID is not reachable from any workspace package, so it should 1802 // be deleted. 1803 if !reachableID[id] { 1804 continue 1805 } 1806 idsInSnapshot[id] = true 1807 result.ids[uri] = append(result.ids[uri], id) 1808 } 1809 } 1810 1811 // Copy the package metadata. We only need to invalidate packages directly 1812 // containing the affected file, and only if it changed in a relevant way. 1813 for k, v := range s.metadata { 1814 if !idsInSnapshot[k] { 1815 // Delete metadata for IDs that are no longer reachable from files 1816 // in the snapshot. 1817 continue 1818 } 1819 invalidateMetadata := idsToInvalidate[k] 1820 // Mark invalidated metadata rather than deleting it outright. 1821 result.metadata[k] = &knownMetadata{ 1822 metadata: v.metadata, 1823 valid: v.valid && !invalidateMetadata, 1824 shouldLoad: v.shouldLoad || invalidateMetadata, 1825 } 1826 } 1827 // Copy the URI to package ID mappings, skipping only those URIs whose 1828 // metadata will be reloaded in future calls to load. 1829 for k, ids := range s.ids { 1830 var newIDs []packageID 1831 for _, id := range ids { 1832 if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok { 1833 continue 1834 } 1835 newIDs = append(newIDs, id) 1836 } 1837 if len(newIDs) != 0 { 1838 result.ids[k] = newIDs 1839 } 1840 } 1841 1842 // Copy the set of initially loaded packages. 1843 for id, pkgPath := range s.workspacePackages { 1844 // Packages with the id "command-line-arguments" are generated by the 1845 // go command when the user is outside of GOPATH and outside of a 1846 // module. Do not cache them as workspace packages for longer than 1847 // necessary. 1848 if source.IsCommandLineArguments(string(id)) { 1849 if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok { 1850 continue 1851 } 1852 } 1853 1854 // If all the files we know about in a package have been deleted, 1855 // the package is gone and we should no longer try to load it. 1856 if m := s.metadata[id]; m != nil { 1857 hasFiles := false 1858 for _, uri := range s.metadata[id].goFiles { 1859 // For internal tests, we need _test files, not just the normal 1860 // ones. External tests only have _test files, but we can check 1861 // them anyway. 1862 if m.forTest != "" && !strings.HasSuffix(string(uri), "_test.go") { 1863 continue 1864 } 1865 if _, ok := result.files[uri]; ok { 1866 hasFiles = true 1867 break 1868 } 1869 } 1870 if !hasFiles { 1871 continue 1872 } 1873 } 1874 1875 // If the package name of a file in the package has changed, it's 1876 // possible that the package ID may no longer exist. Delete it from 1877 // the set of workspace packages, on the assumption that we will add it 1878 // back when the relevant files are reloaded. 1879 if _, ok := changedPkgNames[id]; ok { 1880 continue 1881 } 1882 1883 result.workspacePackages[id] = pkgPath 1884 } 1885 1886 // Inherit all of the go.mod-related handles. 1887 for _, v := range result.modTidyHandles { 1888 newGen.Inherit(v.handle) 1889 } 1890 for _, v := range result.modWhyHandles { 1891 newGen.Inherit(v.handle) 1892 } 1893 for _, v := range result.parseModHandles { 1894 newGen.Inherit(v.handle) 1895 } 1896 // Don't bother copying the importedBy graph, 1897 // as it changes each time we update metadata. 1898 1899 // If the snapshot's workspace mode has changed, the packages loaded using 1900 // the previous mode are no longer relevant, so clear them out. 1901 if workspaceModeChanged { 1902 result.workspacePackages = map[packageID]packagePath{} 1903 } 1904 1905 // The snapshot may need to be reinitialized. 1906 if workspaceReload || vendorChanged { 1907 if workspaceChanged || result.initializedErr != nil { 1908 result.initializeOnce = &sync.Once{} 1909 } 1910 } 1911 return result, workspaceChanged 1912} 1913 1914// guessPackageIDsForURI returns all packages related to uri. If we haven't 1915// seen this URI before, we guess based on files in the same directory. This 1916// is of course incorrect in build systems where packages are not organized by 1917// directory. 1918func guessPackageIDsForURI(uri span.URI, known map[span.URI][]packageID) []packageID { 1919 packages := known[uri] 1920 if len(packages) > 0 { 1921 // We've seen this file before. 1922 return packages 1923 } 1924 // This is a file we don't yet know about. Guess relevant packages by 1925 // considering files in the same directory. 1926 1927 // Cache of FileInfo to avoid unnecessary stats for multiple files in the 1928 // same directory. 1929 stats := make(map[string]struct { 1930 os.FileInfo 1931 error 1932 }) 1933 getInfo := func(dir string) (os.FileInfo, error) { 1934 if res, ok := stats[dir]; ok { 1935 return res.FileInfo, res.error 1936 } 1937 fi, err := os.Stat(dir) 1938 stats[dir] = struct { 1939 os.FileInfo 1940 error 1941 }{fi, err} 1942 return fi, err 1943 } 1944 dir := filepath.Dir(uri.Filename()) 1945 fi, err := getInfo(dir) 1946 if err != nil { 1947 return nil 1948 } 1949 1950 // Aggregate all possibly relevant package IDs. 1951 var found []packageID 1952 for knownURI, ids := range known { 1953 knownDir := filepath.Dir(knownURI.Filename()) 1954 knownFI, err := getInfo(knownDir) 1955 if err != nil { 1956 continue 1957 } 1958 if os.SameFile(fi, knownFI) { 1959 found = append(found, ids...) 1960 } 1961 } 1962 return found 1963} 1964 1965// fileWasSaved reports whether the FileHandle passed in has been saved. It 1966// accomplishes this by checking to see if the original and current FileHandles 1967// are both overlays, and if the current FileHandle is saved while the original 1968// FileHandle was not saved. 1969func fileWasSaved(originalFH, currentFH source.FileHandle) bool { 1970 c, ok := currentFH.(*overlay) 1971 if !ok || c == nil { 1972 return true 1973 } 1974 o, ok := originalFH.(*overlay) 1975 if !ok || o == nil { 1976 return c.saved 1977 } 1978 return !o.saved && c.saved 1979} 1980 1981// shouldInvalidateMetadata reparses a file's package and import declarations to 1982// determine if the file requires a metadata reload. 1983func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged, importDeleted bool) { 1984 if originalFH == nil { 1985 return true, false, false 1986 } 1987 // If the file hasn't changed, there's no need to reload. 1988 if originalFH.FileIdentity() == currentFH.FileIdentity() { 1989 return false, false, false 1990 } 1991 // Get the original and current parsed files in order to check package name 1992 // and imports. Use the new snapshot to parse to avoid modifying the 1993 // current snapshot. 1994 original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseHeader) 1995 current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseHeader) 1996 if originalErr != nil || currentErr != nil { 1997 return (originalErr == nil) != (currentErr == nil), false, (currentErr != nil) // we don't know if an import was deleted 1998 } 1999 // Check if the package's metadata has changed. The cases handled are: 2000 // 1. A package's name has changed 2001 // 2. A file's imports have changed 2002 if original.File.Name.Name != current.File.Name.Name { 2003 invalidate = true 2004 pkgNameChanged = true 2005 } 2006 origImportSet := make(map[string]struct{}) 2007 for _, importSpec := range original.File.Imports { 2008 origImportSet[importSpec.Path.Value] = struct{}{} 2009 } 2010 curImportSet := make(map[string]struct{}) 2011 for _, importSpec := range current.File.Imports { 2012 curImportSet[importSpec.Path.Value] = struct{}{} 2013 } 2014 // If any of the current imports were not in the original imports. 2015 for path := range curImportSet { 2016 if _, ok := origImportSet[path]; ok { 2017 delete(origImportSet, path) 2018 continue 2019 } 2020 // If the import path is obviously not valid, we can skip reloading 2021 // metadata. For now, valid means properly quoted and without a 2022 // terminal slash. 2023 if isBadImportPath(path) { 2024 continue 2025 } 2026 invalidate = true 2027 } 2028 2029 for path := range origImportSet { 2030 if !isBadImportPath(path) { 2031 invalidate = true 2032 importDeleted = true 2033 } 2034 } 2035 2036 if !invalidate { 2037 invalidate = magicCommentsChanged(original.File, current.File) 2038 } 2039 return invalidate, pkgNameChanged, importDeleted 2040} 2041 2042func magicCommentsChanged(original *ast.File, current *ast.File) bool { 2043 oldComments := extractMagicComments(original) 2044 newComments := extractMagicComments(current) 2045 if len(oldComments) != len(newComments) { 2046 return true 2047 } 2048 for i := range oldComments { 2049 if oldComments[i] != newComments[i] { 2050 return true 2051 } 2052 } 2053 return false 2054} 2055 2056func isBadImportPath(path string) bool { 2057 path, err := strconv.Unquote(path) 2058 if err != nil { 2059 return true 2060 } 2061 if path == "" { 2062 return true 2063 } 2064 if path[len(path)-1] == '/' { 2065 return true 2066 } 2067 return false 2068} 2069 2070var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`) 2071 2072// extractMagicComments finds magic comments that affect metadata in f. 2073func extractMagicComments(f *ast.File) []string { 2074 var results []string 2075 for _, cg := range f.Comments { 2076 for _, c := range cg.List { 2077 if buildConstraintOrEmbedRe.MatchString(c.Text) { 2078 results = append(results, c.Text) 2079 } 2080 } 2081 } 2082 return results 2083} 2084 2085func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) { 2086 s.AwaitInitialized(ctx) 2087 2088 s.mu.Lock() 2089 builtin := s.builtin 2090 s.mu.Unlock() 2091 2092 if builtin == "" { 2093 return nil, errors.Errorf("no builtin package for view %s", s.view.name) 2094 } 2095 2096 fh, err := s.GetFile(ctx, builtin) 2097 if err != nil { 2098 return nil, err 2099 } 2100 return s.ParseGo(ctx, fh, source.ParseFull) 2101} 2102 2103func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) bool { 2104 s.mu.Lock() 2105 defer s.mu.Unlock() 2106 // We should always get the builtin URI in a canonical form, so use simple 2107 // string comparison here. span.CompareURI is too expensive. 2108 return uri == s.builtin 2109} 2110 2111func (s *snapshot) setBuiltin(path string) { 2112 s.mu.Lock() 2113 defer s.mu.Unlock() 2114 2115 s.builtin = span.URIFromPath(path) 2116} 2117 2118// BuildGoplsMod generates a go.mod file for all modules in the workspace. It 2119// bypasses any existing gopls.mod. 2120func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) { 2121 allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0) 2122 if err != nil { 2123 return nil, err 2124 } 2125 return buildWorkspaceModFile(ctx, allModules, s) 2126} 2127 2128// TODO(rfindley): move this to workspacemodule.go 2129func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) { 2130 file := &modfile.File{} 2131 file.AddModuleStmt("gopls-workspace") 2132 // Track the highest Go version, to be set on the workspace module. 2133 // Fall back to 1.12 -- old versions insist on having some version. 2134 goVersion := "1.12" 2135 2136 paths := map[string]span.URI{} 2137 excludes := map[string][]string{} 2138 var sortedModURIs []span.URI 2139 for uri := range modFiles { 2140 sortedModURIs = append(sortedModURIs, uri) 2141 } 2142 sort.Slice(sortedModURIs, func(i, j int) bool { 2143 return sortedModURIs[i] < sortedModURIs[j] 2144 }) 2145 for _, modURI := range sortedModURIs { 2146 fh, err := fs.GetFile(ctx, modURI) 2147 if err != nil { 2148 return nil, err 2149 } 2150 content, err := fh.Read() 2151 if err != nil { 2152 return nil, err 2153 } 2154 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2155 if err != nil { 2156 return nil, err 2157 } 2158 if file == nil || parsed.Module == nil { 2159 return nil, fmt.Errorf("no module declaration for %s", modURI) 2160 } 2161 if parsed.Go != nil && semver.Compare(goVersion, parsed.Go.Version) < 0 { 2162 goVersion = parsed.Go.Version 2163 } 2164 path := parsed.Module.Mod.Path 2165 if _, ok := paths[path]; ok { 2166 return nil, fmt.Errorf("found module %q twice in the workspace", path) 2167 } 2168 paths[path] = modURI 2169 // If the module's path includes a major version, we expect it to have 2170 // a matching major version. 2171 _, majorVersion, _ := module.SplitPathVersion(path) 2172 if majorVersion == "" { 2173 majorVersion = "/v0" 2174 } 2175 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions 2176 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false) 2177 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil { 2178 return nil, err 2179 } 2180 for _, exclude := range parsed.Exclude { 2181 excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version) 2182 } 2183 } 2184 if goVersion != "" { 2185 file.AddGoStmt(goVersion) 2186 } 2187 // Go back through all of the modules to handle any of their replace 2188 // statements. 2189 for _, modURI := range sortedModURIs { 2190 fh, err := fs.GetFile(ctx, modURI) 2191 if err != nil { 2192 return nil, err 2193 } 2194 content, err := fh.Read() 2195 if err != nil { 2196 return nil, err 2197 } 2198 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2199 if err != nil { 2200 return nil, err 2201 } 2202 // If any of the workspace modules have replace directives, they need 2203 // to be reflected in the workspace module. 2204 for _, rep := range parsed.Replace { 2205 // Don't replace any modules that are in our workspace--we should 2206 // always use the version in the workspace. 2207 if _, ok := paths[rep.Old.Path]; ok { 2208 continue 2209 } 2210 newPath := rep.New.Path 2211 newVersion := rep.New.Version 2212 // If a replace points to a module in the workspace, make sure we 2213 // direct it to version of the module in the workspace. 2214 if m, ok := paths[rep.New.Path]; ok { 2215 newPath = dirURI(m).Filename() 2216 newVersion = "" 2217 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) { 2218 // Make any relative paths absolute. 2219 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path) 2220 } 2221 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil { 2222 return nil, err 2223 } 2224 } 2225 } 2226 for path, versions := range excludes { 2227 for _, version := range versions { 2228 file.AddExclude(path, version) 2229 } 2230 } 2231 file.SortBlocks() 2232 return file, nil 2233} 2234 2235func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) { 2236 allSums := map[module.Version][]string{} 2237 for modURI := range modFiles { 2238 // TODO(rfindley): factor out this pattern into a uripath package. 2239 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum")) 2240 fh, err := fs.GetFile(ctx, sumURI) 2241 if err != nil { 2242 continue 2243 } 2244 data, err := fh.Read() 2245 if os.IsNotExist(err) { 2246 continue 2247 } 2248 if err != nil { 2249 return nil, errors.Errorf("reading go sum: %w", err) 2250 } 2251 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil { 2252 return nil, err 2253 } 2254 } 2255 // This logic to write go.sum is copied (with minor modifications) from 2256 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2257 var mods []module.Version 2258 for m := range allSums { 2259 mods = append(mods, m) 2260 } 2261 module.Sort(mods) 2262 2263 var buf bytes.Buffer 2264 for _, m := range mods { 2265 list := allSums[m] 2266 sort.Strings(list) 2267 // Note (rfindley): here we add all sum lines without verification, because 2268 // the assumption is that if they come from a go.sum file, they are 2269 // trusted. 2270 for _, h := range list { 2271 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) 2272 } 2273 } 2274 return buf.Bytes(), nil 2275} 2276 2277// readGoSum is copied (with minor modifications) from 2278// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2279func readGoSum(dst map[module.Version][]string, file string, data []byte) error { 2280 lineno := 0 2281 for len(data) > 0 { 2282 var line []byte 2283 lineno++ 2284 i := bytes.IndexByte(data, '\n') 2285 if i < 0 { 2286 line, data = data, nil 2287 } else { 2288 line, data = data[:i], data[i+1:] 2289 } 2290 f := strings.Fields(string(line)) 2291 if len(f) == 0 { 2292 // blank line; skip it 2293 continue 2294 } 2295 if len(f) != 3 { 2296 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) 2297 } 2298 mod := module.Version{Path: f[0], Version: f[1]} 2299 dst[mod] = append(dst[mod], f[2]) 2300 } 2301 return nil 2302} 2303