Skip to content

Commit c5cec67

Browse files
committed
all: consolidate invokeGo implementations
Over time we have accumulated a disturbing number of ways to run the Go command, each of which supported slightly different features and workarounds. Combine all of them. This unavoidably introduces some changes in debug logging behavior; I think we should try to fix them incrementally and consistently. Updates golang/go#37368. Change-Id: I664ca8685bf247a226be3cb807789c2fcddf233d Reviewed-on: https://go-review.googlesource.com/c/tools/+/220737 Run-TryBot: Heschi Kreinick <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent ee3f114 commit c5cec67

File tree

12 files changed

+206
-225
lines changed

12 files changed

+206
-225
lines changed

go/internal/packagesdriver/sizes.go

+23-79
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import (
1111
"encoding/json"
1212
"fmt"
1313
"go/types"
14-
"log"
15-
"os"
1614
"os/exec"
1715
"strings"
18-
"time"
16+
17+
"golang.org/x/tools/internal/gocommand"
1918
)
2019

2120
var debug = false
@@ -78,97 +77,42 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp
7877
}
7978

8079
func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) {
81-
args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"}
82-
args = append(args, buildFlags...)
83-
args = append(args, "--", "unsafe")
84-
stdout, stderr, err := invokeGo(ctx, env, dir, usesExportData, args...)
80+
inv := gocommand.Invocation{
81+
Verb: "list",
82+
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
83+
Env: env,
84+
BuildFlags: buildFlags,
85+
WorkingDir: dir,
86+
}
87+
stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx)
8588
var goarch, compiler string
86-
if err != nil {
87-
if strings.Contains(err.Error(), "cannot find main module") {
89+
if rawErr != nil {
90+
if strings.Contains(rawErr.Error(), "cannot find main module") {
8891
// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
8992
// TODO(matloob): Is this a problem in practice?
90-
envout, _, enverr := invokeGo(ctx, env, dir, usesExportData, "env", "GOARCH")
93+
inv := gocommand.Invocation{
94+
Verb: "env",
95+
Args: []string{"GOARCH"},
96+
Env: env,
97+
WorkingDir: dir,
98+
}
99+
envout, enverr := inv.Run(ctx)
91100
if enverr != nil {
92-
return nil, err
101+
return nil, enverr
93102
}
94103
goarch = strings.TrimSpace(envout.String())
95104
compiler = "gc"
96105
} else {
97-
return nil, err
106+
return nil, friendlyErr
98107
}
99108
} else {
100109
fields := strings.Fields(stdout.String())
101110
if len(fields) < 2 {
102-
return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\" from stdout of go command:\n%s\ndir: %s\nstdout: <<%s>>\nstderr: <<%s>>",
103-
cmdDebugStr(env, args...), dir, stdout.String(), stderr.String())
111+
return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
112+
stdout.String(), stderr.String())
104113
}
105114
goarch = fields[0]
106115
compiler = fields[1]
107116
}
108117
return types.SizesFor(compiler, goarch), nil
109118
}
110-
111-
// invokeGo returns the stdout and stderr of a go command invocation.
112-
func invokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, *bytes.Buffer, error) {
113-
if debug {
114-
defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now())
115-
}
116-
stdout := new(bytes.Buffer)
117-
stderr := new(bytes.Buffer)
118-
cmd := exec.CommandContext(ctx, "go", args...)
119-
// On darwin the cwd gets resolved to the real path, which breaks anything that
120-
// expects the working directory to keep the original path, including the
121-
// go command when dealing with modules.
122-
// The Go stdlib has a special feature where if the cwd and the PWD are the
123-
// same node then it trusts the PWD, so by setting it in the env for the child
124-
// process we fix up all the paths returned by the go command.
125-
cmd.Env = append(append([]string{}, env...), "PWD="+dir)
126-
cmd.Dir = dir
127-
cmd.Stdout = stdout
128-
cmd.Stderr = stderr
129-
if err := cmd.Run(); err != nil {
130-
exitErr, ok := err.(*exec.ExitError)
131-
if !ok {
132-
// Catastrophic error:
133-
// - executable not found
134-
// - context cancellation
135-
return nil, nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
136-
}
137-
138-
// Export mode entails a build.
139-
// If that build fails, errors appear on stderr
140-
// (despite the -e flag) and the Export field is blank.
141-
// Do not fail in that case.
142-
if !usesExportData {
143-
return nil, nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
144-
}
145-
}
146-
147-
// As of writing, go list -export prints some non-fatal compilation
148-
// errors to stderr, even with -e set. We would prefer that it put
149-
// them in the Package.Error JSON (see https://golang.org/issue/26319).
150-
// In the meantime, there's nowhere good to put them, but they can
151-
// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
152-
// is set.
153-
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
154-
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr)
155-
}
156-
157-
// debugging
158-
if false {
159-
fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout)
160-
}
161-
162-
return stdout, stderr, nil
163-
}
164-
165-
func cmdDebugStr(envlist []string, args ...string) string {
166-
env := make(map[string]string)
167-
for _, kv := range envlist {
168-
split := strings.Split(kv, "=")
169-
k, v := split[0], split[1]
170-
env[k] = v
171-
}
172-
173-
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args)
174-
}

go/packages/golist.go

+13-35
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import (
2020
"strconv"
2121
"strings"
2222
"sync"
23-
"time"
2423
"unicode"
2524

2625
"golang.org/x/tools/go/internal/packagesdriver"
26+
"golang.org/x/tools/internal/gocommand"
2727
"golang.org/x/tools/internal/packagesinternal"
2828
)
2929

@@ -707,29 +707,17 @@ func golistargs(cfg *Config, words []string) []string {
707707
func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
708708
cfg := state.cfg
709709

710-
stdout := new(bytes.Buffer)
711-
stderr := new(bytes.Buffer)
712-
goArgs := []string{verb}
713-
if verb != "env" {
714-
goArgs = append(goArgs, cfg.BuildFlags...)
715-
}
716-
goArgs = append(goArgs, args...)
717-
cmd := exec.CommandContext(state.ctx, "go", goArgs...)
718-
// On darwin the cwd gets resolved to the real path, which breaks anything that
719-
// expects the working directory to keep the original path, including the
720-
// go command when dealing with modules.
721-
// The Go stdlib has a special feature where if the cwd and the PWD are the
722-
// same node then it trusts the PWD, so by setting it in the env for the child
723-
// process we fix up all the paths returned by the go command.
724-
cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
725-
cmd.Dir = cfg.Dir
726-
cmd.Stdout = stdout
727-
cmd.Stderr = stderr
728-
defer func(start time.Time) {
729-
cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, goArgs...), stderr, stdout)
730-
}(time.Now())
731-
732-
if err := cmd.Run(); err != nil {
710+
inv := &gocommand.Invocation{
711+
Verb: verb,
712+
Args: args,
713+
BuildFlags: cfg.BuildFlags,
714+
Env: cfg.Env,
715+
Logf: cfg.Logf,
716+
WorkingDir: cfg.Dir,
717+
}
718+
719+
stdout, stderr, _, err := inv.RunRaw(cfg.Context)
720+
if err != nil {
733721
// Check for 'go' executable not being found.
734722
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
735723
return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
@@ -739,7 +727,7 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
739727
if !ok {
740728
// Catastrophic error:
741729
// - context cancellation
742-
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
730+
return nil, fmt.Errorf("couldn't run 'go': %v", err)
743731
}
744732

745733
// Old go version?
@@ -860,16 +848,6 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
860848
return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
861849
}
862850
}
863-
864-
// As of writing, go list -export prints some non-fatal compilation
865-
// errors to stderr, even with -e set. We would prefer that it put
866-
// them in the Package.Error JSON (see https://golang.org/issue/26319).
867-
// In the meantime, there's nowhere good to put them, but they can
868-
// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
869-
// is set.
870-
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
871-
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr)
872-
}
873851
return stdout, nil
874852
}
875853

go/packages/packagestest/modules.go

+10-18
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@ package packagestest
66

77
import (
88
"archive/zip"
9-
"bytes"
9+
"context"
1010
"fmt"
1111
"io/ioutil"
1212
"os"
13-
"os/exec"
1413
"path"
1514
"path/filepath"
1615
"regexp"
1716
"strings"
1817

19-
"golang.org/x/tools/go/packages"
18+
"golang.org/x/tools/internal/gocommand"
2019
)
2120

2221
// Modules is the exporter that produces module layouts.
@@ -170,7 +169,14 @@ func (modules) Finalize(exported *Exported) error {
170169

171170
// Run go mod download to recreate the mod cache dir with all the extra
172171
// stuff in cache. All the files created by Export should be recreated.
173-
if err := invokeGo(exported.Config, "mod", "download"); err != nil {
172+
inv := gocommand.Invocation{
173+
Verb: "mod",
174+
Args: []string{"download"},
175+
Env: exported.Config.Env,
176+
BuildFlags: exported.Config.BuildFlags,
177+
WorkingDir: exported.Config.Dir,
178+
}
179+
if _, err := inv.Run(context.Background()); err != nil {
174180
return err
175181
}
176182
return nil
@@ -237,20 +243,6 @@ func writeModuleProxy(dir, module, ver string, files map[string]string) error {
237243
return nil
238244
}
239245

240-
func invokeGo(cfg *packages.Config, args ...string) error {
241-
stdout := new(bytes.Buffer)
242-
stderr := new(bytes.Buffer)
243-
cmd := exec.Command("go", args...)
244-
cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
245-
cmd.Dir = cfg.Dir
246-
cmd.Stdout = stdout
247-
cmd.Stderr = stderr
248-
if err := cmd.Run(); err != nil {
249-
return fmt.Errorf("go %v: %s: %s", args, err, stderr)
250-
}
251-
return nil
252-
}
253-
254246
func modCache(exported *Exported) string {
255247
return filepath.Join(exported.temp, "modcache/pkg/mod")
256248
}

internal/gocommand/invoke.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Package gocommand is a helper for calling the go command.
2+
package gocommand
3+
4+
import (
5+
"bytes"
6+
"context"
7+
"fmt"
8+
"os/exec"
9+
"strings"
10+
"time"
11+
)
12+
13+
// An Invocation represents a call to the go command.
14+
type Invocation struct {
15+
Verb string
16+
Args []string
17+
BuildFlags []string
18+
Env []string
19+
WorkingDir string
20+
Logf func(format string, args ...interface{})
21+
}
22+
23+
// Run runs the invocation, returning its stdout and an error suitable for
24+
// human consumption, including stderr.
25+
func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) {
26+
stdout, _, friendly, _ := i.RunRaw(ctx)
27+
return stdout, friendly
28+
}
29+
30+
// RunRaw is like Run, but also returns the raw stderr and error for callers
31+
// that want to do low-level error handling/recovery.
32+
func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
33+
log := i.Logf
34+
if log == nil {
35+
log = func(string, ...interface{}) {}
36+
}
37+
38+
goArgs := []string{i.Verb}
39+
switch i.Verb {
40+
case "mod":
41+
// mod needs the sub-verb before build flags.
42+
goArgs = append(goArgs, i.Args[0])
43+
goArgs = append(goArgs, i.BuildFlags...)
44+
goArgs = append(goArgs, i.Args[1:]...)
45+
case "env":
46+
// env doesn't take build flags.
47+
goArgs = append(goArgs, i.Args...)
48+
default:
49+
goArgs = append(goArgs, i.BuildFlags...)
50+
goArgs = append(goArgs, i.Args...)
51+
}
52+
cmd := exec.CommandContext(ctx, "go", goArgs...)
53+
stdout = &bytes.Buffer{}
54+
stderr = &bytes.Buffer{}
55+
cmd.Stdout = stdout
56+
cmd.Stderr = stderr
57+
// On darwin the cwd gets resolved to the real path, which breaks anything that
58+
// expects the working directory to keep the original path, including the
59+
// go command when dealing with modules.
60+
// The Go stdlib has a special feature where if the cwd and the PWD are the
61+
// same node then it trusts the PWD, so by setting it in the env for the child
62+
// process we fix up all the paths returned by the go command.
63+
cmd.Env = append(append([]string{}, i.Env...), "PWD="+i.WorkingDir)
64+
cmd.Dir = i.WorkingDir
65+
66+
defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
67+
68+
rawError = cmd.Run()
69+
friendlyError = rawError
70+
if rawError != nil {
71+
// Check for 'go' executable not being found.
72+
if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
73+
friendlyError = fmt.Errorf("go command required, not found: %v", ee)
74+
}
75+
if ctx.Err() != nil {
76+
friendlyError = ctx.Err()
77+
}
78+
friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr)
79+
}
80+
return
81+
}
82+
83+
func cmdDebugStr(cmd *exec.Cmd) string {
84+
env := make(map[string]string)
85+
for _, kv := range cmd.Env {
86+
split := strings.Split(kv, "=")
87+
k, v := split[0], split[1]
88+
env[k] = v
89+
}
90+
91+
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
92+
}

0 commit comments

Comments
 (0)