Skip to content

Commit

Permalink
internal/lsp: use the -modfile flag to update a different go.mod file
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
ridersofrohan committed Dec 17, 2019
1 parent 210e553 commit 62a9628
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 7 deletions.
66 changes: 66 additions & 0 deletions internal/lsp/cache/modfiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
"context"
"io"
"io/ioutil"
"os"
"strings"

"golang.org/x/tools/internal/lsp/source"
errors "golang.org/x/xerrors"
)

// Borrowed from (internal/imports/mod.go:620)
// 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 modfileFlagExists(ctx context.Context, folder string, env []string) (string, bool, error) {
const format = `{{.GoMod}}
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
`
stdout, err := source.InvokeGo(ctx, folder, env, "list", "-m", "-f", format)
if err != nil {
return "", false, err
}
lines := strings.Split(stdout.String(), "\n")
if len(lines) < 2 {
return "", false, errors.Errorf("unexpected stdout: %q", stdout)
}
return lines[0], lines[1] == "go1.14", nil
}

// The function getModfiles will return the go.mod files associated with the directory that is passed in.
func getModfiles(ctx context.Context, folder string, env []string) (*modfiles, error) {
modfile, flagExists, err := modfileFlagExists(ctx, folder, env)
if err != nil {
return nil, err
}
if !flagExists {
return nil, nil
}
if modfile == "" || modfile == os.DevNull {
return nil, errors.Errorf("go env GOMOD cannot detect a go.mod file in this folder")
}
f, err := ioutil.TempFile("", "go.*.mod")
if err != nil {
return nil, err
}
defer f.Close()
// Copy the current go.mod file into the temporary go.mod file.
origFile, err := os.Open(modfile)
if err != nil {
return nil, err
}
defer origFile.Close()
if _, err := io.Copy(f, origFile); err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
return &modfiles{real: modfile, temp: f.Name()}, nil
}
6 changes: 6 additions & 0 deletions internal/lsp/cache/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
// the spans need to be unrelated and no tag values should pollute it.
baseCtx := trace.Detach(xcontext.Detach(ctx))
backgroundCtx, cancel := context.WithCancel(baseCtx)

modfiles, err := getModfiles(ctx, folder.Filename(), options.Env)
if err != nil {
log.Error(ctx, "error getting modfiles", err, telemetry.Directory.Of(folder))
}
v := &view{
session: s,
id: strconv.FormatInt(index, 10),
Expand All @@ -103,6 +108,7 @@ func (s *session) createView(ctx context.Context, name string, folder span.URI,
backgroundCtx: backgroundCtx,
cancel: cancel,
name: name,
modfiles: modfiles,
folder: folder,
filesByURI: make(map[span.URI]viewFile),
filesByBase: make(map[string][]viewFile),
Expand Down
17 changes: 16 additions & 1 deletion internal/lsp/cache/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type view struct {
// Name is the user visible name of this view.
name string

// modfiles are the go.mod files attributed to this view.
modfiles *modfiles

// Folder is the root of this view.
folder span.URI

Expand Down Expand Up @@ -86,6 +89,11 @@ type view struct {
ignoredURIs map[span.URI]struct{}
}

// modfiles holds the real and temporary go.mod files that are attributed to a view.
type modfiles struct {
real, temp string
}

func (v *view) Session() source.Session {
return v.session
}
Expand Down Expand Up @@ -130,11 +138,18 @@ func (v *view) SetOptions(ctx context.Context, options source.Options) (source.V
// go/packages API. It is shared across all views.
func (v *view) Config(ctx context.Context) *packages.Config {
// TODO: Should we cache the config and/or overlay somewhere?

// We want to run the go commands with the -modfile flag if the version of go
// that we are using supports it.
buildFlags := v.options.BuildFlags
if v.modfiles != nil {
buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.modfiles.temp))
}
return &packages.Config{
Dir: v.folder.Filename(),
Context: ctx,
Env: v.options.Env,
BuildFlags: v.options.BuildFlags,
BuildFlags: buildFlags,
Mode: packages.NeedName |
packages.NeedFiles |
packages.NeedCompiledGoFiles |
Expand Down
5 changes: 3 additions & 2 deletions internal/lsp/source/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func checkCommonErrors(ctx context.Context, view View, uri span.URI) (string, er

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

// invokeGo returns the stdout of a go command invocation.
// Borrowed from golang.org/x/tools/go/packages/golist.go.
func invokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
func InvokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, "go", args...)
Expand All @@ -103,6 +103,7 @@ func invokeGo(ctx context.Context, dir string, env []string, args ...string) (*b
// - context cancellation
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
}
return stdout, fmt.Errorf("%s", stderr)
}
return stdout, nil
}
6 changes: 2 additions & 4 deletions internal/lsp/source/tidy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ func ModTidy(ctx context.Context, view View) error {
// and apply each action as an edit.
//
// TODO(rstambler): This will be possible when golang/go#27005 is resolved.
if _, err := invokeGo(ctx, view.Folder().Filename(), cfg.Env, "mod", "tidy"); err != nil {
return err
}
return nil
_, err := InvokeGo(ctx, view.Folder().Filename(), cfg.Env, "mod", "tidy")
return err
}

0 comments on commit 62a9628

Please sign in to comment.