Skip to content

Commit 62a9628

Browse files
committed
internal/lsp: use the -modfile flag to update a different go.mod file
In the upcoming Go 1.14 release, there is an introduction of the -modfile flag which allows a user to run a go command but choose where to direct the go.mod file updates. The information about this can be found here: golang/go#34506. This change starts setting up the infrastructure to handle the seperate modfile rather than keep changing a user's go.mod file. To support versions of Go that are not 1.14, we run a modified "go list" command that checks the release tags to see if 1.14 is contained. Updates golang/go#31999 Change-Id: Icb71b6402ec4fa07e5f6f1a63954c25520e860b0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/211538 Run-TryBot: Rohan Challa <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 210e553 commit 62a9628

File tree

5 files changed

+93
-7
lines changed

5 files changed

+93
-7
lines changed

internal/lsp/cache/modfiles.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2019 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 cache
6+
7+
import (
8+
"context"
9+
"io"
10+
"io/ioutil"
11+
"os"
12+
"strings"
13+
14+
"golang.org/x/tools/internal/lsp/source"
15+
errors "golang.org/x/xerrors"
16+
)
17+
18+
// Borrowed from (internal/imports/mod.go:620)
19+
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
20+
// flag exists for this version of go.
21+
func modfileFlagExists(ctx context.Context, folder string, env []string) (string, bool, error) {
22+
const format = `{{.GoMod}}
23+
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
24+
`
25+
stdout, err := source.InvokeGo(ctx, folder, env, "list", "-m", "-f", format)
26+
if err != nil {
27+
return "", false, err
28+
}
29+
lines := strings.Split(stdout.String(), "\n")
30+
if len(lines) < 2 {
31+
return "", false, errors.Errorf("unexpected stdout: %q", stdout)
32+
}
33+
return lines[0], lines[1] == "go1.14", nil
34+
}
35+
36+
// The function getModfiles will return the go.mod files associated with the directory that is passed in.
37+
func getModfiles(ctx context.Context, folder string, env []string) (*modfiles, error) {
38+
modfile, flagExists, err := modfileFlagExists(ctx, folder, env)
39+
if err != nil {
40+
return nil, err
41+
}
42+
if !flagExists {
43+
return nil, nil
44+
}
45+
if modfile == "" || modfile == os.DevNull {
46+
return nil, errors.Errorf("go env GOMOD cannot detect a go.mod file in this folder")
47+
}
48+
f, err := ioutil.TempFile("", "go.*.mod")
49+
if err != nil {
50+
return nil, err
51+
}
52+
defer f.Close()
53+
// Copy the current go.mod file into the temporary go.mod file.
54+
origFile, err := os.Open(modfile)
55+
if err != nil {
56+
return nil, err
57+
}
58+
defer origFile.Close()
59+
if _, err := io.Copy(f, origFile); err != nil {
60+
return nil, err
61+
}
62+
if err := f.Close(); err != nil {
63+
return nil, err
64+
}
65+
return &modfiles{real: modfile, temp: f.Name()}, nil
66+
}

internal/lsp/cache/session.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
9595
// the spans need to be unrelated and no tag values should pollute it.
9696
baseCtx := trace.Detach(xcontext.Detach(ctx))
9797
backgroundCtx, cancel := context.WithCancel(baseCtx)
98+
99+
modfiles, err := getModfiles(ctx, folder.Filename(), options.Env)
100+
if err != nil {
101+
log.Error(ctx, "error getting modfiles", err, telemetry.Directory.Of(folder))
102+
}
98103
v := &view{
99104
session: s,
100105
id: strconv.FormatInt(index, 10),
@@ -103,6 +108,7 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
103108
backgroundCtx: backgroundCtx,
104109
cancel: cancel,
105110
name: name,
111+
modfiles: modfiles,
106112
folder: folder,
107113
filesByURI: make(map[span.URI]viewFile),
108114
filesByBase: make(map[string][]viewFile),

internal/lsp/cache/view.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type view struct {
5353
// Name is the user visible name of this view.
5454
name string
5555

56+
// modfiles are the go.mod files attributed to this view.
57+
modfiles *modfiles
58+
5659
// Folder is the root of this view.
5760
folder span.URI
5861

@@ -86,6 +89,11 @@ type view struct {
8689
ignoredURIs map[span.URI]struct{}
8790
}
8891

92+
// modfiles holds the real and temporary go.mod files that are attributed to a view.
93+
type modfiles struct {
94+
real, temp string
95+
}
96+
8997
func (v *view) Session() source.Session {
9098
return v.session
9199
}
@@ -130,11 +138,18 @@ func (v *view) SetOptions(ctx context.Context, options source.Options) (source.V
130138
// go/packages API. It is shared across all views.
131139
func (v *view) Config(ctx context.Context) *packages.Config {
132140
// TODO: Should we cache the config and/or overlay somewhere?
141+
142+
// We want to run the go commands with the -modfile flag if the version of go
143+
// that we are using supports it.
144+
buildFlags := v.options.BuildFlags
145+
if v.modfiles != nil {
146+
buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.modfiles.temp))
147+
}
133148
return &packages.Config{
134149
Dir: v.folder.Filename(),
135150
Context: ctx,
136151
Env: v.options.Env,
137-
BuildFlags: v.options.BuildFlags,
152+
BuildFlags: buildFlags,
138153
Mode: packages.NeedName |
139154
packages.NeedFiles |
140155
packages.NeedCompiledGoFiles |

internal/lsp/source/errors.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func checkCommonErrors(ctx context.Context, view View, uri span.URI) (string, er
3636

3737
// Invoke `go env GOMOD` inside of the directory of the file.
3838
fdir := filepath.Dir(uri.Filename())
39-
b, err := invokeGo(ctx, fdir, cfg.Env, "env", "GOMOD")
39+
b, err := InvokeGo(ctx, fdir, cfg.Env, "env", "GOMOD")
4040
if err != nil {
4141
return "", err
4242
}
@@ -78,7 +78,7 @@ func checkCommonErrors(ctx context.Context, view View, uri span.URI) (string, er
7878

7979
// invokeGo returns the stdout of a go command invocation.
8080
// Borrowed from golang.org/x/tools/go/packages/golist.go.
81-
func invokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
81+
func InvokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
8282
stdout := new(bytes.Buffer)
8383
stderr := new(bytes.Buffer)
8484
cmd := exec.CommandContext(ctx, "go", args...)
@@ -103,6 +103,7 @@ func invokeGo(ctx context.Context, dir string, env []string, args ...string) (*b
103103
// - context cancellation
104104
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
105105
}
106+
return stdout, fmt.Errorf("%s", stderr)
106107
}
107108
return stdout, nil
108109
}

internal/lsp/source/tidy.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ func ModTidy(ctx context.Context, view View) error {
1212
// and apply each action as an edit.
1313
//
1414
// TODO(rstambler): This will be possible when golang/go#27005 is resolved.
15-
if _, err := invokeGo(ctx, view.Folder().Filename(), cfg.Env, "mod", "tidy"); err != nil {
16-
return err
17-
}
18-
return nil
15+
_, err := InvokeGo(ctx, view.Folder().Filename(), cfg.Env, "mod", "tidy")
16+
return err
1917
}

0 commit comments

Comments
 (0)