x/tools/gopls: zero-config gopls workspaces #57979
Labels
gopls/metadata
Issues related to metadata loading in gopls
gopls/workspace
Issues related to support for modules or multi-module workspaces.
gopls
Issues related to the Go language server, gopls.
Tools
This label describes issues relating to any tools in the x/tools repository.
Milestone
Zero-config gopls workspaces
This issue describes a change to gopls' internal data model that will allow it to "Do The Right Thing" when the user opens a Go file. By decoupling the relationship between builds and workspace folders, we can eliminate complexity related to configuring the workspace (hence "zero-config"), and lay the groundwork for later improvements such as better support for working on multiple sets of build tags simultaneously (#29202).
After this change, users can work on multiple modules inside of a workspace regardless of whether they are related by a go.work file or explicitly open as separate workspace folders.
Background
Right now, gopls determines a unique build (called a
View
) for each workspace folder. When a workspace folder is opened, gopls performs the following steps:workspace/configuration
request withscopeUri
set to the folder URI.a. Check
go env GOWORK
.b. Else, look for
go.mod
in a parent directory (recursively).c. Else, look for
go.mod
in a nested directory, if there is only one such nested directory. This was done to support polyglot workspaces where the Go project is in a nested directory, but is a source of both confusion and unpredictable startup time.d. Else, use the folder as root.
go/packages.Load
.Problems
There are several problems with this model:
"experimentalWorkspaceModule"
setting,"expandWorkspaceToModule"
setting, and"directoryFilters"
setting.New Model
We can address these problems by decoupling Views from workspace folders. The set of views will be dynamic, depending on both the set of open folders and the set of open files, and will be chosen to cover all open files.
Specifically, define new
View
andFolder
types approximately as follows:A
Session
consists of a set ofView
objects describing modules (go.mod
files), workspaces (go.work
files), GOPATH directories or ad-hoc packages that the user is working on. This set is determined by both the workspace folders specified by the editor and the set of open files.View types
workspace
View is defined by ago.work
file.source
is the path to thego.work
file.module
View is defined by a singlego.mod
file.source
is the path to thego.mod
file.GOPATH
View is defined by a folder inside aGOPATH
directory, withGO111MODULE=off
orGO111MODULE=auto
and nogo.mod
file.source
is the path to the directory.adhoc
View is defined by a folder outside ofGOPATH
, with no enclosinggo.mod
file. In this case, we consider files in the same directory to be part of a package, andsource
is the path to the directory.The set of Views
We define the set of Views to ensure that we have coverage for each open folder, and each open file.
go env GOWORK
is set, create aworkspace
View.go.mod
in a parent directory. If found, create amodule
View.GOPATH
, andGO111MODULE
is not explicitly set toon
, create aGOPATH
View. IfGO111MODULE=on
explicitly, fail.adhoc
View for the workspace folder. This may not be desirable for the user if they have modules contained in nested directories. In this case we could either prompt the user, or scan for modules in nested directories, creating Views for each (but notably if we do decide to scan the filesystem, we would create a View for each go.mod or go.work file encountered, rather than fail if there are more than one).Match to an existing View
go.mod
files.go.mod
file is found, search for existingworkspace
ormodule
type Views containing this module in theirmodules
set.GOPATH
type Views whosesource
directory contains the file.adhoc
type Views whosesource
is equal tofilepath.Dir(file)
.If no existing View matches the file, create a new one
module
type. Apply an explicitGOWORK=off
to the View configuration to ensure that we can load the module.Initializing views
Initialize views using the following logic. This essentially matches gopls’ current behavior.
workspace
Views, loadmodulepath/...
for each workspace module.module
Views, loadmodulepath/...
for the main module.GOPATH
Views, load./...
from the View dir.ad-hoc
Views, load./
from the View dir.Type-check packages (and report their compiler diagnostics) as follows:
workspace
Views, type-check any package whose module is a workspace module.module
Views, type-check any package whose module is the main module.GOPATH
Views, type-check any package contained indir
.adhoc
Views, type-check the ad-hoc package.Resolving requests to Views
When a file-oriented request is handled by gopls (a request prefixed with
textDocument/
, such astextDocument/definition
), gopls must usually resolve package metadata associated with the file.In most cases, gopls currently chooses an existing view that best applies to the file (
cache.bestViewForURI
), but this is already problematic, because it can lead to path-dependency and incomplete results (c.f. #57558). For example: when finding references from a package imported from multiple views, gopls currently only shows references in one view.Wherever possible, gopls should multiplex queries across all Views and merge their results. This would lead to consistent behavior of cross references. In a future where gopls has better build-tag support, this could also lead to multiple locations for jump-to-definition results.
In some cases (for example
hover
orsignatureHelp
), we must pick one view. In these cases we can apply some heuristic, but it should be of secondary significance (any hover or signatureHelp result is better than none).Updating Views
Based on the algorithms used to determine Views above, the following notifications may affect the set of Views:
didOpen
anddidClose
cause gopls to re-evaluate Views, ensuring that we have a View for each open file contained in a workspace folder.didChangeConfiguration
anddidChangeWorkspaceFolders
causes gopls to updateFolder
layout and configuration. Note that configuration may affect e.g. GOWORK values and therefore may lead to a new set of Views.didChange
ordidChangeWatchedFile
may cause gopls to re-evaluate Views if the change is to ago.mod
orgo.work
file (for example, ago.mod
file deleted or added, or ago.work
file changed in any way).Following these changes, gopls will re-run the algorithm above to determine a new set of Views. It will re-use existing Views that have not changed.
Whenever new Views are created, they are reinitialized as above.
Differences from the current model
The algorithms described above are not vastly divergent from gopls’ current behavior. The significant differences may be summarized as follows:
go.mod
files are added or removed, orgo.work
files changed, we reconfigure the set of Views. This simplifies the logic of handling metadata invalidation in each view.Downsides
While this change will make gopls “do the right thing” in more cases, there are a several notable downsides:
go.work
orgo.mod
in a parent directory of the workspace folder. This means thatworkspace/symbols
requests may return empty results, or results that depend on the set of open files. Users can mitigate this by using ago.work
file.a
andb
in their workspace, anda
depends on a version ofb
in the module cache, find reference on a symbol in theb
directory of the workspace will not include references ina
. Users can mitigate this by using ago.work
file. It would also be possible for us to implement looser heuristics in our references search.Future extension to build tags
By decoupling Views from workspace folders, it becomes possible for gopls to support working on multiple sets of build tags simultaneously. One can imagine that the algorithm above to compute views based on open files could be extended to GOOS and GOARCH: if an open file is not included in an existing view because of its GOOS or GOARCH build constraints, create a new view with updated environment.
The downsides above apply: potentially increased memory, and potentially confusing UX as the behavior of certain workspace-wide queries (such as references or workspace symbols) depends on the set of open files. We can work to mitigate these downsides, and in my opinion they do not outweigh the upsides, as these queries simply don't work in the current model.
The text was updated successfully, but these errors were encountered: