Skip to content

Commit 98aef77

Browse files
committed
internal/lsp/cache: track explicit go.work files outside the workspace
In order to correctly process changes to the go.work file, the workspace must know about GOWORK settings configured in the users environment. Compute this when initializing the view, and thread this through to the workspace. At this point, workspace information is spread around in a few places. Add some TODOs to clean this up. Also remove some module data that was not used in TestBrokenWorkspace_DuplicateModules. Updates golang/go#53631 Change-Id: Ie0577d702c8a229304387bc7fe53a8befb544acb Reviewed-on: https://go-review.googlesource.com/c/tools/+/421500 Reviewed-by: Suzy Mueller <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]>
1 parent fff6d6d commit 98aef77

File tree

7 files changed

+129
-28
lines changed

7 files changed

+129
-28
lines changed

gopls/internal/regtest/workspace/broken_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import (
1616
// This file holds various tests for UX with respect to broken workspaces.
1717
//
1818
// TODO: consolidate other tests here.
19+
//
20+
// TODO: write more tests:
21+
// - an explicit GOWORK value that doesn't exist
22+
// - using modules and/or GOWORK inside of GOPATH?
1923

2024
// Test for golang/go#53933
2125
func TestBrokenWorkspace_DuplicateModules(t *testing.T) {
@@ -28,8 +32,6 @@ func TestBrokenWorkspace_DuplicateModules(t *testing.T) {
2832
module example.com/foo
2933
3034
go 1.12
31-
-- example.com/[email protected]/foo.go --
32-
package foo
3335
`
3436

3537
const src = `
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package workspace
6+
7+
import (
8+
"testing"
9+
10+
. "golang.org/x/tools/internal/lsp/regtest"
11+
)
12+
13+
// Test that setting go.work via environment variables or settings works.
14+
func TestUseGoWorkOutsideTheWorkspace(t *testing.T) {
15+
const files = `
16+
-- work/a/go.mod --
17+
module a.com
18+
19+
go 1.12
20+
-- work/a/a.go --
21+
package a
22+
-- work/b/go.mod --
23+
module b.com
24+
25+
go 1.12
26+
-- work/b/b.go --
27+
package b
28+
29+
func _() {
30+
x := 1 // unused
31+
}
32+
-- config/go.work --
33+
go 1.18
34+
35+
use (
36+
$SANDBOX_WORKDIR/work/a
37+
$SANDBOX_WORKDIR/work/b
38+
)
39+
`
40+
41+
WithOptions(
42+
EnvVars{"GOWORK": "$SANDBOX_WORKDIR/config/go.work"},
43+
).Run(t, files, func(t *testing.T, env *Env) {
44+
// Even though work/b is not open, we should get its diagnostics as it is
45+
// included in the workspace.
46+
env.OpenFile("work/a/a.go")
47+
env.Await(
48+
OnceMet(
49+
env.DoneWithOpen(),
50+
env.DiagnosticAtRegexpWithMessage("work/b/b.go", "x := 1", "not used"),
51+
),
52+
)
53+
})
54+
}

internal/lsp/cache/session.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package cache
77
import (
88
"context"
99
"fmt"
10+
"os"
1011
"strconv"
1112
"strings"
1213
"sync"
@@ -199,8 +200,14 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
199200
}
200201
}
201202

203+
explicitGowork := os.Getenv("GOWORK")
204+
if v, ok := options.Env["GOWORK"]; ok {
205+
explicitGowork = v
206+
}
207+
goworkURI := span.URIFromPath(explicitGowork)
208+
202209
// Build the gopls workspace, collecting active modules in the view.
203-
workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), wsInfo.gomodcache, options), wsInfo.userGo111Module == off, options.ExperimentalWorkspaceModule)
210+
workspace, err := newWorkspace(ctx, root, goworkURI, s, pathExcludedByFilterFunc(root.Filename(), wsInfo.gomodcache, options), wsInfo.userGo111Module == off, options.ExperimentalWorkspaceModule)
204211
if err != nil {
205212
return nil, nil, func() {}, err
206213
}
@@ -223,6 +230,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
223230
filesByURI: map[span.URI]*fileBase{},
224231
filesByBase: map[string][]*fileBase{},
225232
rootURI: root,
233+
explicitGowork: goworkURI,
226234
workspaceInformation: *wsInfo,
227235
}
228236
v.importsState = &importsState{

internal/lsp/cache/snapshot.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,12 @@ func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.Invocat
436436
s.view.optionsMu.Lock()
437437
allowModfileModificationOption := s.view.options.AllowModfileModifications
438438
allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
439+
440+
// TODO(rfindley): this is very hard to follow, and may not even be doing the
441+
// right thing: should inv.Env really trample view.options? Do we ever invoke
442+
// this with a non-empty inv.Env?
443+
//
444+
// We should refactor to make it clearer that the correct env is being used.
439445
inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.effectiveGo111Module)
440446
inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
441447
s.view.optionsMu.Unlock()

internal/lsp/cache/view.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"golang.org/x/tools/internal/event"
2828
"golang.org/x/tools/internal/gocommand"
2929
"golang.org/x/tools/internal/imports"
30+
"golang.org/x/tools/internal/lsp/bug"
3031
"golang.org/x/tools/internal/lsp/protocol"
3132
"golang.org/x/tools/internal/lsp/source"
3233
"golang.org/x/tools/internal/span"
@@ -98,6 +99,13 @@ type View struct {
9899
// is just the folder. If we are in module mode, this is the module rootURI.
99100
rootURI span.URI
100101

102+
// explicitGowork is, if non-empty, the URI for the explicit go.work file
103+
// provided via the users environment.
104+
//
105+
// TODO(rfindley): this is duplicated in the workspace type. Refactor to
106+
// eliminate this duplication.
107+
explicitGowork span.URI
108+
101109
// workspaceInformation tracks various details about this view's
102110
// environment variables, go version, and use of modules.
103111
workspaceInformation
@@ -469,7 +477,7 @@ func (v *View) relevantChange(c source.FileModification) bool {
469477
// TODO(rstambler): Make sure the go.work/gopls.mod files are always known
470478
// to the view.
471479
for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
472-
if c.URI == uriForSource(v.rootURI, src) {
480+
if c.URI == uriForSource(v.rootURI, v.explicitGowork, src) {
473481
return true
474482
}
475483
}
@@ -813,9 +821,13 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI,
813821
}
814822
// The value of GOPACKAGESDRIVER is not returned through the go command.
815823
gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
824+
// TODO(rfindley): this looks wrong, or at least overly defensive. If the
825+
// value of GOPACKAGESDRIVER is not returned from the go command... why do we
826+
// look it up here?
816827
for _, s := range env {
817828
split := strings.SplitN(s, "=", 2)
818829
if split[0] == "GOPACKAGESDRIVER" {
830+
bug.Reportf("found GOPACKAGESDRIVER from the go command") // see note above
819831
gopackagesdriver = split[1]
820832
}
821833
}

internal/lsp/cache/workspace.go

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ func (s workspaceSource) String() string {
4646
}
4747
}
4848

49+
// workspaceCommon holds immutable information about the workspace setup.
50+
//
51+
// TODO(rfindley): there is some redundancy here with workspaceInformation.
52+
// Reconcile these two types.
53+
type workspaceCommon struct {
54+
root span.URI
55+
excludePath func(string) bool
56+
57+
// explicitGowork is, if non-empty, the URI for the explicit go.work file
58+
// provided via the user's environment.
59+
explicitGowork span.URI
60+
}
61+
4962
// workspace tracks go.mod files in the workspace, along with the
5063
// gopls.mod file, to provide support for multi-module workspaces.
5164
//
@@ -58,8 +71,8 @@ func (s workspaceSource) String() string {
5871
// This type is immutable (or rather, idempotent), so that it may be shared
5972
// across multiple snapshots.
6073
type workspace struct {
61-
root span.URI
62-
excludePath func(string) bool
74+
workspaceCommon
75+
6376
moduleSource workspaceSource
6477

6578
// activeModFiles holds the active go.mod files.
@@ -98,17 +111,21 @@ type workspace struct {
98111
//
99112
// TODO(rfindley): newWorkspace should perhaps never fail, relying instead on
100113
// the criticalError method to surface problems in the workspace.
101-
// TODO(rfindley): this function should accept the GOWORK value, if specified
102-
// by the user.
103-
func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, excludePath func(string) bool, go111moduleOff, useWsModule bool) (*workspace, error) {
114+
func newWorkspace(ctx context.Context, root, explicitGowork span.URI, fs source.FileSource, excludePath func(string) bool, go111moduleOff, useWsModule bool) (*workspace, error) {
104115
ws := &workspace{
105-
root: root,
106-
excludePath: excludePath,
116+
workspaceCommon: workspaceCommon{
117+
root: root,
118+
explicitGowork: explicitGowork,
119+
excludePath: excludePath,
120+
},
107121
}
108122

109123
// The user may have a gopls.mod or go.work file that defines their
110124
// workspace.
111-
if err := loadExplicitWorkspaceFile(ctx, ws, fs); err == nil {
125+
//
126+
// TODO(rfindley): if GO111MODULE=off, this looks wrong, though there are
127+
// probably other problems.
128+
if err := ws.loadExplicitWorkspaceFile(ctx, fs); err == nil {
112129
return ws, nil
113130
}
114131

@@ -140,15 +157,15 @@ func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, excl
140157
// loadExplicitWorkspaceFile loads workspace information from go.work or
141158
// gopls.mod files, setting the active modules, mod file, and module source
142159
// accordingly.
143-
func loadExplicitWorkspaceFile(ctx context.Context, ws *workspace, fs source.FileSource) error {
160+
func (ws *workspace) loadExplicitWorkspaceFile(ctx context.Context, fs source.FileSource) error {
144161
for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
145-
fh, err := fs.GetFile(ctx, uriForSource(ws.root, src))
162+
fh, err := fs.GetFile(ctx, uriForSource(ws.root, ws.explicitGowork, src))
146163
if err != nil {
147164
return err
148165
}
149166
contents, err := fh.Read()
150167
if err != nil {
151-
continue
168+
continue // TODO(rfindley): is it correct to proceed here?
152169
}
153170
var file *modfile.File
154171
var activeModFiles map[span.URI]struct{}
@@ -313,15 +330,14 @@ func (w *workspace) Clone(ctx context.Context, changes map[span.URI]*fileChange,
313330
// Clone the workspace. This may be discarded if nothing changed.
314331
changed := false
315332
result := &workspace{
316-
root: w.root,
317-
moduleSource: w.moduleSource,
318-
knownModFiles: make(map[span.URI]struct{}),
319-
activeModFiles: make(map[span.URI]struct{}),
320-
workFile: w.workFile,
321-
mod: w.mod,
322-
sum: w.sum,
323-
wsDirs: w.wsDirs,
324-
excludePath: w.excludePath,
333+
workspaceCommon: w.workspaceCommon,
334+
moduleSource: w.moduleSource,
335+
knownModFiles: make(map[span.URI]struct{}),
336+
activeModFiles: make(map[span.URI]struct{}),
337+
workFile: w.workFile,
338+
mod: w.mod,
339+
sum: w.sum,
340+
wsDirs: w.wsDirs,
325341
}
326342
for k, v := range w.knownModFiles {
327343
result.knownModFiles[k] = v
@@ -391,7 +407,7 @@ func handleWorkspaceFileChanges(ctx context.Context, ws *workspace, changes map[
391407
// exists or walk the filesystem if it has been deleted.
392408
// go.work should override the gopls.mod if both exist.
393409
for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
394-
uri := uriForSource(ws.root, src)
410+
uri := uriForSource(ws.root, ws.explicitGowork, src)
395411
// File opens/closes are just no-ops.
396412
change, ok := changes[uri]
397413
if !ok {
@@ -460,12 +476,15 @@ func handleWorkspaceFileChanges(ctx context.Context, ws *workspace, changes map[
460476
}
461477

462478
// goplsModURI returns the URI for the gopls.mod file contained in root.
463-
func uriForSource(root span.URI, src workspaceSource) span.URI {
479+
func uriForSource(root, explicitGowork span.URI, src workspaceSource) span.URI {
464480
var basename string
465481
switch src {
466482
case goplsModWorkspace:
467483
basename = "gopls.mod"
468484
case goWorkWorkspace:
485+
if explicitGowork != "" {
486+
return explicitGowork
487+
}
469488
basename = "go.work"
470489
default:
471490
return ""

internal/lsp/cache/workspace_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ replace gopls.test => ../../gopls.test2`, false},
280280

281281
fs := &osFileSource{}
282282
excludeNothing := func(string) bool { return false }
283-
w, err := newWorkspace(ctx, root, fs, excludeNothing, false, !test.legacyMode)
283+
w, err := newWorkspace(ctx, root, "", fs, excludeNothing, false, !test.legacyMode)
284284
if err != nil {
285285
t.Fatal(err)
286286
}
@@ -325,7 +325,7 @@ func workspaceFromTxtar(t *testing.T, files string) (*workspace, func(), error)
325325

326326
fs := &osFileSource{}
327327
excludeNothing := func(string) bool { return false }
328-
workspace, err := newWorkspace(ctx, root, fs, excludeNothing, false, false)
328+
workspace, err := newWorkspace(ctx, root, "", fs, excludeNothing, false, false)
329329
return workspace, cleanup, err
330330
}
331331

0 commit comments

Comments
 (0)