vendor.go raw

   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.
   4  
   5  package gocommand
   6  
   7  import (
   8  	"bytes"
   9  	"context"
  10  	"fmt"
  11  	"os"
  12  	"path/filepath"
  13  	"regexp"
  14  	"strings"
  15  	"time"
  16  
  17  	"golang.org/x/mod/semver"
  18  )
  19  
  20  // ModuleJSON holds information about a module.
  21  type ModuleJSON struct {
  22  	Path      string      // module path
  23  	Version   string      // module version
  24  	Versions  []string    // available module versions (with -versions)
  25  	Replace   *ModuleJSON // replaced by this module
  26  	Time      *time.Time  // time version was created
  27  	Update    *ModuleJSON // available update, if any (with -u)
  28  	Main      bool        // is this the main module?
  29  	Indirect  bool        // is this module only an indirect dependency of main module?
  30  	Dir       string      // directory holding files for this module, if any
  31  	GoMod     string      // path to go.mod file used when loading this module, if any
  32  	GoVersion string      // go version used in module
  33  }
  34  
  35  var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
  36  
  37  // VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
  38  // with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
  39  // of which only Verb and Args are modified to run the appropriate Go command.
  40  // Inspired by setDefaultBuildMod in modload/init.go
  41  func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (bool, *ModuleJSON, error) {
  42  	mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
  43  	if err != nil {
  44  		return false, nil, err
  45  	}
  46  
  47  	// We check the GOFLAGS to see if there is anything overridden or not.
  48  	inv.Verb = "env"
  49  	inv.Args = []string{"GOFLAGS"}
  50  	stdout, err := r.Run(ctx, inv)
  51  	if err != nil {
  52  		return false, nil, err
  53  	}
  54  	goflags := string(bytes.TrimSpace(stdout.Bytes()))
  55  	matches := modFlagRegexp.FindStringSubmatch(goflags)
  56  	var modFlag string
  57  	if len(matches) != 0 {
  58  		modFlag = matches[1]
  59  	}
  60  	// Don't override an explicit '-mod=' argument.
  61  	if modFlag == "vendor" {
  62  		return true, mainMod, nil
  63  	} else if modFlag != "" {
  64  		return false, nil, nil
  65  	}
  66  	if mainMod == nil || !go114 {
  67  		return false, nil, nil
  68  	}
  69  	// Check 1.14's automatic vendor mode.
  70  	if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
  71  		if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
  72  			// The Go version is at least 1.14, and a vendor directory exists.
  73  			// Set -mod=vendor by default.
  74  			return true, mainMod, nil
  75  		}
  76  	}
  77  	return false, nil, nil
  78  }
  79  
  80  // getMainModuleAnd114 gets one of the main modules' information and whether the
  81  // go command in use is 1.14+. This is the information needed to figure out
  82  // if vendoring should be enabled.
  83  func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
  84  	const format = `{{.Path}}
  85  {{.Dir}}
  86  {{.GoMod}}
  87  {{.GoVersion}}
  88  {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
  89  `
  90  	inv.Verb = "list"
  91  	inv.Args = []string{"-m", "-f", format}
  92  	stdout, err := r.Run(ctx, inv)
  93  	if err != nil {
  94  		return nil, false, err
  95  	}
  96  
  97  	lines := strings.Split(stdout.String(), "\n")
  98  	if len(lines) < 5 {
  99  		return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
 100  	}
 101  	mod := &ModuleJSON{
 102  		Path:      lines[0],
 103  		Dir:       lines[1],
 104  		GoMod:     lines[2],
 105  		GoVersion: lines[3],
 106  		Main:      true,
 107  	}
 108  	return mod, lines[4] == "go1.14", nil
 109  }
 110  
 111  // WorkspaceVendorEnabled reports whether workspace vendoring is enabled. It takes a *Runner to execute Go commands
 112  // with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
 113  // of which only Verb and Args are modified to run the appropriate Go command.
 114  // Inspired by setDefaultBuildMod in modload/init.go
 115  func WorkspaceVendorEnabled(ctx context.Context, inv Invocation, r *Runner) (bool, []*ModuleJSON, error) {
 116  	inv.Verb = "env"
 117  	inv.Args = []string{"GOWORK"}
 118  	stdout, err := r.Run(ctx, inv)
 119  	if err != nil {
 120  		return false, nil, err
 121  	}
 122  	goWork := string(bytes.TrimSpace(stdout.Bytes()))
 123  	if fi, err := os.Stat(filepath.Join(filepath.Dir(goWork), "vendor")); err == nil && fi.IsDir() {
 124  		mainMods, err := getWorkspaceMainModules(ctx, inv, r)
 125  		if err != nil {
 126  			return false, nil, err
 127  		}
 128  		return true, mainMods, nil
 129  	}
 130  	return false, nil, nil
 131  }
 132  
 133  // getWorkspaceMainModules gets the main modules' information.
 134  // This is the information needed to figure out if vendoring should be enabled.
 135  func getWorkspaceMainModules(ctx context.Context, inv Invocation, r *Runner) ([]*ModuleJSON, error) {
 136  	const format = `{{.Path}}
 137  {{.Dir}}
 138  {{.GoMod}}
 139  {{.GoVersion}}
 140  `
 141  	inv.Verb = "list"
 142  	inv.Args = []string{"-m", "-f", format}
 143  	stdout, err := r.Run(ctx, inv)
 144  	if err != nil {
 145  		return nil, err
 146  	}
 147  
 148  	lines := strings.Split(strings.TrimSuffix(stdout.String(), "\n"), "\n")
 149  	if len(lines) < 4 {
 150  		return nil, fmt.Errorf("unexpected stdout: %q", stdout.String())
 151  	}
 152  	mods := make([]*ModuleJSON, 0, len(lines)/4)
 153  	for i := 0; i < len(lines); i += 4 {
 154  		mods = append(mods, &ModuleJSON{
 155  			Path:      lines[i],
 156  			Dir:       lines[i+1],
 157  			GoMod:     lines[i+2],
 158  			GoVersion: lines[i+3],
 159  			Main:      true,
 160  		})
 161  	}
 162  	return mods, nil
 163  }
 164