diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go index 56a7af84bf..24b89dbbfd 100644 --- a/gopls/internal/regtest/workspace/workspace_test.go +++ b/gopls/internal/regtest/workspace/workspace_test.go @@ -913,3 +913,41 @@ func main() { ) }) } + +// Sometimes users may have their module cache within the workspace. +// We shouldn't consider any module in the module cache to be in the workspace. +func TestGOMODCACHEInWorkspace(t *testing.T) { + const mod = ` +-- a/go.mod -- +module a.com + +go 1.12 +-- a/a.go -- +package a + +func _() {} +-- a/c/c.go -- +package c +-- gopath/src/b/b.go -- +package b +-- gopath/pkg/mod/example.com/go.mod -- +module example.com + +go 1.12 +-- gopath/pkg/mod/example.com/main.go -- +package main +` + WithOptions( + EditorConfig{Env: map[string]string{ + "GOPATH": filepath.FromSlash("$SANDBOX_WORKDIR/gopath"), + }}, + Modes(Singleton), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.Await( + // Confirm that the build configuration is seen as valid, + // even though there are technically multiple go.mod files in the + // worskpace. + LogMatching(protocol.Info, ".*valid build configuration = true.*", 1, false), + ) + }) +} diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index 8057fd11ad..f1dc4ef8e3 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -173,14 +173,14 @@ func (s *Session) createView(ctx context.Context, name string, folder, tempWorks } root := folder if options.ExpandWorkspaceToModule { - root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(options), options.ExperimentalWorkspaceModule) + root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), options.ExperimentalWorkspaceModule) if err != nil { return nil, nil, func() {}, err } } // Build the gopls workspace, collecting active modules in the view. - workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule) + workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule) if err != nil { return nil, nil, func() {}, err } diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index 1eee2de066..f67f4cd86e 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -1905,8 +1905,8 @@ func (s *snapshot) setBuiltin(path string) { // BuildGoplsMod generates a go.mod file for all modules in the workspace. It // bypasses any existing gopls.mod. -func BuildGoplsMod(ctx context.Context, root span.URI, s source.Snapshot) (*modfile.File, error) { - allModules, err := findModules(root, pathExcludedByFilterFunc(s.View().Options()), 0) +func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) { + allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0) if err != nil { return nil, err } diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 63040dfe8e..c26196df93 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -343,7 +343,7 @@ func (s *snapshot) locateTemplateFiles(ctx context.Context) { if err != nil { return err } - if strings.HasSuffix(filepath.Ext(path), "tmpl") && !pathExcludedByFilter(path, s.view.options) && + if strings.HasSuffix(filepath.Ext(path), "tmpl") && !pathExcludedByFilter(path, dir, s.view.gomodcache, s.view.options) && !fi.IsDir() { k := span.URIFromPath(path) fh, err := s.GetVersionedFile(ctx, k) @@ -371,7 +371,7 @@ func (v *View) contains(uri span.URI) bool { } // Filters are applied relative to the workspace folder. if inFolder { - return !pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.Options()) + return !pathExcludedByFilter(strings.TrimPrefix(uri.Filename(), v.folder.Filename()), v.rootURI.Filename(), v.gomodcache, v.Options()) } return true } @@ -1017,24 +1017,25 @@ func (v *View) allFilesExcluded(pkg *packages.Package) bool { if !strings.HasPrefix(f, folder) { return false } - if !pathExcludedByFilter(strings.TrimPrefix(f, folder), opts) { + if !pathExcludedByFilter(strings.TrimPrefix(f, folder), v.rootURI.Filename(), v.gomodcache, opts) { return false } } return true } -func pathExcludedByFilterFunc(opts *source.Options) func(string) bool { +func pathExcludedByFilterFunc(root, gomodcache string, opts *source.Options) func(string) bool { return func(path string) bool { - return pathExcludedByFilter(path, opts) + return pathExcludedByFilter(path, root, gomodcache, opts) } } -func pathExcludedByFilter(path string, opts *source.Options) bool { +func pathExcludedByFilter(path, root, gomodcache string, opts *source.Options) bool { path = strings.TrimPrefix(filepath.ToSlash(path), "/") + gomodcache = strings.TrimPrefix(filepath.ToSlash(strings.TrimPrefix(gomodcache, root)), "/") excluded := false - for _, filter := range opts.DirectoryFilters { + for _, filter := range append(opts.DirectoryFilters, "-"+gomodcache) { op, prefix := filter[0], filter[1:] // Non-empty prefixes have to be precise directory matches. if prefix != "" { diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go index 802215a5aa..f0923d4617 100644 --- a/internal/lsp/cache/view_test.go +++ b/internal/lsp/cache/view_test.go @@ -161,12 +161,12 @@ func TestFilters(t *testing.T) { opts := &source.Options{} opts.DirectoryFilters = tt.filters for _, inc := range tt.included { - if pathExcludedByFilter(inc, opts) { + if pathExcludedByFilter(inc, "root", "root/gopath/pkg/mod", opts) { t.Errorf("filters %q excluded %v, wanted included", tt.filters, inc) } } for _, exc := range tt.excluded { - if !pathExcludedByFilter(exc, opts) { + if !pathExcludedByFilter(exc, "root", "root/gopath/pkg/mod", opts) { t.Errorf("filters %q included %v, wanted excluded", tt.filters, exc) } } diff --git a/internal/lsp/command.go b/internal/lsp/command.go index e877ac1a2e..47a3577b67 100644 --- a/internal/lsp/command.go +++ b/internal/lsp/command.go @@ -18,7 +18,6 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/lsp/cache" "golang.org/x/tools/internal/lsp/command" "golang.org/x/tools/internal/lsp/debug" "golang.org/x/tools/internal/lsp/protocol" @@ -647,7 +646,7 @@ func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIA v := views[0] snapshot, release := v.Snapshot(ctx) defer release() - modFile, err := cache.BuildGoplsMod(ctx, snapshot.View().Folder(), snapshot) + modFile, err := snapshot.BuildGoplsMod(ctx) if err != nil { return errors.Errorf("getting workspace mod file: %w", err) } diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go index 501d32c180..989357d62b 100644 --- a/internal/lsp/fake/editor.go +++ b/internal/lsp/fake/editor.go @@ -206,9 +206,11 @@ func (e *Editor) Client() *Client { func (e *Editor) overlayEnv() map[string]string { env := make(map[string]string) for k, v := range e.defaultEnv { + v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename()) env[k] = v } for k, v := range e.Config.Env { + v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename()) env[k] = v } return env diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index a139c50249..3a6794c47c 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -160,6 +160,10 @@ type Snapshot interface { // GetCriticalError returns any critical errors in the workspace. GetCriticalError(ctx context.Context) *CriticalError + + // BuildGoplsMod generates a go.mod file for all modules in the workspace. + // It bypasses any existing gopls.mod. + BuildGoplsMod(ctx context.Context) (*modfile.File, error) } // PackageFilter sets how a package is filtered out from a set of packages