golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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  	"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  }