1
0
mirror of https://github.com/golang/go synced 2024-09-30 00:24:29 -06: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:
Rebecca Stambler 2020-09-15 02:03:17 -04:00
parent e94ab72881
commit 78fed78f7d
9 changed files with 399 additions and 167 deletions

View File

@ -74,7 +74,7 @@ func main() {
// Reproduce golang/go#40269 by deleting and recreating main.go.
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) {
goModContent := env.ReadWorkspaceFile("go.mod")

View File

@ -185,7 +185,7 @@ func _() {
}
`
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.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) {
t.Skipf("golang/go#37069 is not yet resolved.")
t.Skip("golang/go#40340 is not yet resolved.")
const files = `
-- foo/blah/blah.go --

View File

@ -6,6 +6,7 @@ package regtest
import (
"fmt"
"strings"
"testing"
"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)
}
})
}

View File

@ -177,8 +177,7 @@ func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, c
b.WriteString(string(dep))
}
for _, cgf := range pghs {
b.WriteString(string(cgf.file.URI()))
b.WriteString(cgf.file.FileIdentity().Hash)
b.WriteString(cgf.file.FileIdentity().String())
}
return packageHandleKey(hashContents(b.Bytes()))
}

View File

@ -199,13 +199,18 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
// packages.Loads that occur from within the workspace module.
func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
cleanup = func() {}
if len(s.view.modules) == 0 {
if len(s.modules) == 0 {
return "", cleanup, nil
}
if s.view.workspaceModule == nil {
return "", cleanup, nil
wsModuleHandle, err := s.getWorkspaceModuleHandle(ctx)
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 {
return "", cleanup, err
}

View File

@ -7,8 +7,6 @@ package cache
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@ -173,7 +171,6 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
name: name,
folder: folder,
root: folder,
modules: make(map[span.URI]*moduleRoot),
filesByURI: make(map[span.URI]*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),
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
modWhyHandles: make(map[span.URI]*modWhyHandle),
modules: make(map[span.URI]*moduleRoot),
}
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.
if err := v.findWorkspaceModules(ctx, options); err != nil {
if err := v.snapshot.findWorkspaceModules(ctx, options); err != nil {
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.
v.setBuildConfiguration()
// Build the workspace module, if needed.
if options.ExperimentalWorkspaceModule {
if err := v.buildWorkspaceModule(ctx); err != nil {
return nil, nil, func() {}, err
}
// Decide if we should use the workspace module.
if v.determineWorkspaceModuleLocked() {
v.workspaceMode |= usesWorkspaceModule | moduleMode
}
// We have v.goEnv now.
@ -242,94 +238,12 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
snapshot := v.snapshot
release := snapshot.generation.Acquire(initCtx)
go func() {
v.initialize(initCtx, snapshot, true)
snapshot.initialize(initCtx, true)
release()
}()
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.
func (s *Session) View(name string) source.View {
s.viewMu.Lock()

View File

@ -94,6 +94,13 @@ type snapshot struct {
modTidyHandles map[span.URI]*modTidyHandle
modUpgradeHandles map[span.URI]*modUpgradeHandle
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 {
@ -673,12 +680,15 @@ func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.VersionedF
s.mu.Lock()
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 {
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 {
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
// 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.
@ -851,24 +861,26 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Ve
newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
result := &snapshot{
id: s.id + 1,
generation: newGen,
view: s.view,
builtin: s.builtin,
ids: make(map[span.URI][]packageID),
importedBy: make(map[packageID][]packageID),
metadata: make(map[packageID]*metadata),
packages: make(map[packageKey]*packageHandle),
actions: make(map[actionKey]*actionHandle),
files: make(map[span.URI]source.VersionedFileHandle),
goFiles: make(map[parseKey]*parseGoHandle),
workspaceDirectories: make(map[span.URI]struct{}),
workspacePackages: make(map[packageID]packagePath),
unloadableFiles: make(map[span.URI]struct{}),
parseModHandles: make(map[span.URI]*parseModHandle),
modTidyHandles: make(map[span.URI]*modTidyHandle),
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
modWhyHandles: make(map[span.URI]*modWhyHandle),
id: s.id + 1,
generation: newGen,
view: s.view,
builtin: s.builtin,
ids: make(map[span.URI][]packageID),
importedBy: make(map[packageID][]packageID),
metadata: make(map[packageID]*metadata),
packages: make(map[packageKey]*packageHandle),
actions: make(map[actionKey]*actionHandle),
files: make(map[span.URI]source.VersionedFileHandle),
goFiles: make(map[parseKey]*parseGoHandle),
workspaceDirectories: make(map[span.URI]struct{}),
workspacePackages: make(map[packageID]packagePath),
unloadableFiles: make(map[span.URI]struct{}),
parseModHandles: make(map[span.URI]*parseModHandle),
modTidyHandles: make(map[span.URI]*modTidyHandle),
modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
modWhyHandles: make(map[span.URI]*modWhyHandle),
modules: make(map[span.URI]*moduleRoot),
workspaceModuleHandle: s.workspaceModuleHandle,
}
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.
for k, v := range s.parseModHandles {
newGen.Inherit(v.handle)
result.parseModHandles[k] = v
}
// 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
}
// 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.
// If an ID is present in the map, invalidate its types.
// 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)
}
}
if currentFH.Kind() == source.Mod {
// If the view's go.mod file's contents have changed, invalidate the
// metadata for every known package in the snapshot.
currentExists := currentFH.URI() != ""
if currentExists {
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 {
for k := range s.packages {
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)
if currentFH.URI() == s.view.modURI {
// The go.mod's replace directives may have changed. We may
// need to update our set of workspace directories. Use the new
// snapshot, as it can be locked without causing issues.
result.workspaceDirectories = result.findWorkspaceDirectories(ctx, currentFH)
// 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
// need to update our set of workspace directories. Use the new
// snapshot, as it can be locked without causing issues.
result.workspaceDirectories = result.findWorkspaceDirectories(ctx, currentFH)
}
// If this is a file we don't yet know about,
// 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.
if _, err := currentFH.Read(); os.IsNotExist(err) {
if !currentExists {
delete(result.files, withoutURI)
} else {
result.files[withoutURI] = currentFH
@ -1087,15 +1148,21 @@ copyIDs:
}
// Inherit all of the go.mod-related handles.
for _, v := range s.modTidyHandles {
for _, v := range result.modTidyHandles {
newGen.Inherit(v.handle)
}
for _, v := range s.modUpgradeHandles {
for _, v := range result.modUpgradeHandles {
newGen.Inherit(v.handle)
}
for _, v := range s.modWhyHandles {
for _, v := range result.modWhyHandles {
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,
// 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() {
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 {
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
// 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
}
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
// the workspace.
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")
paths := make(map[string]*moduleRoot)
for _, mod := range s.view.modules {
fh, err := s.view.snapshot.GetFile(ctx, mod.modURI)
for _, mod := range s.modules {
fh, err := s.GetFile(ctx, mod.modURI)
if err != nil {
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
// statements.
for _, module := range s.view.modules {
fh, err := s.view.snapshot.GetFile(ctx, module.modURI)
for _, module := range s.modules {
fh, err := s.GetFile(ctx, module.modURI)
if err != nil {
return nil, err
}
pmf, err := s.view.snapshot.ParseMod(ctx, fh)
pmf, err := s.ParseMod(ctx, fh)
if err != nil {
return nil, err
}
@ -1326,3 +1451,52 @@ func (s *snapshot) buildWorkspaceModule(ctx context.Context) (*modfile.File, err
}
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,
}
}

View File

@ -67,16 +67,6 @@ type View struct {
// is just the folder. If we are in module mode, this is the module root.
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 sync.Mutex
@ -687,42 +677,42 @@ func (v *View) Snapshot(ctx context.Context) (source.Snapshot, func()) {
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 {
case <-ctx.Done():
return
case v.initializationSema <- struct{}{}:
case s.view.initializationSema <- struct{}{}:
}
defer func() {
<-v.initializationSema
<-s.view.initializationSema
}()
if v.initializeOnce == nil {
if s.view.initializeOnce == nil {
return
}
v.initializeOnce.Do(func() {
s.view.initializeOnce.Do(func() {
defer func() {
v.initializeOnce = nil
s.view.initializeOnce = nil
if firstAttempt {
close(v.initialized)
close(s.view.initialized)
}
}()
// If we have multiple modules, we need to load them by paths.
var scopes []interface{}
if len(v.modules) > 0 {
if len(s.modules) > 0 {
// TODO(rstambler): Retry the initial workspace load for whichever
// modules we failed to load.
for _, mod := range v.modules {
for _, mod := range s.modules {
fh, err := s.GetFile(ctx, mod.modURI)
if err != nil {
v.initializedErr = err
s.view.initializedErr = err
continue
}
parsed, err := s.ParseMod(ctx, fh)
if err != nil {
v.initializedErr = err
s.view.initializedErr = err
continue
}
path := parsed.File.Module.Mod.Path
@ -738,7 +728,7 @@ func (v *View) initialize(ctx context.Context, s *snapshot, firstAttempt bool) {
if err != nil {
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() {
v.reinitialize(false)
}
func (v *View) definitelyReinitialize() {
v.reinitialize(true)
}
func (v *View) reinitialize(force bool) {
v.initializationSema <- struct{}{}
defer func() {
<-v.initializationSema
}()
if v.initializedErr == nil {
if !force && v.initializedErr == nil {
return
}
var once sync.Once
@ -856,6 +854,38 @@ func defaultCheckPathCase(path string) error {
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) {
defer func() {
v.hasValidBuildConfiguration = isValid
@ -870,7 +900,7 @@ func (v *View) setBuildConfiguration() (isValid bool) {
if v.modURI != "" {
return true
}
if len(v.modules) > 0 {
if len(v.snapshot.modules) > 0 {
return true
}
// The user may have a multiple directories in their GOPATH.

View File

@ -400,6 +400,10 @@ type FileIdentity struct {
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.
// It can be one of Go, mod, or sum.
type FileKind int