| package source |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| |
| "golang.org/x/tools/internal/span" |
| ) |
| |
| 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, view View, uri span.URI) (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 |
| // |
| gopath := os.Getenv("GOPATH") |
| cfg := view.Config(ctx) |
| |
| // 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") |
| if err != nil { |
| return "", err |
| } |
| modFile := strings.TrimSpace(b.String()) |
| if modFile == filepath.FromSlash("/dev/null") { |
| modFile = "" |
| } |
| |
| // Not inside of a module. |
| inAModule := modFile != "" |
| |
| // 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 |
| } |
| } |
| |
| 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 { |
| 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 |
| } |