internal/lsp: rewrite createView to populate fields independently

The logic of incrementally populating the view was getting unnecessarily
complicated and hard to reason about. Split out helper functions that
we can use to create the view's fields before creating it.

Change-Id: I872fe22a9c2802668facf6b2d795b195aa47de00
Reviewed-on: https://go-review.googlesource.com/c/tools/+/255348
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-16 21:34:30 -04:00
parent 4aa5c68dc7
commit a20a5e7f0e
5 changed files with 262 additions and 238 deletions

View File

@ -61,7 +61,7 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
// Simplify the query if it will be run in the requested directory.
// This ensures compatibility with Go 1.12 that doesn't allow
// <directory>/... in GOPATH mode.
if s.view.root.Filename() == filename {
if s.view.rootURI.Filename() == filename {
q = "./..."
}
query = append(query, q)
@ -199,7 +199,7 @@ 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.modules) == 0 {
if s.view.workspaceMode&usesWorkspaceModule == 0 {
return "", cleanup, nil
}
wsModuleHandle, err := s.getWorkspaceModuleHandle(ctx)

View File

@ -211,7 +211,7 @@ func (s *snapshot) ModWhy(ctx context.Context, fh source.FileHandle) (map[string
sessionID: s.view.session.id,
cfg: hashConfig(cfg),
mod: fh.FileIdentity(),
view: s.view.root.Filename(),
view: s.view.rootURI.Filename(),
verb: why,
}
h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
@ -303,7 +303,7 @@ func (s *snapshot) ModUpgrade(ctx context.Context, fh source.FileHandle) (map[st
sessionID: s.view.session.id,
cfg: hashConfig(cfg),
mod: fh.FileIdentity(),
view: s.view.root.Filename(),
view: s.view.rootURI.Filename(),
verb: upgrade,
}
h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {

View File

@ -7,6 +7,8 @@ package cache
import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@ -153,26 +155,55 @@ func (s *Session) NewView(ctx context.Context, name string, folder span.URI, opt
func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
index := atomic.AddInt64(&viewIndex, 1)
if s.cache.options != nil {
s.cache.options(options)
}
// Set the module-specific information.
ws, err := s.getWorkspaceInformation(ctx, folder, options)
if err != nil {
return nil, nil, func() {}, err
}
// Find all of the modules in the workspace.
modules, err := findWorkspaceModules(ctx, ws.rootURI, options)
if err != nil {
return nil, nil, func() {}, err
}
// Now that we have set all required fields,
// check if the view has a valid build configuration.
validBuildConfiguration := validBuildConfiguration(folder, ws, modules)
mode := determineWorkspaceMode(options, validBuildConfiguration, ws, modules)
// We want a true background context and not a detached context here
// the spans need to be unrelated and no tag values should pollute it.
baseCtx := event.Detach(xcontext.Detach(ctx))
backgroundCtx, cancel := context.WithCancel(baseCtx)
v := &View{
session: s,
initialized: make(chan struct{}),
initializationSema: make(chan struct{}, 1),
initializeOnce: &sync.Once{},
id: strconv.FormatInt(index, 10),
options: options,
baseCtx: baseCtx,
backgroundCtx: backgroundCtx,
cancel: cancel,
name: name,
folder: folder,
root: folder,
filesByURI: make(map[span.URI]*fileBase),
filesByBase: make(map[string][]*fileBase),
session: s,
initialized: make(chan struct{}),
initializationSema: make(chan struct{}, 1),
initializeOnce: &sync.Once{},
id: strconv.FormatInt(index, 10),
options: options,
baseCtx: baseCtx,
backgroundCtx: backgroundCtx,
cancel: cancel,
name: name,
folder: folder,
filesByURI: make(map[span.URI]*fileBase),
filesByBase: make(map[string][]*fileBase),
hasValidBuildConfiguration: validBuildConfiguration,
processEnv: &imports.ProcessEnv{
GocmdRunner: s.gocmdRunner,
WorkingDir: folder.Filename(),
Env: ws.goEnv,
},
workspaceMode: mode,
workspaceInformation: *ws,
}
v.snapshot = &snapshot{
id: snapshotID,
@ -191,39 +222,10 @@ 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 {
v.session.cache.options(v.options)
}
// Set the module-specific information.
if err := v.setBuildInformation(ctx, options); err != nil {
return nil, nil, func() {}, err
}
// Find all of the modules in the workspace.
if err := v.snapshot.findWorkspaceModules(ctx, options); err != nil {
return nil, nil, func() {}, err
}
// Now that we have set all required fields,
// check if the view has a valid build configuration.
v.setBuildConfiguration()
// Decide if we should use the workspace module.
if v.determineWorkspaceModuleLocked() {
v.workspaceMode |= usesWorkspaceModule | moduleMode
}
// We have v.goEnv now.
v.processEnv = &imports.ProcessEnv{
GocmdRunner: s.gocmdRunner,
WorkingDir: folder.Filename(),
Env: v.goEnv,
modules: modules,
}
// TODO(rstambler): Change this function to work without a snapshot.
// Set the first snapshot's workspace directories. The view's modURI was
// set by setBuildInformation.
var fh source.FileHandle
@ -244,6 +246,47 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
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 findWorkspaceModules(ctx context.Context, root span.URI, options *source.Options) (map[span.URI]*moduleRoot, error) {
// Walk the view's folder to find all modules in the view.
modules := make(map[span.URI]*moduleRoot)
return modules, filepath.Walk(root.Filename(), 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.Filename() {
suffix := strings.TrimPrefix(path, root.Filename())
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))
modules[rootURI] = &moduleRoot{
rootURI: rootURI,
modURI: modURI,
sumURI: span.URIFromPath(sumFilename(modURI)),
}
return nil
})
}
// View returns the view by name.
func (s *Session) View(name string) source.View {
s.viewMu.Lock()
@ -605,3 +648,34 @@ func (s *Session) Overlays() []source.Overlay {
}
return overlays
}
// goVersion returns the Go version in use for the given session.
func (s *Session) goVersion(ctx context.Context, folder string, env []string) (int, error) {
// Check the go version by running "go list" with modules off.
// Borrowed from internal/imports/mod.go:620.
const format = `{{context.ReleaseTags}}`
inv := gocommand.Invocation{
Verb: "list",
Args: []string{"-e", "-f", format},
Env: append(env, "GO111MODULE=off"),
WorkingDir: folder,
}
stdoutBytes, err := s.gocmdRunner.Run(ctx, inv)
if err != nil {
return 0, err
}
stdout := stdoutBytes.String()
if len(stdout) < 3 {
return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout)
}
// Split up "[go1.1 go1.15]"
tags := strings.Fields(stdout[1 : len(stdout)-2])
for i := len(tags) - 1; i >= 0; i-- {
var version int
if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil {
continue
}
return version, nil
}
return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags)
}

View File

@ -128,7 +128,7 @@ func (s *snapshot) FileSet() *token.FileSet {
// config returns a *packages.Config with the working directory set to the
// view's root.
func (s *snapshot) config(ctx context.Context) *packages.Config {
return s.configWithDir(ctx, s.view.root.Filename())
return s.configWithDir(ctx, s.view.rootURI.Filename())
}
// configWithDir returns the configuration used for the snapshot's interaction
@ -1195,7 +1195,7 @@ func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *sn
}
// If a go.mod in the workspace has been changed, invalidate metadata.
if kind := originalFH.Kind(); kind == source.Mod {
return isSubdirectory(filepath.Dir(s.view.root.Filename()), filepath.Dir(originalFH.URI().Filename()))
return isSubdirectory(filepath.Dir(s.view.rootURI.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
@ -1248,7 +1248,7 @@ func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *sn
// The caller need not be holding the snapshot's mutex, but it might be.
func (s *snapshot) findWorkspaceDirectories(ctx context.Context, modFH source.FileHandle) map[span.URI]struct{} {
m := map[span.URI]struct{}{
s.view.root: {},
s.view.rootURI: {},
}
// If the view does not have a go.mod file, only the root directory
// is known. In GOPATH mode, we should really watch the entire GOPATH,
@ -1450,42 +1450,6 @@ 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))

View File

@ -63,10 +63,6 @@ type View struct {
// folder is the folder with which this view was constructed.
folder span.URI
// root is the root directory of this view. If we are in GOPATH mode, this
// is just the folder. If we are in module mode, this is the module root.
root span.URI
// importsMu guards imports-related state, particularly the ProcessEnv.
importsMu sync.Mutex
@ -118,27 +114,30 @@ type View struct {
initializeOnce *sync.Once
initializedErr error
// True if the view is either in GOPATH, a module, or some other
// non go command build system.
hasValidBuildConfiguration bool
// The real go.mod and go.sum files that are attributed to a view.
modURI, sumURI span.URI
// The Go version in use: X in Go 1.X.
goversion int
// workspaceInformation tracks various details about this view's
// environment variables, go version, and use of modules.
workspaceInformation
// workspaceMode describes the way in which the view's workspace should be
// loaded.
workspaceMode workspaceMode
// True if the view is either in GOPATH, a module, or some other
// non go command build system.
hasValidBuildConfiguration bool
}
type workspaceInformation struct {
// The Go version in use: X in Go 1.X.
goversion int
// hasGopackagesDriver is true if the user has a value set for the
// GOPACKAGESDRIVER environment variable or a gopackagesdriver binary on
// their machine.
hasGopackagesDriver bool
// `go env` variables that need to be tracked by gopls.
gocache, gomodcache, gopath, goprivate string
environmentVariables
// The value of GO111MODULE we want to run with.
go111module string
@ -146,6 +145,17 @@ type View struct {
// goEnv is the `go env` output collected when a view is created.
// It includes the values of the environment variables above.
goEnv map[string]string
// The real go.mod and go.sum files that are attributed to a view.
modURI, sumURI span.URI
// rootURI is the rootURI directory of this view. If we are in GOPATH mode, this
// is just the folder. If we are in module mode, this is the module rootURI.
rootURI span.URI
}
type environmentVariables struct {
gocache, gopath, goprivate, gomodcache, gomod string
}
type workspaceMode int
@ -350,7 +360,7 @@ func (v *View) WriteEnv(ctx context.Context, w io.Writer) error {
}
fmt.Fprintf(w, "go env for %v\n(root %s)\n(valid build configuration = %v)\n(build flags: %v)\n",
v.folder.Filename(), v.root.Filename(), v.hasValidBuildConfiguration, buildFlags)
v.folder.Filename(), v.rootURI.Filename(), v.hasValidBuildConfiguration, buildFlags)
for k, v := range fullEnv {
fmt.Fprintf(w, "%s=%s\n", k, v)
}
@ -519,7 +529,7 @@ func (v *View) envLocked() ([]string, []string) {
}
func (v *View) contains(uri span.URI) bool {
return strings.HasPrefix(string(uri), string(v.root))
return strings.HasPrefix(string(uri), string(v.rootURI))
}
func (v *View) mapFile(uri span.URI, f *fileBase) {
@ -789,62 +799,73 @@ func (v *View) reinitialize(force bool) {
v.initializeOnce = &once
}
func (v *View) setBuildInformation(ctx context.Context, options *source.Options) error {
if err := checkPathCase(v.Folder().Filename()); err != nil {
return errors.Errorf("invalid workspace configuration: %w", err)
func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, options *source.Options) (*workspaceInformation, error) {
if err := checkPathCase(folder.Filename()); err != nil {
return nil, errors.Errorf("invalid workspace configuration: %w", err)
}
var err error
v.goversion, err = v.goVersion(ctx, v.Options().Env)
goversion, err := s.goVersion(ctx, folder.Filename(), options.Env)
if err != nil {
return err
return nil, err
}
v.go111module = os.Getenv("GO111MODULE")
go111module := os.Getenv("GO111MODULE")
for _, kv := range options.Env {
split := strings.SplitN(kv, "=", 2)
if len(split) != 2 {
continue
}
if split[0] == "GO111MODULE" {
v.go111module = split[1]
go111module = split[1]
}
}
// If using 1.16, change the default back to auto. The primary effect of
// GO111MODULE=on is to break GOPATH, which we aren't too interested in.
if v.goversion >= 16 && v.go111module == "" {
v.go111module = "auto"
if goversion >= 16 && go111module == "" {
go111module = "auto"
}
// Make sure to get the `go env` before continuing with initialization.
modFile, err := v.setGoEnv(ctx, append(options.Env, "GO111MODULE="+v.go111module))
envVars, env, err := s.getGoEnv(ctx, folder.Filename(), append(options.Env, "GO111MODULE="+go111module))
if err != nil {
return err
return nil, err
}
if modFile != "" {
v.workspaceMode |= moduleMode
// The value of GOPACKAGESDRIVER is not returned through the go command.
gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
for _, s := range env {
split := strings.SplitN(s, "=", 2)
if split[0] == "GOPACKAGESDRIVER" {
gopackagesdriver = split[1]
}
}
if modFile == os.DevNull {
return nil
// A user may also have a gopackagesdriver binary on their machine, which
// works the same way as setting GOPACKAGESDRIVER.
tool, _ := exec.LookPath("gopackagesdriver")
hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
var modURI, sumURI span.URI
if envVars.gomod != os.DevNull && envVars.gomod != "" {
modURI = span.URIFromPath(envVars.gomod)
}
v.modURI = span.URIFromPath(modFile)
// Set the sumURI, if the go.sum exists.
sumFilename := filepath.Join(filepath.Dir(modFile), "go.sum")
sumFilename := filepath.Join(filepath.Dir(envVars.gomod), "go.sum")
if stat, _ := os.Stat(sumFilename); stat != nil {
v.sumURI = span.URIFromPath(sumFilename)
sumURI = span.URIFromPath(sumFilename)
}
if options.ExpandWorkspaceToModule && v.modURI != "" {
v.root = span.URIFromPath(filepath.Dir(v.modURI.Filename()))
root := folder
if options.ExpandWorkspaceToModule && modURI != "" {
root = span.URIFromPath(filepath.Dir(modURI.Filename()))
}
// The user has disabled the use of the -modfile flag or has no go.mod file.
if !options.TempModfile || v.modURI == "" {
return nil
}
if v.goversion >= 14 {
v.workspaceMode |= tempModfile
}
return nil
return &workspaceInformation{
hasGopackagesDriver: hasGopackagesDriver,
go111module: go111module,
goversion: goversion,
rootURI: root,
environmentVariables: envVars,
goEnv: env,
modURI: modURI,
sumURI: sumURI,
}, nil
}
// OS-specific path case check, for case-insensitive filesystems.
@ -854,59 +875,24 @@ 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
}()
func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modules map[span.URI]*moduleRoot) bool {
// Since we only really understand the `go` command, if the user has a
// different GOPACKAGESDRIVER, assume that their configuration is valid.
if v.hasGopackagesDriver {
if ws.hasGopackagesDriver {
return true
}
// Check if the user is working within a module or if we have found
// multiple modules in the workspace.
if v.modURI != "" {
if ws.modURI != "" {
return true
}
if len(v.snapshot.modules) > 0 {
if len(modules) > 0 {
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(v.gopath) {
if isSubdirectory(filepath.Join(gp, "src"), v.folder.Filename()) {
for _, gp := range filepath.SplitList(ws.gopath) {
if isSubdirectory(filepath.Join(gp, "src"), folder.Filename()) {
return true
}
}
@ -918,16 +904,15 @@ func isSubdirectory(root, leaf string) bool {
return err == nil && !strings.HasPrefix(rel, "..")
}
// setGoEnv sets the view's various GO* values. It also returns the view's
// GOMOD value, which need not be cached.
func (v *View) setGoEnv(ctx context.Context, configEnv []string) (string, error) {
var gomod string
// getGoEnv gets the view's various GO* values.
func (s *Session) getGoEnv(ctx context.Context, folder string, configEnv []string) (environmentVariables, map[string]string, error) {
envVars := environmentVariables{}
vars := map[string]*string{
"GOCACHE": &v.gocache,
"GOPATH": &v.gopath,
"GOPRIVATE": &v.goprivate,
"GOMODCACHE": &v.gomodcache,
"GOMOD": &gomod,
"GOCACHE": &envVars.gocache,
"GOPATH": &envVars.gopath,
"GOPRIVATE": &envVars.goprivate,
"GOMODCACHE": &envVars.gomodcache,
"GOMOD": &envVars.gomod,
}
// We can save ~200 ms by requesting only the variables we care about.
args := append([]string{"-json"}, imports.RequiredGoEnvVars...)
@ -939,40 +924,28 @@ func (v *View) setGoEnv(ctx context.Context, configEnv []string) (string, error)
Verb: "env",
Args: args,
Env: configEnv,
WorkingDir: v.Folder().Filename(),
WorkingDir: folder,
}
// Don't go through runGoCommand, as we don't need a temporary -modfile to
// run `go env`.
stdout, err := v.session.gocmdRunner.Run(ctx, inv)
stdout, err := s.gocmdRunner.Run(ctx, inv)
if err != nil {
return "", err
return environmentVariables{}, nil, err
}
if err := json.Unmarshal(stdout.Bytes(), &v.goEnv); err != nil {
return "", err
env := make(map[string]string)
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
return environmentVariables{}, nil, err
}
for key, ptr := range vars {
*ptr = v.goEnv[key]
*ptr = env[key]
}
// Old versions of Go don't have GOMODCACHE, so emulate it.
if v.gomodcache == "" && v.gopath != "" {
v.gomodcache = filepath.Join(filepath.SplitList(v.gopath)[0], "pkg/mod")
if envVars.gomodcache == "" && envVars.gopath != "" {
envVars.gomodcache = filepath.Join(filepath.SplitList(envVars.gopath)[0], "pkg/mod")
}
// The value of GOPACKAGESDRIVER is not returned through the go command.
gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
for _, s := range configEnv {
split := strings.SplitN(s, "=", 2)
if split[0] == "GOPACKAGESDRIVER" {
gopackagesdriver = split[1]
}
}
// A user may also have a gopackagesdriver binary on their machine, which
// works the same way as setting GOPACKAGESDRIVER.
tool, _ := exec.LookPath("gopackagesdriver")
v.hasGopackagesDriver = gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
return gomod, nil
return envVars, env, err
}
func (v *View) IsGoPrivatePath(target string) bool {
@ -1021,38 +994,6 @@ func globsMatchPath(globs, target string) bool {
return false
}
// 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) goVersion(ctx context.Context, env []string) (int, error) {
// Check the go version by running "go list" with modules off.
// Borrowed from internal/imports/mod.go:620.
const format = `{{context.ReleaseTags}}`
inv := gocommand.Invocation{
Verb: "list",
Args: []string{"-e", "-f", format},
Env: append(env, "GO111MODULE=off"),
WorkingDir: v.root.Filename(),
}
stdoutBytes, err := v.session.gocmdRunner.Run(ctx, inv)
if err != nil {
return 0, err
}
stdout := stdoutBytes.String()
if len(stdout) < 3 {
return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout)
}
// Split up "[go1.1 go1.15]"
tags := strings.Fields(stdout[1 : len(stdout)-2])
for i := len(tags) - 1; i >= 0; i-- {
var version int
if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil {
continue
}
return version, nil
}
return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags)
}
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
func (v *View) needsModEqualsMod(ctx context.Context, modFH source.FileHandle) (bool, error) {
@ -1092,3 +1033,48 @@ func (v *View) needsModEqualsMod(ctx context.Context, modFH source.FileHandle) (
vendorEnabled := modFile.Go.Version != "" && semver.Compare("v"+modFile.Go.Version, "v1.14") >= 0
return !vendorEnabled, nil
}
// determineWorkspaceMode determines the workspace mode for the given view.
func determineWorkspaceMode(options *source.Options, validBuildConfiguration bool, ws *workspaceInformation, modules map[span.URI]*moduleRoot) workspaceMode {
var mode workspaceMode
// If the view has an invalid configuration, don't build the workspace
// module.
if !validBuildConfiguration {
return mode
}
// 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 ws.modURI == "" && len(modules) == 0 && validBuildConfiguration {
return mode
}
// Check if we should be using module mode.
if ws.modURI != "" || len(modules) > 0 {
mode |= moduleMode
}
// The -modfile flag is available for Go versions >= 1.14.
if options.TempModfile && ws.goversion >= 14 {
mode |= tempModfile
}
// 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 modules {
if info, _ := os.Stat(filepath.Join(mod.rootURI.Filename(), "vendor")); info != nil {
return mode
}
}
// If the user is intentionally limiting their workspace scope, don't
// enable multi-module workspace mode.
// TODO(rstambler): This should only change the calculation of the root,
// not the mode.
if !options.ExpandWorkspaceToModule {
return mode
}
// The workspace module has been disabled by the user.
if !options.ExperimentalWorkspaceModule {
return mode
}
mode |= usesWorkspaceModule
return mode
}