mirror of
https://github.com/golang/go
synced 2024-11-18 19:44:46 -07:00
internal/lsp: push more build-specific logic into the view
The view should really be able to determine if it's valid, not the source package. Expand moduleInformation to be buildInfo, and use it to collect additional details. Use this information to determine if we should load a view's subdirectories as part of the initial workspace load. If a module is initialized, we will recreate the view, so we should be fine. Not sure what will happen if the directory is moved into GOPATH, but that should be less of a concern (I think). Fixes golang/go#35818. Change-Id: Ic8ceedd37386b1653b8965c64d9ba8953778ab78 Reviewed-on: https://go-review.googlesource.com/c/tools/+/216143 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
parent
e23f2f3ad7
commit
e0332898b9
12
internal/lsp/cache/load.go
vendored
12
internal/lsp/cache/load.go
vendored
@ -62,6 +62,14 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) ([]*metadata
|
|||||||
q = "./..."
|
q = "./..."
|
||||||
}
|
}
|
||||||
query = append(query, q)
|
query = append(query, q)
|
||||||
|
case viewLoadScope:
|
||||||
|
// If we are outside of GOPATH, a module, or some other known
|
||||||
|
// build system, don't load subdirectories.
|
||||||
|
if !s.view.hasValidBuildConfiguration {
|
||||||
|
query = append(query, "./")
|
||||||
|
} else {
|
||||||
|
query = append(query, "./...")
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown scope type %T", scope))
|
panic(fmt.Sprintf("unknown scope type %T", scope))
|
||||||
}
|
}
|
||||||
@ -136,9 +144,9 @@ func (s *snapshot) updateMetadata(ctx context.Context, scopes []interface{}, pkg
|
|||||||
// Don't log output for full workspace packages.Loads.
|
// Don't log output for full workspace packages.Loads.
|
||||||
var containsDir bool
|
var containsDir bool
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
if _, ok := scope.(directoryURI); ok {
|
switch scope.(type) {
|
||||||
|
case directoryURI, viewLoadScope:
|
||||||
containsDir = true
|
containsDir = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !containsDir || s.view.Options().VerboseOutput {
|
if !containsDir || s.view.Options().VerboseOutput {
|
||||||
|
1
internal/lsp/cache/pkg.go
vendored
1
internal/lsp/cache/pkg.go
vendored
@ -40,6 +40,7 @@ type packagePath string
|
|||||||
// Declare explicit types for files and directories to distinguish between the two.
|
// Declare explicit types for files and directories to distinguish between the two.
|
||||||
type fileURI span.URI
|
type fileURI span.URI
|
||||||
type directoryURI span.URI
|
type directoryURI span.URI
|
||||||
|
type viewLoadScope span.URI
|
||||||
|
|
||||||
func (p *pkg) ID() string {
|
func (p *pkg) ID() string {
|
||||||
return string(p.id)
|
return string(p.id)
|
||||||
|
8
internal/lsp/cache/session.go
vendored
8
internal/lsp/cache/session.go
vendored
@ -105,14 +105,8 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
if v.session.cache.options != nil {
|
if v.session.cache.options != nil {
|
||||||
v.session.cache.options(&v.options)
|
v.session.cache.options(&v.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to get the `go env` before continuing with initialization.
|
|
||||||
gomod, err := v.setGoEnv(ctx, folder.Filename(), options.Env)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Set the module-specific information.
|
// Set the module-specific information.
|
||||||
if err := v.setModuleInformation(ctx, gomod, v.options.TempModfile); err != nil {
|
if err := v.setBuildInformation(ctx, folder, options.Env, v.options.TempModfile); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
228
internal/lsp/cache/view.go
vendored
228
internal/lsp/cache/view.go
vendored
@ -60,9 +60,6 @@ type view struct {
|
|||||||
// Folder is the root of this view.
|
// Folder is the root of this view.
|
||||||
folder span.URI
|
folder span.URI
|
||||||
|
|
||||||
// mod is the module information for this view.
|
|
||||||
mod moduleInformation
|
|
||||||
|
|
||||||
// importsMu guards imports-related state, particularly the ProcessEnv.
|
// importsMu guards imports-related state, particularly the ProcessEnv.
|
||||||
importsMu sync.Mutex
|
importsMu sync.Mutex
|
||||||
// process is the process env for this view.
|
// process is the process env for this view.
|
||||||
@ -88,9 +85,6 @@ type view struct {
|
|||||||
ignoredURIsMu sync.Mutex
|
ignoredURIsMu sync.Mutex
|
||||||
ignoredURIs map[span.URI]struct{}
|
ignoredURIs map[span.URI]struct{}
|
||||||
|
|
||||||
// `go env` variables that need to be tracked by the view.
|
|
||||||
gopath, gocache string
|
|
||||||
|
|
||||||
// initialized is closed when the view has been fully initialized.
|
// initialized is closed when the view has been fully initialized.
|
||||||
// On initialization, the view's workspace packages are loaded.
|
// On initialization, the view's workspace packages are loaded.
|
||||||
// All of the fields below are set as part of initialization.
|
// All of the fields below are set as part of initialization.
|
||||||
@ -101,6 +95,21 @@ type view struct {
|
|||||||
|
|
||||||
// builtin pins the AST and package for builtin.go in memory.
|
// builtin pins the AST and package for builtin.go in memory.
|
||||||
builtin *builtinPackageHandle
|
builtin *builtinPackageHandle
|
||||||
|
|
||||||
|
// True if the view is either in GOPATH, a module, or some other
|
||||||
|
// non go command build system.
|
||||||
|
hasValidBuildConfiguration bool
|
||||||
|
|
||||||
|
// The real and temporary go.mod files that are attributed to a view.
|
||||||
|
// The temporary go.mod is for use with the Go command's -modfile flag.
|
||||||
|
realMod, tempMod span.URI
|
||||||
|
|
||||||
|
// goCommand indicates if the user is using the go command or some other
|
||||||
|
// build system.
|
||||||
|
goCommand bool
|
||||||
|
|
||||||
|
// `go env` variables that need to be tracked.
|
||||||
|
gopath, gocache string
|
||||||
}
|
}
|
||||||
|
|
||||||
type builtinPackageHandle struct {
|
type builtinPackageHandle struct {
|
||||||
@ -137,14 +146,12 @@ func (f *fileBase) addURI(uri span.URI) int {
|
|||||||
return len(f.uris)
|
return len(f.uris)
|
||||||
}
|
}
|
||||||
|
|
||||||
// moduleInformation holds the real and temporary go.mod files
|
func (v *view) ValidBuildConfiguration() bool {
|
||||||
// that are attributed to a view.
|
return v.hasValidBuildConfiguration
|
||||||
type moduleInformation struct {
|
|
||||||
realMod, tempMod span.URI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *view) ModFiles() (span.URI, span.URI) {
|
func (v *view) ModFiles() (span.URI, span.URI) {
|
||||||
return v.mod.realMod, v.mod.tempMod
|
return v.realMod, v.tempMod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *view) Session() source.Session {
|
func (v *view) Session() source.Session {
|
||||||
@ -257,8 +264,8 @@ func (v *view) Config(ctx context.Context) *packages.Config {
|
|||||||
// We want to run the go commands with the -modfile flag if the version of go
|
// We want to run the go commands with the -modfile flag if the version of go
|
||||||
// that we are using supports it.
|
// that we are using supports it.
|
||||||
buildFlags := v.options.BuildFlags
|
buildFlags := v.options.BuildFlags
|
||||||
if v.mod.tempMod != "" {
|
if v.tempMod != "" {
|
||||||
buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.mod.tempMod.Filename()))
|
buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.tempMod.Filename()))
|
||||||
}
|
}
|
||||||
cfg := &packages.Config{
|
cfg := &packages.Config{
|
||||||
Dir: v.folder.Filename(),
|
Dir: v.folder.Filename(),
|
||||||
@ -299,8 +306,8 @@ func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In module mode, check if the mod file has changed.
|
// In module mode, check if the mod file has changed.
|
||||||
if v.mod.realMod != "" {
|
if v.realMod != "" {
|
||||||
mod, err := v.Snapshot().GetFile(v.mod.realMod)
|
mod, err := v.Snapshot().GetFile(v.realMod)
|
||||||
if err == nil && mod.Identity() != v.cachedModFileVersion {
|
if err == nil && mod.Identity() != v.cachedModFileVersion {
|
||||||
v.processEnv.GetResolver().(*imports.ModuleResolver).ClearForNewMod()
|
v.processEnv.GetResolver().(*imports.ModuleResolver).ClearForNewMod()
|
||||||
v.cachedModFileVersion = mod.Identity()
|
v.cachedModFileVersion = mod.Identity()
|
||||||
@ -488,9 +495,9 @@ func (v *view) shutdown(context.Context) {
|
|||||||
v.cancel()
|
v.cancel()
|
||||||
v.cancel = nil
|
v.cancel = nil
|
||||||
}
|
}
|
||||||
if v.mod.tempMod != "" {
|
if v.tempMod != "" {
|
||||||
os.Remove(v.mod.tempMod.Filename())
|
os.Remove(v.tempMod.Filename())
|
||||||
os.Remove(tempSumFile(v.mod.tempMod.Filename()))
|
os.Remove(tempSumFile(v.tempMod.Filename()))
|
||||||
}
|
}
|
||||||
debug.DropView(debugView{v})
|
debug.DropView(debugView{v})
|
||||||
}
|
}
|
||||||
@ -543,7 +550,7 @@ func (v *view) initialize(ctx context.Context, s *snapshot) {
|
|||||||
|
|
||||||
v.initializationError = func() error {
|
v.initializationError = func() error {
|
||||||
// Do not cancel the call to go/packages.Load for the entire workspace.
|
// Do not cancel the call to go/packages.Load for the entire workspace.
|
||||||
meta, err := s.load(ctx, directoryURI(v.folder), packagePath("builtin"))
|
meta, err := s.load(ctx, viewLoadScope("LOAD_VIEW"), packagePath("builtin"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -611,81 +618,22 @@ func (v *view) cancelBackground() {
|
|||||||
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
|
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setGoEnv sets the view's GOPATH and GOCACHE values.
|
func (v *view) setBuildInformation(ctx context.Context, folder span.URI, env []string, modfileFlagEnabled bool) error {
|
||||||
// It also returns the view's GOMOD value, which need not be cached.
|
// Make sure to get the `go env` before continuing with initialization.
|
||||||
func (v *view) setGoEnv(ctx context.Context, dir string, env []string) (string, error) {
|
gomod, err := v.getGoEnv(ctx, env)
|
||||||
var gocache, gopath bool
|
|
||||||
for _, e := range env {
|
|
||||||
split := strings.Split(e, "=")
|
|
||||||
if len(split) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch split[0] {
|
|
||||||
case "GOCACHE":
|
|
||||||
v.gocache = split[1]
|
|
||||||
gocache = true
|
|
||||||
case "GOPATH":
|
|
||||||
v.gopath = split[1]
|
|
||||||
gopath = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b, err := source.InvokeGo(ctx, dir, env, "env", "-json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
envMap := make(map[string]string)
|
|
||||||
decoder := json.NewDecoder(b)
|
|
||||||
if err := decoder.Decode(&envMap); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if !gopath {
|
|
||||||
if gopath, ok := envMap["GOPATH"]; ok {
|
|
||||||
v.gopath = gopath
|
|
||||||
} else {
|
|
||||||
return "", errors.New("unable to determine GOPATH")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !gocache {
|
|
||||||
if gocache, ok := envMap["GOCACHE"]; ok {
|
|
||||||
v.gocache = gocache
|
|
||||||
} else {
|
|
||||||
return "", errors.New("unable to determine GOCACHE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gomod, ok := envMap["GOMOD"]; ok {
|
|
||||||
return gomod, nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
|
|
||||||
// flag exists for this version of go.
|
|
||||||
func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
|
|
||||||
// Check the go version by running "go list" with modules off.
|
|
||||||
// Borrowed from internal/imports/mod.go:620.
|
|
||||||
const format = `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`
|
|
||||||
folder := v.folder.Filename()
|
|
||||||
stdout, err := source.InvokeGo(ctx, folder, append(env, "GO111MODULE=off"), "list", "-e", "-f", format)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// If the output is not go1.14 or an empty string, then it could be an error.
|
|
||||||
lines := strings.Split(stdout.String(), "\n")
|
|
||||||
if len(lines) < 2 && stdout.String() != "" {
|
|
||||||
log.Error(ctx, "unexpected stdout when checking for go1.14", errors.Errorf("%q", stdout), telemetry.Directory.Of(folder))
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return lines[0] == "go1.14", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *view) setModuleInformation(ctx context.Context, gomod string, modfileFlagEnabled bool) error {
|
|
||||||
modFile := strings.TrimSpace(gomod)
|
modFile := strings.TrimSpace(gomod)
|
||||||
if modFile == os.DevNull {
|
if modFile == os.DevNull {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v.mod = moduleInformation{
|
v.realMod = span.FileURI(modFile)
|
||||||
realMod: span.FileURI(modFile),
|
|
||||||
}
|
// Now that we have set all required fields,
|
||||||
|
// check if the view has a valid build configuration.
|
||||||
|
v.hasValidBuildConfiguration = checkBuildConfiguration(v.goCommand, v.realMod, v.folder, v.gopath)
|
||||||
|
|
||||||
// The user has disabled the use of the -modfile flag.
|
// The user has disabled the use of the -modfile flag.
|
||||||
if !modfileFlagEnabled {
|
if !modfileFlagEnabled {
|
||||||
return nil
|
return nil
|
||||||
@ -713,7 +661,7 @@ func (v *view) setModuleInformation(ctx context.Context, gomod string, modfileFl
|
|||||||
if _, err := io.Copy(tempModFile, origFile); err != nil {
|
if _, err := io.Copy(tempModFile, origFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.mod.tempMod = span.FileURI(tempModFile.Name())
|
v.tempMod = span.FileURI(tempModFile.Name())
|
||||||
|
|
||||||
// Copy go.sum file as well (if there is one).
|
// Copy go.sum file as well (if there is one).
|
||||||
sumFile := filepath.Join(filepath.Dir(modFile), "go.sum")
|
sumFile := filepath.Join(filepath.Dir(modFile), "go.sum")
|
||||||
@ -731,6 +679,108 @@ func (v *view) setModuleInformation(ctx context.Context, gomod string, modfileFl
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkBuildConfiguration(goCommand bool, mod, folder span.URI, gopath string) bool {
|
||||||
|
// Since we only really understand the `go` command, if the user is not
|
||||||
|
// using the go command, assume that their configuration is valid.
|
||||||
|
if !goCommand {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check if the user is working within a module.
|
||||||
|
if mod != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// The user may have a multiple directories in their GOPATH.
|
||||||
|
// Check if the workspace is within any of them.
|
||||||
|
for _, gp := range filepath.SplitList(gopath) {
|
||||||
|
if isSubdirectory(filepath.Join(gp, "src"), folder.Filename()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubdirectory(root, leaf string) bool {
|
||||||
|
rel, err := filepath.Rel(root, leaf)
|
||||||
|
return err == nil && !strings.HasPrefix(rel, "..")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGoEnv sets the view's build information's GOPATH, GOCACHE, and GOPACKAGESDRIVER values.
|
||||||
|
// It also returns the view's GOMOD value, which need not be cached.
|
||||||
|
func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
|
||||||
|
var gocache, gopath, gopackagesdriver bool
|
||||||
|
isGoCommand := func(gopackagesdriver string) bool {
|
||||||
|
return gopackagesdriver == "" || gopackagesdriver == "off"
|
||||||
|
}
|
||||||
|
for _, e := range env {
|
||||||
|
split := strings.Split(e, "=")
|
||||||
|
if len(split) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch split[0] {
|
||||||
|
case "GOCACHE":
|
||||||
|
v.gocache = split[1]
|
||||||
|
gocache = true
|
||||||
|
case "GOPATH":
|
||||||
|
v.gopath = split[1]
|
||||||
|
gopath = true
|
||||||
|
case "GOPACKAGESDRIVER":
|
||||||
|
v.goCommand = isGoCommand(split[1])
|
||||||
|
gopackagesdriver = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := source.InvokeGo(ctx, v.folder.Filename(), env, "env", "-json")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
envMap := make(map[string]string)
|
||||||
|
decoder := json.NewDecoder(b)
|
||||||
|
if err := decoder.Decode(&envMap); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !gopath {
|
||||||
|
if gopath, ok := envMap["GOPATH"]; ok {
|
||||||
|
v.gopath = gopath
|
||||||
|
} else {
|
||||||
|
return "", errors.New("unable to determine GOPATH")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !gocache {
|
||||||
|
if gocache, ok := envMap["GOCACHE"]; ok {
|
||||||
|
v.gocache = gocache
|
||||||
|
} else {
|
||||||
|
return "", errors.New("unable to determine GOCACHE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The value of GOPACKAGESDRIVER is not returned through the go command.
|
||||||
|
if !gopackagesdriver {
|
||||||
|
v.goCommand = isGoCommand(os.Getenv("GOPACKAGESDRIVER"))
|
||||||
|
}
|
||||||
|
if gomod, ok := envMap["GOMOD"]; ok {
|
||||||
|
return gomod, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
|
||||||
|
// flag exists for this version of go.
|
||||||
|
func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
|
||||||
|
// Check the go version by running "go list" with modules off.
|
||||||
|
// Borrowed from internal/imports/mod.go:620.
|
||||||
|
const format = `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`
|
||||||
|
folder := v.folder.Filename()
|
||||||
|
stdout, err := source.InvokeGo(ctx, folder, append(env, "GO111MODULE=off"), "list", "-e", "-f", format)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// If the output is not go1.14 or an empty string, then it could be an error.
|
||||||
|
lines := strings.Split(stdout.String(), "\n")
|
||||||
|
if len(lines) < 2 && stdout.String() != "" {
|
||||||
|
log.Error(ctx, "unexpected stdout when checking for go1.14", errors.Errorf("%q", stdout), telemetry.Directory.Of(folder))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return lines[0] == "go1.14", nil
|
||||||
|
}
|
||||||
|
|
||||||
// tempSumFile returns the path to the copied temporary go.sum file.
|
// tempSumFile returns the path to the copied temporary go.sum file.
|
||||||
// It simply replaces the extension of the temporary go.mod file with "sum".
|
// It simply replaces the extension of the temporary go.mod file with "sum".
|
||||||
func tempSumFile(filename string) string {
|
func tempSumFile(filename string) string {
|
||||||
|
@ -133,6 +133,11 @@ type View interface {
|
|||||||
|
|
||||||
// Rebuild rebuilds the current view, replacing the original view in its session.
|
// Rebuild rebuilds the current view, replacing the original view in its session.
|
||||||
Rebuild(ctx context.Context) (Snapshot, error)
|
Rebuild(ctx context.Context) (Snapshot, error)
|
||||||
|
|
||||||
|
// InvalidBuildConfiguration returns true if there is some error in the
|
||||||
|
// user's workspace. In particular, if they are both outside of a module
|
||||||
|
// and their GOPATH.
|
||||||
|
ValidBuildConfiguration() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session represents a single connection from a client.
|
// Session represents a single connection from a client.
|
||||||
|
Loading…
Reference in New Issue
Block a user