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

View as plain text