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.go219
1 files changed, 219 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..d8fd91f
--- /dev/null
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -0,0 +1,219 @@
+// 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"
+
+ "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
+}
+
+// readVendorList reads the list of vendored modules from vendor/modules.txt.
+func readVendorList() {
+ vendorOnce.Do(func() {
+ vendorList = nil
+ vendorPkgModule = make(map[string]module.Version)
+ vendorVersion = make(map[string]string)
+ vendorMeta = make(map[module.Version]vendorMetadata)
+ data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
+ 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 strings.HasPrefix(line, "## ") {
+ // Metadata. Take the union of annotations across multiple lines, if present.
+ meta := vendorMeta[mod]
+ for _, entry := range strings.Split(strings.TrimPrefix(line, "## "), ";") {
+ entry = strings.TrimSpace(entry)
+ if entry == "explicit" {
+ meta.Explicit = true
+ }
+ // 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 || semver.Compare(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() {
+ readVendorList()
+
+ pre114 := false
+ if semver.Compare(index.goVersionV, "v1.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 ...interface{}) {
+ 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 _, 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
+ for _, r := range modFile.Replace {
+ vr := vendorMeta[r.Old].Replacement
+ if vr == (module.Version{}) {
+ 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 go.mod, but not marked as replaced in vendor/modules.txt")
+ }
+ } else if vr != r.New {
+ 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))
+ }
+ }
+
+ for _, mod := range vendorList {
+ meta := vendorMeta[mod]
+ if meta.Explicit {
+ if _, inGoMod := index.require[mod]; !inGoMod {
+ vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in go.mod")
+ }
+ }
+ }
+
+ for _, mod := range vendorReplaced {
+ r := Replacement(mod)
+ if r == (module.Version{}) {
+ vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
+ continue
+ }
+ if meta := vendorMeta[mod]; r != meta.Replacement {
+ vendErrorf(mod, "is marked as replaced by %s in vendor/modules.txt, but replaced by %s in go.mod", describe(meta.Replacement), describe(r))
+ }
+ }
+
+ if vendErrors.Len() > 0 {
+ 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)
+ }
+}