blob: 5a15641a5f0843eb0f269d0549dd731f2f518188 [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// 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 "errors"
11 "fmt"
12 "go/ast"
13 "go/token"
14 "go/types"
15 "io"
16 "io/ioutil"
17 "log"
18 "os"
19 "path/filepath"
20 "regexp"
21 "runtime"
22 "sort"
23 "strconv"
24 "strings"
25 "sync"
26 "sync/atomic"
27 "unsafe"
28
29 "golang.org/x/mod/modfile"
30 "golang.org/x/mod/module"
31 "golang.org/x/mod/semver"
32 "golang.org/x/sync/errgroup"
33 "golang.org/x/tools/go/packages"
Robert Findleyb15dac22022-08-30 14:40:12 -040034 "golang.org/x/tools/gopls/internal/lsp/source"
Alan Donovan26a95e62022-10-07 10:40:32 -040035 "golang.org/x/tools/gopls/internal/span"
Robert Findley9c639112022-09-28 12:03:51 -040036 "golang.org/x/tools/internal/bug"
37 "golang.org/x/tools/internal/event"
38 "golang.org/x/tools/internal/event/tag"
39 "golang.org/x/tools/internal/gocommand"
Robert Findleyb15dac22022-08-30 14:40:12 -040040 "golang.org/x/tools/internal/memoize"
41 "golang.org/x/tools/internal/packagesinternal"
42 "golang.org/x/tools/internal/persistent"
Robert Findleyb15dac22022-08-30 14:40:12 -040043 "golang.org/x/tools/internal/typesinternal"
44)
45
46type snapshot struct {
Robert Findley0c71b562022-11-14 11:58:07 -050047 sequenceID uint64
48 globalID source.GlobalSnapshotID
49 view *View
Robert Findleyb15dac22022-08-30 14:40:12 -040050
51 cancel func()
52 backgroundCtx context.Context
53
54 store *memoize.Store // cache of handles shared by all snapshots
55
56 refcount sync.WaitGroup // number of references
57 destroyedBy *string // atomically set to non-nil in Destroy once refcount = 0
58
59 // initialized reports whether the snapshot has been initialized. Concurrent
60 // initialization is guarded by the view.initializationSema. Each snapshot is
61 // initialized at most once: concurrent initialization is guarded by
62 // view.initializationSema.
63 initialized bool
64 // initializedErr holds the last error resulting from initialization. If
65 // initialization fails, we only retry when the the workspace modules change,
66 // to avoid too many go/packages calls.
67 initializedErr *source.CriticalError
68
69 // mu guards all of the maps in the snapshot, as well as the builtin URI.
70 mu sync.Mutex
71
72 // builtin pins the AST and package for builtin.go in memory.
73 builtin span.URI
74
75 // meta holds loaded metadata.
76 //
77 // meta is guarded by mu, but the metadataGraph itself is immutable.
78 // TODO(rfindley): in many places we hold mu while operating on meta, even
79 // though we only need to hold mu while reading the pointer.
80 meta *metadataGraph
81
82 // files maps file URIs to their corresponding FileHandles.
83 // It may invalidated when a file's content changes.
84 files filesMap
85
86 // parsedGoFiles maps a parseKey to the handle of the future result of parsing it.
87 parsedGoFiles *persistent.Map // from parseKey to *memoize.Promise[parseGoResult]
88
89 // parseKeysByURI records the set of keys of parsedGoFiles that
90 // need to be invalidated for each URI.
91 // TODO(adonovan): opt: parseKey = ParseMode + URI, so this could
92 // be just a set of ParseModes, or we could loop over AllParseModes.
93 parseKeysByURI parseKeysByURIMap
94
95 // symbolizeHandles maps each file URI to a handle for the future
96 // result of computing the symbols declared in that file.
97 symbolizeHandles *persistent.Map // from span.URI to *memoize.Promise[symbolizeResult]
98
99 // packages maps a packageKey to a *packageHandle.
100 // It may be invalidated when a file's content changes.
101 //
102 // Invariants to preserve:
Alan Donovanff22fab2022-11-18 14:47:36 -0500103 // - packages.Get(id).meta == meta.metadata[id] for all ids
Robert Findleyb15dac22022-08-30 14:40:12 -0400104 // - if a package is in packages, then all of its dependencies should also
105 // be in packages, unless there is a missing import
Alan Donovanff22fab2022-11-18 14:47:36 -0500106 packages *persistent.Map // from packageKey to *packageHandle
Robert Findleyb15dac22022-08-30 14:40:12 -0400107
108 // isActivePackageCache maps package ID to the cached value if it is active or not.
109 // It may be invalidated when metadata changes or a new file is opened or closed.
110 isActivePackageCache isActivePackageCacheMap
111
112 // actions maps an actionKey to the handle for the future
113 // result of execution an analysis pass on a package.
114 actions *persistent.Map // from actionKey to *actionHandle
115
116 // workspacePackages contains the workspace's packages, which are loaded
117 // when the view is created.
118 workspacePackages map[PackageID]PackagePath
119
120 // shouldLoad tracks packages that need to be reloaded, mapping a PackageID
121 // to the package paths that should be used to reload it
122 //
123 // When we try to load a package, we clear it from the shouldLoad map
124 // regardless of whether the load succeeded, to prevent endless loads.
125 shouldLoad map[PackageID][]PackagePath
126
127 // unloadableFiles keeps track of files that we've failed to load.
128 unloadableFiles map[span.URI]struct{}
129
130 // parseModHandles keeps track of any parseModHandles for the snapshot.
131 // The handles need not refer to only the view's go.mod file.
132 parseModHandles *persistent.Map // from span.URI to *memoize.Promise[parseModResult]
133
134 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
135 // The handles need not refer to only the view's go.work file.
136 parseWorkHandles *persistent.Map // from span.URI to *memoize.Promise[parseWorkResult]
137
138 // Preserve go.mod-related handles to avoid garbage-collecting the results
139 // of various calls to the go command. The handles need not refer to only
140 // the view's go.mod file.
141 modTidyHandles *persistent.Map // from span.URI to *memoize.Promise[modTidyResult]
142 modWhyHandles *persistent.Map // from span.URI to *memoize.Promise[modWhyResult]
Robert Findley7cda55e2022-11-22 12:09:11 -0500143 modVulnHandles *persistent.Map // from span.URI to *memoize.Promise[modVulnResult]
Robert Findleyb15dac22022-08-30 14:40:12 -0400144
145 workspace *workspace // (not guarded by mu)
146
147 // The cached result of makeWorkspaceDir, created on demand and deleted by Snapshot.Destroy.
148 workspaceDir string
149 workspaceDirErr error
150
151 // knownSubdirs is the set of subdirectories in the workspace, used to
152 // create glob patterns for file watching.
153 knownSubdirs knownDirsSet
154 knownSubdirsPatternCache string
155 // unprocessedSubdirChanges are any changes that might affect the set of
156 // subdirectories in the workspace. They are not reflected to knownSubdirs
157 // during the snapshot cloning step as it can slow down cloning.
158 unprocessedSubdirChanges []*fileChange
159}
160
Robert Findley0c71b562022-11-14 11:58:07 -0500161var globalSnapshotID uint64
162
163func nextSnapshotID() source.GlobalSnapshotID {
164 return source.GlobalSnapshotID(atomic.AddUint64(&globalSnapshotID, 1))
165}
166
Robert Findleyb15dac22022-08-30 14:40:12 -0400167var _ memoize.RefCounted = (*snapshot)(nil) // snapshots are reference-counted
168
169// Acquire prevents the snapshot from being destroyed until the returned function is called.
170//
171// (s.Acquire().release() could instead be expressed as a pair of
172// method calls s.IncRef(); s.DecRef(). The latter has the advantage
173// that the DecRefs are fungible and don't require holding anything in
174// addition to the refcounted object s, but paradoxically that is also
175// an advantage of the current approach, which forces the caller to
176// consider the release function at every stage, making a reference
177// leak more obvious.)
178func (s *snapshot) Acquire() func() {
179 type uP = unsafe.Pointer
180 if destroyedBy := atomic.LoadPointer((*uP)(uP(&s.destroyedBy))); destroyedBy != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500181 log.Panicf("%d: acquire() after Destroy(%q)", s.globalID, *(*string)(destroyedBy))
Robert Findleyb15dac22022-08-30 14:40:12 -0400182 }
183 s.refcount.Add(1)
184 return s.refcount.Done
185}
186
187func (s *snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) {
188 return p.Get(ctx, s)
189}
190
191// destroy waits for all leases on the snapshot to expire then releases
192// any resources (reference counts and files) associated with it.
193// Snapshots being destroyed can be awaited using v.destroyWG.
194//
195// TODO(adonovan): move this logic into the release function returned
196// by Acquire when the reference count becomes zero. (This would cost
197// us the destroyedBy debug info, unless we add it to the signature of
198// memoize.RefCounted.Acquire.)
199//
200// The destroyedBy argument is used for debugging.
201//
202// v.snapshotMu must be held while calling this function, in order to preserve
203// the invariants described by the the docstring for v.snapshot.
204func (v *View) destroy(s *snapshot, destroyedBy string) {
205 v.snapshotWG.Add(1)
206 go func() {
207 defer v.snapshotWG.Done()
208 s.destroy(destroyedBy)
209 }()
210}
211
212func (s *snapshot) destroy(destroyedBy string) {
213 // Wait for all leases to end before commencing destruction.
214 s.refcount.Wait()
215
216 // Report bad state as a debugging aid.
217 // Not foolproof: another thread could acquire() at this moment.
218 type uP = unsafe.Pointer // looking forward to generics...
219 if old := atomic.SwapPointer((*uP)(uP(&s.destroyedBy)), uP(&destroyedBy)); old != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500220 log.Panicf("%d: Destroy(%q) after Destroy(%q)", s.globalID, destroyedBy, *(*string)(old))
Robert Findleyb15dac22022-08-30 14:40:12 -0400221 }
222
223 s.packages.Destroy()
224 s.isActivePackageCache.Destroy()
225 s.actions.Destroy()
226 s.files.Destroy()
227 s.parsedGoFiles.Destroy()
228 s.parseKeysByURI.Destroy()
229 s.knownSubdirs.Destroy()
230 s.symbolizeHandles.Destroy()
231 s.parseModHandles.Destroy()
232 s.parseWorkHandles.Destroy()
233 s.modTidyHandles.Destroy()
Robert Findley7cda55e2022-11-22 12:09:11 -0500234 s.modVulnHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400235 s.modWhyHandles.Destroy()
236
237 if s.workspaceDir != "" {
238 if err := os.RemoveAll(s.workspaceDir); err != nil {
239 event.Error(context.Background(), "cleaning workspace dir", err)
240 }
241 }
242}
243
Robert Findley0c71b562022-11-14 11:58:07 -0500244func (s *snapshot) SequenceID() uint64 {
245 return s.sequenceID
246}
247
248func (s *snapshot) GlobalID() source.GlobalSnapshotID {
249 return s.globalID
Robert Findleyb15dac22022-08-30 14:40:12 -0400250}
251
252func (s *snapshot) View() source.View {
253 return s.view
254}
255
256func (s *snapshot) BackgroundContext() context.Context {
257 return s.backgroundCtx
258}
259
260func (s *snapshot) FileSet() *token.FileSet {
Robert Findleye8a70a52022-11-28 14:58:06 -0500261 return s.view.cache.fset
Robert Findleyb15dac22022-08-30 14:40:12 -0400262}
263
264func (s *snapshot) ModFiles() []span.URI {
265 var uris []span.URI
Robert Findley39c2fd82022-11-04 13:14:16 -0400266 for modURI := range s.workspace.ActiveModFiles() {
Robert Findleyb15dac22022-08-30 14:40:12 -0400267 uris = append(uris, modURI)
268 }
269 return uris
270}
271
272func (s *snapshot) WorkFile() span.URI {
273 return s.workspace.workFile
274}
275
276func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
277 s.mu.Lock()
278 defer s.mu.Unlock()
279
280 tmpls := map[span.URI]source.VersionedFileHandle{}
281 s.files.Range(func(k span.URI, fh source.VersionedFileHandle) {
282 if s.view.FileKind(fh) == source.Tmpl {
283 tmpls[k] = fh
284 }
285 })
286 return tmpls
287}
288
289func (s *snapshot) ValidBuildConfiguration() bool {
290 // Since we only really understand the `go` command, if the user has a
291 // different GOPACKAGESDRIVER, assume that their configuration is valid.
292 if s.view.hasGopackagesDriver {
293 return true
294 }
295 // Check if the user is working within a module or if we have found
296 // multiple modules in the workspace.
Robert Findley39c2fd82022-11-04 13:14:16 -0400297 if len(s.workspace.ActiveModFiles()) > 0 {
Robert Findleyb15dac22022-08-30 14:40:12 -0400298 return true
299 }
300 // The user may have a multiple directories in their GOPATH.
301 // Check if the workspace is within any of them.
302 for _, gp := range filepath.SplitList(s.view.gopath) {
303 if source.InDir(filepath.Join(gp, "src"), s.view.rootURI.Filename()) {
304 return true
305 }
306 }
307 return false
308}
309
310// workspaceMode describes the way in which the snapshot's workspace should
311// be loaded.
312func (s *snapshot) workspaceMode() workspaceMode {
313 var mode workspaceMode
314
315 // If the view has an invalid configuration, don't build the workspace
316 // module.
317 validBuildConfiguration := s.ValidBuildConfiguration()
318 if !validBuildConfiguration {
319 return mode
320 }
321 // If the view is not in a module and contains no modules, but still has a
322 // valid workspace configuration, do not create the workspace module.
323 // It could be using GOPATH or a different build system entirely.
Robert Findley39c2fd82022-11-04 13:14:16 -0400324 if len(s.workspace.ActiveModFiles()) == 0 && validBuildConfiguration {
Robert Findleyb15dac22022-08-30 14:40:12 -0400325 return mode
326 }
327 mode |= moduleMode
328 options := s.view.Options()
329 // The -modfile flag is available for Go versions >= 1.14.
330 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 {
331 mode |= tempModfile
332 }
333 return mode
334}
335
336// config returns the configuration used for the snapshot's interaction with
337// the go/packages API. It uses the given working directory.
338//
339// TODO(rstambler): go/packages requires that we do not provide overlays for
340// multiple modules in on config, so buildOverlay needs to filter overlays by
341// module.
342func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
343 s.view.optionsMu.Lock()
344 verboseOutput := s.view.options.VerboseOutput
345 s.view.optionsMu.Unlock()
346
347 cfg := &packages.Config{
348 Context: ctx,
349 Dir: inv.WorkingDir,
350 Env: inv.Env,
351 BuildFlags: inv.BuildFlags,
352 Mode: packages.NeedName |
353 packages.NeedFiles |
354 packages.NeedCompiledGoFiles |
355 packages.NeedImports |
356 packages.NeedDeps |
357 packages.NeedTypesSizes |
358 packages.NeedModule |
359 packages.LoadMode(packagesinternal.DepsErrors) |
360 packages.LoadMode(packagesinternal.ForTest),
Alan Donovan6f993662022-11-10 15:07:47 -0500361 Fset: nil, // we do our own parsing
Robert Findleyb15dac22022-08-30 14:40:12 -0400362 Overlay: s.buildOverlay(),
363 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
364 panic("go/packages must not be used to parse files")
365 },
366 Logf: func(format string, args ...interface{}) {
367 if verboseOutput {
368 event.Log(ctx, fmt.Sprintf(format, args...))
369 }
370 },
371 Tests: true,
372 }
373 packagesinternal.SetModFile(cfg, inv.ModFile)
374 packagesinternal.SetModFlag(cfg, inv.ModFlag)
375 // We want to type check cgo code if go/types supports it.
376 if typesinternal.SetUsesCgo(&types.Config{}) {
377 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
378 }
Robert Findleye8a70a52022-11-28 14:58:06 -0500379 packagesinternal.SetGoCmdRunner(cfg, s.view.gocmdRunner)
Robert Findleyb15dac22022-08-30 14:40:12 -0400380 return cfg
381}
382
383func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
384 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
385 if err != nil {
386 return nil, err
387 }
388 defer cleanup()
389
Robert Findleye8a70a52022-11-28 14:58:06 -0500390 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400391}
392
393func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
394 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
395 if err != nil {
396 return err
397 }
398 defer cleanup()
Robert Findleye8a70a52022-11-28 14:58:06 -0500399 return s.view.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
Robert Findleyb15dac22022-08-30 14:40:12 -0400400}
401
402func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
403 var flags source.InvocationFlags
404 if s.workspaceMode()&tempModfile != 0 {
405 flags = source.WriteTemporaryModFile
406 } else {
407 flags = source.Normal
408 }
409 if allowNetwork {
410 flags |= source.AllowNetwork
411 }
412 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
413 if err != nil {
414 return false, nil, nil, err
415 }
416 defer cleanup()
417 invoke := func(args ...string) (*bytes.Buffer, error) {
418 inv.Verb = args[0]
419 inv.Args = args[1:]
Robert Findleye8a70a52022-11-28 14:58:06 -0500420 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400421 }
422 if err := run(invoke); err != nil {
423 return false, nil, nil, err
424 }
425 if flags.Mode() != source.WriteTemporaryModFile {
426 return false, nil, nil, nil
427 }
428 var modBytes, sumBytes []byte
429 modBytes, err = ioutil.ReadFile(tmpURI.Filename())
430 if err != nil && !os.IsNotExist(err) {
431 return false, nil, nil, err
432 }
433 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
434 if err != nil && !os.IsNotExist(err) {
435 return false, nil, nil, err
436 }
437 return true, modBytes, sumBytes, nil
438}
439
440// goCommandInvocation populates inv with configuration for running go commands on the snapshot.
441//
442// TODO(rfindley): refactor this function to compose the required configuration
443// explicitly, rather than implicitly deriving it from flags and inv.
444//
445// TODO(adonovan): simplify cleanup mechanism. It's hard to see, but
446// it used only after call to tempModFile. Clarify that it is only
447// non-nil on success.
448func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
449 s.view.optionsMu.Lock()
450 allowModfileModificationOption := s.view.options.AllowModfileModifications
451 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
452
453 // TODO(rfindley): this is very hard to follow, and may not even be doing the
454 // right thing: should inv.Env really trample view.options? Do we ever invoke
455 // this with a non-empty inv.Env?
456 //
457 // We should refactor to make it clearer that the correct env is being used.
Robert Findleyc7ed4b32022-11-16 13:52:25 -0500458 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
Robert Findleyb15dac22022-08-30 14:40:12 -0400459 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
460 s.view.optionsMu.Unlock()
461 cleanup = func() {} // fallback
462
463 // All logic below is for module mode.
464 if s.workspaceMode()&moduleMode == 0 {
465 return "", inv, cleanup, nil
466 }
467
468 mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
469 if !allowNetwork && !allowNetworkOption {
470 inv.Env = append(inv.Env, "GOPROXY=off")
471 }
472
473 // What follows is rather complicated logic for how to actually run the go
474 // command. A word of warning: this is the result of various incremental
475 // features added to gopls, and varying behavior of the Go command across Go
476 // versions. It can surely be cleaned up significantly, but tread carefully.
477 //
478 // Roughly speaking we need to resolve four things:
479 // - the working directory.
480 // - the -mod flag
481 // - the -modfile flag
482 //
483 // These are dependent on a number of factors: whether we need to run in a
484 // synthetic workspace, whether flags are supported at the current go
485 // version, and what we're actually trying to achieve (the
486 // source.InvocationFlags).
487
488 var modURI span.URI
489 // Select the module context to use.
490 // If we're type checking, we need to use the workspace context, meaning
491 // the main (workspace) module. Otherwise, we should use the module for
492 // the passed-in working dir.
493 if mode == source.LoadWorkspace {
494 switch s.workspace.moduleSource {
495 case legacyWorkspace:
Robert Findley39c2fd82022-11-04 13:14:16 -0400496 for m := range s.workspace.ActiveModFiles() { // range to access the only element
Robert Findleyb15dac22022-08-30 14:40:12 -0400497 modURI = m
498 }
499 case goWorkWorkspace:
500 if s.view.goversion >= 18 {
501 break
502 }
503 // Before go 1.18, the Go command did not natively support go.work files,
504 // so we 'fake' them with a workspace module.
505 fallthrough
506 case fileSystemWorkspace, goplsModWorkspace:
507 var tmpDir span.URI
508 var err error
509 tmpDir, err = s.getWorkspaceDir(ctx)
510 if err != nil {
511 return "", nil, cleanup, err
512 }
513 inv.WorkingDir = tmpDir.Filename()
514 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
515 }
516 } else {
517 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
518 }
519
520 var modContent []byte
521 if modURI != "" {
522 modFH, err := s.GetFile(ctx, modURI)
523 if err != nil {
524 return "", nil, cleanup, err
525 }
526 modContent, err = modFH.Read()
527 if err != nil {
528 return "", nil, cleanup, err
529 }
530 }
531
532 // TODO(rfindley): in the case of go.work mode, modURI is empty and we fall
533 // back on the default behavior of vendorEnabled with an empty modURI. Figure
534 // out what is correct here and implement it explicitly.
535 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
536 if err != nil {
537 return "", nil, cleanup, err
538 }
539
540 mutableModFlag := ""
541 // If the mod flag isn't set, populate it based on the mode and workspace.
542 if inv.ModFlag == "" {
543 if s.view.goversion >= 16 {
544 mutableModFlag = "mod"
545 }
546
547 switch mode {
548 case source.LoadWorkspace, source.Normal:
549 if vendorEnabled {
550 inv.ModFlag = "vendor"
551 } else if !allowModfileModificationOption {
552 inv.ModFlag = "readonly"
553 } else {
554 inv.ModFlag = mutableModFlag
555 }
556 case source.WriteTemporaryModFile:
557 inv.ModFlag = mutableModFlag
558 // -mod must be readonly when using go.work files - see issue #48941
559 inv.Env = append(inv.Env, "GOWORK=off")
560 }
561 }
562
563 // Only use a temp mod file if the modfile can actually be mutated.
564 needTempMod := inv.ModFlag == mutableModFlag
565 useTempMod := s.workspaceMode()&tempModfile != 0
566 if needTempMod && !useTempMod {
567 return "", nil, cleanup, source.ErrTmpModfileUnsupported
568 }
569
570 // We should use -modfile if:
571 // - the workspace mode supports it
572 // - we're using a go.work file on go1.18+, or we need a temp mod file (for
573 // example, if running go mod tidy in a go.work workspace)
574 //
575 // TODO(rfindley): this is very hard to follow. Refactor.
576 useWorkFile := !needTempMod && s.workspace.moduleSource == goWorkWorkspace && s.view.goversion >= 18
577 if useWorkFile {
578 // Since we're running in the workspace root, the go command will resolve GOWORK automatically.
579 } else if useTempMod {
580 if modURI == "" {
581 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
582 }
583 modFH, err := s.GetFile(ctx, modURI)
584 if err != nil {
585 return "", nil, cleanup, err
586 }
587 // Use the go.sum if it happens to be available.
588 gosum := s.goSum(ctx, modURI)
589 tmpURI, cleanup, err = tempModFile(modFH, gosum)
590 if err != nil {
591 return "", nil, cleanup, err
592 }
593 inv.ModFile = tmpURI.Filename()
594 }
595
596 return tmpURI, inv, cleanup, nil
597}
598
599// usesWorkspaceDir reports whether the snapshot should use a synthetic
600// workspace directory for running workspace go commands such as go list.
601//
602// TODO(rfindley): this logic is duplicated with goCommandInvocation. Clean up
603// the latter, and deduplicate.
604func (s *snapshot) usesWorkspaceDir() bool {
605 switch s.workspace.moduleSource {
606 case legacyWorkspace:
607 return false
608 case goWorkWorkspace:
609 if s.view.goversion >= 18 {
610 return false
611 }
612 // Before go 1.18, the Go command did not natively support go.work files,
613 // so we 'fake' them with a workspace module.
614 }
615 return true
616}
617
618func (s *snapshot) buildOverlay() map[string][]byte {
619 s.mu.Lock()
620 defer s.mu.Unlock()
621
622 overlays := make(map[string][]byte)
623 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
624 overlay, ok := fh.(*overlay)
625 if !ok {
626 return
627 }
628 if overlay.saved {
629 return
630 }
631 // TODO(rstambler): Make sure not to send overlays outside of the current view.
632 overlays[uri.Filename()] = overlay.text
633 })
634 return overlays
635}
636
637func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]source.Package, error) {
638 ctx = event.Label(ctx, tag.URI.Of(uri))
639
640 phs, err := s.packageHandlesForFile(ctx, uri, mode, includeTestVariants)
641 if err != nil {
642 return nil, err
643 }
644 var pkgs []source.Package
645 for _, ph := range phs {
646 pkg, err := ph.await(ctx, s)
647 if err != nil {
648 return nil, err
649 }
650 pkgs = append(pkgs, pkg)
651 }
652 return pkgs, nil
653}
654
655func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
656 ctx = event.Label(ctx, tag.URI.Of(uri))
657
Alan Donovanff22fab2022-11-18 14:47:36 -0500658 // TODO(adonovan): opt: apply pkgPolicy to the list of
659 // Metadatas before initiating loading of any package.
Robert Findleyb15dac22022-08-30 14:40:12 -0400660 phs, err := s.packageHandlesForFile(ctx, uri, mode, false)
661 if err != nil {
662 return nil, err
663 }
664
665 if len(phs) < 1 {
666 return nil, fmt.Errorf("no packages")
667 }
668
669 ph := phs[0]
670 for _, handle := range phs[1:] {
671 switch pkgPolicy {
672 case source.WidestPackage:
673 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) {
674 ph = handle
675 }
676 case source.NarrowestPackage:
677 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) {
678 ph = handle
679 }
680 }
681 }
682 if ph == nil {
683 return nil, fmt.Errorf("no packages in input")
684 }
685
686 return ph.await(ctx, s)
687}
688
Robert Findleyff4ff8b2022-10-04 09:19:27 -0400689func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, withIntermediateTestVariants bool) ([]*packageHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400690 // TODO(rfindley): why can't/shouldn't we awaitLoaded here? It seems that if
691 // we ask for package handles for a file, we should wait for pending loads.
692 // Else we will reload orphaned files before the initial load completes.
693
694 // Check if we should reload metadata for the file. We don't invalidate IDs
695 // (though we should), so the IDs will be a better source of truth than the
696 // metadata. If there are no IDs for the file, then we should also reload.
697 fh, err := s.GetFile(ctx, uri)
698 if err != nil {
699 return nil, err
700 }
701 if kind := s.view.FileKind(fh); kind != source.Go {
702 return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind)
703 }
Alan Donovanff22fab2022-11-18 14:47:36 -0500704 metas, err := s.MetadataForFile(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400705 if err != nil {
706 return nil, err
707 }
708
709 var phs []*packageHandle
Alan Donovanff22fab2022-11-18 14:47:36 -0500710 for _, m := range metas {
Robert Findleyb15dac22022-08-30 14:40:12 -0400711 // Filter out any intermediate test variants. We typically aren't
712 // interested in these packages for file= style queries.
Alan Donovanff22fab2022-11-18 14:47:36 -0500713 if m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
Robert Findleyb15dac22022-08-30 14:40:12 -0400714 continue
715 }
Robert Findley906c7332022-10-04 15:01:17 -0400716 parseMode := source.ParseFull
717 if mode == source.TypecheckWorkspace {
Alan Donovanff22fab2022-11-18 14:47:36 -0500718 parseMode = s.workspaceParseMode(m.ID)
Robert Findleyb15dac22022-08-30 14:40:12 -0400719 }
720
Alan Donovanff22fab2022-11-18 14:47:36 -0500721 ph, err := s.buildPackageHandle(ctx, m.ID, parseMode)
Robert Findley906c7332022-10-04 15:01:17 -0400722 if err != nil {
723 return nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400724 }
Robert Findley906c7332022-10-04 15:01:17 -0400725 phs = append(phs, ph)
Robert Findleyb15dac22022-08-30 14:40:12 -0400726 }
727 return phs, nil
728}
729
Alan Donovanff22fab2022-11-18 14:47:36 -0500730// MetadataForFile returns the Metadata for each package associated
731// with the file uri. If no such package exists or if they are known
732// to be stale, it reloads metadata for the file.
733func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400734 s.mu.Lock()
Robert Findleyb2533142022-10-10 13:50:45 -0400735
736 // Start with the set of package associations derived from the last load.
Robert Findleyb15dac22022-08-30 14:40:12 -0400737 ids := s.meta.ids[uri]
Robert Findleyb2533142022-10-10 13:50:45 -0400738
Robert Findleyb2533142022-10-10 13:50:45 -0400739 shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
Robert Findleyb15dac22022-08-30 14:40:12 -0400740 for _, id := range ids {
Robert Findleyb2533142022-10-10 13:50:45 -0400741 if len(s.shouldLoad[id]) > 0 {
742 shouldLoad = true
743 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400744 }
Robert Findleyb2533142022-10-10 13:50:45 -0400745
746 // Check if uri is known to be unloadable.
Robert Findleyb2533142022-10-10 13:50:45 -0400747 _, unloadable := s.unloadableFiles[uri]
748
Robert Findleyb15dac22022-08-30 14:40:12 -0400749 s.mu.Unlock()
750
Robert Findleyb2533142022-10-10 13:50:45 -0400751 // Reload if loading is likely to improve the package associations for uri:
752 // - uri is not contained in any valid packages
753 // - ...or one of the packages containing uri is marked 'shouldLoad'
754 // - ...but uri is not unloadable
Robert Findley23056f62022-11-03 19:06:31 -0400755 if (shouldLoad || len(ids) == 0) && !unloadable {
Robert Findleyb2533142022-10-10 13:50:45 -0400756 scope := fileLoadScope(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400757 err := s.load(ctx, false, scope)
758
Robert Findleyb2533142022-10-10 13:50:45 -0400759 // Guard against failed loads due to context cancellation.
Robert Findleyb15dac22022-08-30 14:40:12 -0400760 //
Robert Findleyb2533142022-10-10 13:50:45 -0400761 // Return the context error here as the current operation is no longer
762 // valid.
763 if ctxErr := ctx.Err(); ctxErr != nil {
764 return nil, ctxErr
Robert Findleyb15dac22022-08-30 14:40:12 -0400765 }
766
Robert Findleyb2533142022-10-10 13:50:45 -0400767 // We must clear scopes after loading.
768 //
769 // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded
770 // packages as loaded. We could do this from snapshot.load and avoid
771 // raciness.
772 s.clearShouldLoad(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -0400773
Robert Findleyb2533142022-10-10 13:50:45 -0400774 // Don't return an error here, as we may still return stale IDs.
Alan Donovanff22fab2022-11-18 14:47:36 -0500775 // Furthermore, the result of MetadataForFile should be consistent upon
Robert Findleyb2533142022-10-10 13:50:45 -0400776 // subsequent calls, even if the file is marked as unloadable.
777 if err != nil && !errors.Is(err, errNoPackages) {
Alan Donovanff22fab2022-11-18 14:47:36 -0500778 event.Error(ctx, "MetadataForFile", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400779 }
780 }
781
Alan Donovanff22fab2022-11-18 14:47:36 -0500782 // Retrieve the metadata.
Robert Findleyb2533142022-10-10 13:50:45 -0400783 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -0500784 defer s.mu.Unlock()
Robert Findleyb2533142022-10-10 13:50:45 -0400785 ids = s.meta.ids[uri]
Alan Donovanff22fab2022-11-18 14:47:36 -0500786 metas := make([]*source.Metadata, len(ids))
787 for i, id := range ids {
788 metas[i] = s.meta.metadata[id]
789 if metas[i] == nil {
790 panic("nil metadata")
791 }
792 }
793 // Metadata is only ever added by loading,
794 // so if we get here and still have
795 // no IDs, uri is unloadable.
Robert Findley23056f62022-11-03 19:06:31 -0400796 if !unloadable && len(ids) == 0 {
797 s.unloadableFiles[uri] = struct{}{}
Robert Findleyb2533142022-10-10 13:50:45 -0400798 }
Alan Donovanff22fab2022-11-18 14:47:36 -0500799 return metas, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400800}
801
Alan Donovan3c3713e2022-11-10 13:02:38 -0500802func (s *snapshot) GetReverseDependencies(ctx context.Context, id PackageID) ([]source.Package, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400803 if err := s.awaitLoaded(ctx); err != nil {
804 return nil, err
805 }
806 s.mu.Lock()
807 meta := s.meta
808 s.mu.Unlock()
Robert Findley5a4eba52022-11-03 18:28:39 -0400809 ids := meta.reverseTransitiveClosure(id)
Robert Findleyb15dac22022-08-30 14:40:12 -0400810
811 // Make sure to delete the original package ID from the map.
Alan Donovan3c3713e2022-11-10 13:02:38 -0500812 delete(ids, id)
Robert Findleyb15dac22022-08-30 14:40:12 -0400813
814 var pkgs []source.Package
815 for id := range ids {
816 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
817 if err != nil {
818 return nil, err
819 }
820 pkgs = append(pkgs, pkg)
821 }
822 return pkgs, nil
823}
824
825func (s *snapshot) checkedPackage(ctx context.Context, id PackageID, mode source.ParseMode) (*pkg, error) {
826 ph, err := s.buildPackageHandle(ctx, id, mode)
827 if err != nil {
828 return nil, err
829 }
830 return ph.await(ctx, s)
831}
832
833func (s *snapshot) getImportedBy(id PackageID) []PackageID {
834 s.mu.Lock()
835 defer s.mu.Unlock()
836 return s.meta.importedBy[id]
837}
838
839func (s *snapshot) workspacePackageIDs() (ids []PackageID) {
840 s.mu.Lock()
841 defer s.mu.Unlock()
842
843 for id := range s.workspacePackages {
844 ids = append(ids, id)
845 }
846 return ids
847}
848
849func (s *snapshot) activePackageIDs() (ids []PackageID) {
850 if s.view.Options().MemoryMode == source.ModeNormal {
851 return s.workspacePackageIDs()
852 }
853
854 s.mu.Lock()
855 defer s.mu.Unlock()
856
857 for id := range s.workspacePackages {
858 if s.isActiveLocked(id) {
859 ids = append(ids, id)
860 }
861 }
862 return ids
863}
864
865func (s *snapshot) isActiveLocked(id PackageID) (active bool) {
866 if seen, ok := s.isActivePackageCache.Get(id); ok {
867 return seen
868 }
869 defer func() {
870 s.isActivePackageCache.Set(id, active)
871 }()
872 m, ok := s.meta.metadata[id]
873 if !ok {
874 return false
875 }
876 for _, cgf := range m.CompiledGoFiles {
877 if s.isOpenLocked(cgf) {
878 return true
879 }
880 }
881 // TODO(rfindley): it looks incorrect that we don't also check GoFiles here.
882 // If a CGo file is open, we want to consider the package active.
Alan Donovan21f61272022-10-20 14:32:57 -0400883 for _, dep := range m.DepsByPkgPath {
Robert Findleyb15dac22022-08-30 14:40:12 -0400884 if s.isActiveLocked(dep) {
885 return true
886 }
887 }
888 return false
889}
890
891func (s *snapshot) resetIsActivePackageLocked() {
892 s.isActivePackageCache.Destroy()
893 s.isActivePackageCache = newIsActivePackageCacheMap()
894}
895
896const fileExtensions = "go,mod,sum,work"
897
898func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
899 extensions := fileExtensions
900 for _, ext := range s.View().Options().TemplateExtensions {
901 extensions += "," + ext
902 }
903 // Work-around microsoft/vscode#100870 by making sure that we are,
904 // at least, watching the user's entire workspace. This will still be
905 // applied to every folder in the workspace.
906 patterns := map[string]struct{}{
907 fmt.Sprintf("**/*.{%s}", extensions): {},
908 }
909
910 if s.view.explicitGowork != "" {
911 patterns[s.view.explicitGowork.Filename()] = struct{}{}
912 }
913
914 // Add a pattern for each Go module in the workspace that is not within the view.
915 dirs := s.workspace.dirs(ctx, s)
916 for _, dir := range dirs {
917 dirName := dir.Filename()
918
919 // If the directory is within the view's folder, we're already watching
920 // it with the pattern above.
921 if source.InDir(s.view.folder.Filename(), dirName) {
922 continue
923 }
924 // TODO(rstambler): If microsoft/vscode#3025 is resolved before
925 // microsoft/vscode#101042, we will need a work-around for Windows
926 // drive letter casing.
927 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
928 }
929
930 // Some clients do not send notifications for changes to directories that
931 // contain Go code (golang/go#42348). To handle this, explicitly watch all
932 // of the directories in the workspace. We find them by adding the
933 // directories of every file in the snapshot's workspace directories.
934 // There may be thousands.
935 if pattern := s.getKnownSubdirsPattern(dirs); pattern != "" {
936 patterns[pattern] = struct{}{}
937 }
938
939 return patterns
940}
941
942func (s *snapshot) getKnownSubdirsPattern(wsDirs []span.URI) string {
943 s.mu.Lock()
944 defer s.mu.Unlock()
945
946 // First, process any pending changes and update the set of known
947 // subdirectories.
948 // It may change list of known subdirs and therefore invalidate the cache.
949 s.applyKnownSubdirsChangesLocked(wsDirs)
950
951 if s.knownSubdirsPatternCache == "" {
952 var builder strings.Builder
953 s.knownSubdirs.Range(func(uri span.URI) {
954 if builder.Len() == 0 {
955 builder.WriteString("{")
956 } else {
957 builder.WriteString(",")
958 }
959 builder.WriteString(uri.Filename())
960 })
961 if builder.Len() > 0 {
962 builder.WriteString("}")
963 s.knownSubdirsPatternCache = builder.String()
964 }
965 }
966
967 return s.knownSubdirsPatternCache
968}
969
970// collectAllKnownSubdirs collects all of the subdirectories within the
971// snapshot's workspace directories. None of the workspace directories are
972// included.
973func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
974 dirs := s.workspace.dirs(ctx, s)
975
976 s.mu.Lock()
977 defer s.mu.Unlock()
978
979 s.knownSubdirs.Destroy()
980 s.knownSubdirs = newKnownDirsSet()
981 s.knownSubdirsPatternCache = ""
982 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
983 s.addKnownSubdirLocked(uri, dirs)
984 })
985}
986
987func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) knownDirsSet {
988 s.mu.Lock()
989 defer s.mu.Unlock()
990
991 // First, process any pending changes and update the set of known
992 // subdirectories.
993 s.applyKnownSubdirsChangesLocked(wsDirs)
994
995 return s.knownSubdirs.Clone()
996}
997
998func (s *snapshot) applyKnownSubdirsChangesLocked(wsDirs []span.URI) {
999 for _, c := range s.unprocessedSubdirChanges {
1000 if c.isUnchanged {
1001 continue
1002 }
1003 if !c.exists {
1004 s.removeKnownSubdirLocked(c.fileHandle.URI())
1005 } else {
1006 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
1007 }
1008 }
1009 s.unprocessedSubdirChanges = nil
1010}
1011
1012func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
1013 dir := filepath.Dir(uri.Filename())
1014 // First check if the directory is already known, because then we can
1015 // return early.
1016 if s.knownSubdirs.Contains(span.URIFromPath(dir)) {
1017 return
1018 }
1019 var matched span.URI
1020 for _, wsDir := range dirs {
1021 if source.InDir(wsDir.Filename(), dir) {
1022 matched = wsDir
1023 break
1024 }
1025 }
1026 // Don't watch any directory outside of the workspace directories.
1027 if matched == "" {
1028 return
1029 }
1030 for {
1031 if dir == "" || dir == matched.Filename() {
1032 break
1033 }
1034 uri := span.URIFromPath(dir)
1035 if s.knownSubdirs.Contains(uri) {
1036 break
1037 }
1038 s.knownSubdirs.Insert(uri)
1039 dir = filepath.Dir(dir)
1040 s.knownSubdirsPatternCache = ""
1041 }
1042}
1043
1044func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
1045 dir := filepath.Dir(uri.Filename())
1046 for dir != "" {
1047 uri := span.URIFromPath(dir)
1048 if !s.knownSubdirs.Contains(uri) {
1049 break
1050 }
1051 if info, _ := os.Stat(dir); info == nil {
1052 s.knownSubdirs.Remove(uri)
1053 s.knownSubdirsPatternCache = ""
1054 }
1055 dir = filepath.Dir(dir)
1056 }
1057}
1058
1059// knownFilesInDir returns the files known to the given snapshot that are in
1060// the given directory. It does not respect symlinks.
1061func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
1062 var files []span.URI
1063 s.mu.Lock()
1064 defer s.mu.Unlock()
1065
1066 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1067 if source.InDir(dir.Filename(), uri.Filename()) {
1068 files = append(files, uri)
1069 }
1070 })
1071 return files
1072}
1073
1074func (s *snapshot) ActivePackages(ctx context.Context) ([]source.Package, error) {
1075 phs, err := s.activePackageHandles(ctx)
1076 if err != nil {
1077 return nil, err
1078 }
1079 var pkgs []source.Package
1080 for _, ph := range phs {
1081 pkg, err := ph.await(ctx, s)
1082 if err != nil {
1083 return nil, err
1084 }
1085 pkgs = append(pkgs, pkg)
1086 }
1087 return pkgs, nil
1088}
1089
1090func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle, error) {
1091 if err := s.awaitLoaded(ctx); err != nil {
1092 return nil, err
1093 }
1094 var phs []*packageHandle
1095 for _, pkgID := range s.activePackageIDs() {
1096 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID))
1097 if err != nil {
1098 return nil, err
1099 }
1100 phs = append(phs, ph)
1101 }
1102 return phs, nil
1103}
1104
1105// Symbols extracts and returns the symbols for each file in all the snapshot's views.
1106func (s *snapshot) Symbols(ctx context.Context) map[span.URI][]source.Symbol {
Alan Donovan19fb30d2022-11-18 16:29:11 -05001107 // Read the set of Go files out of the snapshot.
1108 var goFiles []source.VersionedFileHandle
1109 s.mu.Lock()
1110 s.files.Range(func(uri span.URI, f source.VersionedFileHandle) {
1111 if s.View().FileKind(f) == source.Go {
1112 goFiles = append(goFiles, f)
1113 }
1114 })
1115 s.mu.Unlock()
1116
1117 // Symbolize them in parallel.
Robert Findleyb15dac22022-08-30 14:40:12 -04001118 var (
1119 group errgroup.Group
Alan Donovan19fb30d2022-11-18 16:29:11 -05001120 nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
Robert Findleyb15dac22022-08-30 14:40:12 -04001121 resultMu sync.Mutex
1122 result = make(map[span.URI][]source.Symbol)
1123 )
Alan Donovan19fb30d2022-11-18 16:29:11 -05001124 group.SetLimit(nprocs)
1125 for _, f := range goFiles {
1126 f := f
Robert Findleyb15dac22022-08-30 14:40:12 -04001127 group.Go(func() error {
Robert Findleyb15dac22022-08-30 14:40:12 -04001128 symbols, err := s.symbolize(ctx, f)
1129 if err != nil {
1130 return err
1131 }
1132 resultMu.Lock()
Alan Donovan19fb30d2022-11-18 16:29:11 -05001133 result[f.URI()] = symbols
Robert Findleyb15dac22022-08-30 14:40:12 -04001134 resultMu.Unlock()
1135 return nil
1136 })
Alan Donovan19fb30d2022-11-18 16:29:11 -05001137 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001138 // Keep going on errors, but log the first failure.
1139 // Partial results are better than no symbol results.
1140 if err := group.Wait(); err != nil {
1141 event.Error(ctx, "getting snapshot symbols", err)
1142 }
1143 return result
1144}
1145
Robert Findleyb15dac22022-08-30 14:40:12 -04001146func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
1147 if err := s.awaitLoaded(ctx); err != nil {
1148 return nil, err
1149 }
1150
Robert Findleyb15dac22022-08-30 14:40:12 -04001151 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -05001152 g := s.meta
Robert Findleyb15dac22022-08-30 14:40:12 -04001153 s.mu.Unlock()
1154
Alan Donovanff22fab2022-11-18 14:47:36 -05001155 pkgs := make([]source.Package, 0, len(g.metadata))
1156 for id := range g.metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001157 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
1158 if err != nil {
1159 return nil, err
1160 }
1161 pkgs = append(pkgs, pkg)
1162 }
1163 return pkgs, nil
1164}
1165
Alan Donovan85bf7a82022-11-18 12:03:11 -05001166func (s *snapshot) AllValidMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findley61280302022-10-17 16:35:50 -04001167 if err := s.awaitLoaded(ctx); err != nil {
1168 return nil, err
1169 }
1170
1171 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -05001172 g := s.meta
1173 s.mu.Unlock()
Robert Findley61280302022-10-17 16:35:50 -04001174
Alan Donovanff22fab2022-11-18 14:47:36 -05001175 meta := make([]*source.Metadata, 0, len(g.metadata))
1176 for _, m := range g.metadata {
Robert Findley5a4eba52022-11-03 18:28:39 -04001177 meta = append(meta, m)
Robert Findley61280302022-10-17 16:35:50 -04001178 }
1179 return meta, nil
1180}
1181
Alan Donovan3c3713e2022-11-10 13:02:38 -05001182func (s *snapshot) WorkspacePackageByID(ctx context.Context, id PackageID) (source.Package, error) {
1183 return s.checkedPackage(ctx, id, s.workspaceParseMode(id))
Robert Findley61280302022-10-17 16:35:50 -04001184}
1185
Alan Donovan3c3713e2022-11-10 13:02:38 -05001186func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]source.Package, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001187 // Don't reload workspace package metadata.
1188 // This function is meant to only return currently cached information.
1189 s.AwaitInitialized(ctx)
1190
1191 s.mu.Lock()
1192 defer s.mu.Unlock()
1193
Alan Donovan3c3713e2022-11-10 13:02:38 -05001194 results := map[PackagePath]source.Package{}
Robert Findleyb15dac22022-08-30 14:40:12 -04001195 s.packages.Range(func(_, v interface{}) {
1196 cachedPkg, err := v.(*packageHandle).cached()
1197 if err != nil {
1198 return
1199 }
Alan Donovan21f61272022-10-20 14:32:57 -04001200 for _, newPkg := range cachedPkg.deps {
1201 pkgPath := newPkg.PkgPath()
1202 if oldPkg, ok := results[pkgPath]; ok {
Robert Findleyb15dac22022-08-30 14:40:12 -04001203 // Using the same trick as NarrowestPackage, prefer non-variants.
1204 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
Alan Donovan21f61272022-10-20 14:32:57 -04001205 results[pkgPath] = newPkg
Robert Findleyb15dac22022-08-30 14:40:12 -04001206 }
1207 } else {
Alan Donovan21f61272022-10-20 14:32:57 -04001208 results[pkgPath] = newPkg
Robert Findleyb15dac22022-08-30 14:40:12 -04001209 }
1210 }
1211 })
1212 return results, nil
1213}
1214
1215func (s *snapshot) GoModForFile(uri span.URI) span.URI {
1216 return moduleForURI(s.workspace.activeModFiles, uri)
1217}
1218
1219func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
1220 var match span.URI
1221 for modURI := range modFiles {
1222 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
1223 continue
1224 }
1225 if len(modURI) > len(match) {
1226 match = modURI
1227 }
1228 }
1229 return match
1230}
1231
Alan Donovan85bf7a82022-11-18 12:03:11 -05001232func (s *snapshot) getMetadata(id PackageID) *source.Metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001233 s.mu.Lock()
1234 defer s.mu.Unlock()
1235
1236 return s.meta.metadata[id]
1237}
1238
1239// clearShouldLoad clears package IDs that no longer need to be reloaded after
1240// scopes has been loaded.
Robert Findleyb2533142022-10-10 13:50:45 -04001241func (s *snapshot) clearShouldLoad(scopes ...loadScope) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001242 s.mu.Lock()
1243 defer s.mu.Unlock()
1244
1245 for _, scope := range scopes {
1246 switch scope := scope.(type) {
Robert Findleyb2533142022-10-10 13:50:45 -04001247 case packageLoadScope:
1248 scopePath := PackagePath(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -04001249 var toDelete []PackageID
1250 for id, pkgPaths := range s.shouldLoad {
1251 for _, pkgPath := range pkgPaths {
Robert Findleyb2533142022-10-10 13:50:45 -04001252 if pkgPath == scopePath {
Robert Findleyb15dac22022-08-30 14:40:12 -04001253 toDelete = append(toDelete, id)
1254 }
1255 }
1256 }
1257 for _, id := range toDelete {
1258 delete(s.shouldLoad, id)
1259 }
Robert Findleyb2533142022-10-10 13:50:45 -04001260 case fileLoadScope:
Robert Findleyb15dac22022-08-30 14:40:12 -04001261 uri := span.URI(scope)
1262 ids := s.meta.ids[uri]
1263 for _, id := range ids {
1264 delete(s.shouldLoad, id)
1265 }
1266 }
1267 }
1268}
1269
1270// noValidMetadataForURILocked reports whether there is any valid metadata for
1271// the given URI.
1272func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
Alan Donovanff22fab2022-11-18 14:47:36 -05001273 for _, id := range s.meta.ids[uri] {
Robert Findley5a4eba52022-11-03 18:28:39 -04001274 if _, ok := s.meta.metadata[id]; ok {
Robert Findleyb15dac22022-08-30 14:40:12 -04001275 return false
1276 }
1277 }
1278 return true
1279}
1280
1281func (s *snapshot) isWorkspacePackage(id PackageID) bool {
1282 s.mu.Lock()
1283 defer s.mu.Unlock()
1284
1285 _, ok := s.workspacePackages[id]
1286 return ok
1287}
1288
1289func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
1290 f := s.view.getFile(uri)
1291
1292 s.mu.Lock()
1293 defer s.mu.Unlock()
1294
1295 result, _ := s.files.Get(f.URI())
1296 return result
1297}
1298
1299// GetVersionedFile returns a File for the given URI. If the file is unknown it
1300// is added to the managed set.
1301//
1302// GetVersionedFile succeeds even if the file does not exist. A non-nil error return
1303// indicates some type of internal error, for example if ctx is cancelled.
1304func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
1305 f := s.view.getFile(uri)
1306
1307 s.mu.Lock()
1308 defer s.mu.Unlock()
1309 return s.getFileLocked(ctx, f)
1310}
1311
1312// GetFile implements the fileSource interface by wrapping GetVersionedFile.
1313func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
1314 return s.GetVersionedFile(ctx, uri)
1315}
1316
1317func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
1318 if fh, ok := s.files.Get(f.URI()); ok {
1319 return fh, nil
1320 }
1321
Robert Findleye8a70a52022-11-28 14:58:06 -05001322 fh, err := s.view.cache.getFile(ctx, f.URI()) // read the file
Robert Findleyb15dac22022-08-30 14:40:12 -04001323 if err != nil {
1324 return nil, err
1325 }
1326 closed := &closedFile{fh}
1327 s.files.Set(f.URI(), closed)
1328 return closed, nil
1329}
1330
1331func (s *snapshot) IsOpen(uri span.URI) bool {
1332 s.mu.Lock()
1333 defer s.mu.Unlock()
1334 return s.isOpenLocked(uri)
1335
1336}
1337
1338func (s *snapshot) openFiles() []source.VersionedFileHandle {
1339 s.mu.Lock()
1340 defer s.mu.Unlock()
1341
1342 var open []source.VersionedFileHandle
1343 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1344 if isFileOpen(fh) {
1345 open = append(open, fh)
1346 }
1347 })
1348 return open
1349}
1350
1351func (s *snapshot) isOpenLocked(uri span.URI) bool {
1352 fh, _ := s.files.Get(uri)
1353 return isFileOpen(fh)
1354}
1355
1356func isFileOpen(fh source.VersionedFileHandle) bool {
1357 _, open := fh.(*overlay)
1358 return open
1359}
1360
1361func (s *snapshot) awaitLoaded(ctx context.Context) error {
1362 loadErr := s.awaitLoadedAllErrors(ctx)
1363
Robert Findley5a4eba52022-11-03 18:28:39 -04001364 // TODO(rfindley): eliminate this function as part of simplifying
1365 // CriticalErrors.
Robert Findleyb15dac22022-08-30 14:40:12 -04001366 if loadErr != nil {
1367 return loadErr.MainError
1368 }
1369 return nil
1370}
1371
1372func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
1373 if wsErr := s.workspace.criticalError(ctx, s); wsErr != nil {
1374 return wsErr
1375 }
1376
1377 loadErr := s.awaitLoadedAllErrors(ctx)
1378 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
1379 return nil
1380 }
1381
1382 // Even if packages didn't fail to load, we still may want to show
1383 // additional warnings.
1384 if loadErr == nil {
1385 wsPkgs, _ := s.ActivePackages(ctx)
1386 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" {
1387 return &source.CriticalError{
1388 MainError: errors.New(msg),
1389 }
1390 }
1391 // Even if workspace packages were returned, there still may be an error
1392 // with the user's workspace layout. Workspace packages that only have the
1393 // ID "command-line-arguments" are usually a symptom of a bad workspace
1394 // configuration.
1395 //
1396 // TODO(rfindley): re-evaluate this heuristic.
1397 if containsCommandLineArguments(wsPkgs) {
1398 return s.workspaceLayoutError(ctx)
1399 }
1400 return nil
1401 }
1402
1403 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
1404 return s.workspaceLayoutError(ctx)
1405 }
1406 return loadErr
1407}
1408
1409const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
1410If you are using modules, please open your editor to a directory in your module.
1411If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
1412
1413func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
1414 if snapshot.ValidBuildConfiguration() {
1415 return ""
1416 }
1417 for _, pkg := range pkgs {
Alan Donovand39685a2022-11-22 13:18:01 -05001418 if hasMissingDependencies(pkg) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001419 return adHocPackagesWarning
1420 }
1421 }
1422 return ""
1423}
1424
Alan Donovand39685a2022-11-22 13:18:01 -05001425func hasMissingDependencies(pkg source.Package) bool {
1426 // We don't invalidate metadata for import deletions,
1427 // so check the package imports via the syntax tree
1428 // (not via types.Package.Imports, since it contains packages
1429 // synthesized under the vendoring-hostile assumption that
1430 // ImportPath equals PackagePath).
1431 //
1432 // rfindley says: it looks like this is intending to implement
1433 // a heuristic "if go list couldn't resolve import paths to
1434 // packages, then probably you're not in GOPATH or a module".
1435 // This is used to determine if we need to show a warning diagnostic.
1436 // It looks like this logic is implementing the heuristic that
1437 // "even if the metadata has a MissingDep, if the types.Package
1438 // doesn't need that dep anymore we shouldn't show the warning".
1439 // But either we're outside of GOPATH/Module, or we're not...
1440 //
1441 // If we invalidate the metadata for import deletions (which
1442 // should be fast) then we can simply return the blank entries
1443 // in DepsByImpPath.
1444 for _, f := range pkg.GetSyntax() {
1445 for _, imp := range f.Imports {
1446 importPath := source.UnquoteImportPath(imp)
1447 if _, err := pkg.ResolveImportPath(importPath); err != nil {
1448 return true
1449 }
1450 }
1451 }
1452 return false
1453}
1454
Robert Findleyb15dac22022-08-30 14:40:12 -04001455func containsCommandLineArguments(pkgs []source.Package) bool {
1456 for _, pkg := range pkgs {
1457 if source.IsCommandLineArguments(pkg.ID()) {
1458 return true
1459 }
1460 }
1461 return false
1462}
1463
1464func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
1465 // Do not return results until the snapshot's view has been initialized.
1466 s.AwaitInitialized(ctx)
1467
1468 // TODO(rfindley): Should we be more careful about returning the
1469 // initialization error? Is it possible for the initialization error to be
1470 // corrected without a successful reinitialization?
Alan Donovanff22fab2022-11-18 14:47:36 -05001471 if err := s.getInitializationError(); err != nil {
1472 return err
Robert Findleyb15dac22022-08-30 14:40:12 -04001473 }
1474
1475 // TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
1476 // cancelled context should have the same effect, so this preemptive handling
1477 // should not be necessary.
1478 //
1479 // Also: GetCriticalError ignores context cancellation errors. Should we be
1480 // returning nil here?
1481 if ctx.Err() != nil {
1482 return &source.CriticalError{MainError: ctx.Err()}
1483 }
1484
1485 // TODO(rfindley): reloading is not idempotent: if we try to reload or load
1486 // orphaned files below and fail, we won't try again. For that reason, we
1487 // could get different results from subsequent calls to this function, which
1488 // may cause critical errors to be suppressed.
1489
1490 if err := s.reloadWorkspace(ctx); err != nil {
1491 diags := s.extractGoCommandErrors(ctx, err)
1492 return &source.CriticalError{
1493 MainError: err,
1494 Diagnostics: diags,
1495 }
1496 }
1497
1498 if err := s.reloadOrphanedFiles(ctx); err != nil {
1499 diags := s.extractGoCommandErrors(ctx, err)
1500 return &source.CriticalError{
1501 MainError: err,
1502 Diagnostics: diags,
1503 }
1504 }
1505 return nil
1506}
1507
Alan Donovan051f03f2022-10-21 11:10:37 -04001508func (s *snapshot) getInitializationError() *source.CriticalError {
Robert Findleyb15dac22022-08-30 14:40:12 -04001509 s.mu.Lock()
1510 defer s.mu.Unlock()
1511
1512 return s.initializedErr
1513}
1514
1515func (s *snapshot) AwaitInitialized(ctx context.Context) {
1516 select {
1517 case <-ctx.Done():
1518 return
1519 case <-s.view.initialWorkspaceLoad:
1520 }
1521 // We typically prefer to run something as intensive as the IWL without
1522 // blocking. I'm not sure if there is a way to do that here.
1523 s.initialize(ctx, false)
1524}
1525
1526// reloadWorkspace reloads the metadata for all invalidated workspace packages.
1527func (s *snapshot) reloadWorkspace(ctx context.Context) error {
Robert Findleyb2533142022-10-10 13:50:45 -04001528 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001529 var seen map[PackagePath]bool
1530 s.mu.Lock()
1531 for _, pkgPaths := range s.shouldLoad {
1532 for _, pkgPath := range pkgPaths {
1533 if seen == nil {
1534 seen = make(map[PackagePath]bool)
1535 }
1536 if seen[pkgPath] {
1537 continue
1538 }
1539 seen[pkgPath] = true
Robert Findleyb2533142022-10-10 13:50:45 -04001540 scopes = append(scopes, packageLoadScope(pkgPath))
Robert Findleyb15dac22022-08-30 14:40:12 -04001541 }
1542 }
1543 s.mu.Unlock()
1544
1545 if len(scopes) == 0 {
1546 return nil
1547 }
1548
1549 // If the view's build configuration is invalid, we cannot reload by
1550 // package path. Just reload the directory instead.
1551 if !s.ValidBuildConfiguration() {
Robert Findleyb2533142022-10-10 13:50:45 -04001552 scopes = []loadScope{viewLoadScope("LOAD_INVALID_VIEW")}
Robert Findleyb15dac22022-08-30 14:40:12 -04001553 }
1554
1555 err := s.load(ctx, false, scopes...)
1556
1557 // Unless the context was canceled, set "shouldLoad" to false for all
1558 // of the metadata we attempted to load.
1559 if !errors.Is(err, context.Canceled) {
1560 s.clearShouldLoad(scopes...)
1561 }
1562
1563 return err
1564}
1565
1566func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
1567 // When we load ./... or a package path directly, we may not get packages
1568 // that exist only in overlays. As a workaround, we search all of the files
1569 // available in the snapshot and reload their metadata individually using a
1570 // file= query if the metadata is unavailable.
1571 files := s.orphanedFiles()
1572
1573 // Files without a valid package declaration can't be loaded. Don't try.
Robert Findleyb2533142022-10-10 13:50:45 -04001574 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001575 for _, file := range files {
1576 pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
1577 if err != nil {
1578 continue
1579 }
1580 if !pgf.File.Package.IsValid() {
1581 continue
1582 }
Robert Findleyb2533142022-10-10 13:50:45 -04001583
1584 scopes = append(scopes, fileLoadScope(file.URI()))
Robert Findleyb15dac22022-08-30 14:40:12 -04001585 }
1586
1587 if len(scopes) == 0 {
1588 return nil
1589 }
1590
1591 // The regtests match this exact log message, keep them in sync.
1592 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes))
1593 err := s.load(ctx, false, scopes...)
1594
1595 // If we failed to load some files, i.e. they have no metadata,
1596 // mark the failures so we don't bother retrying until the file's
1597 // content changes.
1598 //
1599 // TODO(rstambler): This may be an overestimate if the load stopped
1600 // early for an unrelated errors. Add a fallback?
1601 //
1602 // Check for context cancellation so that we don't incorrectly mark files
1603 // as unloadable, but don't return before setting all workspace packages.
1604 if ctx.Err() == nil && err != nil {
1605 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
1606 s.mu.Lock()
1607 for _, scope := range scopes {
Robert Findleyb2533142022-10-10 13:50:45 -04001608 uri := span.URI(scope.(fileLoadScope))
Robert Findleyb15dac22022-08-30 14:40:12 -04001609 if s.noValidMetadataForURILocked(uri) {
1610 s.unloadableFiles[uri] = struct{}{}
1611 }
1612 }
1613 s.mu.Unlock()
1614 }
1615 return nil
1616}
1617
1618func (s *snapshot) orphanedFiles() []source.VersionedFileHandle {
1619 s.mu.Lock()
1620 defer s.mu.Unlock()
1621
1622 var files []source.VersionedFileHandle
1623 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1624 // Don't try to reload metadata for go.mod files.
1625 if s.view.FileKind(fh) != source.Go {
1626 return
1627 }
1628 // If the URI doesn't belong to this view, then it's not in a workspace
1629 // package and should not be reloaded directly.
1630 if !source.InDir(s.view.folder.Filename(), uri.Filename()) {
1631 return
1632 }
1633 // If the file is not open and is in a vendor directory, don't treat it
1634 // like a workspace package.
1635 if _, ok := fh.(*overlay); !ok && inVendor(uri) {
1636 return
1637 }
1638 // Don't reload metadata for files we've already deemed unloadable.
1639 if _, ok := s.unloadableFiles[uri]; ok {
1640 return
1641 }
1642 if s.noValidMetadataForURILocked(uri) {
1643 files = append(files, fh)
1644 }
1645 })
1646 return files
1647}
1648
Robert Findleyb15dac22022-08-30 14:40:12 -04001649// TODO(golang/go#53756): this function needs to consider more than just the
1650// absolute URI, for example:
1651// - the position of /vendor/ with respect to the relevant module root
1652// - whether or not go.work is in use (as vendoring isn't supported in workspace mode)
1653//
1654// Most likely, each call site of inVendor needs to be reconsidered to
1655// understand and correctly implement the desired behavior.
1656func inVendor(uri span.URI) bool {
1657 if !strings.Contains(string(uri), "/vendor/") {
1658 return false
1659 }
1660 // Only packages in _subdirectories_ of /vendor/ are considered vendored
1661 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
1662 split := strings.Split(string(uri), "/vendor/")
1663 if len(split) < 2 {
1664 return false
1665 }
1666 return strings.Contains(split[1], "/")
1667}
1668
1669// unappliedChanges is a file source that handles an uncloned snapshot.
1670type unappliedChanges struct {
1671 originalSnapshot *snapshot
1672 changes map[span.URI]*fileChange
1673}
1674
1675func (ac *unappliedChanges) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
1676 if c, ok := ac.changes[uri]; ok {
1677 return c.fileHandle, nil
1678 }
1679 return ac.originalSnapshot.GetFile(ctx, uri)
1680}
1681
1682func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
1683 ctx, done := event.Start(ctx, "snapshot.clone")
1684 defer done()
1685
1686 newWorkspace, reinit := s.workspace.Clone(ctx, changes, &unappliedChanges{
1687 originalSnapshot: s,
1688 changes: changes,
1689 })
1690
1691 s.mu.Lock()
1692 defer s.mu.Unlock()
1693
1694 // If there is an initialization error and a vendor directory changed, try to
1695 // reinit.
1696 if s.initializedErr != nil {
1697 for uri := range changes {
1698 if inVendor(uri) {
1699 reinit = true
1700 break
1701 }
1702 }
1703 }
1704
1705 bgCtx, cancel := context.WithCancel(bgCtx)
1706 result := &snapshot{
Robert Findley0c71b562022-11-14 11:58:07 -05001707 sequenceID: s.sequenceID + 1,
1708 globalID: nextSnapshotID(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001709 store: s.store,
1710 view: s.view,
1711 backgroundCtx: bgCtx,
1712 cancel: cancel,
1713 builtin: s.builtin,
1714 initialized: s.initialized,
1715 initializedErr: s.initializedErr,
1716 packages: s.packages.Clone(),
1717 isActivePackageCache: s.isActivePackageCache.Clone(),
1718 actions: s.actions.Clone(),
1719 files: s.files.Clone(),
1720 parsedGoFiles: s.parsedGoFiles.Clone(),
1721 parseKeysByURI: s.parseKeysByURI.Clone(),
1722 symbolizeHandles: s.symbolizeHandles.Clone(),
1723 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
1724 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
1725 parseModHandles: s.parseModHandles.Clone(),
1726 parseWorkHandles: s.parseWorkHandles.Clone(),
1727 modTidyHandles: s.modTidyHandles.Clone(),
1728 modWhyHandles: s.modWhyHandles.Clone(),
Robert Findley7cda55e2022-11-22 12:09:11 -05001729 modVulnHandles: s.modVulnHandles.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001730 knownSubdirs: s.knownSubdirs.Clone(),
1731 workspace: newWorkspace,
1732 }
1733
1734 // The snapshot should be initialized if either s was uninitialized, or we've
1735 // detected a change that triggers reinitialization.
1736 if reinit {
1737 result.initialized = false
1738 }
1739
1740 // Create a lease on the new snapshot.
1741 // (Best to do this early in case the code below hides an
1742 // incref/decref operation that might destroy it prematurely.)
1743 release := result.Acquire()
1744
1745 // Copy the set of unloadable files.
1746 //
1747 // TODO(rfindley): this looks wrong. Shouldn't we clear unloadableFiles on
1748 // changes to environment or workspace layout, or more generally on any
1749 // metadata change?
Robert Findley23056f62022-11-03 19:06:31 -04001750 //
1751 // Maybe not, as major configuration changes cause a new view.
Robert Findleyb15dac22022-08-30 14:40:12 -04001752 for k, v := range s.unloadableFiles {
1753 result.unloadableFiles[k] = v
1754 }
1755
1756 // TODO(adonovan): merge loops over "changes".
1757 for uri := range changes {
1758 keys, ok := result.parseKeysByURI.Get(uri)
1759 if ok {
1760 for _, key := range keys {
1761 result.parsedGoFiles.Delete(key)
1762 }
1763 result.parseKeysByURI.Delete(uri)
1764 }
1765
1766 // Invalidate go.mod-related handles.
1767 result.modTidyHandles.Delete(uri)
1768 result.modWhyHandles.Delete(uri)
Robert Findley7cda55e2022-11-22 12:09:11 -05001769 result.modVulnHandles.Delete(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001770
1771 // Invalidate handles for cached symbols.
1772 result.symbolizeHandles.Delete(uri)
1773 }
1774
1775 // Add all of the known subdirectories, but don't update them for the
1776 // changed files. We need to rebuild the workspace module to know the
1777 // true set of known subdirectories, but we don't want to do that in clone.
1778 result.knownSubdirs = s.knownSubdirs.Clone()
1779 result.knownSubdirsPatternCache = s.knownSubdirsPatternCache
1780 for _, c := range changes {
1781 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
1782 }
1783
1784 // directIDs keeps track of package IDs that have directly changed.
1785 // It maps id->invalidateMetadata.
1786 directIDs := map[PackageID]bool{}
1787
1788 // Invalidate all package metadata if the workspace module has changed.
1789 if reinit {
1790 for k := range s.meta.metadata {
1791 directIDs[k] = true
1792 }
1793 }
1794
1795 // Compute invalidations based on file changes.
1796 anyImportDeleted := false // import deletions can resolve cycles
1797 anyFileOpenedOrClosed := false // opened files affect workspace packages
1798 anyFileAdded := false // adding a file can resolve missing dependencies
1799
1800 for uri, change := range changes {
1801 // The original FileHandle for this URI is cached on the snapshot.
1802 originalFH, _ := s.files.Get(uri)
1803 var originalOpen, newOpen bool
1804 _, originalOpen = originalFH.(*overlay)
1805 _, newOpen = change.fileHandle.(*overlay)
1806 anyFileOpenedOrClosed = anyFileOpenedOrClosed || (originalOpen != newOpen)
1807 anyFileAdded = anyFileAdded || (originalFH == nil && change.fileHandle != nil)
1808
1809 // If uri is a Go file, check if it has changed in a way that would
1810 // invalidate metadata. Note that we can't use s.view.FileKind here,
1811 // because the file type that matters is not what the *client* tells us,
1812 // but what the Go command sees.
1813 var invalidateMetadata, pkgFileChanged, importDeleted bool
1814 if strings.HasSuffix(uri.Filename(), ".go") {
1815 invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, originalFH, change.fileHandle)
1816 }
1817
1818 invalidateMetadata = invalidateMetadata || forceReloadMetadata || reinit
1819 anyImportDeleted = anyImportDeleted || importDeleted
1820
1821 // Mark all of the package IDs containing the given file.
1822 filePackageIDs := invalidatedPackageIDs(uri, s.meta.ids, pkgFileChanged)
1823 for id := range filePackageIDs {
1824 directIDs[id] = directIDs[id] || invalidateMetadata
1825 }
1826
1827 // Invalidate the previous modTidyHandle if any of the files have been
1828 // saved or if any of the metadata has been invalidated.
1829 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
1830 // TODO(maybe): Only delete mod handles for
1831 // which the withoutURI is relevant.
1832 // Requires reverse-engineering the go command. (!)
Robert Findleyb15dac22022-08-30 14:40:12 -04001833 result.modTidyHandles.Clear()
1834 result.modWhyHandles.Clear()
Robert Findley7cda55e2022-11-22 12:09:11 -05001835 result.modVulnHandles.Clear()
Robert Findleyb15dac22022-08-30 14:40:12 -04001836 }
1837
1838 result.parseModHandles.Delete(uri)
1839 result.parseWorkHandles.Delete(uri)
1840 // Handle the invalidated file; it may have new contents or not exist.
1841 if !change.exists {
1842 result.files.Delete(uri)
1843 } else {
1844 result.files.Set(uri, change.fileHandle)
1845 }
1846
1847 // Make sure to remove the changed file from the unloadable set.
1848 delete(result.unloadableFiles, uri)
1849 }
1850
1851 // Deleting an import can cause list errors due to import cycles to be
1852 // resolved. The best we can do without parsing the list error message is to
1853 // hope that list errors may have been resolved by a deleted import.
1854 //
1855 // We could do better by parsing the list error message. We already do this
1856 // to assign a better range to the list error, but for such critical
1857 // functionality as metadata, it's better to be conservative until it proves
1858 // impractical.
1859 //
1860 // We could also do better by looking at which imports were deleted and
1861 // trying to find cycles they are involved in. This fails when the file goes
1862 // from an unparseable state to a parseable state, as we don't have a
1863 // starting point to compare with.
1864 if anyImportDeleted {
1865 for id, metadata := range s.meta.metadata {
1866 if len(metadata.Errors) > 0 {
1867 directIDs[id] = true
1868 }
1869 }
1870 }
1871
1872 // Adding a file can resolve missing dependencies from existing packages.
1873 //
1874 // We could be smart here and try to guess which packages may have been
1875 // fixed, but until that proves necessary, just invalidate metadata for any
1876 // package with missing dependencies.
1877 if anyFileAdded {
1878 for id, metadata := range s.meta.metadata {
Alan Donovan21f61272022-10-20 14:32:57 -04001879 for _, impID := range metadata.DepsByImpPath {
1880 if impID == "" { // missing import
1881 directIDs[id] = true
1882 break
1883 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001884 }
1885 }
1886 }
1887
1888 // Invalidate reverse dependencies too.
1889 // idsToInvalidate keeps track of transitive reverse dependencies.
1890 // If an ID is present in the map, invalidate its types.
1891 // If an ID's value is true, invalidate its metadata too.
1892 idsToInvalidate := map[PackageID]bool{}
1893 var addRevDeps func(PackageID, bool)
1894 addRevDeps = func(id PackageID, invalidateMetadata bool) {
1895 current, seen := idsToInvalidate[id]
1896 newInvalidateMetadata := current || invalidateMetadata
1897
1898 // If we've already seen this ID, and the value of invalidate
1899 // metadata has not changed, we can return early.
1900 if seen && current == newInvalidateMetadata {
1901 return
1902 }
1903 idsToInvalidate[id] = newInvalidateMetadata
1904 for _, rid := range s.meta.importedBy[id] {
1905 addRevDeps(rid, invalidateMetadata)
1906 }
1907 }
1908 for id, invalidateMetadata := range directIDs {
1909 addRevDeps(id, invalidateMetadata)
1910 }
1911
Alan Donovanff22fab2022-11-18 14:47:36 -05001912 // Delete invalidated package type information.
1913 for id := range idsToInvalidate {
1914 for _, mode := range source.AllParseModes {
1915 key := packageKey{mode, id}
1916 result.packages.Delete(key)
1917 }
1918 }
1919
1920 // Delete invalidated analysis actions.
1921 var actionsToDelete []actionKey
1922 result.actions.Range(func(k, _ interface{}) {
1923 key := k.(actionKey)
1924 if _, ok := idsToInvalidate[key.pkgid]; ok {
1925 actionsToDelete = append(actionsToDelete, key)
1926 }
1927 })
1928 for _, key := range actionsToDelete {
1929 result.actions.Delete(key)
1930 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001931
1932 // If a file has been deleted, we must delete metadata for all packages
1933 // containing that file.
1934 //
1935 // TODO(rfindley): why not keep invalid metadata in this case? If we
1936 // otherwise allow operate on invalid metadata, why not continue to do so,
1937 // skipping the missing file?
1938 skipID := map[PackageID]bool{}
1939 for _, c := range changes {
1940 if c.exists {
1941 continue
1942 }
1943 // The file has been deleted.
1944 if ids, ok := s.meta.ids[c.fileHandle.URI()]; ok {
1945 for _, id := range ids {
1946 skipID[id] = true
1947 }
1948 }
1949 }
1950
1951 // Any packages that need loading in s still need loading in the new
1952 // snapshot.
1953 for k, v := range s.shouldLoad {
1954 if result.shouldLoad == nil {
1955 result.shouldLoad = make(map[PackageID][]PackagePath)
1956 }
1957 result.shouldLoad[k] = v
1958 }
1959
Robert Findleyb15dac22022-08-30 14:40:12 -04001960 // Compute which metadata updates are required. We only need to invalidate
1961 // packages directly containing the affected file, and only if it changed in
1962 // a relevant way.
Alan Donovan85bf7a82022-11-18 12:03:11 -05001963 metadataUpdates := make(map[PackageID]*source.Metadata)
Robert Findleyb15dac22022-08-30 14:40:12 -04001964 for k, v := range s.meta.metadata {
1965 invalidateMetadata := idsToInvalidate[k]
1966
1967 // For metadata that has been newly invalidated, capture package paths
1968 // requiring reloading in the shouldLoad map.
Alan Donovan3c3713e2022-11-10 13:02:38 -05001969 if invalidateMetadata && !source.IsCommandLineArguments(v.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001970 if result.shouldLoad == nil {
1971 result.shouldLoad = make(map[PackageID][]PackagePath)
1972 }
1973 needsReload := []PackagePath{v.PkgPath}
1974 if v.ForTest != "" && v.ForTest != v.PkgPath {
1975 // When reloading test variants, always reload their ForTest package as
1976 // well. Otherwise, we may miss test variants in the resulting load.
1977 //
1978 // TODO(rfindley): is this actually sufficient? Is it possible that
1979 // other test variants may be invalidated? Either way, we should
1980 // determine exactly what needs to be reloaded here.
1981 needsReload = append(needsReload, v.ForTest)
1982 }
1983 result.shouldLoad[k] = needsReload
1984 }
1985
1986 // Check whether the metadata should be deleted.
Robert Findley5a4eba52022-11-03 18:28:39 -04001987 if skipID[k] || invalidateMetadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001988 metadataUpdates[k] = nil
1989 continue
1990 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001991 }
1992
1993 // Update metadata, if necessary.
1994 result.meta = s.meta.Clone(metadataUpdates)
1995
1996 // Update workspace and active packages, if necessary.
1997 if result.meta != s.meta || anyFileOpenedOrClosed {
1998 result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
1999 result.resetIsActivePackageLocked()
2000 } else {
2001 result.workspacePackages = s.workspacePackages
2002 }
2003
2004 // Don't bother copying the importedBy graph,
2005 // as it changes each time we update metadata.
2006
Robert Findley5a4eba52022-11-03 18:28:39 -04002007 // TODO(rfindley): consolidate the this workspace mode detection with
2008 // workspace invalidation.
2009 workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
2010
Robert Findleyb15dac22022-08-30 14:40:12 -04002011 // If the snapshot's workspace mode has changed, the packages loaded using
2012 // the previous mode are no longer relevant, so clear them out.
2013 if workspaceModeChanged {
2014 result.workspacePackages = map[PackageID]PackagePath{}
2015 }
2016 result.dumpWorkspace("clone")
2017 return result, release
2018}
2019
2020// invalidatedPackageIDs returns all packages invalidated by a change to uri.
2021// If we haven't seen this URI before, we guess based on files in the same
2022// directory. This is of course incorrect in build systems where packages are
2023// not organized by directory.
2024//
2025// If packageFileChanged is set, the file is either a new file, or has a new
2026// package name. In this case, all known packages in the directory will be
2027// invalidated.
2028func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
2029 invalidated := make(map[PackageID]struct{})
2030
2031 // At a minimum, we invalidate packages known to contain uri.
2032 for _, id := range known[uri] {
2033 invalidated[id] = struct{}{}
2034 }
2035
2036 // If the file didn't move to a new package, we should only invalidate the
2037 // packages it is currently contained inside.
2038 if !packageFileChanged && len(invalidated) > 0 {
2039 return invalidated
2040 }
2041
2042 // This is a file we don't yet know about, or which has moved packages. Guess
2043 // relevant packages by considering files in the same directory.
2044
2045 // Cache of FileInfo to avoid unnecessary stats for multiple files in the
2046 // same directory.
2047 stats := make(map[string]struct {
2048 os.FileInfo
2049 error
2050 })
2051 getInfo := func(dir string) (os.FileInfo, error) {
2052 if res, ok := stats[dir]; ok {
2053 return res.FileInfo, res.error
2054 }
2055 fi, err := os.Stat(dir)
2056 stats[dir] = struct {
2057 os.FileInfo
2058 error
2059 }{fi, err}
2060 return fi, err
2061 }
2062 dir := filepath.Dir(uri.Filename())
2063 fi, err := getInfo(dir)
2064 if err == nil {
2065 // Aggregate all possibly relevant package IDs.
2066 for knownURI, ids := range known {
2067 knownDir := filepath.Dir(knownURI.Filename())
2068 knownFI, err := getInfo(knownDir)
2069 if err != nil {
2070 continue
2071 }
2072 if os.SameFile(fi, knownFI) {
2073 for _, id := range ids {
2074 invalidated[id] = struct{}{}
2075 }
2076 }
2077 }
2078 }
2079 return invalidated
2080}
2081
Robert Findleyb15dac22022-08-30 14:40:12 -04002082// fileWasSaved reports whether the FileHandle passed in has been saved. It
2083// accomplishes this by checking to see if the original and current FileHandles
2084// are both overlays, and if the current FileHandle is saved while the original
2085// FileHandle was not saved.
2086func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
2087 c, ok := currentFH.(*overlay)
2088 if !ok || c == nil {
2089 return true
2090 }
2091 o, ok := originalFH.(*overlay)
2092 if !ok || o == nil {
2093 return c.saved
2094 }
2095 return !o.saved && c.saved
2096}
2097
2098// metadataChanges detects features of the change from oldFH->newFH that may
2099// affect package metadata.
2100//
2101// It uses lockedSnapshot to access cached parse information. lockedSnapshot
2102// must be locked.
2103//
2104// The result parameters have the following meaning:
2105// - invalidate means that package metadata for packages containing the file
2106// should be invalidated.
2107// - pkgFileChanged means that the file->package associates for the file have
2108// changed (possibly because the file is new, or because its package name has
2109// changed).
2110// - importDeleted means that an import has been deleted, or we can't
2111// determine if an import was deleted due to errors.
2112func metadataChanges(ctx context.Context, lockedSnapshot *snapshot, oldFH, newFH source.FileHandle) (invalidate, pkgFileChanged, importDeleted bool) {
2113 if oldFH == nil || newFH == nil { // existential changes
2114 changed := (oldFH == nil) != (newFH == nil)
2115 return changed, changed, (newFH == nil) // we don't know if an import was deleted
2116 }
2117
2118 // If the file hasn't changed, there's no need to reload.
2119 if oldFH.FileIdentity() == newFH.FileIdentity() {
2120 return false, false, false
2121 }
2122
2123 // Parse headers to compare package names and imports.
2124 oldHead, oldErr := peekOrParse(ctx, lockedSnapshot, oldFH, source.ParseHeader)
2125 newHead, newErr := peekOrParse(ctx, lockedSnapshot, newFH, source.ParseHeader)
2126
2127 if oldErr != nil || newErr != nil {
2128 // TODO(rfindley): we can get here if newFH does not exists. There is
2129 // asymmetry here, in that newFH may be non-nil even if the underlying file
2130 // does not exist.
2131 //
2132 // We should not produce a non-nil filehandle for a file that does not exist.
2133 errChanged := (oldErr == nil) != (newErr == nil)
2134 return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted
2135 }
2136
2137 // `go list` fails completely if the file header cannot be parsed. If we go
2138 // from a non-parsing state to a parsing state, we should reload.
2139 if oldHead.ParseErr != nil && newHead.ParseErr == nil {
2140 return true, true, true // We don't know what changed, so fall back on full invalidation.
2141 }
2142
2143 // If a package name has changed, the set of package imports may have changed
2144 // in ways we can't detect here. Assume an import has been deleted.
2145 if oldHead.File.Name.Name != newHead.File.Name.Name {
2146 return true, true, true
2147 }
2148
2149 // Check whether package imports have changed. Only consider potentially
2150 // valid imports paths.
2151 oldImports := validImports(oldHead.File.Imports)
2152 newImports := validImports(newHead.File.Imports)
2153
2154 for path := range newImports {
2155 if _, ok := oldImports[path]; ok {
2156 delete(oldImports, path)
2157 } else {
2158 invalidate = true // a new, potentially valid import was added
2159 }
2160 }
2161
2162 if len(oldImports) > 0 {
2163 invalidate = true
2164 importDeleted = true
2165 }
2166
2167 // If the change does not otherwise invalidate metadata, get the full ASTs in
2168 // order to check magic comments.
2169 //
2170 // Note: if this affects performance we can probably avoid parsing in the
2171 // common case by first scanning the source for potential comments.
2172 if !invalidate {
2173 origFull, oldErr := peekOrParse(ctx, lockedSnapshot, oldFH, source.ParseFull)
2174 currFull, newErr := peekOrParse(ctx, lockedSnapshot, newFH, source.ParseFull)
2175 if oldErr == nil && newErr == nil {
2176 invalidate = magicCommentsChanged(origFull.File, currFull.File)
2177 } else {
2178 // At this point, we shouldn't ever fail to produce a ParsedGoFile, as
2179 // we're already past header parsing.
2180 bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr)
2181 }
2182 }
2183
2184 return invalidate, pkgFileChanged, importDeleted
2185}
2186
2187// peekOrParse returns the cached ParsedGoFile if it exists,
2188// otherwise parses without populating the cache.
2189//
2190// It returns an error if the file could not be read (note that parsing errors
2191// are stored in ParsedGoFile.ParseErr).
2192//
2193// lockedSnapshot must be locked.
2194func peekOrParse(ctx context.Context, lockedSnapshot *snapshot, fh source.FileHandle, mode source.ParseMode) (*source.ParsedGoFile, error) {
2195 // Peek in the cache without populating it.
2196 // We do this to reduce retained heap, not work.
2197 if parsed, _ := lockedSnapshot.peekParseGoLocked(fh, mode); parsed != nil {
2198 return parsed, nil // cache hit
2199 }
2200 return parseGoImpl(ctx, token.NewFileSet(), fh, mode)
2201}
2202
2203func magicCommentsChanged(original *ast.File, current *ast.File) bool {
2204 oldComments := extractMagicComments(original)
2205 newComments := extractMagicComments(current)
2206 if len(oldComments) != len(newComments) {
2207 return true
2208 }
2209 for i := range oldComments {
2210 if oldComments[i] != newComments[i] {
2211 return true
2212 }
2213 }
2214 return false
2215}
2216
2217// validImports extracts the set of valid import paths from imports.
2218func validImports(imports []*ast.ImportSpec) map[string]struct{} {
2219 m := make(map[string]struct{})
2220 for _, spec := range imports {
2221 if path := spec.Path.Value; validImportPath(path) {
2222 m[path] = struct{}{}
2223 }
2224 }
2225 return m
2226}
2227
2228func validImportPath(path string) bool {
2229 path, err := strconv.Unquote(path)
2230 if err != nil {
2231 return false
2232 }
2233 if path == "" {
2234 return false
2235 }
2236 if path[len(path)-1] == '/' {
2237 return false
2238 }
2239 return true
2240}
2241
2242var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
2243
2244// extractMagicComments finds magic comments that affect metadata in f.
2245func extractMagicComments(f *ast.File) []string {
2246 var results []string
2247 for _, cg := range f.Comments {
2248 for _, c := range cg.List {
2249 if buildConstraintOrEmbedRe.MatchString(c.Text) {
2250 results = append(results, c.Text)
2251 }
2252 }
2253 }
2254 return results
2255}
2256
2257func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) {
2258 s.AwaitInitialized(ctx)
2259
2260 s.mu.Lock()
2261 builtin := s.builtin
2262 s.mu.Unlock()
2263
2264 if builtin == "" {
2265 return nil, fmt.Errorf("no builtin package for view %s", s.view.name)
2266 }
2267
2268 fh, err := s.GetFile(ctx, builtin)
2269 if err != nil {
2270 return nil, err
2271 }
2272 return s.ParseGo(ctx, fh, source.ParseFull)
2273}
2274
2275func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) bool {
2276 s.mu.Lock()
2277 defer s.mu.Unlock()
2278 // We should always get the builtin URI in a canonical form, so use simple
2279 // string comparison here. span.CompareURI is too expensive.
2280 return uri == s.builtin
2281}
2282
2283func (s *snapshot) setBuiltin(path string) {
2284 s.mu.Lock()
2285 defer s.mu.Unlock()
2286
2287 s.builtin = span.URIFromPath(path)
2288}
2289
2290// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
2291// bypasses any existing gopls.mod.
2292func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) {
2293 allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0)
2294 if err != nil {
2295 return nil, err
2296 }
2297 return buildWorkspaceModFile(ctx, allModules, s)
2298}
2299
2300// TODO(rfindley): move this to workspace.go
2301func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
2302 file := &modfile.File{}
2303 file.AddModuleStmt("gopls-workspace")
2304 // Track the highest Go version, to be set on the workspace module.
2305 // Fall back to 1.12 -- old versions insist on having some version.
2306 goVersion := "1.12"
2307
2308 paths := map[string]span.URI{}
2309 excludes := map[string][]string{}
2310 var sortedModURIs []span.URI
2311 for uri := range modFiles {
2312 sortedModURIs = append(sortedModURIs, uri)
2313 }
2314 sort.Slice(sortedModURIs, func(i, j int) bool {
2315 return sortedModURIs[i] < sortedModURIs[j]
2316 })
2317 for _, modURI := range sortedModURIs {
2318 fh, err := fs.GetFile(ctx, modURI)
2319 if err != nil {
2320 return nil, err
2321 }
2322 content, err := fh.Read()
2323 if err != nil {
2324 return nil, err
2325 }
2326 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
2327 if err != nil {
2328 return nil, err
2329 }
2330 if file == nil || parsed.Module == nil {
2331 return nil, fmt.Errorf("no module declaration for %s", modURI)
2332 }
2333 // Prepend "v" to go versions to make them valid semver.
2334 if parsed.Go != nil && semver.Compare("v"+goVersion, "v"+parsed.Go.Version) < 0 {
2335 goVersion = parsed.Go.Version
2336 }
2337 path := parsed.Module.Mod.Path
2338 if seen, ok := paths[path]; ok {
2339 return nil, fmt.Errorf("found module %q multiple times in the workspace, at:\n\t%q\n\t%q", path, seen, modURI)
2340 }
2341 paths[path] = modURI
2342 // If the module's path includes a major version, we expect it to have
2343 // a matching major version.
2344 _, majorVersion, _ := module.SplitPathVersion(path)
2345 if majorVersion == "" {
2346 majorVersion = "/v0"
2347 }
2348 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
2349 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
2350 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
2351 return nil, err
2352 }
2353 for _, exclude := range parsed.Exclude {
2354 excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version)
2355 }
2356 }
2357 if goVersion != "" {
2358 file.AddGoStmt(goVersion)
2359 }
2360 // Go back through all of the modules to handle any of their replace
2361 // statements.
2362 for _, modURI := range sortedModURIs {
2363 fh, err := fs.GetFile(ctx, modURI)
2364 if err != nil {
2365 return nil, err
2366 }
2367 content, err := fh.Read()
2368 if err != nil {
2369 return nil, err
2370 }
2371 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
2372 if err != nil {
2373 return nil, err
2374 }
2375 // If any of the workspace modules have replace directives, they need
2376 // to be reflected in the workspace module.
2377 for _, rep := range parsed.Replace {
2378 // Don't replace any modules that are in our workspace--we should
2379 // always use the version in the workspace.
2380 if _, ok := paths[rep.Old.Path]; ok {
2381 continue
2382 }
2383 newPath := rep.New.Path
2384 newVersion := rep.New.Version
2385 // If a replace points to a module in the workspace, make sure we
2386 // direct it to version of the module in the workspace.
2387 if m, ok := paths[rep.New.Path]; ok {
2388 newPath = dirURI(m).Filename()
2389 newVersion = ""
2390 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
2391 // Make any relative paths absolute.
2392 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
2393 }
2394 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
2395 return nil, err
2396 }
2397 }
2398 }
2399 for path, versions := range excludes {
2400 for _, version := range versions {
2401 file.AddExclude(path, version)
2402 }
2403 }
2404 file.SortBlocks()
2405 return file, nil
2406}
2407
2408func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) {
2409 allSums := map[module.Version][]string{}
2410 for modURI := range modFiles {
2411 // TODO(rfindley): factor out this pattern into a uripath package.
2412 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum"))
2413 fh, err := fs.GetFile(ctx, sumURI)
2414 if err != nil {
2415 continue
2416 }
2417 data, err := fh.Read()
2418 if os.IsNotExist(err) {
2419 continue
2420 }
2421 if err != nil {
2422 return nil, fmt.Errorf("reading go sum: %w", err)
2423 }
2424 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil {
2425 return nil, err
2426 }
2427 }
2428 // This logic to write go.sum is copied (with minor modifications) from
2429 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
2430 var mods []module.Version
2431 for m := range allSums {
2432 mods = append(mods, m)
2433 }
2434 module.Sort(mods)
2435
2436 var buf bytes.Buffer
2437 for _, m := range mods {
2438 list := allSums[m]
2439 sort.Strings(list)
2440 // Note (rfindley): here we add all sum lines without verification, because
2441 // the assumption is that if they come from a go.sum file, they are
2442 // trusted.
2443 for _, h := range list {
2444 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
2445 }
2446 }
2447 return buf.Bytes(), nil
2448}
2449
2450// readGoSum is copied (with minor modifications) from
2451// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
2452func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
2453 lineno := 0
2454 for len(data) > 0 {
2455 var line []byte
2456 lineno++
2457 i := bytes.IndexByte(data, '\n')
2458 if i < 0 {
2459 line, data = data, nil
2460 } else {
2461 line, data = data[:i], data[i+1:]
2462 }
2463 f := strings.Fields(string(line))
2464 if len(f) == 0 {
2465 // blank line; skip it
2466 continue
2467 }
2468 if len(f) != 3 {
2469 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
2470 }
2471 mod := module.Version{Path: f[0], Version: f[1]}
2472 dst[mod] = append(dst[mod], f[2])
2473 }
2474 return nil
2475}