github.com/jd-ly/tools@v0.5.7/internal/gocommand/vendor.go (about)

     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  
    16  	"golang.org/x/mod/semver"
    17  )
    18  
    19  // ModuleJSON holds information about a module.
    20  type ModuleJSON struct {
    21  	Path      string      // module path
    22  	Replace   *ModuleJSON // replaced by this module
    23  	Main      bool        // is this the main module?
    24  	Indirect  bool        // is this module only an indirect dependency of main module?
    25  	Dir       string      // directory holding files for this module, if any
    26  	GoMod     string      // path to go.mod file for this module, if any
    27  	GoVersion string      // go version used in module
    28  }
    29  
    30  var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
    31  
    32  // VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
    33  // with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
    34  // of which only Verb and Args are modified to run the appropriate Go command.
    35  // Inspired by setDefaultBuildMod in modload/init.go
    36  func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
    37  	mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
    38  	if err != nil {
    39  		return nil, false, err
    40  	}
    41  
    42  	// We check the GOFLAGS to see if there is anything overridden or not.
    43  	inv.Verb = "env"
    44  	inv.Args = []string{"GOFLAGS"}
    45  	stdout, err := r.Run(ctx, inv)
    46  	if err != nil {
    47  		return nil, false, err
    48  	}
    49  	goflags := string(bytes.TrimSpace(stdout.Bytes()))
    50  	matches := modFlagRegexp.FindStringSubmatch(goflags)
    51  	var modFlag string
    52  	if len(matches) != 0 {
    53  		modFlag = matches[1]
    54  	}
    55  	if modFlag != "" {
    56  		// Don't override an explicit '-mod=' argument.
    57  		return mainMod, modFlag == "vendor", nil
    58  	}
    59  	if mainMod == nil || !go114 {
    60  		return mainMod, false, nil
    61  	}
    62  	// Check 1.14's automatic vendor mode.
    63  	if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
    64  		if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
    65  			// The Go version is at least 1.14, and a vendor directory exists.
    66  			// Set -mod=vendor by default.
    67  			return mainMod, true, nil
    68  		}
    69  	}
    70  	return mainMod, false, nil
    71  }
    72  
    73  // getMainModuleAnd114 gets the main module's information and whether the
    74  // go command in use is 1.14+. This is the information needed to figure out
    75  // if vendoring should be enabled.
    76  func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
    77  	const format = `{{.Path}}
    78  {{.Dir}}
    79  {{.GoMod}}
    80  {{.GoVersion}}
    81  {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
    82  `
    83  	inv.Verb = "list"
    84  	inv.Args = []string{"-m", "-f", format}
    85  	stdout, err := r.Run(ctx, inv)
    86  	if err != nil {
    87  		return nil, false, err
    88  	}
    89  
    90  	lines := strings.Split(stdout.String(), "\n")
    91  	if len(lines) < 5 {
    92  		return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
    93  	}
    94  	mod := &ModuleJSON{
    95  		Path:      lines[0],
    96  		Dir:       lines[1],
    97  		GoMod:     lines[2],
    98  		GoVersion: lines[3],
    99  		Main:      true,
   100  	}
   101  	return mod, lines[4] == "go1.14", nil
   102  }