* change to new code location * vendor * tagged version v0.2.0 * gitea-vet v0.2.1 Co-authored-by: techknowlogick <techknowlogick@gitea.io>
		
			
				
	
	
		
			231 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| // Copyright 2020 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // Package gocommand is a helper for calling the go command.
 | |
| package gocommand
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"golang.org/x/tools/internal/event"
 | |
| )
 | |
| 
 | |
| // An Runner will run go command invocations and serialize
 | |
| // them if it sees a concurrency error.
 | |
| type Runner struct {
 | |
| 	// once guards the runner initialization.
 | |
| 	once sync.Once
 | |
| 
 | |
| 	// inFlight tracks available workers.
 | |
| 	inFlight chan struct{}
 | |
| 
 | |
| 	// serialized guards the ability to run a go command serially,
 | |
| 	// to avoid deadlocks when claiming workers.
 | |
| 	serialized chan struct{}
 | |
| }
 | |
| 
 | |
| const maxInFlight = 10
 | |
| 
 | |
| func (runner *Runner) initialize() {
 | |
| 	runner.once.Do(func() {
 | |
| 		runner.inFlight = make(chan struct{}, maxInFlight)
 | |
| 		runner.serialized = make(chan struct{}, 1)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // 1.13: go: updates to go.mod needed, but contents have changed
 | |
| // 1.14: go: updating go.mod: existing contents have changed since last read
 | |
| var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
 | |
| 
 | |
| // Run is a convenience wrapper around RunRaw.
 | |
| // It returns only stdout and a "friendly" error.
 | |
| func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
 | |
| 	stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
 | |
| 	return stdout, friendly
 | |
| }
 | |
| 
 | |
| // RunPiped runs the invocation serially, always waiting for any concurrent
 | |
| // invocations to complete first.
 | |
| func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
 | |
| 	_, err := runner.runPiped(ctx, inv, stdout, stderr)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // RunRaw runs the invocation, serializing requests only if they fight over
 | |
| // go.mod changes.
 | |
| func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
 | |
| 	// Make sure the runner is always initialized.
 | |
| 	runner.initialize()
 | |
| 
 | |
| 	// First, try to run the go command concurrently.
 | |
| 	stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
 | |
| 
 | |
| 	// If we encounter a load concurrency error, we need to retry serially.
 | |
| 	if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
 | |
| 		return stdout, stderr, friendlyErr, err
 | |
| 	}
 | |
| 	event.Error(ctx, "Load concurrency error, will retry serially", err)
 | |
| 
 | |
| 	// Run serially by calling runPiped.
 | |
| 	stdout.Reset()
 | |
| 	stderr.Reset()
 | |
| 	friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
 | |
| 	return stdout, stderr, friendlyErr, err
 | |
| }
 | |
| 
 | |
| func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
 | |
| 	// Wait for 1 worker to become available.
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		return nil, nil, nil, ctx.Err()
 | |
| 	case runner.inFlight <- struct{}{}:
 | |
| 		defer func() { <-runner.inFlight }()
 | |
| 	}
 | |
| 
 | |
| 	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
 | |
| 	friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
 | |
| 	return stdout, stderr, friendlyErr, err
 | |
| }
 | |
| 
 | |
| func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
 | |
| 	// Make sure the runner is always initialized.
 | |
| 	runner.initialize()
 | |
| 
 | |
| 	// Acquire the serialization lock. This avoids deadlocks between two
 | |
| 	// runPiped commands.
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		return nil, ctx.Err()
 | |
| 	case runner.serialized <- struct{}{}:
 | |
| 		defer func() { <-runner.serialized }()
 | |
| 	}
 | |
| 
 | |
| 	// Wait for all in-progress go commands to return before proceeding,
 | |
| 	// to avoid load concurrency errors.
 | |
| 	for i := 0; i < maxInFlight; i++ {
 | |
| 		select {
 | |
| 		case <-ctx.Done():
 | |
| 			return nil, ctx.Err()
 | |
| 		case runner.inFlight <- struct{}{}:
 | |
| 			// Make sure we always "return" any workers we took.
 | |
| 			defer func() { <-runner.inFlight }()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return inv.runWithFriendlyError(ctx, stdout, stderr)
 | |
| }
 | |
| 
 | |
| // An Invocation represents a call to the go command.
 | |
| type Invocation struct {
 | |
| 	Verb       string
 | |
| 	Args       []string
 | |
| 	BuildFlags []string
 | |
| 	Env        []string
 | |
| 	WorkingDir string
 | |
| 	Logf       func(format string, args ...interface{})
 | |
| }
 | |
| 
 | |
| func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
 | |
| 	rawError = i.run(ctx, stdout, stderr)
 | |
| 	if rawError != nil {
 | |
| 		friendlyError = rawError
 | |
| 		// Check for 'go' executable not being found.
 | |
| 		if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
 | |
| 			friendlyError = fmt.Errorf("go command required, not found: %v", ee)
 | |
| 		}
 | |
| 		if ctx.Err() != nil {
 | |
| 			friendlyError = ctx.Err()
 | |
| 		}
 | |
| 		friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr)
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
 | |
| 	log := i.Logf
 | |
| 	if log == nil {
 | |
| 		log = func(string, ...interface{}) {}
 | |
| 	}
 | |
| 
 | |
| 	goArgs := []string{i.Verb}
 | |
| 	switch i.Verb {
 | |
| 	case "mod":
 | |
| 		// mod needs the sub-verb before build flags.
 | |
| 		goArgs = append(goArgs, i.Args[0])
 | |
| 		goArgs = append(goArgs, i.BuildFlags...)
 | |
| 		goArgs = append(goArgs, i.Args[1:]...)
 | |
| 	case "env":
 | |
| 		// env doesn't take build flags.
 | |
| 		goArgs = append(goArgs, i.Args...)
 | |
| 	default:
 | |
| 		goArgs = append(goArgs, i.BuildFlags...)
 | |
| 		goArgs = append(goArgs, i.Args...)
 | |
| 	}
 | |
| 	cmd := exec.Command("go", goArgs...)
 | |
| 	cmd.Stdout = stdout
 | |
| 	cmd.Stderr = stderr
 | |
| 	// 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(os.Environ(), i.Env...)
 | |
| 	if i.WorkingDir != "" {
 | |
| 		cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
 | |
| 		cmd.Dir = i.WorkingDir
 | |
| 	}
 | |
| 	defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
 | |
| 
 | |
| 	return runCmdContext(ctx, cmd)
 | |
| }
 | |
| 
 | |
| // runCmdContext is like exec.CommandContext except it sends os.Interrupt
 | |
| // before os.Kill.
 | |
| func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
 | |
| 	if err := cmd.Start(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	resChan := make(chan error, 1)
 | |
| 	go func() {
 | |
| 		resChan <- cmd.Wait()
 | |
| 	}()
 | |
| 
 | |
| 	select {
 | |
| 	case err := <-resChan:
 | |
| 		return err
 | |
| 	case <-ctx.Done():
 | |
| 	}
 | |
| 	// Cancelled. Interrupt and see if it ends voluntarily.
 | |
| 	cmd.Process.Signal(os.Interrupt)
 | |
| 	select {
 | |
| 	case err := <-resChan:
 | |
| 		return err
 | |
| 	case <-time.After(time.Second):
 | |
| 	}
 | |
| 	// Didn't shut down in response to interrupt. Kill it hard.
 | |
| 	cmd.Process.Kill()
 | |
| 	return <-resChan
 | |
| }
 | |
| 
 | |
| func cmdDebugStr(cmd *exec.Cmd) string {
 | |
| 	env := make(map[string]string)
 | |
| 	for _, kv := range cmd.Env {
 | |
| 		split := strings.Split(kv, "=")
 | |
| 		k, v := split[0], split[1]
 | |
| 		env[k] = v
 | |
| 	}
 | |
| 
 | |
| 	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)
 | |
| }
 |