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.
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"
41 type snapshot struct {
42 memoize.Arg // allow as a memoize.Function arg
48 backgroundCtx context.Context
50 // the cache generation that contains the data for this snapshot.
51 generation *memoize.Generation
53 // builtin pins the AST and package for builtin.go in memory.
54 builtin *builtinPackageHandle
56 // The snapshot's initialization state is controlled by the fields below.
58 // initializeOnce guards snapshot initialization. Each snapshot is
59 // initialized at most once: reinitialization is triggered on later snapshots
60 // by invalidating this field.
61 initializeOnce *sync.Once
62 // initializedErr holds the last error resulting from initialization. If
63 // initialization fails, we only retry when the the workspace modules change,
64 // to avoid too many go/packages calls.
65 initializedErr *source.CriticalError
67 // mu guards all of the maps in the snapshot.
70 // ids maps file URIs to package IDs.
71 // It may be invalidated on calls to go/packages.
72 ids map[span.URI][]packageID
74 // metadata maps file IDs to their associated metadata.
75 // It may invalidated on calls to go/packages.
76 metadata map[packageID]*metadata
78 // importedBy maps package IDs to the list of packages that import them.
79 importedBy map[packageID][]packageID
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
85 // goFiles maps a parseKey to its parseGoHandle.
86 goFiles map[parseKey]*parseGoHandle
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
92 // actions maps an actionkey to its actionHandle.
93 actions map[actionKey]*actionHandle
95 // workspacePackages contains the workspace's packages, which are loaded
96 // when the view is created.
97 workspacePackages map[packageID]packagePath
99 // unloadableFiles keeps track of files that we've failed to load.
100 unloadableFiles map[span.URI]struct{}
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
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
113 workspaceDirHandle *memoize.Handle
116 type packageKey struct {
117 mode source.ParseMode
121 type actionKey struct {
123 analyzer *analysis.Analyzer
126 func (s *snapshot) ID() uint64 {
130 func (s *snapshot) View() source.View {
134 func (s *snapshot) BackgroundContext() context.Context {
135 return s.backgroundCtx
138 func (s *snapshot) FileSet() *token.FileSet {
139 return s.view.session.cache.fset
142 func (s *snapshot) ModFiles() []span.URI {
144 for modURI := range s.workspace.getActiveModFiles() {
145 uris = append(uris, modURI)
150 func (s *snapshot) ValidBuildConfiguration() bool {
151 return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles())
154 // workspaceMode describes the way in which the snapshot's workspace should
156 func (s *snapshot) workspaceMode() workspaceMode {
157 var mode workspaceMode
159 // If the view has an invalid configuration, don't build the workspace
161 validBuildConfiguration := s.ValidBuildConfiguration()
162 if !validBuildConfiguration {
165 // If the view is not in a module and contains no modules, but still has a
166 // valid workspace configuration, do not create the workspace module.
167 // It could be using GOPATH or a different build system entirely.
168 if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration {
172 options := s.view.Options()
173 // The -modfile flag is available for Go versions >= 1.14.
174 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 {
177 // If the user is intentionally limiting their workspace scope, don't
178 // enable multi-module workspace mode.
179 // TODO(rstambler): This should only change the calculation of the root,
181 if !options.ExpandWorkspaceToModule {
184 // The workspace module has been disabled by the user.
185 if !options.ExperimentalWorkspaceModule {
188 mode |= usesWorkspaceModule
192 // config returns the configuration used for the snapshot's interaction with
193 // the go/packages API. It uses the given working directory.
195 // TODO(rstambler): go/packages requires that we do not provide overlays for
196 // multiple modules in on config, so buildOverlay needs to filter overlays by
198 func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
199 s.view.optionsMu.Lock()
200 verboseOutput := s.view.options.VerboseOutput
201 s.view.optionsMu.Unlock()
203 // Forcibly disable GOPACKAGESDRIVER. It's incompatible with the
204 // packagesinternal APIs we use, and we really only support the go command
206 env := append(append([]string{}, inv.Env...), "GOPACKAGESDRIVER=off")
207 cfg := &packages.Config{
211 BuildFlags: inv.BuildFlags,
212 Mode: packages.NeedName |
214 packages.NeedCompiledGoFiles |
215 packages.NeedImports |
217 packages.NeedTypesSizes |
219 Fset: s.view.session.cache.fset,
220 Overlay: s.buildOverlay(),
221 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
222 panic("go/packages must not be used to parse files")
224 Logf: func(format string, args ...interface{}) {
226 event.Log(ctx, fmt.Sprintf(format, args...))
231 packagesinternal.SetModFile(cfg, inv.ModFile)
232 packagesinternal.SetModFlag(cfg, inv.ModFlag)
233 // We want to type check cgo code if go/types supports it.
234 if typesinternal.SetUsesCgo(&types.Config{}) {
235 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
237 packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner)
241 func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
242 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
248 return s.view.session.gocmdRunner.Run(ctx, *inv)
251 func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
252 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
257 return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
260 func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
261 var flags source.InvocationFlags
262 if s.workspaceMode()&tempModfile != 0 {
263 flags = source.WriteTemporaryModFile
265 flags = source.Normal
268 flags |= source.AllowNetwork
270 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
272 return false, nil, nil, err
275 invoke := func(args ...string) (*bytes.Buffer, error) {
278 return s.view.session.gocmdRunner.Run(ctx, *inv)
280 if err := run(invoke); err != nil {
281 return false, nil, nil, err
283 if flags.Mode() != source.WriteTemporaryModFile {
284 return false, nil, nil, nil
286 var modBytes, sumBytes []byte
287 modBytes, err = ioutil.ReadFile(tmpURI.Filename())
288 if err != nil && !os.IsNotExist(err) {
289 return false, nil, nil, err
291 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
292 if err != nil && !os.IsNotExist(err) {
293 return false, nil, nil, err
295 return true, modBytes, sumBytes, nil
298 func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
299 s.view.optionsMu.Lock()
300 allowModfileModificationOption := s.view.options.AllowModfileModifications
301 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
302 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.effectiveGo111Module)
303 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
304 s.view.optionsMu.Unlock()
305 cleanup = func() {} // fallback
307 // All logic below is for module mode.
308 if s.workspaceMode()&moduleMode == 0 {
309 return "", inv, cleanup, nil
312 mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
313 if !allowNetwork && !allowNetworkOption {
314 inv.Env = append(inv.Env, "GOPROXY=off")
318 // Select the module context to use.
319 // If we're type checking, we need to use the workspace context, meaning
320 // the main (workspace) module. Otherwise, we should use the module for
321 // the passed-in working dir.
322 if mode == source.LoadWorkspace {
323 if s.workspaceMode()&usesWorkspaceModule == 0 {
324 for m := range s.workspace.getActiveModFiles() { // range to access the only element
330 tmpDir, err = s.getWorkspaceDir(ctx)
332 return "", nil, cleanup, err
334 inv.WorkingDir = tmpDir.Filename()
335 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
338 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
341 var modContent []byte
343 modFH, err := s.GetFile(ctx, modURI)
345 return "", nil, cleanup, err
347 modContent, err = modFH.Read()
349 return "", nil, cleanup, err
353 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
355 return "", nil, cleanup, err
359 if s.view.goversion >= 16 {
360 mutableModFlag = "mod"
364 case source.LoadWorkspace, source.Normal:
366 inv.ModFlag = "vendor"
367 } else if !allowModfileModificationOption {
368 inv.ModFlag = "readonly"
370 inv.ModFlag = mutableModFlag
372 case source.UpdateUserModFile, source.WriteTemporaryModFile:
373 inv.ModFlag = mutableModFlag
376 wantTempMod := mode != source.UpdateUserModFile
377 needTempMod := mode == source.WriteTemporaryModFile
378 tempMod := wantTempMod && s.workspaceMode()&tempModfile != 0
379 if needTempMod && !tempMod {
380 return "", nil, cleanup, source.ErrTmpModfileUnsupported
385 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
387 modFH, err := s.GetFile(ctx, modURI)
389 return "", nil, cleanup, err
391 // Use the go.sum if it happens to be available.
392 gosum := s.goSum(ctx, modURI)
393 tmpURI, cleanup, err = tempModFile(modFH, gosum)
395 return "", nil, cleanup, err
397 inv.ModFile = tmpURI.Filename()
400 return tmpURI, inv, cleanup, nil
403 func (s *snapshot) buildOverlay() map[string][]byte {
407 overlays := make(map[string][]byte)
408 for uri, fh := range s.files {
409 overlay, ok := fh.(*overlay)
416 // TODO(rstambler): Make sure not to send overlays outside of the current view.
417 overlays[uri.Filename()] = overlay.text
422 func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string {
424 for uri, fh := range files {
425 if overlay, ok := fh.(*overlay); ok && !overlay.saved {
426 unsaved = append(unsaved, uri.Filename())
429 sort.Strings(unsaved)
430 return hashContents([]byte(strings.Join(unsaved, "")))
433 func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]source.Package, error) {
434 ctx = event.Label(ctx, tag.URI.Of(uri))
436 phs, err := s.packageHandlesForFile(ctx, uri, mode)
440 var pkgs []source.Package
441 for _, ph := range phs {
442 pkg, err := ph.check(ctx, s)
446 pkgs = append(pkgs, pkg)
451 func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
452 ctx = event.Label(ctx, tag.URI.Of(uri))
454 phs, err := s.packageHandlesForFile(ctx, uri, mode)
460 return nil, errors.Errorf("no packages")
464 for _, handle := range phs[1:] {
466 case source.WidestPackage:
467 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) {
470 case source.NarrowestPackage:
471 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) {
477 return nil, errors.Errorf("no packages in input")
480 return ph.check(ctx, s)
483 func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]*packageHandle, error) {
484 // Check if we should reload metadata for the file. We don't invalidate IDs
485 // (though we should), so the IDs will be a better source of truth than the
486 // metadata. If there are no IDs for the file, then we should also reload.
487 fh, err := s.GetFile(ctx, uri)
491 if fh.Kind() != source.Go {
492 return nil, fmt.Errorf("no packages for non-Go file %s", uri)
494 ids := s.getIDsForURI(uri)
495 reload := len(ids) == 0
496 for _, id := range ids {
497 // Reload package metadata if any of the metadata has missing
498 // dependencies, in case something has changed since the last time we
500 if m := s.getMetadata(id); m == nil {
504 // TODO(golang/go#36918): Previously, we would reload any package with
505 // missing dependencies. This is expensive and results in too many
506 // calls to packages.Load. Determine what we should do instead.
509 if err := s.load(ctx, false, fileURI(uri)); err != nil {
513 // Get the list of IDs from the snapshot again, in case it has changed.
514 var phs []*packageHandle
515 for _, id := range s.getIDsForURI(uri) {
516 var parseModes []source.ParseMode
518 case source.TypecheckAll:
519 if s.workspaceParseMode(id) == source.ParseFull {
520 parseModes = []source.ParseMode{source.ParseFull}
522 parseModes = []source.ParseMode{source.ParseExported, source.ParseFull}
524 case source.TypecheckFull:
525 parseModes = []source.ParseMode{source.ParseFull}
526 case source.TypecheckWorkspace:
527 parseModes = []source.ParseMode{s.workspaceParseMode(id)}
530 for _, parseMode := range parseModes {
531 ph, err := s.buildPackageHandle(ctx, id, parseMode)
535 phs = append(phs, ph)
542 func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) {
543 if err := s.awaitLoaded(ctx); err != nil {
546 ids := make(map[packageID]struct{})
547 s.transitiveReverseDependencies(packageID(id), ids)
549 // Make sure to delete the original package ID from the map.
550 delete(ids, packageID(id))
552 var pkgs []source.Package
553 for id := range ids {
554 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
558 pkgs = append(pkgs, pkg)
563 func (s *snapshot) checkedPackage(ctx context.Context, id packageID, mode source.ParseMode) (*pkg, error) {
564 ph, err := s.buildPackageHandle(ctx, id, mode)
568 return ph.check(ctx, s)
571 // transitiveReverseDependencies populates the uris map with file URIs
572 // belonging to the provided package and its transitive reverse dependencies.
573 func (s *snapshot) transitiveReverseDependencies(id packageID, ids map[packageID]struct{}) {
574 if _, ok := ids[id]; ok {
577 if s.getMetadata(id) == nil {
581 importedBy := s.getImportedBy(id)
582 for _, parentID := range importedBy {
583 s.transitiveReverseDependencies(parentID, ids)
587 func (s *snapshot) getGoFile(key parseKey) *parseGoHandle {
590 return s.goFiles[key]
593 func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle {
596 if existing, ok := s.goFiles[key]; ok {
603 func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle {
606 return s.parseModHandles[uri]
609 func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle {
612 return s.modWhyHandles[uri]
615 func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle {
618 return s.modTidyHandles[uri]
621 func (s *snapshot) getImportedBy(id packageID) []packageID {
624 return s.getImportedByLocked(id)
627 func (s *snapshot) getImportedByLocked(id packageID) []packageID {
628 // If we haven't rebuilt the import graph since creating the snapshot.
629 if len(s.importedBy) == 0 {
630 s.rebuildImportGraph()
632 return s.importedBy[id]
635 func (s *snapshot) clearAndRebuildImportGraph() {
639 // Completely invalidate the original map.
640 s.importedBy = make(map[packageID][]packageID)
641 s.rebuildImportGraph()
644 func (s *snapshot) rebuildImportGraph() {
645 for id, m := range s.metadata {
646 for _, importID := range m.deps {
647 s.importedBy[importID] = append(s.importedBy[importID], id)
652 func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle {
656 // If the package handle has already been cached,
657 // return the cached handle instead of overriding it.
658 if ph, ok := s.packages[ph.packageKey()]; ok {
661 s.packages[ph.packageKey()] = ph
665 func (s *snapshot) workspacePackageIDs() (ids []packageID) {
669 for id := range s.workspacePackages {
670 ids = append(ids, id)
675 func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
676 // Work-around microsoft/vscode#100870 by making sure that we are,
677 // at least, watching the user's entire workspace. This will still be
678 // applied to every folder in the workspace.
679 patterns := map[string]struct{}{
680 "**/*.{go,mod,sum}": {},
682 dirs := s.workspace.dirs(ctx, s)
683 for _, dir := range dirs {
684 dirName := dir.Filename()
686 // If the directory is within the view's folder, we're already watching
687 // it with the pattern above.
688 if source.InDir(s.view.folder.Filename(), dirName) {
691 // TODO(rstambler): If microsoft/vscode#3025 is resolved before
692 // microsoft/vscode#101042, we will need a work-around for Windows
693 // drive letter casing.
694 patterns[fmt.Sprintf("%s/**/*.{go,mod,sum}", dirName)] = struct{}{}
697 // Some clients do not send notifications for changes to directories that
698 // contain Go code (golang/go#42348). To handle this, explicitly watch all
699 // of the directories in the workspace. We find them by adding the
700 // directories of every file in the snapshot's workspace directories.
701 var dirNames []string
702 for uri := range s.allKnownSubdirs(ctx) {
703 dirNames = append(dirNames, uri.Filename())
705 sort.Strings(dirNames)
706 if len(dirNames) > 0 {
707 patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{}
712 // allKnownSubdirs returns all of the subdirectories within the snapshot's
713 // workspace directories. None of the workspace directories are included.
714 func (s *snapshot) allKnownSubdirs(ctx context.Context) map[span.URI]struct{} {
715 dirs := s.workspace.dirs(ctx, s)
719 seen := make(map[span.URI]struct{})
720 for uri := range s.files {
721 dir := filepath.Dir(uri.Filename())
723 for _, wsDir := range dirs {
724 if source.InDir(wsDir.Filename(), dir) {
729 // Don't watch any directory outside of the workspace directories.
734 if dir == "" || dir == matched.Filename() {
737 uri := span.URIFromPath(dir)
738 if _, ok := seen[uri]; ok {
741 seen[uri] = struct{}{}
742 dir = filepath.Dir(dir)
748 // knownFilesInDir returns the files known to the given snapshot that are in
749 // the given directory. It does not respect symlinks.
750 func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
755 for uri := range s.files {
756 if source.InDir(dir.Filename(), uri.Filename()) {
757 files = append(files, uri)
763 func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.Package, error) {
764 if err := s.awaitLoaded(ctx); err != nil {
767 var pkgs []source.Package
768 for _, pkgID := range s.workspacePackageIDs() {
769 pkg, err := s.checkedPackage(ctx, pkgID, s.workspaceParseMode(pkgID))
773 pkgs = append(pkgs, pkg)
778 func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
779 if err := s.awaitLoaded(ctx); err != nil {
783 // The WorkspaceSymbols implementation relies on this function returning
784 // workspace packages first.
785 ids := s.workspacePackageIDs()
787 for id := range s.metadata {
788 if _, ok := s.workspacePackages[id]; ok {
791 ids = append(ids, id)
795 var pkgs []source.Package
796 for _, id := range ids {
797 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
801 pkgs = append(pkgs, pkg)
806 func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
807 // Don't reload workspace package metadata.
808 // This function is meant to only return currently cached information.
809 s.AwaitInitialized(ctx)
814 results := map[string]source.Package{}
815 for _, ph := range s.packages {
816 cachedPkg, err := ph.cached(s.generation)
820 for importPath, newPkg := range cachedPkg.imports {
821 if oldPkg, ok := results[string(importPath)]; ok {
822 // Using the same trick as NarrowestPackage, prefer non-variants.
823 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
824 results[string(importPath)] = newPkg
827 results[string(importPath)] = newPkg
834 func (s *snapshot) GoModForFile(uri span.URI) span.URI {
835 return moduleForURI(s.workspace.activeModFiles, uri)
838 func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
840 for modURI := range modFiles {
841 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
844 if len(modURI) > len(match) {
851 func (s *snapshot) getPackage(id packageID, mode source.ParseMode) *packageHandle {
859 return s.packages[key]
862 func (s *snapshot) getActionHandle(id packageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle {
873 return s.actions[key]
876 func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle {
881 analyzer: ah.analyzer,
887 if ah, ok := s.actions[key]; ok {
894 func (s *snapshot) getIDsForURI(uri span.URI) []packageID {
901 func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) {
902 // TODO(matloob): uri can be a file or directory. Should we update the mappings
903 // to map directories to their contained packages?
905 for _, id := range s.ids[uri] {
906 if m, ok := s.metadata[id]; ok {
907 metadata = append(metadata, m)
913 func (s *snapshot) getMetadata(id packageID) *metadata {
917 return s.metadata[id]
920 func (s *snapshot) addID(uri span.URI, id packageID) {
924 for i, existingID := range s.ids[uri] {
925 // TODO: We should make sure not to set duplicate IDs,
926 // and instead panic here. This can be done by making sure not to
927 // reset metadata information for packages we've already seen.
928 if existingID == id {
931 // If we are setting a real ID, when the package had only previously
932 // had a command-line-arguments ID, we should just replace it.
933 if isCommandLineArguments(string(existingID)) {
935 // Delete command-line-arguments if it was a workspace package.
936 delete(s.workspacePackages, existingID)
940 s.ids[uri] = append(s.ids[uri], id)
943 // isCommandLineArguments reports whether a given value denotes
944 // "command-line-arguments" package, which is a package with an unknown ID
945 // created by the go command. It can have a test variant, which is why callers
946 // should not check that a value equals "command-line-arguments" directly.
947 func isCommandLineArguments(s string) bool {
948 return strings.Contains(s, "command-line-arguments")
951 func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
955 scope, ok := s.workspacePackages[id]
959 func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
960 f := s.view.getFile(uri)
965 return s.files[f.URI()]
968 // GetVersionedFile returns a File for the given URI. If the file is unknown it
969 // is added to the managed set.
971 // GetVersionedFile succeeds even if the file does not exist. A non-nil error return
972 // indicates some type of internal error, for example if ctx is cancelled.
973 func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
974 f := s.view.getFile(uri)
978 return s.getFileLocked(ctx, f)
981 // GetFile implements the fileSource interface by wrapping GetVersionedFile.
982 func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
983 return s.GetVersionedFile(ctx, uri)
986 func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
987 if fh, ok := s.files[f.URI()]; ok {
991 fh, err := s.view.session.cache.getFile(ctx, f.URI())
995 closed := &closedFile{fh}
996 s.files[f.URI()] = closed
1000 func (s *snapshot) IsOpen(uri span.URI) bool {
1003 return s.isOpenLocked(uri)
1007 func (s *snapshot) openFiles() []source.VersionedFileHandle {
1011 var open []source.VersionedFileHandle
1012 for _, fh := range s.files {
1013 if s.isOpenLocked(fh.URI()) {
1014 open = append(open, fh)
1020 func (s *snapshot) isOpenLocked(uri span.URI) bool {
1021 _, open := s.files[uri].(*overlay)
1025 func (s *snapshot) awaitLoaded(ctx context.Context) error {
1026 loadErr := s.awaitLoadedAllErrors(ctx)
1028 // If we still have absolutely no metadata, check if the view failed to
1029 // initialize and return any errors.
1030 // TODO(rstambler): Should we clear the error after we return it?
1033 if len(s.metadata) == 0 && loadErr != nil {
1034 return loadErr.MainError
1039 func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
1040 loadErr := s.awaitLoadedAllErrors(ctx)
1041 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
1045 // Even if packages didn't fail to load, we still may want to show
1046 // additional warnings.
1048 wsPkgs, _ := s.WorkspacePackages(ctx)
1049 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" {
1050 return &source.CriticalError{
1051 MainError: errors.New(msg),
1054 // Even if workspace packages were returned, there still may be an error
1055 // with the user's workspace layout. Workspace packages that only have the
1056 // ID "command-line-arguments" are usually a symptom of a bad workspace
1058 if containsCommandLineArguments(wsPkgs) {
1059 return s.workspaceLayoutError(ctx)
1064 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
1065 return s.workspaceLayoutError(ctx)
1070 const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
1071 If you are using modules, please open your editor to a directory in your module.
1072 If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
1074 func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
1075 if snapshot.ValidBuildConfiguration() {
1078 for _, pkg := range pkgs {
1079 if len(pkg.MissingDependencies()) > 0 {
1080 return adHocPackagesWarning
1086 func containsCommandLineArguments(pkgs []source.Package) bool {
1087 for _, pkg := range pkgs {
1088 if strings.Contains(pkg.ID(), "command-line-arguments") {
1095 func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
1096 // Do not return results until the snapshot's view has been initialized.
1097 s.AwaitInitialized(ctx)
1099 if ctx.Err() != nil {
1100 return &source.CriticalError{MainError: ctx.Err()}
1103 if err := s.reloadWorkspace(ctx); err != nil {
1104 diags, _ := s.extractGoCommandErrors(ctx, err.Error())
1105 return &source.CriticalError{
1110 if err := s.reloadOrphanedFiles(ctx); err != nil {
1111 diags, _ := s.extractGoCommandErrors(ctx, err.Error())
1112 return &source.CriticalError{
1117 // TODO(rstambler): Should we be more careful about returning the
1118 // initialization error? Is it possible for the initialization error to be
1119 // corrected without a successful reinitialization?
1120 return s.initializedErr
1123 func (s *snapshot) AwaitInitialized(ctx context.Context) {
1127 case <-s.view.initialWorkspaceLoad:
1129 // We typically prefer to run something as intensive as the IWL without
1130 // blocking. I'm not sure if there is a way to do that here.
1131 s.initialize(ctx, false)
1134 // reloadWorkspace reloads the metadata for all invalidated workspace packages.
1135 func (s *snapshot) reloadWorkspace(ctx context.Context) error {
1136 // See which of the workspace packages are missing metadata.
1138 missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0
1139 pkgPathSet := map[packagePath]struct{}{}
1140 for id, pkgPath := range s.workspacePackages {
1141 if s.metadata[id] != nil {
1144 missingMetadata = true
1146 // Don't try to reload "command-line-arguments" directly.
1147 if isCommandLineArguments(string(pkgPath)) {
1150 pkgPathSet[pkgPath] = struct{}{}
1154 // If the view's build configuration is invalid, we cannot reload by
1155 // package path. Just reload the directory instead.
1156 if missingMetadata && !s.ValidBuildConfiguration() {
1157 return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW"))
1160 if len(pkgPathSet) == 0 {
1164 var pkgPaths []interface{}
1165 for pkgPath := range pkgPathSet {
1166 pkgPaths = append(pkgPaths, pkgPath)
1168 return s.load(ctx, false, pkgPaths...)
1171 func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
1172 // When we load ./... or a package path directly, we may not get packages
1173 // that exist only in overlays. As a workaround, we search all of the files
1174 // available in the snapshot and reload their metadata individually using a
1175 // file= query if the metadata is unavailable.
1176 scopes := s.orphanedFileScopes()
1177 if len(scopes) == 0 {
1181 err := s.load(ctx, false, scopes...)
1183 // If we failed to load some files, i.e. they have no metadata,
1184 // mark the failures so we don't bother retrying until the file's
1187 // TODO(rstambler): This may be an overestimate if the load stopped
1188 // early for an unrelated errors. Add a fallback?
1190 // Check for context cancellation so that we don't incorrectly mark files
1191 // as unloadable, but don't return before setting all workspace packages.
1192 if ctx.Err() == nil && err != nil {
1193 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
1195 for _, scope := range scopes {
1196 uri := span.URI(scope.(fileURI))
1197 if s.getMetadataForURILocked(uri) == nil {
1198 s.unloadableFiles[uri] = struct{}{}
1206 func (s *snapshot) orphanedFileScopes() []interface{} {
1210 scopeSet := make(map[span.URI]struct{})
1211 for uri, fh := range s.files {
1212 // Don't try to reload metadata for go.mod files.
1213 if fh.Kind() != source.Go {
1216 // If the URI doesn't belong to this view, then it's not in a workspace
1217 // package and should not be reloaded directly.
1218 if !contains(s.view.session.viewsOf(uri), s.view) {
1221 // If the file is not open and is in a vendor directory, don't treat it
1222 // like a workspace package.
1223 if _, ok := fh.(*overlay); !ok && inVendor(uri) {
1226 // Don't reload metadata for files we've already deemed unloadable.
1227 if _, ok := s.unloadableFiles[uri]; ok {
1230 if s.getMetadataForURILocked(uri) == nil {
1231 scopeSet[uri] = struct{}{}
1234 var scopes []interface{}
1235 for uri := range scopeSet {
1236 scopes = append(scopes, fileURI(uri))
1241 func contains(views []*View, view *View) bool {
1242 for _, v := range views {
1250 func inVendor(uri span.URI) bool {
1251 toSlash := filepath.ToSlash(uri.Filename())
1252 if !strings.Contains(toSlash, "/vendor/") {
1255 // Only packages in _subdirectories_ of /vendor/ are considered vendored
1256 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
1257 split := strings.Split(toSlash, "/vendor/")
1261 return strings.Contains(split[1], "/")
1264 func generationName(v *View, snapshotID uint64) string {
1265 return fmt.Sprintf("v%v/%v", v.id, snapshotID)
1268 // checkSnapshotLocked verifies that some invariants are preserved on the
1270 func checkSnapshotLocked(ctx context.Context, s *snapshot) {
1271 // Check that every go file for a workspace package is identified as
1272 // belonging to that workspace package.
1273 for wsID := range s.workspacePackages {
1274 if m, ok := s.metadata[wsID]; ok {
1275 for _, uri := range m.goFiles {
1277 for _, id := range s.ids[uri] {
1284 log.Error.Logf(ctx, "workspace package %v not associated with %v", wsID, uri)
1291 func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, bool) {
1292 var vendorChanged bool
1293 newWorkspace, workspaceChanged, workspaceReload := s.workspace.invalidate(ctx, changes)
1298 checkSnapshotLocked(ctx, s)
1300 newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
1301 bgCtx, cancel := context.WithCancel(bgCtx)
1302 result := &snapshot{
1306 backgroundCtx: bgCtx,
1309 initializeOnce: s.initializeOnce,
1310 initializedErr: s.initializedErr,
1311 ids: make(map[span.URI][]packageID),
1312 importedBy: make(map[packageID][]packageID),
1313 metadata: make(map[packageID]*metadata),
1314 packages: make(map[packageKey]*packageHandle),
1315 actions: make(map[actionKey]*actionHandle),
1316 files: make(map[span.URI]source.VersionedFileHandle),
1317 goFiles: make(map[parseKey]*parseGoHandle),
1318 workspacePackages: make(map[packageID]packagePath),
1319 unloadableFiles: make(map[span.URI]struct{}),
1320 parseModHandles: make(map[span.URI]*parseModHandle),
1321 modTidyHandles: make(map[span.URI]*modTidyHandle),
1322 modWhyHandles: make(map[span.URI]*modWhyHandle),
1323 workspace: newWorkspace,
1326 if !workspaceChanged && s.workspaceDirHandle != nil {
1327 result.workspaceDirHandle = s.workspaceDirHandle
1328 newGen.Inherit(s.workspaceDirHandle)
1331 if s.builtin != nil {
1332 newGen.Inherit(s.builtin.handle)
1335 // Copy all of the FileHandles.
1336 for k, v := range s.files {
1340 // Copy the set of unloadable files.
1341 for k, v := range s.unloadableFiles {
1342 result.unloadableFiles[k] = v
1344 // Copy all of the modHandles.
1345 for k, v := range s.parseModHandles {
1346 result.parseModHandles[k] = v
1349 for k, v := range s.goFiles {
1350 if _, ok := changes[k.file.URI]; ok {
1353 newGen.Inherit(v.handle)
1354 newGen.Inherit(v.astCacheHandle)
1355 result.goFiles[k] = v
1358 // Copy all of the go.mod-related handles. They may be invalidated later,
1359 // so we inherit them at the end of the function.
1360 for k, v := range s.modTidyHandles {
1361 if _, ok := changes[k]; ok {
1364 result.modTidyHandles[k] = v
1366 for k, v := range s.modWhyHandles {
1367 if _, ok := changes[k]; ok {
1370 result.modWhyHandles[k] = v
1373 // directIDs keeps track of package IDs that have directly changed.
1374 // It maps id->invalidateMetadata.
1375 directIDs := map[packageID]bool{}
1376 // Invalidate all package metadata if the workspace module has changed.
1377 if workspaceReload {
1378 for k := range s.metadata {
1383 changedPkgNames := map[packageID]struct{}{}
1384 for uri, change := range changes {
1385 // Maybe reinitialize the view if we see a change in the vendor
1388 vendorChanged = true
1391 // The original FileHandle for this URI is cached on the snapshot.
1392 originalFH := s.files[uri]
1394 // Check if the file's package name or imports have changed,
1395 // and if so, invalidate this file's packages' metadata.
1396 shouldInvalidateMetadata, pkgNameChanged := s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle)
1397 invalidateMetadata := forceReloadMetadata || workspaceReload || shouldInvalidateMetadata
1399 // Mark all of the package IDs containing the given file.
1400 // TODO: if the file has moved into a new package, we should invalidate that too.
1401 filePackageIDs := guessPackageIDsForURI(uri, s.ids)
1403 for _, id := range filePackageIDs {
1404 changedPkgNames[id] = struct{}{}
1407 for _, id := range filePackageIDs {
1408 directIDs[id] = directIDs[id] || invalidateMetadata
1411 // Invalidate the previous modTidyHandle if any of the files have been
1412 // saved or if any of the metadata has been invalidated.
1413 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
1414 // TODO(rstambler): Only delete mod handles for which the
1415 // withoutURI is relevant.
1416 for k := range s.modTidyHandles {
1417 delete(result.modTidyHandles, k)
1419 for k := range s.modWhyHandles {
1420 delete(result.modWhyHandles, k)
1424 delete(result.parseModHandles, uri)
1426 // Handle the invalidated file; it may have new contents or not exist.
1428 delete(result.files, uri)
1430 result.files[uri] = change.fileHandle
1433 // Make sure to remove the changed file from the unloadable set.
1434 delete(result.unloadableFiles, uri)
1437 // Invalidate reverse dependencies too.
1438 // TODO(heschi): figure out the locking model and use transitiveReverseDeps?
1439 // transitiveIDs keeps track of transitive reverse dependencies.
1440 // If an ID is present in the map, invalidate its types.
1441 // If an ID's value is true, invalidate its metadata too.
1442 transitiveIDs := make(map[packageID]bool)
1443 var addRevDeps func(packageID, bool)
1444 addRevDeps = func(id packageID, invalidateMetadata bool) {
1445 current, seen := transitiveIDs[id]
1446 newInvalidateMetadata := current || invalidateMetadata
1448 // If we've already seen this ID, and the value of invalidate
1449 // metadata has not changed, we can return early.
1450 if seen && current == newInvalidateMetadata {
1453 transitiveIDs[id] = newInvalidateMetadata
1454 for _, rid := range s.getImportedByLocked(id) {
1455 addRevDeps(rid, invalidateMetadata)
1458 for id, invalidateMetadata := range directIDs {
1459 addRevDeps(id, invalidateMetadata)
1462 // Copy the package type information.
1463 for k, v := range s.packages {
1464 if _, ok := transitiveIDs[k.id]; ok {
1467 newGen.Inherit(v.handle)
1468 result.packages[k] = v
1470 // Copy the package analysis information.
1471 for k, v := range s.actions {
1472 if _, ok := transitiveIDs[k.pkg.id]; ok {
1475 newGen.Inherit(v.handle)
1476 result.actions[k] = v
1478 // Copy the package metadata. We only need to invalidate packages directly
1479 // containing the affected file, and only if it changed in a relevant way.
1480 for k, v := range s.metadata {
1481 if invalidateMetadata, ok := transitiveIDs[k]; invalidateMetadata && ok {
1484 result.metadata[k] = v
1486 // Copy the URI to package ID mappings, skipping only those URIs whose
1487 // metadata will be reloaded in future calls to load.
1489 for k, ids := range s.ids {
1490 for _, id := range ids {
1491 if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
1497 // Copy the set of initially loaded packages.
1498 for id, pkgPath := range s.workspacePackages {
1499 // Packages with the id "command-line-arguments" are generated by the
1500 // go command when the user is outside of GOPATH and outside of a
1501 // module. Do not cache them as workspace packages for longer than
1503 if isCommandLineArguments(string(id)) {
1504 if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
1509 // If all the files we know about in a package have been deleted,
1510 // the package is gone and we should no longer try to load it.
1511 if m := s.metadata[id]; m != nil {
1513 for _, uri := range s.metadata[id].goFiles {
1514 // For internal tests, we need _test files, not just the normal
1515 // ones. External tests only have _test files, but we can check
1517 if m.forTest != "" && !strings.HasSuffix(uri.Filename(), "_test.go") {
1520 if _, ok := result.files[uri]; ok {
1530 // If the package name of a file in the package has changed, it's
1531 // possible that the package ID may no longer exist. Delete it from
1532 // the set of workspace packages, on the assumption that we will add it
1533 // back when the relevant files are reloaded.
1534 if _, ok := changedPkgNames[id]; ok {
1538 result.workspacePackages[id] = pkgPath
1541 // Inherit all of the go.mod-related handles.
1542 for _, v := range result.modTidyHandles {
1543 newGen.Inherit(v.handle)
1545 for _, v := range result.modWhyHandles {
1546 newGen.Inherit(v.handle)
1548 for _, v := range result.parseModHandles {
1549 newGen.Inherit(v.handle)
1551 // Don't bother copying the importedBy graph,
1552 // as it changes each time we update metadata.
1554 // If the snapshot's workspace mode has changed, the packages loaded using
1555 // the previous mode are no longer relevant, so clear them out.
1556 if s.workspaceMode() != result.workspaceMode() {
1557 result.workspacePackages = map[packageID]packagePath{}
1560 // The snapshot may need to be reinitialized.
1561 if workspaceReload || vendorChanged {
1562 if workspaceChanged || result.initializedErr != nil {
1563 result.initializeOnce = &sync.Once{}
1566 return result, workspaceChanged
1569 // guessPackageIDsForURI returns all packages related to uri. If we haven't
1570 // seen this URI before, we guess based on files in the same directory. This
1571 // is of course incorrect in build systems where packages are not organized by
1573 func guessPackageIDsForURI(uri span.URI, known map[span.URI][]packageID) []packageID {
1574 packages := known[uri]
1575 if len(packages) > 0 {
1576 // We've seen this file before.
1579 // This is a file we don't yet know about. Guess relevant packages by
1580 // considering files in the same directory.
1582 // Cache of FileInfo to avoid unnecessary stats for multiple files in the
1584 stats := make(map[string]struct {
1588 getInfo := func(dir string) (os.FileInfo, error) {
1589 if res, ok := stats[dir]; ok {
1590 return res.FileInfo, res.error
1592 fi, err := os.Stat(dir)
1593 stats[dir] = struct {
1599 dir := filepath.Dir(uri.Filename())
1600 fi, err := getInfo(dir)
1605 // Aggregate all possibly relevant package IDs.
1606 var found []packageID
1607 for knownURI, ids := range known {
1608 knownDir := filepath.Dir(knownURI.Filename())
1609 knownFI, err := getInfo(knownDir)
1613 if os.SameFile(fi, knownFI) {
1614 found = append(found, ids...)
1620 // fileWasSaved reports whether the FileHandle passed in has been saved. It
1621 // accomplishes this by checking to see if the original and current FileHandles
1622 // are both overlays, and if the current FileHandle is saved while the original
1623 // FileHandle was not saved.
1624 func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
1625 c, ok := currentFH.(*overlay)
1626 if !ok || c == nil {
1629 o, ok := originalFH.(*overlay)
1630 if !ok || o == nil {
1633 return !o.saved && c.saved
1636 // shouldInvalidateMetadata reparses a file's package and import declarations to
1637 // determine if the file requires a metadata reload.
1638 func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged bool) {
1639 if originalFH == nil {
1642 // If the file hasn't changed, there's no need to reload.
1643 if originalFH.FileIdentity() == currentFH.FileIdentity() {
1646 // Get the original and current parsed files in order to check package name
1647 // and imports. Use the new snapshot to parse to avoid modifying the
1648 // current snapshot.
1649 original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseHeader)
1650 current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseHeader)
1651 if originalErr != nil || currentErr != nil {
1652 return (originalErr == nil) != (currentErr == nil), false
1654 // Check if the package's metadata has changed. The cases handled are:
1655 // 1. A package's name has changed
1656 // 2. A file's imports have changed
1657 if original.File.Name.Name != current.File.Name.Name {
1660 importSet := make(map[string]struct{})
1661 for _, importSpec := range original.File.Imports {
1662 importSet[importSpec.Path.Value] = struct{}{}
1664 // If any of the current imports were not in the original imports.
1665 for _, importSpec := range current.File.Imports {
1666 if _, ok := importSet[importSpec.Path.Value]; ok {
1669 // If the import path is obviously not valid, we can skip reloading
1670 // metadata. For now, valid means properly quoted and without a
1672 path, err := strconv.Unquote(importSpec.Path.Value)
1679 if path[len(path)-1] == '/' {
1685 // Re-evaluate build constraints and embed patterns. It would be preferable
1686 // to only do this on save, but we don't have the prior versions accessible.
1687 oldComments := extractMagicComments(original.File)
1688 newComments := extractMagicComments(current.File)
1689 if len(oldComments) != len(newComments) {
1692 for i := range oldComments {
1693 if oldComments[i] != newComments[i] {
1701 var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
1703 // extractMagicComments finds magic comments that affect metadata in f.
1704 func extractMagicComments(f *ast.File) []string {
1705 var results []string
1706 for _, cg := range f.Comments {
1707 for _, c := range cg.List {
1708 if buildConstraintOrEmbedRe.MatchString(c.Text) {
1709 results = append(results, c.Text)
1716 func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) {
1717 s.AwaitInitialized(ctx)
1719 if s.builtin == nil {
1720 return nil, errors.Errorf("no builtin package for view %s", s.view.name)
1722 d, err := s.builtin.handle.Get(ctx, s.generation, s)
1726 data := d.(*builtinPackageData)
1727 return data.parsed, data.err
1730 func (s *snapshot) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
1731 if len(goFiles) != 1 {
1732 return errors.Errorf("only expected 1 file, got %v", len(goFiles))
1734 uri := span.URIFromPath(goFiles[0])
1736 // Get the FileHandle through the cache to avoid adding it to the snapshot
1737 // and to get the file content from disk.
1738 fh, err := s.view.session.cache.getFile(ctx, uri)
1742 h := s.generation.Bind(fh.FileIdentity(), func(ctx context.Context, arg memoize.Arg) interface{} {
1743 snapshot := arg.(*snapshot)
1745 pgh := snapshot.parseGoHandle(ctx, fh, source.ParseFull)
1746 pgf, _, err := snapshot.parseGo(ctx, pgh)
1748 return &builtinPackageData{err: err}
1750 pkg, err := ast.NewPackage(snapshot.view.session.cache.fset, map[string]*ast.File{
1751 pgf.URI.Filename(): pgf.File,
1754 return &builtinPackageData{err: err}
1756 return &builtinPackageData{
1757 parsed: &source.BuiltinPackage{
1763 s.builtin = &builtinPackageHandle{handle: h}
1767 // BuildGoplsMod generates a go.mod file for all modules in the workspace. It
1768 // bypasses any existing gopls.mod.
1769 func BuildGoplsMod(ctx context.Context, root span.URI, s source.Snapshot) (*modfile.File, error) {
1770 allModules, err := findModules(ctx, root, pathExcludedByFilterFunc(s.View().Options()), 0)
1774 return buildWorkspaceModFile(ctx, allModules, s)
1777 // TODO(rfindley): move this to workspacemodule.go
1778 func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
1779 file := &modfile.File{}
1780 file.AddModuleStmt("gopls-workspace")
1781 // Track the highest Go version, to be set on the workspace module.
1782 // Fall back to 1.12 -- old versions insist on having some version.
1785 paths := make(map[string]span.URI)
1786 var sortedModURIs []span.URI
1787 for uri := range modFiles {
1788 sortedModURIs = append(sortedModURIs, uri)
1790 sort.Slice(sortedModURIs, func(i, j int) bool {
1791 return sortedModURIs[i] < sortedModURIs[j]
1793 for _, modURI := range sortedModURIs {
1794 fh, err := fs.GetFile(ctx, modURI)
1798 content, err := fh.Read()
1802 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
1806 if file == nil || parsed.Module == nil {
1807 return nil, fmt.Errorf("no module declaration for %s", modURI)
1809 if parsed.Go != nil && semver.Compare(goVersion, parsed.Go.Version) < 0 {
1810 goVersion = parsed.Go.Version
1812 path := parsed.Module.Mod.Path
1813 if _, ok := paths[path]; ok {
1814 return nil, fmt.Errorf("found module %q twice in the workspace", path)
1816 paths[path] = modURI
1817 // If the module's path includes a major version, we expect it to have
1818 // a matching major version.
1819 _, majorVersion, _ := module.SplitPathVersion(path)
1820 if majorVersion == "" {
1821 majorVersion = "/v0"
1823 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
1824 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
1825 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
1829 if goVersion != "" {
1830 file.AddGoStmt(goVersion)
1832 // Go back through all of the modules to handle any of their replace
1834 for _, modURI := range sortedModURIs {
1835 fh, err := fs.GetFile(ctx, modURI)
1839 content, err := fh.Read()
1843 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
1847 // If any of the workspace modules have replace directives, they need
1848 // to be reflected in the workspace module.
1849 for _, rep := range parsed.Replace {
1850 // Don't replace any modules that are in our workspace--we should
1851 // always use the version in the workspace.
1852 if _, ok := paths[rep.Old.Path]; ok {
1855 newPath := rep.New.Path
1856 newVersion := rep.New.Version
1857 // If a replace points to a module in the workspace, make sure we
1858 // direct it to version of the module in the workspace.
1859 if m, ok := paths[rep.New.Path]; ok {
1860 newPath = dirURI(m).Filename()
1862 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
1863 // Make any relative paths absolute.
1864 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
1866 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
1875 func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) {
1876 allSums := map[module.Version][]string{}
1877 for modURI := range modFiles {
1878 // TODO(rfindley): factor out this pattern into a uripath package.
1879 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum"))
1880 fh, err := fs.GetFile(ctx, sumURI)
1884 data, err := fh.Read()
1885 if os.IsNotExist(err) {
1889 return nil, errors.Errorf("reading go sum: %w", err)
1891 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil {
1895 // This logic to write go.sum is copied (with minor modifications) from
1896 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
1897 var mods []module.Version
1898 for m := range allSums {
1899 mods = append(mods, m)
1903 var buf bytes.Buffer
1904 for _, m := range mods {
1907 // Note (rfindley): here we add all sum lines without verification, because
1908 // the assumption is that if they come from a go.sum file, they are
1910 for _, h := range list {
1911 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
1914 return buf.Bytes(), nil
1917 // readGoSum is copied (with minor modifications) from
1918 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
1919 func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
1924 i := bytes.IndexByte(data, '\n')
1926 line, data = data, nil
1928 line, data = data[:i], data[i+1:]
1930 f := strings.Fields(string(line))
1932 // blank line; skip it
1936 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
1938 mod := module.Version{Path: f[0], Version: f[1]}
1939 dst[mod] = append(dst[mod], f[2])