2019-09-06 16:25:36 -06:00
package source
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
2020-01-14 11:59:17 -07:00
errors "golang.org/x/xerrors"
2019-09-06 16:25:36 -06:00
)
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"
)
2020-01-11 22:56:36 -07:00
func checkCommonErrors ( ctx context . Context , v View ) ( 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
//
2020-01-11 22:56:36 -07:00
// TODO(rstambler): Get the values for GOPATH and GOMOD from
// the view, once it's possible to do so: golang.org/cl/214417.
2019-09-06 16:25:36 -06:00
gopath := os . Getenv ( "GOPATH" )
// Invoke `go env GOMOD` inside of the directory of the file.
2020-01-11 22:56:36 -07:00
b , err := InvokeGo ( ctx , v . Folder ( ) . Filename ( ) , v . Config ( ctx ) . 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 = ""
}
2020-01-11 22:56:36 -07:00
modRoot := filepath . Dir ( modFile )
2019-09-06 16:25:36 -06:00
// Not inside of a module.
inAModule := modFile != ""
2020-01-11 22:56:36 -07:00
folder := v . Folder ( ) . Filename ( )
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 ) {
2020-01-11 22:56:36 -07:00
if strings . HasPrefix ( folder , filepath . Join ( gp , "src" ) ) {
2019-12-13 11:50:15 -07:00
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 {
2020-01-11 22:56:36 -07:00
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 )
2019-09-06 16:25:36 -06:00
}
} 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
}
2020-01-14 11:59:17 -07:00
// InvokeGo returns the output of a go command invocation.
// It does not try to recover from errors.
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 )
}
2020-01-14 11:59:17 -07:00
if ctx . Err ( ) != nil {
return nil , ctx . Err ( )
2019-09-06 16:25:36 -06:00
}
2020-01-14 11:59:17 -07:00
return stdout , errors . Errorf ( "err: %v: stderr: %s" , err , stderr )
2019-09-06 16:25:36 -06:00
}
return stdout , nil
}