summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/modload/vendor.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/modload/vendor.go')
-rw-r--r--src/cmd/go/internal/modload/vendor.go284
1 files changed, 284 insertions, 0 deletions
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
new file mode 100644
index 0000000..b2cb441
--- /dev/null
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -0,0 +1,284 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modload
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/gover"
+
+ "golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
+)
+
+var (
+ vendorOnce sync.Once
+ vendorList []module.Version // modules that contribute packages to the build, in order of appearance
+ vendorReplaced []module.Version // all replaced modules; may or may not also contribute packages
+ vendorVersion map[string]string // module path → selected version (if known)
+ vendorPkgModule map[string]module.Version // package → containing module
+ vendorMeta map[module.Version]vendorMetadata
+)
+
+type vendorMetadata struct {
+ Explicit bool
+ Replacement module.Version
+ GoVersion string
+}
+
+// readVendorList reads the list of vendored modules from vendor/modules.txt.
+func readVendorList(vendorDir string) {
+ vendorOnce.Do(func() {
+ vendorList = nil
+ vendorPkgModule = make(map[string]module.Version)
+ vendorVersion = make(map[string]string)
+ vendorMeta = make(map[module.Version]vendorMetadata)
+ vendorFile := filepath.Join(vendorDir, "modules.txt")
+ data, err := os.ReadFile(vendorFile)
+ if err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ base.Fatalf("go: %s", err)
+ }
+ return
+ }
+
+ var mod module.Version
+ for _, line := range strings.Split(string(data), "\n") {
+ if strings.HasPrefix(line, "# ") {
+ f := strings.Fields(line)
+
+ if len(f) < 3 {
+ continue
+ }
+ if semver.IsValid(f[2]) {
+ // A module, but we don't yet know whether it is in the build list or
+ // only included to indicate a replacement.
+ mod = module.Version{Path: f[1], Version: f[2]}
+ f = f[3:]
+ } else if f[2] == "=>" {
+ // A wildcard replacement found in the main module's go.mod file.
+ mod = module.Version{Path: f[1]}
+ f = f[2:]
+ } else {
+ // Not a version or a wildcard replacement.
+ // We don't know how to interpret this module line, so ignore it.
+ mod = module.Version{}
+ continue
+ }
+
+ if len(f) >= 2 && f[0] == "=>" {
+ meta := vendorMeta[mod]
+ if len(f) == 2 {
+ // File replacement.
+ meta.Replacement = module.Version{Path: f[1]}
+ vendorReplaced = append(vendorReplaced, mod)
+ } else if len(f) == 3 && semver.IsValid(f[2]) {
+ // Path and version replacement.
+ meta.Replacement = module.Version{Path: f[1], Version: f[2]}
+ vendorReplaced = append(vendorReplaced, mod)
+ } else {
+ // We don't understand this replacement. Ignore it.
+ }
+ vendorMeta[mod] = meta
+ }
+ continue
+ }
+
+ // Not a module line. Must be a package within a module or a metadata
+ // directive, either of which requires a preceding module line.
+ if mod.Path == "" {
+ continue
+ }
+
+ if annotations, ok := strings.CutPrefix(line, "## "); ok {
+ // Metadata. Take the union of annotations across multiple lines, if present.
+ meta := vendorMeta[mod]
+ for _, entry := range strings.Split(annotations, ";") {
+ entry = strings.TrimSpace(entry)
+ if entry == "explicit" {
+ meta.Explicit = true
+ }
+ if goVersion, ok := strings.CutPrefix(entry, "go "); ok {
+ meta.GoVersion = goVersion
+ rawGoVersion.Store(mod, meta.GoVersion)
+ if gover.Compare(goVersion, gover.Local()) > 0 {
+ base.Fatal(&gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion})
+ }
+ }
+ // All other tokens are reserved for future use.
+ }
+ vendorMeta[mod] = meta
+ continue
+ }
+
+ if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil {
+ // A package within the current module.
+ vendorPkgModule[f[0]] = mod
+
+ // Since this module provides a package for the build, we know that it
+ // is in the build list and is the selected version of its path.
+ // If this information is new, record it.
+ if v, ok := vendorVersion[mod.Path]; !ok || gover.ModCompare(mod.Path, v, mod.Version) < 0 {
+ vendorList = append(vendorList, mod)
+ vendorVersion[mod.Path] = mod.Version
+ }
+ }
+ }
+ })
+}
+
+// checkVendorConsistency verifies that the vendor/modules.txt file matches (if
+// go 1.14) or at least does not contradict (go 1.13 or earlier) the
+// requirements and replacements listed in the main module's go.mod file.
+func checkVendorConsistency(indexes []*modFileIndex, modFiles []*modfile.File, modRoots []string) {
+ // readVendorList only needs the main module to get the directory
+ // the vendor directory is in.
+ readVendorList(VendorDir())
+
+ if len(modFiles) < 1 {
+ // We should never get here if there are zero modfiles. Either
+ // we're in single module mode and there's a single module, or
+ // we're in workspace mode, and we fail earlier reporting that
+ // "no modules were found in the current workspace".
+ panic("checkVendorConsistency called with zero modfiles")
+ }
+
+ pre114 := false
+ if !inWorkspaceMode() { // workspace mode was added after Go 1.14
+ if len(indexes) != 1 {
+ panic(fmt.Errorf("not in workspace mode but number of indexes is %v, not 1", len(indexes)))
+ }
+ index := indexes[0]
+ if gover.Compare(index.goVersion, "1.14") < 0 {
+ // Go versions before 1.14 did not include enough information in
+ // vendor/modules.txt to check for consistency.
+ // If we know that we're on an earlier version, relax the consistency check.
+ pre114 = true
+ }
+ }
+
+ vendErrors := new(strings.Builder)
+ vendErrorf := func(mod module.Version, format string, args ...any) {
+ detail := fmt.Sprintf(format, args...)
+ if mod.Version == "" {
+ fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail)
+ } else {
+ fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail)
+ }
+ }
+
+ // Iterate over the Require directives in their original (not indexed) order
+ // so that the errors match the original file.
+ for _, modFile := range modFiles {
+ for _, r := range modFile.Require {
+ if !vendorMeta[r.Mod].Explicit {
+ if pre114 {
+ // Before 1.14, modules.txt did not indicate whether modules were listed
+ // explicitly in the main module's go.mod file.
+ // However, we can at least detect a version mismatch if packages were
+ // vendored from a non-matching version.
+ if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version {
+ vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv))
+ }
+ } else {
+ vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt")
+ }
+ }
+ }
+ }
+
+ describe := func(m module.Version) string {
+ if m.Version == "" {
+ return m.Path
+ }
+ return m.Path + "@" + m.Version
+ }
+
+ // We need to verify *all* replacements that occur in modfile: even if they
+ // don't directly apply to any module in the vendor list, the replacement
+ // go.mod file can affect the selected versions of other (transitive)
+ // dependencies
+ seenrep := make(map[module.Version]bool)
+ checkReplace := func(replaces []*modfile.Replace) {
+ for _, r := range replaces {
+ if seenrep[r.Old] {
+ continue // Don't print the same error more than once
+ }
+ seenrep[r.Old] = true
+ rNew, modRoot, replacementSource := replacementFrom(r.Old)
+ rNewCanonical := canonicalizeReplacePath(rNew, modRoot)
+ vr := vendorMeta[r.Old].Replacement
+ if vr == (module.Version{}) {
+ if rNewCanonical == (module.Version{}) {
+ // r.Old is not actually replaced. It might be a main module.
+ // Don't return an error.
+ } else if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) {
+ // Before 1.14, modules.txt omitted wildcard replacements and
+ // replacements for modules that did not have any packages to vendor.
+ } else {
+ vendErrorf(r.Old, "is replaced in %s, but not marked as replaced in vendor/modules.txt", base.ShortPath(replacementSource))
+ }
+ } else if vr != rNewCanonical {
+ 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))
+ }
+ }
+ }
+ for _, modFile := range modFiles {
+ checkReplace(modFile.Replace)
+ }
+ if MainModules.workFile != nil {
+ checkReplace(MainModules.workFile.Replace)
+ }
+
+ for _, mod := range vendorList {
+ meta := vendorMeta[mod]
+ if meta.Explicit {
+ // in workspace mode, check that it's required by at least one of the main modules
+ var foundRequire bool
+ for _, index := range indexes {
+ if _, inGoMod := index.require[mod]; inGoMod {
+ foundRequire = true
+ }
+ }
+ if !foundRequire {
+ article := ""
+ if inWorkspaceMode() {
+ article = "a "
+ }
+ vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in %vgo.mod", article)
+ }
+
+ }
+ }
+
+ for _, mod := range vendorReplaced {
+ r := Replacement(mod)
+ replacementSource := "go.mod"
+ if inWorkspaceMode() {
+ replacementSource = "the workspace"
+ }
+ if r == (module.Version{}) {
+ vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in %s", replacementSource)
+ continue
+ }
+ // If both replacements exist, we've already reported that they're different above.
+ }
+
+ if vendErrors.Len() > 0 {
+ subcmd := "mod"
+ if inWorkspaceMode() {
+ subcmd = "work"
+ }
+ 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)
+ }
+}