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  
    18  	"golang.org/x/mod/modfile"
    19  	"golang.org/x/mod/module"
    20  	"golang.org/x/mod/semver"
    21  )
    22  
    23  var (
    24  	vendorOnce      sync.Once
    25  	vendorList      []module.Version          // modules that contribute packages to the build, in order of appearance
    26  	vendorReplaced  []module.Version          // all replaced modules; may or may not also contribute packages
    27  	vendorVersion   map[string]string         // module path → selected version (if known)
    28  	vendorPkgModule map[string]module.Version // package → containing module
    29  	vendorMeta      map[module.Version]vendorMetadata
    30  )
    31  
    32  type vendorMetadata struct {
    33  	Explicit    bool
    34  	Replacement module.Version
    35  	GoVersion   string
    36  }
    37  
    38  // readVendorList reads the list of vendored modules from vendor/modules.txt.
    39  func readVendorList(mainModule module.Version) {
    40  	vendorOnce.Do(func() {
    41  		vendorList = nil
    42  		vendorPkgModule = make(map[string]module.Version)
    43  		vendorVersion = make(map[string]string)
    44  		vendorMeta = make(map[module.Version]vendorMetadata)
    45  		data, err := os.ReadFile(filepath.Join(MainModules.ModRoot(mainModule), "vendor/modules.txt"))
    46  		if err != nil {
    47  			if !errors.Is(err, fs.ErrNotExist) {
    48  				base.Fatalf("go: %s", err)
    49  			}
    50  			return
    51  		}
    52  
    53  		var mod module.Version
    54  		for _, line := range strings.Split(string(data), "\n") {
    55  			if strings.HasPrefix(line, "# ") {
    56  				f := strings.Fields(line)
    57  
    58  				if len(f) < 3 {
    59  					continue
    60  				}
    61  				if semver.IsValid(f[2]) {
    62  					// A module, but we don't yet know whether it is in the build list or
    63  					// only included to indicate a replacement.
    64  					mod = module.Version{Path: f[1], Version: f[2]}
    65  					f = f[3:]
    66  				} else if f[2] == "=>" {
    67  					// A wildcard replacement found in the main module's go.mod file.
    68  					mod = module.Version{Path: f[1]}
    69  					f = f[2:]
    70  				} else {
    71  					// Not a version or a wildcard replacement.
    72  					// We don't know how to interpret this module line, so ignore it.
    73  					mod = module.Version{}
    74  					continue
    75  				}
    76  
    77  				if len(f) >= 2 && f[0] == "=>" {
    78  					meta := vendorMeta[mod]
    79  					if len(f) == 2 {
    80  						// File replacement.
    81  						meta.Replacement = module.Version{Path: f[1]}
    82  						vendorReplaced = append(vendorReplaced, mod)
    83  					} else if len(f) == 3 && semver.IsValid(f[2]) {
    84  						// Path and version replacement.
    85  						meta.Replacement = module.Version{Path: f[1], Version: f[2]}
    86  						vendorReplaced = append(vendorReplaced, mod)
    87  					} else {
    88  						// We don't understand this replacement. Ignore it.
    89  					}
    90  					vendorMeta[mod] = meta
    91  				}
    92  				continue
    93  			}
    94  
    95  			// Not a module line. Must be a package within a module or a metadata
    96  			// directive, either of which requires a preceding module line.
    97  			if mod.Path == "" {
    98  				continue
    99  			}
   100  
   101  			if strings.HasPrefix(line, "## ") {
   102  				// Metadata. Take the union of annotations across multiple lines, if present.
   103  				meta := vendorMeta[mod]
   104  				for _, entry := range strings.Split(strings.TrimPrefix(line, "## "), ";") {
   105  					entry = strings.TrimSpace(entry)
   106  					if entry == "explicit" {
   107  						meta.Explicit = true
   108  					}
   109  					if strings.HasPrefix(entry, "go ") {
   110  						meta.GoVersion = strings.TrimPrefix(entry, "go ")
   111  						rawGoVersion.Store(mod, meta.GoVersion)
   112  					}
   113  					// All other tokens are reserved for future use.
   114  				}
   115  				vendorMeta[mod] = meta
   116  				continue
   117  			}
   118  
   119  			if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil {
   120  				// A package within the current module.
   121  				vendorPkgModule[f[0]] = mod
   122  
   123  				// Since this module provides a package for the build, we know that it
   124  				// is in the build list and is the selected version of its path.
   125  				// If this information is new, record it.
   126  				if v, ok := vendorVersion[mod.Path]; !ok || semver.Compare(v, mod.Version) < 0 {
   127  					vendorList = append(vendorList, mod)
   128  					vendorVersion[mod.Path] = mod.Version
   129  				}
   130  			}
   131  		}
   132  	})
   133  }
   134  
   135  // checkVendorConsistency verifies that the vendor/modules.txt file matches (if
   136  // go 1.14) or at least does not contradict (go 1.13 or earlier) the
   137  // requirements and replacements listed in the main module's go.mod file.
   138  func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
   139  	readVendorList(MainModules.mustGetSingleMainModule())
   140  
   141  	pre114 := false
   142  	if semver.Compare(index.goVersionV, "v1.14") < 0 {
   143  		// Go versions before 1.14 did not include enough information in
   144  		// vendor/modules.txt to check for consistency.
   145  		// If we know that we're on an earlier version, relax the consistency check.
   146  		pre114 = true
   147  	}
   148  
   149  	vendErrors := new(strings.Builder)
   150  	vendErrorf := func(mod module.Version, format string, args ...any) {
   151  		detail := fmt.Sprintf(format, args...)
   152  		if mod.Version == "" {
   153  			fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail)
   154  		} else {
   155  			fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail)
   156  		}
   157  	}
   158  
   159  	// Iterate over the Require directives in their original (not indexed) order
   160  	// so that the errors match the original file.
   161  	for _, r := range modFile.Require {
   162  		if !vendorMeta[r.Mod].Explicit {
   163  			if pre114 {
   164  				// Before 1.14, modules.txt did not indicate whether modules were listed
   165  				// explicitly in the main module's go.mod file.
   166  				// However, we can at least detect a version mismatch if packages were
   167  				// vendored from a non-matching version.
   168  				if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version {
   169  					vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv))
   170  				}
   171  			} else {
   172  				vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt")
   173  			}
   174  		}
   175  	}
   176  
   177  	describe := func(m module.Version) string {
   178  		if m.Version == "" {
   179  			return m.Path
   180  		}
   181  		return m.Path + "@" + m.Version
   182  	}
   183  
   184  	// We need to verify *all* replacements that occur in modfile: even if they
   185  	// don't directly apply to any module in the vendor list, the replacement
   186  	// go.mod file can affect the selected versions of other (transitive)
   187  	// dependencies
   188  	for _, r := range modFile.Replace {
   189  		vr := vendorMeta[r.Old].Replacement
   190  		if vr == (module.Version{}) {
   191  			if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) {
   192  				// Before 1.14, modules.txt omitted wildcard replacements and
   193  				// replacements for modules that did not have any packages to vendor.
   194  			} else {
   195  				vendErrorf(r.Old, "is replaced in go.mod, but not marked as replaced in vendor/modules.txt")
   196  			}
   197  		} else if vr != r.New {
   198  			vendErrorf(r.Old, "is replaced by %s in go.mod, but marked as replaced by %s in vendor/modules.txt", describe(r.New), describe(vr))
   199  		}
   200  	}
   201  
   202  	for _, mod := range vendorList {
   203  		meta := vendorMeta[mod]
   204  		if meta.Explicit {
   205  			if _, inGoMod := index.require[mod]; !inGoMod {
   206  				vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod")
   207  			}
   208  		}
   209  	}
   210  
   211  	for _, mod := range vendorReplaced {
   212  		r := Replacement(mod)
   213  		if r == (module.Version{}) {
   214  			vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
   215  			continue
   216  		}
   217  		if meta := vendorMeta[mod]; r != meta.Replacement {
   218  			vendErrorf(mod, "is marked as replaced by %s in vendor/modules.txt, but replaced by %s in go.mod", describe(meta.Replacement), describe(r))
   219  		}
   220  	}
   221  
   222  	if vendErrors.Len() > 0 {
   223  		modRoot := MainModules.ModRoot(MainModules.mustGetSingleMainModule())
   224  		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 mod vendor", modRoot, vendErrors)
   225  	}
   226  }
   227  

View as plain text