internal/lsp/cache: consider gopls.mod when finding workspace root

gopls.mod files should take precedence over go.mod files when finding a
workspace root. To allow this, implement our own algorithm for expanding
the workspace, rather than using the go command.

For golang/go#41837

Change-Id: I943c08bdbdbdd164f108e44bae95d2c659a6e21e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/263897
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
This commit is contained in:
Rob Findley 2020-10-19 22:06:43 -04:00 committed by Robert Findley
parent d463eb0e41
commit f239dba448
2 changed files with 93 additions and 8 deletions

View File

@ -126,7 +126,7 @@ type workspaceInformation struct {
}
type environmentVariables struct {
gocache, gopath, goprivate, gomodcache, gomod string
gocache, gopath, goprivate, gomodcache string
}
type workspaceMode int
@ -651,13 +651,15 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI,
tool, _ := exec.LookPath("gopackagesdriver")
hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
var modURI span.URI
if envVars.gomod != os.DevNull && envVars.gomod != "" {
modURI = span.URIFromPath(envVars.gomod)
}
root := folder
if options.ExpandWorkspaceToModule && modURI != "" {
root = span.URIFromPath(filepath.Dir(modURI.Filename()))
if options.ExpandWorkspaceToModule {
wsRoot, err := findWorkspaceRoot(ctx, root, s)
if err != nil {
return nil, err
}
if wsRoot != "" {
root = wsRoot
}
}
return &workspaceInformation{
hasGopackagesDriver: hasGopackagesDriver,
@ -669,6 +671,39 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI,
}, nil
}
func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource) (span.URI, error) {
for _, basename := range []string{"gopls.mod", "go.mod"} {
dir, err := findRootPattern(ctx, folder, basename, fs)
if err != nil {
return "", errors.Errorf("finding %s: %w", basename, err)
}
if dir != "" {
return dir, nil
}
}
return "", nil
}
func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) {
dir := folder.Filename()
for dir != "" {
target := filepath.Join(dir, basename)
exists, err := fileExists(ctx, span.URIFromPath(target), fs)
if err != nil {
return "", err
}
if exists {
return span.URIFromPath(dir), nil
}
next, _ := filepath.Split(dir)
if next == dir {
break
}
dir = next
}
return "", nil
}
// OS-specific path case check, for case-insensitive filesystems.
var checkPathCase = defaultCheckPathCase
@ -710,7 +745,6 @@ func (s *Session) getGoEnv(ctx context.Context, folder string, configEnv []strin
"GOPATH": &envVars.gopath,
"GOPRIVATE": &envVars.goprivate,
"GOMODCACHE": &envVars.gomodcache,
"GOMOD": &envVars.gomod,
}
// We can save ~200 ms by requesting only the variables we care about.
args := append([]string{"-json"}, imports.RequiredGoEnvVars...)

View File

@ -4,10 +4,14 @@
package cache
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/span"
)
func TestCaseInsensitiveFilesystem(t *testing.T) {
@ -43,3 +47,50 @@ func TestCaseInsensitiveFilesystem(t *testing.T) {
}
}
}
func TestFindWorkspaceRoot(t *testing.T) {
workspace := `
-- a/go.mod --
module a
-- a/x/x.go
package x
-- b/go.mod --
module b
-- b/c/go.mod --
module bc
-- d/gopls.mod --
module d-goplsworkspace
-- d/e/go.mod
module de
`
dir, err := fake.Tempdir(workspace)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
tests := []struct {
folder, want string
}{
// no module at root.
{"", ""},
{"a", "a"},
{"a/x", "a"},
{"b/c", "b/c"},
{"d", "d"},
{"d/e", "d"},
}
for _, test := range tests {
ctx := context.Background()
rel := fake.RelativeTo(dir)
folderURI := span.URIFromPath(rel.AbsPath(test.folder))
got, err := findWorkspaceRoot(ctx, folderURI, osFileSource{})
if err != nil {
t.Fatal(err)
}
if rel.RelPath(got.Filename()) != test.want {
t.Errorf("fileWorkspaceRoot(%q) = %q, want %q", test.folder, got, test.want)
}
}
}