1
0
mirror of https://github.com/golang/go synced 2024-11-18 21:14:44 -07:00
go/internal/lsp/source/errors.go
Rebecca Stambler 5a294e27f3 internal/lsp: allow subdirectories of module roots when checking errors
Previously, we would surface a warning message if a user had a missing
dependency in a subdirectory of their module root. This is not
necessary, so do a better job checking for that case.

Change-Id: Ib6fcdcbf6ac191478b9bb1f5f8a55d154fd30b5a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/214420
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-14 00:24:04 +00:00

111 lines
3.6 KiB
Go

package source
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
const (
// TODO(rstambler): We should really be able to point to a link on the website.
modulesWiki = "https://github.com/golang/go/wiki/Modules"
)
func checkCommonErrors(ctx context.Context, v View) (string, error) {
// Unfortunately, we probably can't have go/packages expose a function like this.
// Since we only really understand the `go` command, check the user's GOPACKAGESDRIVER
// and, if they are using `go list`, consider the possible error cases.
gopackagesdriver := os.Getenv("GOPACKAGESDRIVER")
if gopackagesdriver != "" && gopackagesdriver != "off" {
return "", nil
}
// Some cases we should be able to detect:
//
// 1. The user is in GOPATH mode and is working outside their GOPATH
// 2. The user is in module mode and has opened a subdirectory of their module
//
// TODO(rstambler): Get the values for GOPATH and GOMOD from
// the view, once it's possible to do so: golang.org/cl/214417.
gopath := os.Getenv("GOPATH")
// Invoke `go env GOMOD` inside of the directory of the file.
b, err := InvokeGo(ctx, v.Folder().Filename(), v.Config(ctx).Env, "env", "GOMOD")
if err != nil {
return "", err
}
modFile := strings.TrimSpace(b.String())
if modFile == filepath.FromSlash("/dev/null") {
modFile = ""
}
modRoot := filepath.Dir(modFile)
// Not inside of a module.
inAModule := modFile != ""
folder := v.Folder().Filename()
// The user may have a multiple directories in their GOPATH.
var inGopath bool
for _, gp := range filepath.SplitList(gopath) {
if strings.HasPrefix(folder, filepath.Join(gp, "src")) {
inGopath = true
break
}
}
moduleMode := os.Getenv("GO111MODULE")
var msg string
// The user is in a module.
if inAModule {
rel, err := filepath.Rel(modRoot, folder)
if err != nil || strings.HasPrefix(rel, "..") {
msg = fmt.Sprintf("Your workspace root is %s, but your module root is %s. Please add %s or a subdirectory as a workspace folder.", folder, modRoot, modRoot)
}
} else if inGopath {
if moduleMode == "on" {
msg = "You are in module mode, but you are not inside of a module. Please create a module."
}
} else {
msg = fmt.Sprintf("You are neither in a module nor in your GOPATH. Please see %s for information on how to set up your Go project.", modulesWiki)
}
return msg, nil
}
// 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) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.CommandContext(ctx, "go", args...)
// On darwin the cwd gets resolved to the real path, which breaks anything that
// expects the working directory to keep the original path, including the
// go command when dealing with modules.
// The Go stdlib has a special feature where if the cwd and the PWD are the
// same node then it trusts the PWD, so by setting it in the env for the child
// process we fix up all the paths returned by the go command.
cmd.Env = append(append([]string{}, env...), "PWD="+dir)
cmd.Dir = dir
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
// Check for 'go' executable not being found.
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
return nil, fmt.Errorf("'gopls requires 'go', but %s", exec.ErrNotFound)
}
if _, ok := err.(*exec.ExitError); !ok {
// Catastrophic error:
// - 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
}