1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package gocommand is a helper for calling the go command.
20 "mvdan.cc/gofumpt/gofumports/internal/event"
23 // An Runner will run go command invocations and serialize
24 // them if it sees a concurrency error.
26 // once guards the runner initialization.
29 // inFlight tracks available workers.
30 inFlight chan struct{}
32 // serialized guards the ability to run a go command serially,
33 // to avoid deadlocks when claiming workers.
34 serialized chan struct{}
37 const maxInFlight = 10
39 func (runner *Runner) initialize() {
40 runner.once.Do(func() {
41 runner.inFlight = make(chan struct{}, maxInFlight)
42 runner.serialized = make(chan struct{}, 1)
46 // 1.13: go: updates to go.mod needed, but contents have changed
47 // 1.14: go: updating go.mod: existing contents have changed since last read
48 var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
50 // Run is a convenience wrapper around RunRaw.
51 // It returns only stdout and a "friendly" error.
52 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
53 stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
54 return stdout, friendly
57 // RunPiped runs the invocation serially, always waiting for any concurrent
58 // invocations to complete first.
59 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
60 _, err := runner.runPiped(ctx, inv, stdout, stderr)
64 // RunRaw runs the invocation, serializing requests only if they fight over
66 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
67 // Make sure the runner is always initialized.
70 // First, try to run the go command concurrently.
71 stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
73 // If we encounter a load concurrency error, we need to retry serially.
74 if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
75 return stdout, stderr, friendlyErr, err
77 event.Error(ctx, "Load concurrency error, will retry serially", err)
79 // Run serially by calling runPiped.
82 friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
83 return stdout, stderr, friendlyErr, err
86 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
87 // Wait for 1 worker to become available.
90 return nil, nil, nil, ctx.Err()
91 case runner.inFlight <- struct{}{}:
92 defer func() { <-runner.inFlight }()
95 stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
96 friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
97 return stdout, stderr, friendlyErr, err
100 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
101 // Make sure the runner is always initialized.
104 // Acquire the serialization lock. This avoids deadlocks between two
105 // runPiped commands.
108 return nil, ctx.Err()
109 case runner.serialized <- struct{}{}:
110 defer func() { <-runner.serialized }()
113 // Wait for all in-progress go commands to return before proceeding,
114 // to avoid load concurrency errors.
115 for i := 0; i < maxInFlight; i++ {
118 return nil, ctx.Err()
119 case runner.inFlight <- struct{}{}:
120 // Make sure we always "return" any workers we took.
121 defer func() { <-runner.inFlight }()
125 return inv.runWithFriendlyError(ctx, stdout, stderr)
128 // An Invocation represents a call to the go command.
129 type Invocation struct {
135 Logf func(format string, args ...interface{})
138 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
139 rawError = i.run(ctx, stdout, stderr)
141 friendlyError = rawError
142 // Check for 'go' executable not being found.
143 if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
144 friendlyError = fmt.Errorf("go command required, not found: %v", ee)
146 if ctx.Err() != nil {
147 friendlyError = ctx.Err()
149 friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr)
154 func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
157 log = func(string, ...interface{}) {}
160 goArgs := []string{i.Verb}
163 // mod needs the sub-verb before build flags.
164 goArgs = append(goArgs, i.Args[0])
165 goArgs = append(goArgs, i.BuildFlags...)
166 goArgs = append(goArgs, i.Args[1:]...)
168 // env doesn't take build flags.
169 goArgs = append(goArgs, i.Args...)
171 goArgs = append(goArgs, i.BuildFlags...)
172 goArgs = append(goArgs, i.Args...)
174 cmd := exec.Command("go", goArgs...)
177 // On darwin the cwd gets resolved to the real path, which breaks anything that
178 // expects the working directory to keep the original path, including the
179 // go command when dealing with modules.
180 // The Go stdlib has a special feature where if the cwd and the PWD are the
181 // same node then it trusts the PWD, so by setting it in the env for the child
182 // process we fix up all the paths returned by the go command.
183 cmd.Env = append(os.Environ(), i.Env...)
184 if i.WorkingDir != "" {
185 cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
186 cmd.Dir = i.WorkingDir
188 defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
190 return runCmdContext(ctx, cmd)
193 // runCmdContext is like exec.CommandContext except it sends os.Interrupt
195 func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
196 if err := cmd.Start(); err != nil {
199 resChan := make(chan error, 1)
201 resChan <- cmd.Wait()
205 case err := <-resChan:
209 // Cancelled. Interrupt and see if it ends voluntarily.
210 cmd.Process.Signal(os.Interrupt)
212 case err := <-resChan:
214 case <-time.After(time.Second):
216 // Didn't shut down in response to interrupt. Kill it hard.
221 func cmdDebugStr(cmd *exec.Cmd) string {
222 env := make(map[string]string)
223 for _, kv := range cmd.Env {
224 split := strings.Split(kv, "=")
225 k, v := split[0], split[1]
229 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)