Source file src/cmd/go/internal/modload/vendor.go

     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 modload
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  
    16  	"cmd/go/internal/base"
    17  	"cmd/go/internal/gover"
    18  
    19  	"golang.org/x/mod/modfile"
    20  	"golang.org/x/mod/module"
    21  	"golang.org/x/mod/semver"
    22  )
    23  
    24  var (
    25  	vendorOnce      sync.Once
    26  	vendorList      []module.Version          // modules that contribute packages to the build, in order of appearance
    27  	vendorReplaced  []module.Version          // all replaced modules; may or may not also contribute packages
    28  	vendorVersion   map[string]string         // module path → selected version (if known)
    29  	vendorPkgModule map[string]module.Version // package → containing module
    30  	vendorMeta      map[module.Version]vendorMetadata
    31  )
    32  
    33  type vendorMetadata struct {
    34  	Explicit    bool
    35  	Replacement module.Version
    36  	GoVersion   string
    37  }
    38  
    39  // readVendorList reads the list of vendored modules from vendor/modules.txt.
    40  func readVendorList(vendorDir string) {
    41  	vendorOnce.Do(func() {
    42  		vendorList = nil
    43  		vendorPkgModule = make(map[string]module.Version)
    44  		vendorVersion = make(map[string]string)
    45  		vendorMeta = make(map[module.Version]vendorMetadata)
    46  		vendorFile := filepath.Join(vendorDir, "modules.txt")
    47  		data, err := os.ReadFile(vendorFile)
    48  		if err != nil {
    49  			if !errors.Is(err, fs.ErrNotExist) {
    50  				base.Fatalf("go: %s", err)
    51  			}
    52  			return
    53  		}
    54  
    55  		var mod module.Version
    56  		for _, line := range strings.Split(string(data), "\n") {
    57  			if strings.HasPrefix(line, "# ") {
    58  				f := strings.Fields(line)
    59  
    60  				if len(f) < 3 {
    61  					continue
    62  				}
    63  				if semver.IsValid(f[2]) {
    64  					// A module, but we don't yet know whether it is in the build list or
    65  					// only included to indicate a replacement.
    66  					mod = module.Version{Path: f[1], Version: f[2]}
    67  					f = f[3:]
    68  				} else if f[2] == "=>" {
    69  					// A wildcard replacement found in the main module's go.mod file.
    70  					mod = module.Version{Path: f[1]}
    71  					f = f[2:]
    72  				} else {
    73  					// Not a version or a wildcard replacement.
    74  					// We don't know how to interpret this module line, so ignore it.
    75  					mod = module.Version{}
    76  					continue
    77  				}
    78  
    79  				if len(f) >= 2 && f[0] == "=>" {
    80  					meta := vendorMeta[mod]
    81  					if len(f) == 2 {
    82  						// File replacement.
    83  						meta.Replacement = module.Version{Path: f[1]}
    84  						vendorReplaced = append(vendorReplaced, mod)
    85  					} else if len(f) == 3 && semver.IsValid(f[2]) {
    86  						// Path and version replacement.
    87  						meta.Replacement = module.Version{Path: f[1], Version: f[2]}
    88  						vendorReplaced = append(vendorReplaced, mod)
    89  					} else {
    90  						// We don't understand this replacement. Ignore it.
    91  					}
    92  					vendorMeta[mod] = meta
    93  				}
    94  				continue
    95  			}
    96  
    97  			// Not a module line. Must be a package within a module or a metadata
    98  			// directive, either of which requires a preceding module line.
    99  			if mod.Path == "" {
   100  				continue
   101  			}
   102  
   103  			if annotations, ok := strings.CutPrefix(line, "## "); ok {
   104  				// Metadata. Take the union of annotations across multiple lines, if present.
   105  				meta := vendorMeta[mod]
   106  				for _, entry := range strings.Split(annotations, ";") {
   107  					entry = strings.TrimSpace(entry)
   108  					if entry == "explicit" {
   109  						meta.Explicit = true
   110  					}
   111  					if goVersion, ok := strings.CutPrefix(entry, "go "); ok {
   112  						meta.GoVersion = goVersion
   113  						rawGoVersion.Store(mod, meta.GoVersion)
   114  						if gover.Compare(goVersion, gover.Local()) > 0 {
   115  							base.Fatal(&gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion})
   116  						}
   117  					}
   118  					// All other tokens are reserved for future use.
   119  				}
   120  				vendorMeta[mod] = meta
   121  				continue
   122  			}
   123  
   124  			if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil {
   125  				// A package within the current module.
   126  				vendorPkgModule[f[0]] = mod
   127  
   128  				// Since this module provides a package for the build, we know that it
   129  				// is in the build list and is the selected version of its path.
   130  				// If this information is new, record it.
   131  				if v, ok := vendorVersion[mod.Path]; !ok || gover.ModCompare(mod.Path, v, mod.Version) < 0 {
   132  					vendorList = append(vendorList, mod)
   133  					vendorVersion[mod.Path] = mod.Version
   134  				}
   135  			}
   136  		}
   137  	})
   138  }
   139  
   140  // checkVendorConsistency verifies that the vendor/modules.txt file matches (if
   141  // go 1.14) or at least does not contradict (go 1.13 or earlier) the
   142  // requirements and replacements listed in the main module's go.mod file.
   143  func checkVendorConsistency(indexes []*modFileIndex, modFiles []*modfile.File, modRoots []string) {
   144  	// readVendorList only needs the main module to get the directory
   145  	// the vendor directory is in.
   146  	readVendorList(VendorDir())
   147  
   148  	if len(modFiles) < 1 {
   149  		// We should never get here if there are zero modfiles. Either
   150  		// we're in single module mode and there's a single module, or
   151  		// we're in workspace mode, and we fail earlier reporting that
   152  		// "no modules were found in the current workspace".
   153  		panic("checkVendorConsistency called with zero modfiles")
   154  	}
   155  
   156  	pre114 := false
   157  	if !inWorkspaceMode() { // workspace mode was added after Go 1.14
   158  		if len(indexes) != 1 {
   159  			panic(fmt.Errorf("not in workspace mode but number of indexes is %v, not 1", len(indexes)))
   160  		}
   161  		index := indexes[0]
   162  		if gover.Compare(index.goVersion, "1.14") < 0 {
   163  			// Go versions before 1.14 did not include enough information in
   164  			// vendor/modules.txt to check for consistency.
   165  			// If we know that we're on an earlier version, relax the consistency check.
   166  			pre114 = true
   167  		}
   168  	}
   169  
   170  	vendErrors := new(strings.Builder)
   171  	vendErrorf := func(mod module.Version, format string, args ...any) {
   172  		detail := fmt.Sprintf(format, args...)
   173  		if mod.Version == "" {
   174  			fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail)
   175  		} else {
   176  			fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail)
   177  		}
   178  	}
   179  
   180  	// Iterate over the Require directives in their original (not indexed) order
   181  	// so that the errors match the original file.
   182  	for _, modFile := range modFiles {
   183  		for _, r := range modFile.Require {
   184  			if !vendorMeta[r.Mod].Explicit {
   185  				if pre114 {
   186  					// Before 1.14, modules.txt did not indicate whether modules were listed
   187  					// explicitly in the main module's go.mod file.
   188  					// However, we can at least detect a version mismatch if packages were
   189  					// vendored from a non-matching version.
   190  					if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version {
   191  						vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv))
   192  					}
   193  				} else {
   194  					vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt")
   195  				}
   196  			}
   197  		}
   198  	}
   199  
   200  	describe := func(m module.Version) string {
   201  		if m.Version == "" {
   202  			return m.Path
   203  		}
   204  		return m.Path + "@" + m.Version
   205  	}
   206  
   207  	// We need to verify *all* replacements that occur in modfile: even if they
   208  	// don't directly apply to any module in the vendor list, the replacement
   209  	// go.mod file can affect the selected versions of other (transitive)
   210  	// dependencies
   211  	seenrep := make(map[module.Version]bool)
   212  	checkReplace := func(replaces []*modfile.Replace) {
   213  		for _, r := range replaces {
   214  			if seenrep[r.Old] {
   215  				continue // Don't print the same error more than once
   216  			}
   217  			seenrep[r.Old] = true
   218  			rNew, modRoot, replacementSource := replacementFrom(r.Old)
   219  			rNewCanonical := canonicalizeReplacePath(rNew, modRoot)
   220  			vr := vendorMeta[r.Old].Replacement
   221  			if vr == (module.Version{}) {
   222  				if rNewCanonical == (module.Version{}) {
   223  					// r.Old is not actually replaced. It might be a main module.
   224  					// Don't return an error.
   225  				} else if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) {
   226  					// Before 1.14, modules.txt omitted wildcard replacements and
   227  					// replacements for modules that did not have any packages to vendor.
   228  				} else {
   229  					vendErrorf(r.Old, "is replaced in %s, but not marked as replaced in vendor/modules.txt", base.ShortPath(replacementSource))
   230  				}
   231  			} else if vr != rNewCanonical {
   232  				vendErrorf(r.Old, "is replaced by %s in %s, but marked as replaced by %s in vendor/modules.txt", describe(rNew), base.ShortPath(replacementSource), describe(vr))
   233  			}
   234  		}
   235  	}
   236  	for _, modFile := range modFiles {
   237  		checkReplace(modFile.Replace)
   238  	}
   239  	if MainModules.workFile != nil {
   240  		checkReplace(MainModules.workFile.Replace)
   241  	}
   242  
   243  	for _, mod := range vendorList {
   244  		meta := vendorMeta[mod]
   245  		if meta.Explicit {
   246  			// in workspace mode, check that it's required by at least one of the main modules
   247  			var foundRequire bool
   248  			for _, index := range indexes {
   249  				if _, inGoMod := index.require[mod]; inGoMod {
   250  					foundRequire = true
   251  				}
   252  			}
   253  			if !foundRequire {
   254  				article := ""
   255  				if inWorkspaceMode() {
   256  					article = "a "
   257  				}
   258  				vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in %vgo.mod", article)
   259  			}
   260  
   261  		}
   262  	}
   263  
   264  	for _, mod := range vendorReplaced {
   265  		r := Replacement(mod)
   266  		replacementSource := "go.mod"
   267  		if inWorkspaceMode() {
   268  			replacementSource = "the workspace"
   269  		}
   270  		if r == (module.Version{}) {
   271  			vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in %s", replacementSource)
   272  			continue
   273  		}
   274  		// If both replacements exist, we've already reported that they're different above.
   275  	}
   276  
   277  	if vendErrors.Len() > 0 {
   278  		subcmd := "mod"
   279  		if inWorkspaceMode() {
   280  			subcmd = "work"
   281  		}
   282  		base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo %s vendor", filepath.Dir(VendorDir()), vendErrors, subcmd)
   283  	}
   284  }
   285  

View as plain text