internal/lsp/cache: keep a cached workspace module dir

Keep a workspace module dir around for running the go command against a
snapshot, bound to the contents of the workspace modfile.

This uses the cache's resource model to share the workspace module dir
across snapshots if it is not invalidated, and to delete it when it is
no longer in-use by a snapshot. Of course, the go command will still
only see files on the filesystem, but using this immutable model was
most consistent with the immutable workspace.

For golang/go#41836

Change-Id: Iaec544283b2f545071e5cab1d0ff2a66e6d24dff
Reviewed-on: https://go-review.googlesource.com/c/tools/+/263938
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
This commit is contained in:
Rob Findley 2020-10-20 12:58:16 -04:00 committed by Robert Findley
parent 3734b81991
commit 4fc0492b8e
2 changed files with 56 additions and 24 deletions

View File

@ -19,6 +19,7 @@ import (
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/packagesinternal"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
@ -184,37 +185,62 @@ func (s *snapshot) parseLoadError(ctx context.Context, loadErr error) *source.Er
return srcErrs
}
// tempWorkspaceModule creates a temporary directory for use with
// 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 s.workspaceMode()&usesWorkspaceModule == 0 {
return "", cleanup, nil
type workspaceDirKey string
type workspaceDirData struct {
dir string
err error
}
// getWorkspaceDir gets the URI for the workspace directory associated with
// this snapshot. The workspace directory is a temp directory containing the
// go.mod file computed from all active modules.
func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
s.mu.Lock()
h := s.workspaceDirHandle
s.mu.Unlock()
if h != nil {
return getWorkspaceDir(ctx, h, s.generation)
}
file, err := s.workspace.modFile(ctx, s)
if err != nil {
return "", nil, err
return "", err
}
content, err := file.Format()
if err != nil {
return "", cleanup, err
return "", err
}
// Create a temporary working directory for the go command that contains
// the workspace module file.
name, err := ioutil.TempDir("", "gopls-mod")
key := workspaceDirKey(hashContents(content))
s.mu.Lock()
s.workspaceDirHandle = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
if err != nil {
return &workspaceDirData{err: err}
}
filename := filepath.Join(tmpdir, "go.mod")
if err := ioutil.WriteFile(filename, content, 0644); err != nil {
os.RemoveAll(tmpdir)
return &workspaceDirData{err: err}
}
return &workspaceDirData{dir: tmpdir}
}, func(v interface{}) {
d := v.(*workspaceDirData)
if d.dir != "" {
if err := os.RemoveAll(d.dir); err != nil {
event.Error(context.Background(), "cleaning workspace dir", err)
}
}
})
s.mu.Unlock()
return getWorkspaceDir(ctx, s.workspaceDirHandle, s.generation)
}
func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
v, err := h.Get(ctx, g, nil)
if err != nil {
return "", cleanup, err
return "", err
}
cleanup = func() {
os.RemoveAll(name)
}
filename := filepath.Join(name, "go.mod")
if err := ioutil.WriteFile(filename, content, 0644); err != nil {
cleanup()
return "", cleanup, err
}
return span.URIFromPath(filepath.Dir(filename)), cleanup, nil
return span.URIFromPath(v.(*workspaceDirData).dir), nil
}
// setMetadata extracts metadata from pkg and records it in s. It

View File

@ -92,7 +92,8 @@ type snapshot struct {
modUpgradeHandles map[span.URI]*modUpgradeHandle
modWhyHandles map[span.URI]*modWhyHandle
workspace *workspace
workspace *workspace
workspaceDirHandle *memoize.Handle
}
type packageKey struct {
@ -252,7 +253,7 @@ func (s *snapshot) goCommandInvocation(ctx context.Context, mode source.Invocati
} else {
var tmpDir span.URI
var err error
tmpDir, cleanup, err = s.tempWorkspaceModule(ctx)
tmpDir, err = s.getWorkspaceDir(ctx)
if err != nil {
return "", nil, cleanup, err
}
@ -1001,6 +1002,11 @@ func (s *snapshot) clone(ctx context.Context, changes map[span.URI]*fileChange,
workspace: newWorkspace,
}
if !workspaceChanged && s.workspaceDirHandle != nil {
result.workspaceDirHandle = s.workspaceDirHandle
newGen.Inherit(s.workspaceDirHandle)
}
if s.builtin != nil {
newGen.Inherit(s.builtin.handle)
}