2019-09-06 16:25:36 -06:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/tools/internal/span"
|
|
|
|
)
|
|
|
|
|
2019-09-23 18:19:50 -06:00
|
|
|
const (
|
|
|
|
// TODO(rstambler): We should really be able to point to a link on the website.
|
|
|
|
modulesWiki = "https://github.com/golang/go/wiki/Modules"
|
|
|
|
)
|
|
|
|
|
2019-09-06 16:25:36 -06:00
|
|
|
func checkCommonErrors(ctx context.Context, view View, uri span.URI) (string, error) {
|
2019-09-23 18:19:50 -06:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-09-06 16:25:36 -06:00
|
|
|
// 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
|
|
|
|
//
|
|
|
|
gopath := os.Getenv("GOPATH")
|
|
|
|
cfg := view.Config(ctx)
|
|
|
|
|
|
|
|
// Invoke `go env GOMOD` inside of the directory of the file.
|
|
|
|
fdir := filepath.Dir(uri.Filename())
|
2019-12-16 13:40:24 -07:00
|
|
|
b, err := InvokeGo(ctx, fdir, cfg.Env, "env", "GOMOD")
|
2019-09-06 16:25:36 -06:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
modFile := strings.TrimSpace(b.String())
|
|
|
|
if modFile == filepath.FromSlash("/dev/null") {
|
|
|
|
modFile = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not inside of a module.
|
|
|
|
inAModule := modFile != ""
|
2019-12-13 11:50:15 -07:00
|
|
|
|
|
|
|
// The user may have a multiple directories in their GOPATH.
|
|
|
|
var inGopath bool
|
|
|
|
for _, gp := range filepath.SplitList(gopath) {
|
|
|
|
if strings.HasPrefix(uri.Filename(), filepath.Join(gp, "src")) {
|
|
|
|
inGopath = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 16:25:36 -06:00
|
|
|
moduleMode := os.Getenv("GO111MODULE")
|
|
|
|
|
|
|
|
var msg string
|
|
|
|
// The user is in a module.
|
|
|
|
if inAModule {
|
|
|
|
// The workspace root is open to a directory different from the module root.
|
|
|
|
if modRoot := filepath.Dir(modFile); cfg.Dir != filepath.Dir(modFile) {
|
|
|
|
msg = fmt.Sprintf("Your workspace root is %s, but your module root is %s. Please add %s as a workspace folder.", cfg.Dir, 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 {
|
2019-09-23 18:19:50 -06:00
|
|
|
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)
|
2019-09-06 16:25:36 -06:00
|
|
|
}
|
|
|
|
return msg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// invokeGo returns the stdout of a go command invocation.
|
|
|
|
// Borrowed from golang.org/x/tools/go/packages/golist.go.
|
2019-12-16 13:40:24 -07:00
|
|
|
func InvokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
|
2019-09-06 16:25:36 -06:00
|
|
|
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)
|
|
|
|
}
|
2019-12-16 13:40:24 -07:00
|
|
|
return stdout, fmt.Errorf("%s", stderr)
|
2019-09-06 16:25:36 -06:00
|
|
|
}
|
|
|
|
return stdout, nil
|
|
|
|
}
|