Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/modload

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

View as plain text