src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

vendor.go (5242B)


      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 }