internal/lsp: exclude the module cache from the workspace

This change treats the GOMODCACHE like a directory filter, and it
excludes any modules under the module cache from being considered part
of the workspace. This can happen when users open their entire GOPATH.

To do this, I had to propagate the view's root and gomodcache through
the exclusion functions, and I also had to add BuildGoplsMod to the
snapshot's interface.

Change-Id: Id80b359d73d09a525b380389917451e85357b784
Reviewed-on: https://go-review.googlesource.com/c/tools/+/326816
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Rebecca Stambler 2021-06-11 00:40:37 -04:00
parent 9a55cb1fbb
commit 4b484fb136
8 changed files with 59 additions and 15 deletions

View File

@ -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),
)
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 != "" {

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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