1
0
mirror of https://github.com/golang/go synced 2024-10-01 08:28:43 -06:00
go/internal/lsp/source/errors.go
Rebecca Stambler a7dab0268b internal/lsp: diagnose the snapshot on every text synchronization event
This change moves to our ultimate approach of diagnostics the snapshot
on every file change, instead of carefully picking which files and
packages to diagnose. Analyses are shown for packages whose files are
open in the editor. Reverse dependencies are no longer needed for
source.Diagnostics because they will be invalidated when the snapshot is
cloned, so diagnosing the entire snapshot will bring them up to date.

This even works for go.mod files because all of workspace-level `go list`s
will be canceled as the user types, and then we trigger an uncancellable
go/packages.Load when the user saves. There is still room for improvement
here, but it will require much more careful invalidation of metadata for
go.mod files.

Change-Id: Id068505634b5e701c6f861a61b09a4c6704c565f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/214419
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-15 23:07:48 +00:00

111 lines
3.5 KiB
Go

package source
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
errors "golang.org/x/xerrors"
)
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")
folder := v.Folder().Filename()
// Invoke `go env GOMOD` inside of the directory of the view.
b, err := InvokeGo(ctx, folder, 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 != ""
// 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 output of a go command invocation.
// It does not try to recover from errors.
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 ctx.Err() != nil {
return nil, ctx.Err()
}
return stdout, errors.Errorf("err: %v: stderr: %s", err, stderr)
}
return stdout, nil
}