gopls/api-diff: fix api-diff command for Go 1.18

Also add some better error reporting.

Change-Id: I9363ac324fc8ad89ccc77491a9e6399506ec8dcb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/356930
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/api-diff/api_diff.go b/gopls/api-diff/api_diff.go
index 1b98a64..167bdbd 100644
--- a/gopls/api-diff/api_diff.go
+++ b/gopls/api-diff/api_diff.go
@@ -9,6 +9,7 @@
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"flag"
 	"fmt"
@@ -20,6 +21,7 @@
 	"path/filepath"
 	"strings"
 
+	"golang.org/x/tools/internal/gocommand"
 	difflib "golang.org/x/tools/internal/lsp/diff"
 	"golang.org/x/tools/internal/lsp/diff/myers"
 	"golang.org/x/tools/internal/lsp/source"
@@ -48,18 +50,19 @@
 }
 
 func diffAPI(version, prev string) (string, error) {
-	previousApi, err := loadAPI(prev)
+	ctx := context.Background()
+	previousApi, err := loadAPI(ctx, prev)
 	if err != nil {
-		return "", err
+		return "", fmt.Errorf("load previous API: %v", err)
 	}
 	var currentApi *source.APIJSON
 	if version == "" {
 		currentApi = source.GeneratedAPIJSON
 	} else {
 		var err error
-		currentApi, err = loadAPI(version)
+		currentApi, err = loadAPI(ctx, version)
 		if err != nil {
-			return "", err
+			return "", fmt.Errorf("load current API: %v", err)
 		}
 	}
 
@@ -67,17 +70,17 @@
 	if err := diff(b, previousApi.Commands, currentApi.Commands, "command", func(c *source.CommandJSON) string {
 		return c.Command
 	}, diffCommands); err != nil {
-		return "", err
+		return "", fmt.Errorf("diff commands: %v", err)
 	}
 	if diff(b, previousApi.Analyzers, currentApi.Analyzers, "analyzer", func(a *source.AnalyzerJSON) string {
 		return a.Name
 	}, diffAnalyzers); err != nil {
-		return "", err
+		return "", fmt.Errorf("diff analyzers: %v", err)
 	}
 	if err := diff(b, previousApi.Lenses, currentApi.Lenses, "code lens", func(l *source.LensJSON) string {
 		return l.Lens
 	}, diffLenses); err != nil {
-		return "", err
+		return "", fmt.Errorf("diff lenses: %v", err)
 	}
 	for key, prev := range previousApi.Options {
 		current, ok := currentApi.Options[key]
@@ -87,7 +90,7 @@
 		if err := diff(b, prev, current, "option", func(o *source.OptionJSON) string {
 			return o.Name
 		}, diffOptions); err != nil {
-			return "", err
+			return "", fmt.Errorf("diff options (%s): %v", key, err)
 		}
 	}
 
@@ -138,41 +141,48 @@
 	return m
 }
 
-func loadAPI(version string) (*source.APIJSON, error) {
-	dir, err := ioutil.TempDir("", "gopath*")
-	if err != nil {
-		return nil, err
-	}
-	defer os.RemoveAll(dir)
+var goCmdRunner = gocommand.Runner{}
 
-	if err := os.Mkdir(fmt.Sprintf("%s/src", dir), 0776); err != nil {
-		return nil, err
-	}
-	goCmd, err := exec.LookPath("go")
+func loadAPI(ctx context.Context, version string) (*source.APIJSON, error) {
+	tmpGopath, err := ioutil.TempDir("", "gopath*")
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("temp dir: %v", err)
+	}
+	defer os.RemoveAll(tmpGopath)
+
+	exampleDir := fmt.Sprintf("%s/src/example.com", tmpGopath)
+	if err := os.MkdirAll(exampleDir, 0776); err != nil {
+		return nil, fmt.Errorf("mkdir: %v", err)
+	}
+
+	if stdout, err := goCmdRunner.Run(ctx, gocommand.Invocation{
+		Verb:       "mod",
+		Args:       []string{"init", "example.com"},
+		WorkingDir: exampleDir,
+		Env:        append(os.Environ(), fmt.Sprintf("GOPATH=%s", tmpGopath)),
+	}); err != nil {
+		return nil, fmt.Errorf("go mod init failed: %v (stdout: %v)", err, stdout)
+	}
+	if stdout, err := goCmdRunner.Run(ctx, gocommand.Invocation{
+		Verb:       "install",
+		Args:       []string{fmt.Sprintf("golang.org/x/tools/gopls@%s", version)},
+		WorkingDir: exampleDir,
+		Env:        append(os.Environ(), fmt.Sprintf("GOPATH=%s", tmpGopath)),
+	}); err != nil {
+		return nil, fmt.Errorf("go install failed: %v (stdout: %v)", err, stdout.String())
 	}
 	cmd := exec.Cmd{
-		Path: goCmd,
-		Args: []string{"go", "get", fmt.Sprintf("golang.org/x/tools/gopls@%s", version)},
-		Dir:  dir,
-		Env:  append(os.Environ(), fmt.Sprintf("GOPATH=%s", dir)),
-	}
-	if err := cmd.Run(); err != nil {
-		return nil, err
-	}
-	cmd = exec.Cmd{
-		Path: filepath.Join(dir, "bin", "gopls"),
+		Path: filepath.Join(tmpGopath, "bin", "gopls"),
 		Args: []string{"gopls", "api-json"},
-		Dir:  dir,
+		Dir:  tmpGopath,
 	}
 	out, err := cmd.Output()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("output: %v", err)
 	}
 	apiJson := &source.APIJSON{}
 	if err := json.Unmarshal(out, apiJson); err != nil {
-		return nil, err
+		return nil, fmt.Errorf("unmarshal: %v", err)
 	}
 	return apiJson, nil
 }