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 }