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.
21 "mvdan.cc/gofumpt/gofumports/internal/event"
24 // An Runner will run go command invocations and serialize
25 // them if it sees a concurrency error.
27 // once guards the runner initialization.
30 // inFlight tracks available workers.
31 inFlight chan struct{}
33 // serialized guards the ability to run a go command serially,
34 // to avoid deadlocks when claiming workers.
35 serialized chan struct{}
38 const maxInFlight = 10
40 func (runner *Runner) initialize() {
41 runner.once.Do(func() {
42 runner.inFlight = make(chan struct{}, maxInFlight)
43 runner.serialized = make(chan struct{}, 1)
47 // 1.13: go: updates to go.mod needed, but contents have changed
48 // 1.14: go: updating go.mod: existing contents have changed since last read
49 var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
51 // Run is a convenience wrapper around RunRaw.
52 // It returns only stdout and a "friendly" error.
53 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
54 stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
55 return stdout, friendly
58 // RunPiped runs the invocation serially, always waiting for any concurrent
59 // invocations to complete first.
60 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
61 _, err := runner.runPiped(ctx, inv, stdout, stderr)
65 // RunRaw runs the invocation, serializing requests only if they fight over
67 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
68 // Make sure the runner is always initialized.
71 // First, try to run the go command concurrently.
72 stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
74 // If we encounter a load concurrency error, we need to retry serially.
75 if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
76 return stdout, stderr, friendlyErr, err
78 event.Error(ctx, "Load concurrency error, will retry serially", err)
80 // Run serially by calling runPiped.
83 friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
84 return stdout, stderr, friendlyErr, err
87 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
88 // Wait for 1 worker to become available.
91 return nil, nil, nil, ctx.Err()
92 case runner.inFlight <- struct{}{}:
93 defer func() { <-runner.inFlight }()
96 stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
97 friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
98 return stdout, stderr, friendlyErr, err
101 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
102 // Make sure the runner is always initialized.
105 // Acquire the serialization lock. This avoids deadlocks between two
106 // runPiped commands.
109 return nil, ctx.Err()
110 case runner.serialized <- struct{}{}:
111 defer func() { <-runner.serialized }()
114 // Wait for all in-progress go commands to return before proceeding,
115 // to avoid load concurrency errors.
116 for i := 0; i < maxInFlight; i++ {
119 return nil, ctx.Err()
120 case runner.inFlight <- struct{}{}:
121 // Make sure we always "return" any workers we took.
122 defer func() { <-runner.inFlight }()
126 return inv.runWithFriendlyError(ctx, stdout, stderr)
129 // An Invocation represents a call to the go command.
130 type Invocation struct {
137 // If CleanEnv is set, the invocation will run only with the environment
138 // in Env, not starting with os.Environ.
142 Logf func(format string, args ...interface{})
145 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
146 rawError = i.run(ctx, stdout, stderr)
148 friendlyError = rawError
149 // Check for 'go' executable not being found.
150 if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
151 friendlyError = fmt.Errorf("go command required, not found: %v", ee)
153 if ctx.Err() != nil {
154 friendlyError = ctx.Err()
156 friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr)
161 func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
164 log = func(string, ...interface{}) {}
167 goArgs := []string{i.Verb}
169 appendModFile := func() {
171 goArgs = append(goArgs, "-modfile="+i.ModFile)
174 appendModFlag := func() {
176 goArgs = append(goArgs, "-mod="+i.ModFlag)
179 appendOverlayFlag := func() {
181 goArgs = append(goArgs, "-overlay="+i.Overlay)
186 case "env", "version":
187 goArgs = append(goArgs, i.Args...)
189 // mod needs the sub-verb before flags.
190 goArgs = append(goArgs, i.Args[0])
192 goArgs = append(goArgs, i.Args[1:]...)
194 goArgs = append(goArgs, i.BuildFlags...)
196 goArgs = append(goArgs, i.Args...)
198 default: // notably list and build.
199 goArgs = append(goArgs, i.BuildFlags...)
203 goArgs = append(goArgs, i.Args...)
205 cmd := exec.Command("go", goArgs...)
208 // On darwin the cwd gets resolved to the real path, which breaks anything that
209 // expects the working directory to keep the original path, including the
210 // go command when dealing with modules.
211 // The Go stdlib has a special feature where if the cwd and the PWD are the
212 // same node then it trusts the PWD, so by setting it in the env for the child
213 // process we fix up all the paths returned by the go command.
215 cmd.Env = os.Environ()
217 cmd.Env = append(cmd.Env, i.Env...)
218 if i.WorkingDir != "" {
219 cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
220 cmd.Dir = i.WorkingDir
222 defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
224 return runCmdContext(ctx, cmd)
227 // runCmdContext is like exec.CommandContext except it sends os.Interrupt
229 func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
230 if err := cmd.Start(); err != nil {
233 resChan := make(chan error, 1)
235 resChan <- cmd.Wait()
239 case err := <-resChan:
243 // Cancelled. Interrupt and see if it ends voluntarily.
244 cmd.Process.Signal(os.Interrupt)
246 case err := <-resChan:
248 case <-time.After(time.Second):
250 // Didn't shut down in response to interrupt. Kill it hard.
255 func cmdDebugStr(cmd *exec.Cmd) string {
256 env := make(map[string]string)
257 for _, kv := range cmd.Env {
258 split := strings.SplitN(kv, "=", 2)
259 k, v := split[0], split[1]
264 for _, arg := range cmd.Args {
265 quoted := strconv.Quote(arg)
266 if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") {
267 args = append(args, quoted)
269 args = append(args, arg)
272 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " "))