2018-11-05 12:48:08 -07:00
|
|
|
// Copyright 2018 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2019-09-19 14:53:53 -06:00
|
|
|
// Package cache implements the caching layer for gopls.
|
2018-12-05 15:00:36 -07:00
|
|
|
package cache
|
2018-09-27 16:15:45 -06:00
|
|
|
|
|
|
|
import (
|
2018-12-18 14:18:03 -07:00
|
|
|
"context"
|
2019-07-14 21:08:10 -06:00
|
|
|
"fmt"
|
2019-04-29 19:08:16 -06:00
|
|
|
"go/ast"
|
2019-06-04 20:14:37 -06:00
|
|
|
"go/token"
|
2019-03-27 07:25:30 -06:00
|
|
|
"os"
|
2019-08-28 14:02:38 -06:00
|
|
|
"os/exec"
|
2019-12-17 14:53:57 -07:00
|
|
|
"path/filepath"
|
2019-11-08 11:25:29 -07:00
|
|
|
"reflect"
|
2019-07-03 13:23:05 -06:00
|
|
|
"strings"
|
2018-09-27 16:15:45 -06:00
|
|
|
"sync"
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
"time"
|
2018-09-27 16:15:45 -06:00
|
|
|
|
2018-10-19 14:03:29 -06:00
|
|
|
"golang.org/x/tools/go/packages"
|
2019-07-03 13:23:05 -06:00
|
|
|
"golang.org/x/tools/internal/imports"
|
2019-05-29 12:55:52 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug"
|
2019-10-15 16:07:52 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2018-12-05 15:00:36 -07:00
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
2019-02-19 19:11:15 -07:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-08-13 13:07:39 -06:00
|
|
|
"golang.org/x/tools/internal/telemetry/log"
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
2019-12-19 13:47:07 -07:00
|
|
|
"golang.org/x/tools/internal/xcontext"
|
2019-09-16 16:17:51 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2018-09-27 16:15:45 -06:00
|
|
|
)
|
|
|
|
|
2019-05-14 21:04:23 -06:00
|
|
|
type view struct {
|
2019-05-15 10:24:49 -06:00
|
|
|
session *session
|
2019-05-29 12:55:52 -06:00
|
|
|
id string
|
2019-05-15 10:24:49 -06:00
|
|
|
|
2019-09-11 11:13:44 -06:00
|
|
|
options source.Options
|
2019-09-05 22:17:36 -06:00
|
|
|
|
2019-03-04 16:01:51 -07:00
|
|
|
// mu protects all mutable state of the view.
|
|
|
|
mu sync.Mutex
|
2018-11-02 14:15:31 -06:00
|
|
|
|
2019-03-29 17:04:29 -06:00
|
|
|
// baseCtx is the context handed to NewView. This is the parent of all
|
|
|
|
// background contexts created for this view.
|
|
|
|
baseCtx context.Context
|
|
|
|
|
2019-03-05 15:30:44 -07:00
|
|
|
// backgroundCtx is the current context used by background tasks initiated
|
|
|
|
// by the view.
|
|
|
|
backgroundCtx context.Context
|
|
|
|
|
|
|
|
// cancel is called when all action being performed by the current view
|
|
|
|
// should be stopped.
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
2019-03-28 06:49:42 -06:00
|
|
|
// Name is the user visible name of this view.
|
2019-05-14 21:04:23 -06:00
|
|
|
name string
|
2019-03-28 06:49:42 -06:00
|
|
|
|
2019-12-16 13:40:24 -07:00
|
|
|
// modfiles are the go.mod files attributed to this view.
|
|
|
|
modfiles *modfiles
|
|
|
|
|
2019-03-28 06:49:42 -06:00
|
|
|
// Folder is the root of this view.
|
2019-05-14 21:04:23 -06:00
|
|
|
folder span.URI
|
2019-03-28 06:49:42 -06:00
|
|
|
|
2019-07-03 13:23:05 -06:00
|
|
|
// process is the process env for this view.
|
|
|
|
// Note: this contains cached module and filesystem state.
|
2019-07-12 16:54:06 -06:00
|
|
|
//
|
|
|
|
// TODO(suzmue): the state cached in the process env is specific to each view,
|
|
|
|
// however, there is state that can be shared between views that is not currently
|
|
|
|
// cached, like the module cache.
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
processEnv *imports.ProcessEnv
|
|
|
|
cacheRefreshTime time.Time
|
2019-07-03 13:23:05 -06:00
|
|
|
|
2019-07-12 16:54:06 -06:00
|
|
|
// modFileVersions stores the last seen versions of the module files that are used
|
|
|
|
// by processEnvs resolver.
|
|
|
|
// TODO(suzmue): These versions may not actually be on disk.
|
|
|
|
modFileVersions map[string]string
|
|
|
|
|
2019-03-27 07:25:30 -06:00
|
|
|
// keep track of files by uri and by basename, a single file may be mapped
|
|
|
|
// to multiple uris, and the same basename may map to multiple files
|
2019-12-17 16:57:54 -07:00
|
|
|
filesByURI map[span.URI]*fileBase
|
|
|
|
filesByBase map[string][]*fileBase
|
2019-02-06 16:47:00 -07:00
|
|
|
|
2019-09-23 18:06:15 -06:00
|
|
|
snapshotMu sync.Mutex
|
|
|
|
snapshot *snapshot
|
2019-03-04 16:01:51 -07:00
|
|
|
|
2019-09-16 15:17:59 -06:00
|
|
|
// builtin is used to resolve builtin types.
|
|
|
|
builtin *builtinPkg
|
2019-05-15 15:58:16 -06:00
|
|
|
|
|
|
|
// ignoredURIs is the set of URIs of files that we ignore.
|
2019-08-12 12:54:57 -06:00
|
|
|
ignoredURIsMu sync.Mutex
|
|
|
|
ignoredURIs map[span.URI]struct{}
|
2019-12-19 12:31:39 -07:00
|
|
|
|
|
|
|
// initialized is closed when we have attempted to load the view's workspace packages.
|
|
|
|
// If we failed to load initially, we don't re-try to avoid too many go/packages calls.
|
|
|
|
initializeOnce sync.Once
|
|
|
|
initialized chan struct{}
|
|
|
|
initializationError error
|
2020-01-07 19:37:41 -07:00
|
|
|
|
|
|
|
// buildCachePath is the value of `go env GOCACHE`.
|
|
|
|
buildCachePath string
|
2018-09-27 16:15:45 -06:00
|
|
|
}
|
|
|
|
|
2020-01-06 16:08:39 -07:00
|
|
|
// fileBase holds the common functionality for all files.
|
|
|
|
// It is intended to be embedded in the file implementations
|
|
|
|
type fileBase struct {
|
|
|
|
uris []span.URI
|
|
|
|
fname string
|
|
|
|
|
|
|
|
view *view
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fileBase) URI() span.URI {
|
|
|
|
return f.uris[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fileBase) filename() string {
|
|
|
|
return f.fname
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fileBase) addURI(uri span.URI) int {
|
|
|
|
f.uris = append(f.uris, uri)
|
|
|
|
return len(f.uris)
|
|
|
|
}
|
|
|
|
|
2019-12-16 13:40:24 -07:00
|
|
|
// modfiles holds the real and temporary go.mod files that are attributed to a view.
|
|
|
|
type modfiles struct {
|
|
|
|
real, temp string
|
|
|
|
}
|
|
|
|
|
2019-05-15 10:24:49 -06:00
|
|
|
func (v *view) Session() source.Session {
|
|
|
|
return v.session
|
2018-09-27 16:15:45 -06:00
|
|
|
}
|
|
|
|
|
2019-05-14 21:04:23 -06:00
|
|
|
// Name returns the user visible name of this view.
|
|
|
|
func (v *view) Name() string {
|
|
|
|
return v.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Folder returns the root of this view.
|
|
|
|
func (v *view) Folder() span.URI {
|
|
|
|
return v.folder
|
|
|
|
}
|
|
|
|
|
2019-09-11 11:13:44 -06:00
|
|
|
func (v *view) Options() source.Options {
|
2019-09-05 22:17:36 -06:00
|
|
|
return v.options
|
|
|
|
}
|
|
|
|
|
2019-11-08 11:25:29 -07:00
|
|
|
func minorOptionsChange(a, b source.Options) bool {
|
|
|
|
// Check if any of the settings that modify our understanding of files have been changed
|
|
|
|
if !reflect.DeepEqual(a.Env, b.Env) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(a.BuildFlags, b.BuildFlags) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// the rest of the options are benign
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
|
|
|
|
// no need to rebuild the view if the options were not materially changed
|
|
|
|
if minorOptionsChange(v.options, options) {
|
|
|
|
v.options = options
|
|
|
|
return v, nil
|
|
|
|
}
|
2019-11-15 12:47:29 -07:00
|
|
|
newView, _, err := v.session.updateView(ctx, v, options)
|
2019-11-08 11:25:29 -07:00
|
|
|
return newView, err
|
2019-09-11 16:21:01 -06:00
|
|
|
}
|
|
|
|
|
2019-05-14 21:04:23 -06:00
|
|
|
// Config returns the configuration used for the view's interaction with the
|
|
|
|
// go/packages API. It is shared across all views.
|
2019-07-14 11:59:24 -06:00
|
|
|
func (v *view) Config(ctx context.Context) *packages.Config {
|
2019-06-24 14:34:21 -06:00
|
|
|
// TODO: Should we cache the config and/or overlay somewhere?
|
2019-12-16 13:40:24 -07:00
|
|
|
|
|
|
|
// We want to run the go commands with the -modfile flag if the version of go
|
|
|
|
// that we are using supports it.
|
|
|
|
buildFlags := v.options.BuildFlags
|
|
|
|
if v.modfiles != nil {
|
|
|
|
buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.modfiles.temp))
|
|
|
|
}
|
2019-05-17 08:51:19 -06:00
|
|
|
return &packages.Config{
|
2019-06-06 11:51:00 -06:00
|
|
|
Dir: v.folder.Filename(),
|
2019-10-09 12:11:22 -06:00
|
|
|
Context: ctx,
|
2019-09-09 11:04:12 -06:00
|
|
|
Env: v.options.Env,
|
2019-12-16 13:40:24 -07:00
|
|
|
BuildFlags: buildFlags,
|
2019-05-17 08:51:19 -06:00
|
|
|
Mode: packages.NeedName |
|
|
|
|
packages.NeedFiles |
|
|
|
|
packages.NeedCompiledGoFiles |
|
|
|
|
packages.NeedImports |
|
|
|
|
packages.NeedDeps |
|
|
|
|
packages.NeedTypesSizes,
|
2019-06-04 20:14:37 -06:00
|
|
|
Fset: v.session.cache.fset,
|
|
|
|
Overlay: v.session.buildOverlay(),
|
|
|
|
ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
|
|
|
|
panic("go/packages must not be used to parse files")
|
|
|
|
},
|
2019-07-14 11:59:24 -06:00
|
|
|
Logf: func(format string, args ...interface{}) {
|
2019-11-03 19:09:58 -07:00
|
|
|
if v.options.VerboseOutput {
|
|
|
|
log.Print(ctx, fmt.Sprintf(format, args...))
|
|
|
|
}
|
2019-07-14 11:59:24 -06:00
|
|
|
},
|
2019-06-04 20:14:37 -06:00
|
|
|
Tests: true,
|
2019-05-17 08:51:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-27 12:07:33 -07:00
|
|
|
func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
|
2019-07-03 13:23:05 -06:00
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
if v.processEnv == nil {
|
2019-08-28 14:02:38 -06:00
|
|
|
var err error
|
|
|
|
if v.processEnv, err = v.buildProcessEnv(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-03 13:23:05 -06:00
|
|
|
}
|
2019-07-12 16:54:06 -06:00
|
|
|
|
|
|
|
// Before running the user provided function, clear caches in the resolver.
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
if v.modFilesChanged() {
|
|
|
|
v.processEnv.GetResolver().(*imports.ModuleResolver).ClearForNewMod()
|
2019-07-12 16:54:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run the user function.
|
2019-12-27 12:07:33 -07:00
|
|
|
opts := &imports.Options{
|
|
|
|
// Defaults.
|
|
|
|
AllErrors: true,
|
|
|
|
Comments: true,
|
|
|
|
Fragment: true,
|
|
|
|
FormatOnly: false,
|
|
|
|
TabIndent: true,
|
|
|
|
TabWidth: 8,
|
|
|
|
Env: v.processEnv,
|
|
|
|
}
|
|
|
|
|
2019-07-12 16:54:06 -06:00
|
|
|
if err := fn(opts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
if v.cacheRefreshTime.IsZero() {
|
|
|
|
v.cacheRefreshTime = time.Now()
|
|
|
|
}
|
2019-07-12 16:54:06 -06:00
|
|
|
|
|
|
|
// If applicable, store the file versions of the 'go.mod' files that are
|
|
|
|
// looked at by the resolver.
|
|
|
|
v.storeModFileVersions()
|
|
|
|
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
if time.Since(v.cacheRefreshTime) > 30*time.Second {
|
|
|
|
go func() {
|
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
|
|
|
|
log.Print(context.Background(), "background imports cache refresh starting")
|
|
|
|
v.processEnv.GetResolver().ClearForNewScan()
|
2019-12-26 17:13:58 -07:00
|
|
|
// TODO(heschi): prime the cache
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
v.cacheRefreshTime = time.Now()
|
2019-12-26 17:13:58 -07:00
|
|
|
log.Print(context.Background(), "background refresh finished with err: ", tag.Of("err", nil))
|
internal/lsp/cache: only refresh imports cache every 30 seconds
Loading completion suggestions can be slow, especially in GOPATH mode
where basically anything can change at any time. As a compromise, cache
everything for 30 seconds. Specifically, after a completion operation
finishes, if the cache is more than 30 seconds old, refresh it
asynchronously. That keeps user-facing latency consistent, without
chewing up CPU when the editor isn't in use. It does mean that if you
walk away for an hour and come back, the first completion may be stale.
In module mode this is relatively benign. The only things the
longer caching affects are the main module and replace targets, and
relevant packages in those will generally be loaded by gopls, so they'll
have full, up-to-date type information regardless.
In GOPATH mode this may be more troublesome, since it affects
everything. In particular, go get -u of a package that isn't imported
yet won't be reflected until the cache period expires. I think that's a
rare enough case not to worry about.
Change-Id: Iaadfd0ff647cda2b1dcdead9254b5492b397e86e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205163
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-04 12:05:41 -07:00
|
|
|
}()
|
|
|
|
}
|
2019-07-12 16:54:06 -06:00
|
|
|
return nil
|
2019-07-03 13:23:05 -06:00
|
|
|
}
|
|
|
|
|
2019-08-28 14:02:38 -06:00
|
|
|
func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
|
2019-07-14 11:59:24 -06:00
|
|
|
cfg := v.Config(ctx)
|
2019-07-03 13:23:05 -06:00
|
|
|
env := &imports.ProcessEnv{
|
|
|
|
WorkingDir: cfg.Dir,
|
2019-07-14 21:08:10 -06:00
|
|
|
Logf: func(format string, args ...interface{}) {
|
|
|
|
log.Print(ctx, fmt.Sprintf(format, args...))
|
2019-07-03 13:23:05 -06:00
|
|
|
},
|
2019-11-01 14:19:19 -06:00
|
|
|
LocalPrefix: v.options.LocalPrefix,
|
2019-11-03 19:09:58 -07:00
|
|
|
Debug: v.options.VerboseOutput,
|
2019-07-03 13:23:05 -06:00
|
|
|
}
|
|
|
|
for _, kv := range cfg.Env {
|
|
|
|
split := strings.Split(kv, "=")
|
|
|
|
if len(split) < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch split[0] {
|
|
|
|
case "GOPATH":
|
|
|
|
env.GOPATH = split[1]
|
|
|
|
case "GOROOT":
|
|
|
|
env.GOROOT = split[1]
|
|
|
|
case "GO111MODULE":
|
|
|
|
env.GO111MODULE = split[1]
|
|
|
|
case "GOPROXY":
|
2019-09-08 00:42:38 -06:00
|
|
|
env.GOPROXY = split[1]
|
2019-07-03 13:23:05 -06:00
|
|
|
case "GOFLAGS":
|
|
|
|
env.GOFLAGS = split[1]
|
|
|
|
case "GOSUMDB":
|
|
|
|
env.GOSUMDB = split[1]
|
|
|
|
}
|
|
|
|
}
|
2019-08-28 14:02:38 -06:00
|
|
|
|
|
|
|
if env.GOPATH == "" {
|
2020-01-07 19:37:41 -07:00
|
|
|
gopath, err := getGoEnvVar(ctx, cfg, "GOPATH")
|
|
|
|
if err != nil {
|
2019-08-28 14:02:38 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-07 19:37:41 -07:00
|
|
|
env.GOPATH = gopath
|
2019-08-28 14:02:38 -06:00
|
|
|
}
|
|
|
|
return env, nil
|
2019-07-03 13:23:05 -06:00
|
|
|
}
|
|
|
|
|
2019-07-12 16:54:06 -06:00
|
|
|
func (v *view) modFilesChanged() bool {
|
|
|
|
// Check the versions of the 'go.mod' files of the main module
|
|
|
|
// and modules included by a replace directive. Return true if
|
|
|
|
// any of these file versions do not match.
|
|
|
|
for filename, version := range v.modFileVersions {
|
2020-01-06 16:08:39 -07:00
|
|
|
if version != v.fileVersion(filename) {
|
2019-07-12 16:54:06 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) storeModFileVersions() {
|
|
|
|
// Store the mod files versions, if we are using a ModuleResolver.
|
|
|
|
r, moduleMode := v.processEnv.GetResolver().(*imports.ModuleResolver)
|
|
|
|
if !moduleMode || !r.Initialized {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v.modFileVersions = make(map[string]string)
|
|
|
|
|
|
|
|
// Get the file versions of the 'go.mod' files of the main module
|
|
|
|
// and modules included by a replace directive in the resolver.
|
|
|
|
for _, mod := range r.ModsByModPath {
|
|
|
|
if (mod.Main || mod.Replace != nil) && mod.GoMod != "" {
|
2020-01-06 16:08:39 -07:00
|
|
|
v.modFileVersions[mod.GoMod] = v.fileVersion(mod.GoMod)
|
2019-07-12 16:54:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 16:08:39 -07:00
|
|
|
func (v *view) fileVersion(filename string) string {
|
2019-07-12 16:54:06 -06:00
|
|
|
uri := span.FileURI(filename)
|
2020-01-06 16:08:39 -07:00
|
|
|
fh := v.session.GetFile(uri)
|
2019-11-07 15:52:35 -07:00
|
|
|
return fh.Identity().String()
|
2019-07-12 16:54:06 -06:00
|
|
|
}
|
|
|
|
|
2020-01-06 16:08:39 -07:00
|
|
|
func (v *view) mapFile(uri span.URI, f *fileBase) {
|
|
|
|
v.filesByURI[uri] = f
|
|
|
|
if f.addURI(uri) == 1 {
|
|
|
|
basename := basename(f.filename())
|
|
|
|
v.filesByBase[basename] = append(v.filesByBase[basename], f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func basename(filename string) string {
|
|
|
|
return strings.ToLower(filepath.Base(filename))
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindFile returns the file if the given URI is already a part of the view.
|
2020-01-09 16:18:27 -07:00
|
|
|
func (v *view) findFileLocked(ctx context.Context, uri span.URI) (*fileBase, error) {
|
2020-01-06 16:08:39 -07:00
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
|
2020-01-09 16:18:27 -07:00
|
|
|
return v.findFile(uri)
|
2020-01-06 16:08:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// getFileLocked returns a File for the given URI. It will always succeed because it
|
|
|
|
// adds the file to the managed set if needed.
|
|
|
|
func (v *view) getFileLocked(ctx context.Context, uri span.URI) (*fileBase, error) {
|
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
|
|
|
|
return v.getFile(ctx, uri)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getFile is the unlocked internal implementation of GetFile.
|
|
|
|
func (v *view) getFile(ctx context.Context, uri span.URI) (*fileBase, error) {
|
|
|
|
f, err := v.findFile(uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if f != nil {
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
f = &fileBase{
|
|
|
|
view: v,
|
|
|
|
fname: uri.Filename(),
|
|
|
|
}
|
|
|
|
v.mapFile(uri, f)
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// findFile checks the cache for any file matching the given uri.
|
|
|
|
//
|
|
|
|
// An error is only returned for an irreparable failure, for example, if the
|
|
|
|
// filename in question does not exist.
|
|
|
|
func (v *view) findFile(uri span.URI) (*fileBase, error) {
|
|
|
|
if f := v.filesByURI[uri]; f != nil {
|
|
|
|
// a perfect match
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
// no exact match stored, time to do some real work
|
|
|
|
// check for any files with the same basename
|
|
|
|
fname := uri.Filename()
|
|
|
|
basename := basename(fname)
|
|
|
|
if candidates := v.filesByBase[basename]; candidates != nil {
|
|
|
|
pathStat, err := os.Stat(fname)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil // the file may exist, return without an error
|
|
|
|
}
|
|
|
|
for _, c := range candidates {
|
|
|
|
if cStat, err := os.Stat(c.filename()); err == nil {
|
|
|
|
if os.SameFile(pathStat, cStat) {
|
|
|
|
// same file, map it
|
|
|
|
v.mapFile(uri, c)
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// no file with a matching name was found, it wasn't in our cache
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2019-05-15 10:24:49 -06:00
|
|
|
func (v *view) Shutdown(ctx context.Context) {
|
|
|
|
v.session.removeView(ctx, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) shutdown(context.Context) {
|
2019-05-02 08:55:04 -06:00
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
if v.cancel != nil {
|
|
|
|
v.cancel()
|
|
|
|
v.cancel = nil
|
|
|
|
}
|
2019-12-23 09:33:23 -07:00
|
|
|
if v.modfiles != nil {
|
|
|
|
os.Remove(v.modfiles.temp)
|
|
|
|
}
|
2019-05-29 12:55:52 -06:00
|
|
|
debug.DropView(debugView{v})
|
2019-05-02 08:55:04 -06:00
|
|
|
}
|
|
|
|
|
2019-05-23 13:03:11 -06:00
|
|
|
// Ignore checks if the given URI is a URI we ignore.
|
|
|
|
// As of right now, we only ignore files in the "builtin" package.
|
|
|
|
func (v *view) Ignore(uri span.URI) bool {
|
2019-08-12 12:54:57 -06:00
|
|
|
v.ignoredURIsMu.Lock()
|
|
|
|
defer v.ignoredURIsMu.Unlock()
|
|
|
|
|
2019-05-23 13:03:11 -06:00
|
|
|
_, ok := v.ignoredURIs[uri]
|
2019-12-17 14:53:57 -07:00
|
|
|
|
|
|
|
// Files with _ prefixes are always ignored.
|
|
|
|
if !ok && strings.HasPrefix(filepath.Base(uri.Filename()), "_") {
|
|
|
|
v.ignoredURIs[uri] = struct{}{}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-05-23 13:03:11 -06:00
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:04:23 -06:00
|
|
|
func (v *view) BackgroundContext() context.Context {
|
2019-03-05 15:30:44 -07:00
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
|
|
|
|
return v.backgroundCtx
|
|
|
|
}
|
|
|
|
|
2019-09-16 15:17:59 -06:00
|
|
|
func (v *view) BuiltinPackage() source.BuiltinPackage {
|
|
|
|
return v.builtin
|
2018-12-18 14:18:03 -07:00
|
|
|
}
|
|
|
|
|
2019-09-27 11:17:59 -06:00
|
|
|
func (v *view) Snapshot() source.Snapshot {
|
|
|
|
return v.getSnapshot()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) getSnapshot() *snapshot {
|
|
|
|
v.snapshotMu.Lock()
|
|
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
|
|
|
|
return v.snapshot
|
|
|
|
}
|
|
|
|
|
2019-12-19 12:31:39 -07:00
|
|
|
func (v *view) WorkspacePackageIDs(ctx context.Context) ([]string, error) {
|
|
|
|
s := v.getSnapshot()
|
|
|
|
|
2020-01-09 23:45:57 -07:00
|
|
|
if err := s.awaitInitialized(ctx); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return s.workspacePackageIDs(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) initialize(ctx context.Context, s *snapshot) {
|
2019-12-19 12:31:39 -07:00
|
|
|
v.initializeOnce.Do(func() {
|
|
|
|
defer close(v.initialized)
|
|
|
|
|
|
|
|
// Do not cancel the call to go/packages.Load for the entire workspace.
|
2020-01-09 23:45:57 -07:00
|
|
|
meta, err := s.load(ctx, directoryURI(v.folder))
|
2019-12-19 12:31:39 -07:00
|
|
|
if err != nil {
|
|
|
|
v.initializationError = err
|
|
|
|
}
|
2020-01-07 19:37:41 -07:00
|
|
|
// A test variant of a package can only be loaded directly by loading
|
|
|
|
// the non-test variant with -test. Track the import path of the non-test variant.
|
2019-12-19 12:31:39 -07:00
|
|
|
for _, m := range meta {
|
2020-01-07 19:37:41 -07:00
|
|
|
s.setWorkspacePackage(m.id, m.pkgPath)
|
2019-12-19 12:31:39 -07:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *snapshot) awaitInitialized(ctx context.Context) error {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
case <-s.view.initialized:
|
|
|
|
}
|
|
|
|
return s.view.initializationError
|
|
|
|
}
|
|
|
|
|
2019-12-17 14:53:57 -07:00
|
|
|
// invalidateContent invalidates the content of a Go file,
|
|
|
|
// including any position and type information that depends on it.
|
|
|
|
// It returns true if we were already tracking the given file, false otherwise.
|
2019-12-17 16:57:54 -07:00
|
|
|
func (v *view) invalidateContent(ctx context.Context, uri span.URI, kind source.FileKind, action source.FileAction) source.Snapshot {
|
2019-12-19 13:47:07 -07:00
|
|
|
// Detach the context so that content invalidation cannot be canceled.
|
|
|
|
ctx = xcontext.Detach(ctx)
|
|
|
|
|
2019-12-17 14:53:57 -07:00
|
|
|
// Cancel all still-running previous requests, since they would be
|
|
|
|
// operating on stale data.
|
|
|
|
switch action {
|
|
|
|
case source.Change, source.Close:
|
|
|
|
v.cancelBackground()
|
|
|
|
}
|
|
|
|
|
2019-12-19 12:31:39 -07:00
|
|
|
// Do not clone a snapshot until the workspace load has been completed.
|
|
|
|
<-v.initialized
|
|
|
|
|
2019-12-17 14:53:57 -07:00
|
|
|
// This should be the only time we hold the view's snapshot lock for any period of time.
|
|
|
|
v.snapshotMu.Lock()
|
|
|
|
defer v.snapshotMu.Unlock()
|
|
|
|
|
2019-12-17 16:57:54 -07:00
|
|
|
v.snapshot = v.snapshot.clone(ctx, uri, kind)
|
2019-12-17 14:53:57 -07:00
|
|
|
return v.snapshot
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) cancelBackground() {
|
2019-03-05 15:30:44 -07:00
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
2019-04-29 17:47:54 -06:00
|
|
|
|
2019-03-05 15:30:44 -07:00
|
|
|
v.cancel()
|
2019-03-29 17:04:29 -06:00
|
|
|
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
|
2019-03-05 15:30:44 -07:00
|
|
|
}
|
|
|
|
|
2019-11-21 12:54:31 -07:00
|
|
|
func (v *view) FindPosInPackage(searchpkg source.Package, pos token.Pos) (*ast.File, source.Package, error) {
|
2019-11-12 14:33:11 -07:00
|
|
|
tok := v.session.cache.fset.File(pos)
|
|
|
|
if tok == nil {
|
2019-11-21 12:54:31 -07:00
|
|
|
return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
2019-11-21 12:54:31 -07:00
|
|
|
uri := span.FileURI(tok.Name())
|
2019-11-12 14:33:11 -07:00
|
|
|
|
2019-10-29 16:13:19 -06:00
|
|
|
// Special case for ignored files.
|
2019-11-12 14:33:11 -07:00
|
|
|
var (
|
|
|
|
ph source.ParseGoHandle
|
|
|
|
pkg source.Package
|
|
|
|
err error
|
|
|
|
)
|
2019-10-29 16:13:19 -06:00
|
|
|
if v.Ignore(uri) {
|
2019-11-12 14:33:11 -07:00
|
|
|
ph, pkg, err = v.findIgnoredFile(uri)
|
|
|
|
} else {
|
|
|
|
ph, pkg, err = findFileInPackage(searchpkg, uri)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2019-11-21 12:54:31 -07:00
|
|
|
return nil, nil, err
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
2019-11-21 12:54:31 -07:00
|
|
|
file, _, _, err := ph.Cached()
|
2019-11-12 14:33:11 -07:00
|
|
|
if err != nil {
|
2019-11-21 12:54:31 -07:00
|
|
|
return nil, nil, err
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
|
|
|
if !(file.Pos() <= pos && pos <= file.End()) {
|
2019-11-21 12:54:31 -07:00
|
|
|
return nil, nil, fmt.Errorf("pos %v, apparently in file %q, is not between %v and %v", pos, ph.File().Identity().URI, file.Pos(), file.End())
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
2019-11-21 12:54:31 -07:00
|
|
|
return file, pkg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) FindMapperInPackage(searchpkg source.Package, uri span.URI) (*protocol.ColumnMapper, error) {
|
|
|
|
// Special case for ignored files.
|
|
|
|
var (
|
|
|
|
ph source.ParseGoHandle
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
if v.Ignore(uri) {
|
|
|
|
ph, _, err = v.findIgnoredFile(uri)
|
|
|
|
} else {
|
|
|
|
ph, _, err = findFileInPackage(searchpkg, uri)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, m, _, err := ph.Cached()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return m, nil
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *view) findIgnoredFile(uri span.URI) (source.ParseGoHandle, source.Package, error) {
|
|
|
|
// Check the builtin package.
|
2019-11-20 14:15:00 -07:00
|
|
|
for _, h := range v.BuiltinPackage().CompiledGoFiles() {
|
2019-11-12 14:33:11 -07:00
|
|
|
if h.File().Identity().URI == uri {
|
|
|
|
return h, nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil, errors.Errorf("no ignored file for %s", uri)
|
|
|
|
}
|
|
|
|
|
|
|
|
func findFileInPackage(pkg source.Package, uri span.URI) (source.ParseGoHandle, source.Package, error) {
|
|
|
|
queue := []source.Package{pkg}
|
|
|
|
seen := make(map[string]bool)
|
|
|
|
|
|
|
|
for len(queue) > 0 {
|
|
|
|
pkg := queue[0]
|
|
|
|
queue = queue[1:]
|
|
|
|
seen[pkg.ID()] = true
|
|
|
|
|
2019-11-21 12:54:31 -07:00
|
|
|
if f, err := pkg.File(uri); err == nil {
|
|
|
|
return f, pkg, nil
|
2019-11-12 14:33:11 -07:00
|
|
|
}
|
|
|
|
for _, dep := range pkg.Imports() {
|
|
|
|
if !seen[dep.ID()] {
|
|
|
|
queue = append(queue, dep)
|
|
|
|
}
|
|
|
|
}
|
2019-10-29 16:13:19 -06:00
|
|
|
}
|
2019-11-12 14:33:11 -07:00
|
|
|
return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
|
2019-10-29 16:13:19 -06:00
|
|
|
}
|
2020-01-07 19:37:41 -07:00
|
|
|
|
|
|
|
func (v *view) getBuildCachePath(ctx context.Context) (string, error) {
|
|
|
|
v.mu.Lock()
|
|
|
|
defer v.mu.Unlock()
|
|
|
|
|
|
|
|
if v.buildCachePath == "" {
|
|
|
|
path, err := getGoEnvVar(ctx, v.Config(ctx), "GOCACHE")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
v.buildCachePath = path
|
|
|
|
}
|
|
|
|
return v.buildCachePath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getGoEnvVar(ctx context.Context, cfg *packages.Config, value string) (string, error) {
|
|
|
|
var result string
|
|
|
|
for _, kv := range cfg.Env {
|
|
|
|
split := strings.Split(kv, "=")
|
|
|
|
if len(split) < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if split[0] == value {
|
|
|
|
result = split[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if result == "" {
|
|
|
|
cmd := exec.CommandContext(ctx, "go", "env", value)
|
|
|
|
cmd.Env = cfg.Env
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
result = strings.TrimSpace(string(out))
|
|
|
|
if result == "" {
|
|
|
|
return "", errors.Errorf("no value for %s", value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|