1
0
mirror of https://github.com/golang/go synced 2024-10-01 16:08:33 -06:00
go/internal/lsp/fake/sandbox.go
Rob Findley 2b5917cebf internal/lsp/lsprpc: forward the go environment in initialize requests
The gopls workspace environment defaults to the process environment in
which gopls was started. This means that when switching environments,
gopls can potentially get a different environment when connecting as an
editor sidecar from when forwarding requests to the daemon.

To (hopefully mostly) mitigate this pain point, inject the Go
environment when forwarding the 'initialize' request, which contains
InitializationOptions containing the 'env' configuration. We could go
further and send the entire os.Environ(), but that seems problematic
both in its unbounded nature, and because in many cases the user may not
actually want to send their process env over the wire. Gopls behavior
should *mostly* be parameterized by gopls binary and Go env, and after
this change these should match for forwarder and daemon.

For go1.15, Explicitly set the GOMODCACHE environment variable in the
regtest sandbox. Without this, regtests were failing in the forwarded
environment because they implicitly shared a module cache.

Fixes golang/go#37830

Change-Id: Ic1b335506f8b481505eac9f74c0df6293dc07158
Reviewed-on: https://go-review.googlesource.com/c/tools/+/234109
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-06-16 15:43:56 +00:00

152 lines
4.2 KiB
Go

// Copyright 2020 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 fake
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/txtar"
)
// Sandbox holds a collection of temporary resources to use for working with Go
// code in tests.
type Sandbox struct {
name string
gopath string
basedir string
Proxy *Proxy
Workdir *Workdir
}
// NewSandbox creates a collection of named temporary resources, with a
// working directory populated by the txtar-encoded content in srctxt, and a
// file-based module proxy populated with the txtar-encoded content in
// proxytxt.
func NewSandbox(name, srctxt, proxytxt string, inGopath bool) (_ *Sandbox, err error) {
sb := &Sandbox{
name: name,
}
defer func() {
// Clean up if we fail at any point in this constructor.
if err != nil {
sb.Close()
}
}()
basedir, err := ioutil.TempDir("", fmt.Sprintf("goplstest-sandbox-%s-", name))
if err != nil {
return nil, fmt.Errorf("creating temporary workdir: %v", err)
}
sb.basedir = basedir
proxydir := filepath.Join(sb.basedir, "proxy")
sb.gopath = filepath.Join(sb.basedir, "gopath")
// Set the working directory as $GOPATH/src if inGopath is true.
workdir := filepath.Join(sb.gopath, "src")
dirs := []string{sb.gopath, proxydir}
if !inGopath {
workdir = filepath.Join(sb.basedir, "work")
dirs = append(dirs, workdir)
}
for _, subdir := range dirs {
if err := os.Mkdir(subdir, 0755); err != nil {
return nil, err
}
}
sb.Proxy, err = NewProxy(proxydir, proxytxt)
sb.Workdir, err = NewWorkdir(workdir, srctxt)
return sb, nil
}
func unpackTxt(txt string) map[string][]byte {
dataMap := make(map[string][]byte)
archive := txtar.Parse([]byte(txt))
for _, f := range archive.Files {
dataMap[f.Name] = f.Data
}
return dataMap
}
// splitModuleVersionPath extracts module information from files stored in the
// directory structure modulePath@version/suffix.
// For example:
// splitModuleVersionPath("mod.com@v1.2.3/package") = ("mod.com", "v1.2.3", "package")
func splitModuleVersionPath(path string) (modulePath, version, suffix string) {
parts := strings.Split(path, "/")
var modulePathParts []string
for i, p := range parts {
if strings.Contains(p, "@") {
mv := strings.SplitN(p, "@", 2)
modulePathParts = append(modulePathParts, mv[0])
return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/")
}
modulePathParts = append(modulePathParts, p)
}
// Default behavior: this is just a module path.
return path, "", ""
}
// GOPATH returns the value of the Sandbox GOPATH.
func (sb *Sandbox) GOPATH() string {
return sb.gopath
}
// GoEnv returns the default environment variables that can be used for
// invoking Go commands in the sandbox.
func (sb *Sandbox) GoEnv() []string {
vars := []string{
"GOPATH=" + sb.GOPATH(),
"GOPROXY=" + sb.Proxy.GOPROXY(),
"GO111MODULE=",
"GOSUMDB=off",
}
if testenv.Go1Point() >= 5 {
vars = append(vars, "GOMODCACHE=")
}
return vars
}
// RunGoCommand executes a go command in the sandbox.
func (sb *Sandbox) RunGoCommand(ctx context.Context, verb string, args ...string) error {
inv := gocommand.Invocation{
Verb: verb,
Args: args,
WorkingDir: sb.Workdir.workdir,
Env: sb.GoEnv(),
}
gocmdRunner := &gocommand.Runner{}
_, _, _, err := gocmdRunner.RunRaw(ctx, inv)
if err != nil {
return err
}
// Since running a go command may result in changes to workspace files,
// check if we need to send any any "watched" file events.
if err := sb.Workdir.CheckForFileChanges(ctx); err != nil {
return fmt.Errorf("checking for file changes: %w", err)
}
return nil
}
// Close removes all state associated with the sandbox.
func (sb *Sandbox) Close() error {
var goCleanErr error
if sb.gopath != "" {
if err := sb.RunGoCommand(context.Background(), "clean", "-modcache"); err != nil {
goCleanErr = fmt.Errorf("cleaning modcache: %v", err)
}
}
err := os.RemoveAll(sb.basedir)
if err != nil || goCleanErr != nil {
return fmt.Errorf("error(s) cleaning sandbox: cleaning modcache: %v; removing files: %v", goCleanErr, err)
}
return nil
}