mirror of
https://github.com/golang/go
synced 2024-11-17 22:24:47 -07:00
internal/lsp: handle modifications to the workspace module
We should be able to smoothly add and remove modules from the workspace. This change moves the module-related fields from the view into the snapshot so that they can be easily modified and recomputed. The workspace module is now a workspaceModuleHandle. Updates golang/go#32394 Change-Id: I6ade7f223cc6070a29b6021b825586b753a0daf1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/254940 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:
parent
e94ab72881
commit
78fed78f7d
@ -74,7 +74,7 @@ func main() {
|
|||||||
|
|
||||||
// Reproduce golang/go#40269 by deleting and recreating main.go.
|
// Reproduce golang/go#40269 by deleting and recreating main.go.
|
||||||
t.Run("delete main.go", func(t *testing.T) {
|
t.Run("delete main.go", func(t *testing.T) {
|
||||||
t.Skipf("This test will be flaky until golang/go#40269 is resolved.")
|
t.Skip("This test will be flaky until golang/go#40269 is resolved.")
|
||||||
|
|
||||||
withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
|
withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
|
||||||
goModContent := env.ReadWorkspaceFile("go.mod")
|
goModContent := env.ReadWorkspaceFile("go.mod")
|
||||||
|
@ -185,7 +185,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
runner.Run(t, missing, func(t *testing.T, env *Env) {
|
runner.Run(t, missing, func(t *testing.T, env *Env) {
|
||||||
t.Skipf("the initial workspace load fails and never retries")
|
t.Skip("the initial workspace load fails and never retries")
|
||||||
|
|
||||||
env.Await(
|
env.Await(
|
||||||
env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""),
|
env.DiagnosticAtRegexp("a/a.go", "\"mod.com/c\""),
|
||||||
@ -586,9 +586,9 @@ func main() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reproduces golang/go#37069.
|
// Reproduces golang/go#40340.
|
||||||
func TestSwitchFromGOPATHToModules(t *testing.T) {
|
func TestSwitchFromGOPATHToModules(t *testing.T) {
|
||||||
t.Skipf("golang/go#37069 is not yet resolved.")
|
t.Skip("golang/go#40340 is not yet resolved.")
|
||||||
|
|
||||||
const files = `
|
const files = `
|
||||||
-- foo/blah/blah.go --
|
-- foo/blah/blah.go --
|
||||||
|
@ -6,6 +6,7 @@ package regtest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp"
|
"golang.org/x/tools/internal/lsp"
|
||||||
@ -205,3 +206,108 @@ func Hello() int {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This change tests that the version of the module used changes after it has
|
||||||
|
// been deleted from the workspace.
|
||||||
|
func TestDeleteModule_Interdependent(t *testing.T) {
|
||||||
|
const multiModule = `
|
||||||
|
-- moda/a/go.mod --
|
||||||
|
module a.com
|
||||||
|
|
||||||
|
require b.com v1.2.3
|
||||||
|
|
||||||
|
-- moda/a/a.go --
|
||||||
|
package a
|
||||||
|
|
||||||
|
import (
|
||||||
|
"b.com/b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var x int
|
||||||
|
_ = b.Hello()
|
||||||
|
}
|
||||||
|
-- modb/go.mod --
|
||||||
|
module b.com
|
||||||
|
|
||||||
|
-- modb/b/b.go --
|
||||||
|
package b
|
||||||
|
|
||||||
|
func Hello() int {
|
||||||
|
var x int
|
||||||
|
}
|
||||||
|
`
|
||||||
|
withOptions(
|
||||||
|
WithProxyFiles(workspaceModuleProxy),
|
||||||
|
).run(t, multiModule, func(t *testing.T, env *Env) {
|
||||||
|
env.Await(InitialWorkspaceLoad)
|
||||||
|
env.OpenFile("moda/a/a.go")
|
||||||
|
|
||||||
|
original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
|
||||||
|
if want := "modb/b/b.go"; !strings.HasSuffix(original, want) {
|
||||||
|
t.Errorf("expected %s, got %v", want, original)
|
||||||
|
}
|
||||||
|
env.CloseBuffer(original)
|
||||||
|
env.RemoveWorkspaceFile("modb/b/b.go")
|
||||||
|
env.RemoveWorkspaceFile("modb/go.mod")
|
||||||
|
env.Await(
|
||||||
|
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
|
||||||
|
)
|
||||||
|
got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
|
||||||
|
if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) {
|
||||||
|
t.Errorf("expected %s, got %v", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This change tests that the version of the module used changes after it has
|
||||||
|
// been added to the workspace.
|
||||||
|
func TestCreateModule_Interdependent(t *testing.T) {
|
||||||
|
const multiModule = `
|
||||||
|
-- moda/a/go.mod --
|
||||||
|
module a.com
|
||||||
|
|
||||||
|
require b.com v1.2.3
|
||||||
|
|
||||||
|
-- moda/a/a.go --
|
||||||
|
package a
|
||||||
|
|
||||||
|
import (
|
||||||
|
"b.com/b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var x int
|
||||||
|
_ = b.Hello()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
withOptions(
|
||||||
|
WithProxyFiles(workspaceModuleProxy),
|
||||||
|
).run(t, multiModule, func(t *testing.T, env *Env) {
|
||||||
|
env.Await(InitialWorkspaceLoad)
|
||||||
|
env.OpenFile("moda/a/a.go")
|
||||||
|
original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
|
||||||
|
if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) {
|
||||||
|
t.Errorf("expected %s, got %v", want, original)
|
||||||
|
}
|
||||||
|
env.WriteWorkspaceFiles(map[string]string{
|
||||||
|
"modb/go.mod": "module b.com",
|
||||||
|
"modb/b/b.go": `package b
|
||||||
|
|
||||||
|
func Hello() int {
|
||||||
|
var x int
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
env.Await(
|
||||||
|
OnceMet(
|
||||||
|
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
|
||||||
|
env.DiagnosticAtRegexp("modb/b/b.go", "x"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
|
||||||
|
if want := "modb/b/b.go"; !strings.HasSuffix(got, want) {
|
||||||
|
t.Errorf("expected %s, got %v", want, original)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
3
internal/lsp/cache/check.go
vendored
3
internal/lsp/cache/check.go
vendored
@ -177,8 +177,7 @@ func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, c
|
|||||||
b.WriteString(string(dep))
|
b.WriteString(string(dep))
|
||||||
}
|
}
|
||||||
for _, cgf := range pghs {
|
for _, cgf := range pghs {
|
||||||
b.WriteString(string(cgf.file.URI()))
|
b.WriteString(cgf.file.FileIdentity().String())
|
||||||
b.WriteString(cgf.file.FileIdentity().Hash)
|
|
||||||
}
|
}
|
||||||
return packageHandleKey(hashContents(b.Bytes()))
|
return packageHandleKey(hashContents(b.Bytes()))
|
||||||
}
|
}
|
||||||
|
13
internal/lsp/cache/load.go
vendored
13
internal/lsp/cache/load.go
vendored
@ -199,13 +199,18 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
|
|||||||
// packages.Loads that occur from within the workspace module.
|
// packages.Loads that occur from within the workspace module.
|
||||||
func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
|
func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
|
||||||
cleanup = func() {}
|
cleanup = func() {}
|
||||||
if len(s.view.modules) == 0 {
|
if len(s.modules) == 0 {
|
||||||
return "", cleanup, nil
|
return "", cleanup, nil
|
||||||
}
|
}
|
||||||
if s.view.workspaceModule == nil {
|
wsModuleHandle, err := s.getWorkspaceModuleHandle(ctx)
|
||||||
return "", cleanup, nil
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
}
|
}
|
||||||
content, err := s.view.workspaceModule.Format()
|
file, err := wsModuleHandle.build(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
content, err := file.Format()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", cleanup, err
|
return "", cleanup, err
|
||||||
}
|
}
|
||||||
|
98
internal/lsp/cache/session.go
vendored
98
internal/lsp/cache/session.go
vendored
@ -7,8 +7,6 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -173,7 +171,6 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
name: name,
|
name: name,
|
||||||
folder: folder,
|
folder: folder,
|
||||||
root: folder,
|
root: folder,
|
||||||
modules: make(map[span.URI]*moduleRoot),
|
|
||||||
filesByURI: make(map[span.URI]*fileBase),
|
filesByURI: make(map[span.URI]*fileBase),
|
||||||
filesByBase: make(map[string][]*fileBase),
|
filesByBase: make(map[string][]*fileBase),
|
||||||
}
|
}
|
||||||
@ -194,6 +191,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
modTidyHandles: make(map[span.URI]*modTidyHandle),
|
modTidyHandles: make(map[span.URI]*modTidyHandle),
|
||||||
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
|
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
|
||||||
modWhyHandles: make(map[span.URI]*modWhyHandle),
|
modWhyHandles: make(map[span.URI]*modWhyHandle),
|
||||||
|
modules: make(map[span.URI]*moduleRoot),
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.session.cache.options != nil {
|
if v.session.cache.options != nil {
|
||||||
@ -206,7 +204,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find all of the modules in the workspace.
|
// Find all of the modules in the workspace.
|
||||||
if err := v.findWorkspaceModules(ctx, options); err != nil {
|
if err := v.snapshot.findWorkspaceModules(ctx, options); err != nil {
|
||||||
return nil, nil, func() {}, err
|
return nil, nil, func() {}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,11 +212,9 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
// check if the view has a valid build configuration.
|
// check if the view has a valid build configuration.
|
||||||
v.setBuildConfiguration()
|
v.setBuildConfiguration()
|
||||||
|
|
||||||
// Build the workspace module, if needed.
|
// Decide if we should use the workspace module.
|
||||||
if options.ExperimentalWorkspaceModule {
|
if v.determineWorkspaceModuleLocked() {
|
||||||
if err := v.buildWorkspaceModule(ctx); err != nil {
|
v.workspaceMode |= usesWorkspaceModule | moduleMode
|
||||||
return nil, nil, func() {}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have v.goEnv now.
|
// We have v.goEnv now.
|
||||||
@ -242,94 +238,12 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
snapshot := v.snapshot
|
snapshot := v.snapshot
|
||||||
release := snapshot.generation.Acquire(initCtx)
|
release := snapshot.generation.Acquire(initCtx)
|
||||||
go func() {
|
go func() {
|
||||||
v.initialize(initCtx, snapshot, true)
|
snapshot.initialize(initCtx, true)
|
||||||
release()
|
release()
|
||||||
}()
|
}()
|
||||||
return v, snapshot, snapshot.generation.Acquire(ctx), nil
|
return v, snapshot, snapshot.generation.Acquire(ctx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findWorkspaceModules walks the view's root folder, looking for go.mod files.
|
|
||||||
// Any that are found are added to the view's set of modules, which are then
|
|
||||||
// used to construct the workspace module.
|
|
||||||
//
|
|
||||||
// It assumes that the caller has not yet created the view, and therefore does
|
|
||||||
// not lock any of the internal data structures before accessing them.
|
|
||||||
func (v *View) findWorkspaceModules(ctx context.Context, options *source.Options) error {
|
|
||||||
// If the user is intentionally limiting their workspace scope, add their
|
|
||||||
// folder to the roots and return early.
|
|
||||||
if !options.ExpandWorkspaceToModule {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// The workspace module has been disabled by the user.
|
|
||||||
if !options.ExperimentalWorkspaceModule {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk the view's folder to find all modules in the view.
|
|
||||||
root := v.root.Filename()
|
|
||||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// For any path that is not the workspace folder, check if the path
|
|
||||||
// would be ignored by the go command. Vendor directories also do not
|
|
||||||
// contain workspace modules.
|
|
||||||
if info.IsDir() && path != root {
|
|
||||||
suffix := strings.TrimPrefix(path, root)
|
|
||||||
switch {
|
|
||||||
case checkIgnored(suffix),
|
|
||||||
strings.Contains(filepath.ToSlash(suffix), "/vendor/"):
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We're only interested in go.mod files.
|
|
||||||
if filepath.Base(path) != "go.mod" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// At this point, we definitely have a go.mod file in the workspace,
|
|
||||||
// so add it to the view.
|
|
||||||
modURI := span.URIFromPath(path)
|
|
||||||
rootURI := span.URIFromPath(filepath.Dir(path))
|
|
||||||
v.modules[rootURI] = &moduleRoot{
|
|
||||||
rootURI: rootURI,
|
|
||||||
modURI: modURI,
|
|
||||||
sumURI: span.URIFromPath(sumFilename(modURI)),
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) buildWorkspaceModule(ctx context.Context) error {
|
|
||||||
// If the view has an invalid configuration, don't build the workspace
|
|
||||||
// module.
|
|
||||||
if !v.hasValidBuildConfiguration {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// If the view is not in a module and contains no modules, but still has a
|
|
||||||
// valid workspace configuration, do not create the workspace module.
|
|
||||||
// It could be using GOPATH or a different build system entirely.
|
|
||||||
if v.modURI == "" && len(v.modules) == 0 && v.hasValidBuildConfiguration {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
v.workspaceMode |= moduleMode
|
|
||||||
|
|
||||||
// Don't default to multi-workspace mode if one of the modules contains a
|
|
||||||
// vendor directory. We still have to decide how to handle vendoring.
|
|
||||||
for _, mod := range v.modules {
|
|
||||||
if info, _ := os.Stat(filepath.Join(mod.rootURI.Filename(), "vendor")); info != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v.workspaceMode |= usesWorkspaceModule
|
|
||||||
|
|
||||||
// If the user does not have a gopls.mod, we need to create one, based on
|
|
||||||
// modules we found in the user's workspace.
|
|
||||||
var err error
|
|
||||||
v.workspaceModule, err = v.snapshot.buildWorkspaceModule(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// View returns the view by name.
|
// View returns the view by name.
|
||||||
func (s *Session) View(name string) source.View {
|
func (s *Session) View(name string) source.View {
|
||||||
s.viewMu.Lock()
|
s.viewMu.Lock()
|
||||||
|
214
internal/lsp/cache/snapshot.go
vendored
214
internal/lsp/cache/snapshot.go
vendored
@ -94,6 +94,13 @@ type snapshot struct {
|
|||||||
modTidyHandles map[span.URI]*modTidyHandle
|
modTidyHandles map[span.URI]*modTidyHandle
|
||||||
modUpgradeHandles map[span.URI]*modUpgradeHandle
|
modUpgradeHandles map[span.URI]*modUpgradeHandle
|
||||||
modWhyHandles map[span.URI]*modWhyHandle
|
modWhyHandles map[span.URI]*modWhyHandle
|
||||||
|
|
||||||
|
// modules is the set of modules currently in this workspace.
|
||||||
|
modules map[span.URI]*moduleRoot
|
||||||
|
|
||||||
|
// workspaceModuleHandle keeps track of the in-memory representation of the
|
||||||
|
// go.mod file for the workspace module.
|
||||||
|
workspaceModuleHandle *workspaceModuleHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
type packageKey struct {
|
type packageKey struct {
|
||||||
@ -673,12 +680,15 @@ func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.VersionedF
|
|||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
return s.getFileLocked(ctx, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
|
||||||
if fh, ok := s.files[f.URI()]; ok {
|
if fh, ok := s.files[f.URI()]; ok {
|
||||||
return fh, nil
|
return fh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fh, err := s.view.session.cache.getFile(ctx, uri)
|
fh, err := s.view.session.cache.getFile(ctx, f.URI())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -732,7 +742,7 @@ func (s *snapshot) AwaitInitialized(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
// We typically prefer to run something as intensive as the IWL without
|
// We typically prefer to run something as intensive as the IWL without
|
||||||
// blocking. I'm not sure if there is a way to do that here.
|
// blocking. I'm not sure if there is a way to do that here.
|
||||||
s.view.initialize(ctx, s, false)
|
s.initialize(ctx, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reloadWorkspace reloads the metadata for all invalidated workspace packages.
|
// reloadWorkspace reloads the metadata for all invalidated workspace packages.
|
||||||
@ -869,6 +879,8 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
|
|||||||
modTidyHandles: make(map[span.URI]*modTidyHandle),
|
modTidyHandles: make(map[span.URI]*modTidyHandle),
|
||||||
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
|
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
|
||||||
modWhyHandles: make(map[span.URI]*modWhyHandle),
|
modWhyHandles: make(map[span.URI]*modWhyHandle),
|
||||||
|
modules: make(map[span.URI]*moduleRoot),
|
||||||
|
workspaceModuleHandle: s.workspaceModuleHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.builtin != nil {
|
if s.builtin != nil {
|
||||||
@ -885,7 +897,6 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
|
|||||||
}
|
}
|
||||||
// Copy all of the modHandles.
|
// Copy all of the modHandles.
|
||||||
for k, v := range s.parseModHandles {
|
for k, v := range s.parseModHandles {
|
||||||
newGen.Inherit(v.handle)
|
|
||||||
result.parseModHandles[k] = v
|
result.parseModHandles[k] = v
|
||||||
}
|
}
|
||||||
// Copy all of the workspace directories. They may be reset later.
|
// Copy all of the workspace directories. They may be reset later.
|
||||||
@ -923,6 +934,11 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
|
|||||||
result.modWhyHandles[k] = v
|
result.modWhyHandles[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add all of the modules now. They may be deleted or added to later.
|
||||||
|
for k, v := range s.modules {
|
||||||
|
result.modules[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
// transitiveIDs keeps track of transitive reverse dependencies.
|
// transitiveIDs keeps track of transitive reverse dependencies.
|
||||||
// If an ID is present in the map, invalidate its types.
|
// If an ID is present in the map, invalidate its types.
|
||||||
// If an ID's value is true, invalidate its metadata too.
|
// If an ID's value is true, invalidate its metadata too.
|
||||||
@ -957,24 +973,69 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
|
|||||||
delete(result.modWhyHandles, k)
|
delete(result.modWhyHandles, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if currentFH.Kind() == source.Mod {
|
currentExists := currentFH.URI() != ""
|
||||||
// If the view's go.mod file's contents have changed, invalidate the
|
if currentExists {
|
||||||
// metadata for every known package in the snapshot.
|
if _, err := currentFH.Read(); os.IsNotExist(err) {
|
||||||
|
currentExists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the file invalidation is for a go.mod. originalFH is nil if the
|
||||||
|
// file is newly created.
|
||||||
|
currentMod := currentExists && currentFH.Kind() == source.Mod
|
||||||
|
originalMod := originalFH != nil && originalFH.Kind() == source.Mod
|
||||||
|
if currentMod || originalMod {
|
||||||
|
// If the view's go.mod file's contents have changed, invalidate
|
||||||
|
// the metadata for every known package in the snapshot.
|
||||||
if invalidateMetadata {
|
if invalidateMetadata {
|
||||||
for k := range s.packages {
|
for k := range s.packages {
|
||||||
directIDs[k.id] = struct{}{}
|
directIDs[k.id] = struct{}{}
|
||||||
}
|
}
|
||||||
|
// If a go.mod file in the workspace has changed, we need to
|
||||||
|
// rebuild the workspace module.
|
||||||
|
result.workspaceModuleHandle = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(result.parseModHandles, withoutURI)
|
delete(result.parseModHandles, withoutURI)
|
||||||
|
|
||||||
if currentFH.URI() == s.view.modURI {
|
// Check if this is a newly created go.mod file. When a new module
|
||||||
|
// is created, we have to retry the initial workspace load.
|
||||||
|
rootURI := span.URIFromPath(filepath.Dir(withoutURI.Filename()))
|
||||||
|
if currentMod {
|
||||||
|
if _, ok := result.modules[rootURI]; !ok {
|
||||||
|
result.addModule(ctx, currentFH.URI())
|
||||||
|
result.view.definitelyReinitialize()
|
||||||
|
}
|
||||||
|
} else if originalMod {
|
||||||
|
// Similarly, we need to retry the IWL if a go.mod in the workspace
|
||||||
|
// was deleted.
|
||||||
|
if _, ok := result.modules[rootURI]; ok {
|
||||||
|
delete(result.modules, rootURI)
|
||||||
|
result.view.definitelyReinitialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Keep track of the creations and deletions of go.sum files.
|
||||||
|
// Creating a go.sum without an associated go.mod has no effect on the
|
||||||
|
// set of modules.
|
||||||
|
currentSum := currentExists && currentFH.Kind() == source.Sum
|
||||||
|
originalSum := originalFH != nil && originalFH.Kind() == source.Sum
|
||||||
|
if currentSum || originalSum {
|
||||||
|
rootURI := span.URIFromPath(filepath.Dir(withoutURI.Filename()))
|
||||||
|
if currentSum {
|
||||||
|
if mod, ok := result.modules[rootURI]; ok {
|
||||||
|
mod.sumURI = currentFH.URI()
|
||||||
|
}
|
||||||
|
} else if originalSum {
|
||||||
|
if mod, ok := result.modules[rootURI]; ok {
|
||||||
|
mod.sumURI = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if withoutURI == s.view.modURI {
|
||||||
// The go.mod's replace directives may have changed. We may
|
// The go.mod's replace directives may have changed. We may
|
||||||
// need to update our set of workspace directories. Use the new
|
// need to update our set of workspace directories. Use the new
|
||||||
// snapshot, as it can be locked without causing issues.
|
// snapshot, as it can be locked without causing issues.
|
||||||
result.workspaceDirectories = result.findWorkspaceDirectories(ctx, currentFH)
|
result.workspaceDirectories = result.findWorkspaceDirectories(ctx, currentFH)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a file we don't yet know about,
|
// If this is a file we don't yet know about,
|
||||||
// then we do not yet know what packages it should belong to.
|
// then we do not yet know what packages it should belong to.
|
||||||
@ -1017,7 +1078,7 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle the invalidated file; it may have new contents or not exist.
|
// Handle the invalidated file; it may have new contents or not exist.
|
||||||
if _, err := currentFH.Read(); os.IsNotExist(err) {
|
if !currentExists {
|
||||||
delete(result.files, withoutURI)
|
delete(result.files, withoutURI)
|
||||||
} else {
|
} else {
|
||||||
result.files[withoutURI] = currentFH
|
result.files[withoutURI] = currentFH
|
||||||
@ -1087,15 +1148,21 @@ copyIDs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inherit all of the go.mod-related handles.
|
// Inherit all of the go.mod-related handles.
|
||||||
for _, v := range s.modTidyHandles {
|
for _, v := range result.modTidyHandles {
|
||||||
newGen.Inherit(v.handle)
|
newGen.Inherit(v.handle)
|
||||||
}
|
}
|
||||||
for _, v := range s.modUpgradeHandles {
|
for _, v := range result.modUpgradeHandles {
|
||||||
newGen.Inherit(v.handle)
|
newGen.Inherit(v.handle)
|
||||||
}
|
}
|
||||||
for _, v := range s.modWhyHandles {
|
for _, v := range result.modWhyHandles {
|
||||||
newGen.Inherit(v.handle)
|
newGen.Inherit(v.handle)
|
||||||
}
|
}
|
||||||
|
for _, v := range result.parseModHandles {
|
||||||
|
newGen.Inherit(v.handle)
|
||||||
|
}
|
||||||
|
if result.workspaceModuleHandle != nil {
|
||||||
|
newGen.Inherit(result.workspaceModuleHandle.handle)
|
||||||
|
}
|
||||||
|
|
||||||
// Don't bother copying the importedBy graph,
|
// Don't bother copying the importedBy graph,
|
||||||
// as it changes each time we update metadata.
|
// as it changes each time we update metadata.
|
||||||
@ -1128,9 +1195,9 @@ func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *sn
|
|||||||
if originalFH.FileIdentity() == currentFH.FileIdentity() {
|
if originalFH.FileIdentity() == currentFH.FileIdentity() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// If a go.mod file's contents have changed, always invalidate metadata.
|
// If a go.mod in the workspace has been changed, invalidate metadata.
|
||||||
if kind := originalFH.Kind(); kind == source.Mod {
|
if kind := originalFH.Kind(); kind == source.Mod {
|
||||||
return originalFH.URI() == s.view.modURI
|
return isSubdirectory(filepath.Dir(s.view.root.Filename()), filepath.Dir(originalFH.URI().Filename()))
|
||||||
}
|
}
|
||||||
// Get the original and current parsed files in order to check package name
|
// Get the original and current parsed files in order to check package name
|
||||||
// and imports. Use the new snapshot to parse to avoid modifying the
|
// and imports. Use the new snapshot to parse to avoid modifying the
|
||||||
@ -1263,6 +1330,64 @@ func (s *snapshot) buildBuiltinPackage(ctx context.Context, goFiles []string) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type workspaceModuleHandle struct {
|
||||||
|
handle *memoize.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
type workspaceModuleData struct {
|
||||||
|
file *modfile.File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type workspaceModuleKey string
|
||||||
|
|
||||||
|
func (wmh *workspaceModuleHandle) build(ctx context.Context, snapshot *snapshot) (*modfile.File, error) {
|
||||||
|
v, err := wmh.handle.Get(ctx, snapshot.generation, snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := v.(*workspaceModuleData)
|
||||||
|
return data.file, data.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshot) getWorkspaceModuleHandle(ctx context.Context) (*workspaceModuleHandle, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
wsModule := s.workspaceModuleHandle
|
||||||
|
s.mu.Unlock()
|
||||||
|
if wsModule != nil {
|
||||||
|
return wsModule, nil
|
||||||
|
}
|
||||||
|
var fhs []source.FileHandle
|
||||||
|
for _, mod := range s.modules {
|
||||||
|
fh, err := s.GetFile(ctx, mod.modURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fhs = append(fhs, fh)
|
||||||
|
}
|
||||||
|
sort.Slice(fhs, func(i, j int) bool {
|
||||||
|
return fhs[i].URI() < fhs[j].URI()
|
||||||
|
})
|
||||||
|
var k string
|
||||||
|
for _, fh := range fhs {
|
||||||
|
k += fh.FileIdentity().String()
|
||||||
|
}
|
||||||
|
key := workspaceModuleKey(hashContents([]byte(k)))
|
||||||
|
h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
|
||||||
|
s := arg.(*snapshot)
|
||||||
|
data := &workspaceModuleData{}
|
||||||
|
data.file, data.err = s.buildWorkspaceModule(ctx)
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
wsModule = &workspaceModuleHandle{
|
||||||
|
handle: h,
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.workspaceModuleHandle = wsModule
|
||||||
|
return s.workspaceModuleHandle, nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildWorkspaceModule generates a workspace module given the modules in the
|
// buildWorkspaceModule generates a workspace module given the modules in the
|
||||||
// the workspace.
|
// the workspace.
|
||||||
func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, error) {
|
func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, error) {
|
||||||
@ -1270,8 +1395,8 @@ func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, err
|
|||||||
file.AddModuleStmt("gopls-workspace")
|
file.AddModuleStmt("gopls-workspace")
|
||||||
|
|
||||||
paths := make(map[string]*moduleRoot)
|
paths := make(map[string]*moduleRoot)
|
||||||
for _, mod := range s.view.modules {
|
for _, mod := range s.modules {
|
||||||
fh, err := s.view.snapshot.GetFile(ctx, mod.modURI)
|
fh, err := s.GetFile(ctx, mod.modURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1291,12 +1416,12 @@ func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, err
|
|||||||
}
|
}
|
||||||
// Go back through all of the modules to handle any of their replace
|
// Go back through all of the modules to handle any of their replace
|
||||||
// statements.
|
// statements.
|
||||||
for _, module := range s.view.modules {
|
for _, module := range s.modules {
|
||||||
fh, err := s.view.snapshot.GetFile(ctx, module.modURI)
|
fh, err := s.GetFile(ctx, module.modURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pmf, err := s.view.snapshot.ParseMod(ctx, fh)
|
pmf, err := s.ParseMod(ctx, fh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1326,3 +1451,52 @@ func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, err
|
|||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findWorkspaceModules walks the view's root folder, looking for go.mod
|
||||||
|
// files. Any that are found are added to the view's set of modules, which are
|
||||||
|
// then used to construct the workspace module.
|
||||||
|
//
|
||||||
|
// It assumes that the caller has not yet created the view, and therefore does
|
||||||
|
// not lock any of the internal data structures before accessing them.
|
||||||
|
func (s *snapshot) findWorkspaceModules(ctx context.Context, options *source.Options) error {
|
||||||
|
// Walk the view's folder to find all modules in the view.
|
||||||
|
root := s.view.root.Filename()
|
||||||
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// For any path that is not the workspace folder, check if the path
|
||||||
|
// would be ignored by the go command. Vendor directories also do not
|
||||||
|
// contain workspace modules.
|
||||||
|
if info.IsDir() && path != root {
|
||||||
|
suffix := strings.TrimPrefix(path, root)
|
||||||
|
switch {
|
||||||
|
case checkIgnored(suffix),
|
||||||
|
strings.Contains(filepath.ToSlash(suffix), "/vendor/"):
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We're only interested in go.mod files.
|
||||||
|
if filepath.Base(path) != "go.mod" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// At this point, we definitely have a go.mod file in the workspace,
|
||||||
|
// so add it to the view.
|
||||||
|
modURI := span.URIFromPath(path)
|
||||||
|
s.addModule(ctx, modURI)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshot) addModule(ctx context.Context, modURI span.URI) {
|
||||||
|
rootURI := span.URIFromPath(filepath.Dir(modURI.Filename()))
|
||||||
|
sumURI := span.URIFromPath(sumFilename(modURI))
|
||||||
|
if info, _ := os.Stat(sumURI.Filename()); info == nil {
|
||||||
|
sumURI = ""
|
||||||
|
}
|
||||||
|
s.modules[rootURI] = &moduleRoot{
|
||||||
|
rootURI: rootURI,
|
||||||
|
modURI: modURI,
|
||||||
|
sumURI: sumURI,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
78
internal/lsp/cache/view.go
vendored
78
internal/lsp/cache/view.go
vendored
@ -67,16 +67,6 @@ type View struct {
|
|||||||
// is just the folder. If we are in module mode, this is the module root.
|
// is just the folder. If we are in module mode, this is the module root.
|
||||||
root span.URI
|
root span.URI
|
||||||
|
|
||||||
// TODO: The modules and workspaceModule fields should probably be moved to
|
|
||||||
// the snapshot and invalidated on file changes.
|
|
||||||
|
|
||||||
// modules is the set of modules currently in this workspace.
|
|
||||||
modules map[span.URI]*moduleRoot
|
|
||||||
|
|
||||||
// workspaceModule is an in-memory representation of the go.mod file for
|
|
||||||
// the workspace module.
|
|
||||||
workspaceModule *modfile.File
|
|
||||||
|
|
||||||
// importsMu guards imports-related state, particularly the ProcessEnv.
|
// importsMu guards imports-related state, particularly the ProcessEnv.
|
||||||
importsMu sync.Mutex
|
importsMu sync.Mutex
|
||||||
|
|
||||||
@ -687,42 +677,42 @@ func (v *View) Snapshot(ctx context.Context) (source.Snapshot, func()) {
|
|||||||
return v.snapshot, v.snapshot.generation.Acquire(ctx)
|
return v.snapshot, v.snapshot.generation.Acquire(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) initialize(ctx context.Context, s *snapshot, firstAttempt bool) {
|
func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case v.initializationSema <- struct{}{}:
|
case s.view.initializationSema <- struct{}{}:
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
<-v.initializationSema
|
<-s.view.initializationSema
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if v.initializeOnce == nil {
|
if s.view.initializeOnce == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v.initializeOnce.Do(func() {
|
s.view.initializeOnce.Do(func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
v.initializeOnce = nil
|
s.view.initializeOnce = nil
|
||||||
if firstAttempt {
|
if firstAttempt {
|
||||||
close(v.initialized)
|
close(s.view.initialized)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// If we have multiple modules, we need to load them by paths.
|
// If we have multiple modules, we need to load them by paths.
|
||||||
var scopes []interface{}
|
var scopes []interface{}
|
||||||
if len(v.modules) > 0 {
|
if len(s.modules) > 0 {
|
||||||
// TODO(rstambler): Retry the initial workspace load for whichever
|
// TODO(rstambler): Retry the initial workspace load for whichever
|
||||||
// modules we failed to load.
|
// modules we failed to load.
|
||||||
for _, mod := range v.modules {
|
for _, mod := range s.modules {
|
||||||
fh, err := s.GetFile(ctx, mod.modURI)
|
fh, err := s.GetFile(ctx, mod.modURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.initializedErr = err
|
s.view.initializedErr = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parsed, err := s.ParseMod(ctx, fh)
|
parsed, err := s.ParseMod(ctx, fh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.initializedErr = err
|
s.view.initializedErr = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := parsed.File.Module.Mod.Path
|
path := parsed.File.Module.Mod.Path
|
||||||
@ -738,7 +728,7 @@ func (v *View) initialize(ctx context.Context, s *snapshot, firstAttempt bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
event.Error(ctx, "initial workspace load failed", err)
|
event.Error(ctx, "initial workspace load failed", err)
|
||||||
}
|
}
|
||||||
v.initializedErr = err
|
s.view.initializedErr = err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,12 +769,20 @@ func (v *View) cancelBackground() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) maybeReinitialize() {
|
func (v *View) maybeReinitialize() {
|
||||||
|
v.reinitialize(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) definitelyReinitialize() {
|
||||||
|
v.reinitialize(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) reinitialize(force bool) {
|
||||||
v.initializationSema <- struct{}{}
|
v.initializationSema <- struct{}{}
|
||||||
defer func() {
|
defer func() {
|
||||||
<-v.initializationSema
|
<-v.initializationSema
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if v.initializedErr == nil {
|
if !force && v.initializedErr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
@ -856,6 +854,38 @@ func defaultCheckPathCase(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *View) determineWorkspaceModuleLocked() bool {
|
||||||
|
// If the user is intentionally limiting their workspace scope, add their
|
||||||
|
// folder to the roots and return early.
|
||||||
|
if !v.options.ExpandWorkspaceToModule {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// The workspace module has been disabled by the user.
|
||||||
|
if !v.options.ExperimentalWorkspaceModule {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the view has an invalid configuration, don't build the workspace
|
||||||
|
// module.
|
||||||
|
if !v.hasValidBuildConfiguration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the view is not in a module and contains no modules, but still has a
|
||||||
|
// valid workspace configuration, do not create the workspace module.
|
||||||
|
// It could be using GOPATH or a different build system entirely.
|
||||||
|
if v.modURI == "" && len(v.snapshot.modules) == 0 && v.hasValidBuildConfiguration {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't default to multi-workspace mode if one of the modules contains a
|
||||||
|
// vendor directory. We still have to decide how to handle vendoring.
|
||||||
|
for _, mod := range v.snapshot.modules {
|
||||||
|
if info, _ := os.Stat(filepath.Join(mod.rootURI.Filename(), "vendor")); info != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (v *View) setBuildConfiguration() (isValid bool) {
|
func (v *View) setBuildConfiguration() (isValid bool) {
|
||||||
defer func() {
|
defer func() {
|
||||||
v.hasValidBuildConfiguration = isValid
|
v.hasValidBuildConfiguration = isValid
|
||||||
@ -870,7 +900,7 @@ func (v *View) setBuildConfiguration() (isValid bool) {
|
|||||||
if v.modURI != "" {
|
if v.modURI != "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(v.modules) > 0 {
|
if len(v.snapshot.modules) > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// The user may have a multiple directories in their GOPATH.
|
// The user may have a multiple directories in their GOPATH.
|
||||||
|
@ -400,6 +400,10 @@ type FileIdentity struct {
|
|||||||
Kind FileKind
|
Kind FileKind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (id FileIdentity) String() string {
|
||||||
|
return fmt.Sprintf("%s%s%s", id.URI, id.Hash, id.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
// FileKind describes the kind of the file in question.
|
// FileKind describes the kind of the file in question.
|
||||||
// It can be one of Go, mod, or sum.
|
// It can be one of Go, mod, or sum.
|
||||||
type FileKind int
|
type FileKind int
|
||||||
|
Loading…
Reference in New Issue
Block a user