aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2020-03-06 17:18:16 -0500
committerBryan C. Mills <bcmills@google.com>2020-03-09 19:17:27 +0000
commit2818a11b1824ca52803a3a335354970fbccfcf2e (patch)
treeb75a94adfd714040e5e0ab3e109bb21f58a9706d
parentb559a173f9f187e1185f8de00a9cc1f5b05aceef (diff)
downloadgo-2818a11b1824ca52803a3a335354970fbccfcf2e.tar.gz
go-2818a11b1824ca52803a3a335354970fbccfcf2e.zip
cmd/go/internal/modload: factor smaller files out of load.go and init.go
No semantic changes intended: just structural cleanup. Updates #36460 Change-Id: I405bc2572d3ff00f595dae645e673a11e01621ca Reviewed-on: https://go-review.googlesource.com/c/go/+/222340 Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Jay Conrod <jayconrod@google.com>
-rw-r--r--src/cmd/go/internal/modload/init.go226
-rw-r--r--src/cmd/go/internal/modload/load.go355
-rw-r--r--src/cmd/go/internal/modload/modfile.go164
-rw-r--r--src/cmd/go/internal/modload/mvs.go253
-rw-r--r--src/cmd/go/internal/modload/vendor.go217
5 files changed, 634 insertions, 581 deletions
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 61cbdf2c54..8b57d8005b 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -59,27 +59,6 @@ var (
allowMissingModuleImports bool
)
-var modFile *modfile.File
-
-// A modFileIndex is an index of data corresponding to a modFile
-// at a specific point in time.
-type modFileIndex struct {
- data []byte
- dataNeedsFix bool // true if fixVersion applied a change while parsing data
- module module.Version
- goVersion string
- require map[module.Version]requireMeta
- replace map[module.Version]module.Version
- exclude map[module.Version]bool
-}
-
-// index is the index of the go.mod file as of when it was last read or written.
-var index *modFileIndex
-
-type requireMeta struct {
- indirect bool
-}
-
// ModFile returns the parsed go.mod file.
//
// Note that after calling ImportPaths or LoadBuildList,
@@ -555,101 +534,6 @@ func setDefaultBuildMod() {
}
}
-// 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 modFile.Go == nil || semver.Compare("v"+modFile.Go.Version, "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)
- }
- }
-
- 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\nrun 'go mod vendor' to sync, or use -mod=mod or -mod=readonly to ignore the vendor directory", modRoot, vendErrors)
- }
-}
-
-// Allowed reports whether module m is allowed (not excluded) by the main module's go.mod.
-func Allowed(m module.Version) bool {
- return index == nil || !index.exclude[m]
-}
-
func legacyModInit() {
if modFile == nil {
path, err := findModulePath(modRoot)
@@ -983,113 +867,3 @@ func WriteGoMod() {
base.Fatalf("go: updating go.mod: %v", err)
}
}
-
-// indexModFile rebuilds the index of modFile.
-// If modFile has been changed since it was first read,
-// modFile.Cleanup must be called before indexModFile.
-func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
- i := new(modFileIndex)
- i.data = data
- i.dataNeedsFix = needsFix
-
- i.module = module.Version{}
- if modFile.Module != nil {
- i.module = modFile.Module.Mod
- }
-
- i.goVersion = ""
- if modFile.Go != nil {
- i.goVersion = modFile.Go.Version
- }
-
- i.require = make(map[module.Version]requireMeta, len(modFile.Require))
- for _, r := range modFile.Require {
- i.require[r.Mod] = requireMeta{indirect: r.Indirect}
- }
-
- i.replace = make(map[module.Version]module.Version, len(modFile.Replace))
- for _, r := range modFile.Replace {
- if prev, dup := i.replace[r.Old]; dup && prev != r.New {
- base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
- }
- i.replace[r.Old] = r.New
- }
-
- i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
- for _, x := range modFile.Exclude {
- i.exclude[x.Mod] = true
- }
-
- return i
-}
-
-// modFileIsDirty reports whether the go.mod file differs meaningfully
-// from what was indexed.
-// If modFile has been changed (even cosmetically) since it was first read,
-// modFile.Cleanup must be called before modFileIsDirty.
-func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
- if i == nil {
- return modFile != nil
- }
-
- if i.dataNeedsFix {
- return true
- }
-
- if modFile.Module == nil {
- if i.module != (module.Version{}) {
- return true
- }
- } else if modFile.Module.Mod != i.module {
- return true
- }
-
- if modFile.Go == nil {
- if i.goVersion != "" {
- return true
- }
- } else if modFile.Go.Version != i.goVersion {
- if i.goVersion == "" && cfg.BuildMod == "readonly" {
- // go.mod files did not always require a 'go' version, so do not error out
- // if one is missing — we may be inside an older module in the module
- // cache, and should bias toward providing useful behavior.
- } else {
- return true
- }
- }
-
- if len(modFile.Require) != len(i.require) ||
- len(modFile.Replace) != len(i.replace) ||
- len(modFile.Exclude) != len(i.exclude) {
- return true
- }
-
- for _, r := range modFile.Require {
- if meta, ok := i.require[r.Mod]; !ok {
- return true
- } else if r.Indirect != meta.indirect {
- if cfg.BuildMod == "readonly" {
- // The module's requirements are consistent; only the "// indirect"
- // comments that are wrong. But those are only guaranteed to be accurate
- // after a "go mod tidy" — it's a good idea to run those before
- // committing a change, but it's certainly not mandatory.
- } else {
- return true
- }
- }
- }
-
- for _, r := range modFile.Replace {
- if r.New != i.replace[r.Old] {
- return true
- }
- }
-
- for _, x := range modFile.Exclude {
- if !i.exclude[x.Mod] {
- return true
- }
- }
-
- return false
-}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 6ea7d8c69b..21601cb13e 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -9,14 +9,12 @@ import (
"errors"
"fmt"
"go/build"
- "io/ioutil"
"os"
"path"
pathpkg "path"
"path/filepath"
"sort"
"strings"
- "sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -27,9 +25,7 @@ import (
"cmd/go/internal/search"
"cmd/go/internal/str"
- "golang.org/x/mod/modfile"
"golang.org/x/mod/module"
- "golang.org/x/mod/semver"
)
// buildList is the list of modules to use for building packages.
@@ -1033,354 +1029,3 @@ func WhyDepth(path string) int {
}
return n
}
-
-// Replacement returns the replacement for mod, if any, from go.mod.
-// If there is no replacement for mod, Replacement returns
-// a module.Version with Path == "".
-func Replacement(mod module.Version) module.Version {
- if index != nil {
- if r, ok := index.replace[mod]; ok {
- return r
- }
- if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
- return r
- }
- }
- return module.Version{}
-}
-
-// mvsReqs implements mvs.Reqs for module semantic versions,
-// with any exclusions or replacements applied internally.
-type mvsReqs struct {
- buildList []module.Version
- cache par.Cache
- versions sync.Map
-}
-
-// Reqs returns the current module requirement graph.
-// Future calls to SetBuildList do not affect the operation
-// of the returned Reqs.
-func Reqs() mvs.Reqs {
- r := &mvsReqs{
- buildList: buildList,
- }
- return r
-}
-
-func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
- type cached struct {
- list []module.Version
- err error
- }
-
- c := r.cache.Do(mod, func() interface{} {
- list, err := r.required(mod)
- if err != nil {
- return cached{nil, err}
- }
- for i, mv := range list {
- if index != nil {
- for index.exclude[mv] {
- mv1, err := r.next(mv)
- if err != nil {
- return cached{nil, err}
- }
- if mv1.Version == "none" {
- return cached{nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)}
- }
- mv = mv1
- }
- }
- list[i] = mv
- }
-
- return cached{list, nil}
- }).(cached)
-
- return c.list, c.err
-}
-
-var vendorOnce sync.Once
-
-type vendorMetadata struct {
- Explicit bool
- Replacement module.Version
-}
-
-var (
- 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
-)
-
-// 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 := ioutil.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
- if err != nil {
- if !errors.Is(err, os.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
- }
- }
- }
- })
-}
-
-func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version {
- list := make([]module.Version, 0, len(f.Require))
- for _, r := range f.Require {
- list = append(list, r.Mod)
- }
- return list
-}
-
-// required returns a unique copy of the requirements of mod.
-func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
- if mod == Target {
- if modFile != nil && modFile.Go != nil {
- r.versions.LoadOrStore(mod, modFile.Go.Version)
- }
- return append([]module.Version(nil), r.buildList[1:]...), nil
- }
-
- if cfg.BuildMod == "vendor" {
- // For every module other than the target,
- // return the full list of modules from modules.txt.
- readVendorList()
- return append([]module.Version(nil), vendorList...), nil
- }
-
- origPath := mod.Path
- if repl := Replacement(mod); repl.Path != "" {
- if repl.Version == "" {
- // TODO: need to slip the new version into the tags list etc.
- dir := repl.Path
- if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
- }
- gomod := filepath.Join(dir, "go.mod")
- data, err := ioutil.ReadFile(gomod)
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
- }
- f, err := modfile.ParseLax(gomod, data, nil)
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
- }
- if f.Go != nil {
- r.versions.LoadOrStore(mod, f.Go.Version)
- }
- return r.modFileToList(f), nil
- }
- mod = repl
- }
-
- if mod.Version == "none" {
- return nil, nil
- }
-
- if !semver.IsValid(mod.Version) {
- // Disallow the broader queries supported by fetch.Lookup.
- base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", mod.Path, mod.Version)
- }
-
- data, err := modfetch.GoMod(mod.Path, mod.Version)
- if err != nil {
- return nil, err
- }
- f, err := modfile.ParseLax("go.mod", data, nil)
- if err != nil {
- return nil, module.VersionError(mod, fmt.Errorf("parsing go.mod: %v", err))
- }
-
- if f.Module == nil {
- return nil, module.VersionError(mod, errors.New("parsing go.mod: missing module line"))
- }
- if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
- return nil, module.VersionError(mod, fmt.Errorf(`parsing go.mod:
- module declares its path as: %s
- but was required as: %s`, mpath, mod.Path))
- }
- if f.Go != nil {
- r.versions.LoadOrStore(mod, f.Go.Version)
- }
-
- return r.modFileToList(f), nil
-}
-
-func (*mvsReqs) Max(v1, v2 string) string {
- if v1 != "" && semver.Compare(v1, v2) == -1 {
- return v2
- }
- return v1
-}
-
-// Upgrade is a no-op, here to implement mvs.Reqs.
-// The upgrade logic for go get -u is in ../modget/get.go.
-func (*mvsReqs) Upgrade(m module.Version) (module.Version, error) {
- return m, nil
-}
-
-func versions(path string) ([]string, error) {
- // Note: modfetch.Lookup and repo.Versions are cached,
- // so there's no need for us to add extra caching here.
- var versions []string
- err := modfetch.TryProxies(func(proxy string) error {
- repo, err := modfetch.Lookup(proxy, path)
- if err == nil {
- versions, err = repo.Versions("")
- }
- return err
- })
- return versions, err
-}
-
-// Previous returns the tagged version of m.Path immediately prior to
-// m.Version, or version "none" if no prior version is tagged.
-func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
- list, err := versions(m.Path)
- if err != nil {
- return module.Version{}, err
- }
- i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 })
- if i > 0 {
- return module.Version{Path: m.Path, Version: list[i-1]}, nil
- }
- return module.Version{Path: m.Path, Version: "none"}, nil
-}
-
-// next returns the next version of m.Path after m.Version.
-// It is only used by the exclusion processing in the Required method,
-// not called directly by MVS.
-func (*mvsReqs) next(m module.Version) (module.Version, error) {
- list, err := versions(m.Path)
- if err != nil {
- return module.Version{}, err
- }
- i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 })
- if i < len(list) {
- return module.Version{Path: m.Path, Version: list[i]}, nil
- }
- return module.Version{Path: m.Path, Version: "none"}, nil
-}
-
-// fetch downloads the given module (or its replacement)
-// and returns its location.
-//
-// The isLocal return value reports whether the replacement,
-// if any, is local to the filesystem.
-func fetch(mod module.Version) (dir string, isLocal bool, err error) {
- if mod == Target {
- return ModRoot(), true, nil
- }
- if r := Replacement(mod); r.Path != "" {
- if r.Version == "" {
- dir = r.Path
- if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
- }
- // Ensure that the replacement directory actually exists:
- // dirInModule does not report errors for missing modules,
- // so if we don't report the error now, later failures will be
- // very mysterious.
- if _, err := os.Stat(dir); err != nil {
- if os.IsNotExist(err) {
- // Semantically the module version itself “exists” — we just don't
- // have its source code. Remove the equivalence to os.ErrNotExist,
- // and make the message more concise while we're at it.
- err = fmt.Errorf("replacement directory %s does not exist", r.Path)
- } else {
- err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
- }
- return dir, true, module.VersionError(mod, err)
- }
- return dir, true, nil
- }
- mod = r
- }
-
- dir, err = modfetch.Download(mod)
- return dir, false, err
-}
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
new file mode 100644
index 0000000000..9f4ec5a49f
--- /dev/null
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -0,0 +1,164 @@
+// 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 (
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+
+ "golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
+)
+
+var modFile *modfile.File
+
+// A modFileIndex is an index of data corresponding to a modFile
+// at a specific point in time.
+type modFileIndex struct {
+ data []byte
+ dataNeedsFix bool // true if fixVersion applied a change while parsing data
+ module module.Version
+ goVersion string
+ require map[module.Version]requireMeta
+ replace map[module.Version]module.Version
+ exclude map[module.Version]bool
+}
+
+// index is the index of the go.mod file as of when it was last read or written.
+var index *modFileIndex
+
+type requireMeta struct {
+ indirect bool
+}
+
+// Allowed reports whether module m is allowed (not excluded) by the main module's go.mod.
+func Allowed(m module.Version) bool {
+ return index == nil || !index.exclude[m]
+}
+
+// Replacement returns the replacement for mod, if any, from go.mod.
+// If there is no replacement for mod, Replacement returns
+// a module.Version with Path == "".
+func Replacement(mod module.Version) module.Version {
+ if index != nil {
+ if r, ok := index.replace[mod]; ok {
+ return r
+ }
+ if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
+ return r
+ }
+ }
+ return module.Version{}
+}
+
+// indexModFile rebuilds the index of modFile.
+// If modFile has been changed since it was first read,
+// modFile.Cleanup must be called before indexModFile.
+func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
+ i := new(modFileIndex)
+ i.data = data
+ i.dataNeedsFix = needsFix
+
+ i.module = module.Version{}
+ if modFile.Module != nil {
+ i.module = modFile.Module.Mod
+ }
+
+ i.goVersion = ""
+ if modFile.Go != nil {
+ i.goVersion = modFile.Go.Version
+ }
+
+ i.require = make(map[module.Version]requireMeta, len(modFile.Require))
+ for _, r := range modFile.Require {
+ i.require[r.Mod] = requireMeta{indirect: r.Indirect}
+ }
+
+ i.replace = make(map[module.Version]module.Version, len(modFile.Replace))
+ for _, r := range modFile.Replace {
+ if prev, dup := i.replace[r.Old]; dup && prev != r.New {
+ base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
+ }
+ i.replace[r.Old] = r.New
+ }
+
+ i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
+ for _, x := range modFile.Exclude {
+ i.exclude[x.Mod] = true
+ }
+
+ return i
+}
+
+// modFileIsDirty reports whether the go.mod file differs meaningfully
+// from what was indexed.
+// If modFile has been changed (even cosmetically) since it was first read,
+// modFile.Cleanup must be called before modFileIsDirty.
+func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
+ if i == nil {
+ return modFile != nil
+ }
+
+ if i.dataNeedsFix {
+ return true
+ }
+
+ if modFile.Module == nil {
+ if i.module != (module.Version{}) {
+ return true
+ }
+ } else if modFile.Module.Mod != i.module {
+ return true
+ }
+
+ if modFile.Go == nil {
+ if i.goVersion != "" {
+ return true
+ }
+ } else if modFile.Go.Version != i.goVersion {
+ if i.goVersion == "" && cfg.BuildMod == "readonly" {
+ // go.mod files did not always require a 'go' version, so do not error out
+ // if one is missing — we may be inside an older module in the module
+ // cache, and should bias toward providing useful behavior.
+ } else {
+ return true
+ }
+ }
+
+ if len(modFile.Require) != len(i.require) ||
+ len(modFile.Replace) != len(i.replace) ||
+ len(modFile.Exclude) != len(i.exclude) {
+ return true
+ }
+
+ for _, r := range modFile.Require {
+ if meta, ok := i.require[r.Mod]; !ok {
+ return true
+ } else if r.Indirect != meta.indirect {
+ if cfg.BuildMod == "readonly" {
+ // The module's requirements are consistent; only the "// indirect"
+ // comments that are wrong. But those are only guaranteed to be accurate
+ // after a "go mod tidy" — it's a good idea to run those before
+ // committing a change, but it's certainly not mandatory.
+ } else {
+ return true
+ }
+ }
+ }
+
+ for _, r := range modFile.Replace {
+ if r.New != i.replace[r.Old] {
+ return true
+ }
+ }
+
+ for _, x := range modFile.Exclude {
+ if !i.exclude[x.Mod] {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go
new file mode 100644
index 0000000000..50620e30b6
--- /dev/null
+++ b/src/cmd/go/internal/modload/mvs.go
@@ -0,0 +1,253 @@
+// 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/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "sync"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/mvs"
+ "cmd/go/internal/par"
+
+ "golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/semver"
+)
+
+// mvsReqs implements mvs.Reqs for module semantic versions,
+// with any exclusions or replacements applied internally.
+type mvsReqs struct {
+ buildList []module.Version
+ cache par.Cache
+ versions sync.Map
+}
+
+// Reqs returns the current module requirement graph.
+// Future calls to SetBuildList do not affect the operation
+// of the returned Reqs.
+func Reqs() mvs.Reqs {
+ r := &mvsReqs{
+ buildList: buildList,
+ }
+ return r
+}
+
+func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
+ type cached struct {
+ list []module.Version
+ err error
+ }
+
+ c := r.cache.Do(mod, func() interface{} {
+ list, err := r.required(mod)
+ if err != nil {
+ return cached{nil, err}
+ }
+ for i, mv := range list {
+ if index != nil {
+ for index.exclude[mv] {
+ mv1, err := r.next(mv)
+ if err != nil {
+ return cached{nil, err}
+ }
+ if mv1.Version == "none" {
+ return cached{nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)}
+ }
+ mv = mv1
+ }
+ }
+ list[i] = mv
+ }
+
+ return cached{list, nil}
+ }).(cached)
+
+ return c.list, c.err
+}
+
+func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version {
+ list := make([]module.Version, 0, len(f.Require))
+ for _, r := range f.Require {
+ list = append(list, r.Mod)
+ }
+ return list
+}
+
+// required returns a unique copy of the requirements of mod.
+func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
+ if mod == Target {
+ if modFile != nil && modFile.Go != nil {
+ r.versions.LoadOrStore(mod, modFile.Go.Version)
+ }
+ return append([]module.Version(nil), r.buildList[1:]...), nil
+ }
+
+ if cfg.BuildMod == "vendor" {
+ // For every module other than the target,
+ // return the full list of modules from modules.txt.
+ readVendorList()
+ return append([]module.Version(nil), vendorList...), nil
+ }
+
+ origPath := mod.Path
+ if repl := Replacement(mod); repl.Path != "" {
+ if repl.Version == "" {
+ // TODO: need to slip the new version into the tags list etc.
+ dir := repl.Path
+ if !filepath.IsAbs(dir) {
+ dir = filepath.Join(ModRoot(), dir)
+ }
+ gomod := filepath.Join(dir, "go.mod")
+ data, err := ioutil.ReadFile(gomod)
+ if err != nil {
+ return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
+ }
+ f, err := modfile.ParseLax(gomod, data, nil)
+ if err != nil {
+ return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err)
+ }
+ if f.Go != nil {
+ r.versions.LoadOrStore(mod, f.Go.Version)
+ }
+ return r.modFileToList(f), nil
+ }
+ mod = repl
+ }
+
+ if mod.Version == "none" {
+ return nil, nil
+ }
+
+ if !semver.IsValid(mod.Version) {
+ // Disallow the broader queries supported by fetch.Lookup.
+ base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", mod.Path, mod.Version)
+ }
+
+ data, err := modfetch.GoMod(mod.Path, mod.Version)
+ if err != nil {
+ return nil, err
+ }
+ f, err := modfile.ParseLax("go.mod", data, nil)
+ if err != nil {
+ return nil, module.VersionError(mod, fmt.Errorf("parsing go.mod: %v", err))
+ }
+
+ if f.Module == nil {
+ return nil, module.VersionError(mod, errors.New("parsing go.mod: missing module line"))
+ }
+ if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
+ return nil, module.VersionError(mod, fmt.Errorf(`parsing go.mod:
+ module declares its path as: %s
+ but was required as: %s`, mpath, mod.Path))
+ }
+ if f.Go != nil {
+ r.versions.LoadOrStore(mod, f.Go.Version)
+ }
+
+ return r.modFileToList(f), nil
+}
+
+func (*mvsReqs) Max(v1, v2 string) string {
+ if v1 != "" && semver.Compare(v1, v2) == -1 {
+ return v2
+ }
+ return v1
+}
+
+// Upgrade is a no-op, here to implement mvs.Reqs.
+// The upgrade logic for go get -u is in ../modget/get.go.
+func (*mvsReqs) Upgrade(m module.Version) (module.Version, error) {
+ return m, nil
+}
+
+func versions(path string) ([]string, error) {
+ // Note: modfetch.Lookup and repo.Versions are cached,
+ // so there's no need for us to add extra caching here.
+ var versions []string
+ err := modfetch.TryProxies(func(proxy string) error {
+ repo, err := modfetch.Lookup(proxy, path)
+ if err == nil {
+ versions, err = repo.Versions("")
+ }
+ return err
+ })
+ return versions, err
+}
+
+// Previous returns the tagged version of m.Path immediately prior to
+// m.Version, or version "none" if no prior version is tagged.
+func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
+ list, err := versions(m.Path)
+ if err != nil {
+ return module.Version{}, err
+ }
+ i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 })
+ if i > 0 {
+ return module.Version{Path: m.Path, Version: list[i-1]}, nil
+ }
+ return module.Version{Path: m.Path, Version: "none"}, nil
+}
+
+// next returns the next version of m.Path after m.Version.
+// It is only used by the exclusion processing in the Required method,
+// not called directly by MVS.
+func (*mvsReqs) next(m module.Version) (module.Version, error) {
+ list, err := versions(m.Path)
+ if err != nil {
+ return module.Version{}, err
+ }
+ i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 })
+ if i < len(list) {
+ return module.Version{Path: m.Path, Version: list[i]}, nil
+ }
+ return module.Version{Path: m.Path, Version: "none"}, nil
+}
+
+// fetch downloads the given module (or its replacement)
+// and returns its location.
+//
+// The isLocal return value reports whether the replacement,
+// if any, is local to the filesystem.
+func fetch(mod module.Version) (dir string, isLocal bool, err error) {
+ if mod == Target {
+ return ModRoot(), true, nil
+ }
+ if r := Replacement(mod); r.Path != "" {
+ if r.Version == "" {
+ dir = r.Path
+ if !filepath.IsAbs(dir) {
+ dir = filepath.Join(ModRoot(), dir)
+ }
+ // Ensure that the replacement directory actually exists:
+ // dirInModule does not report errors for missing modules,
+ // so if we don't report the error now, later failures will be
+ // very mysterious.
+ if _, err := os.Stat(dir); err != nil {
+ if os.IsNotExist(err) {
+ // Semantically the module version itself “exists” — we just don't
+ // have its source code. Remove the equivalence to os.ErrNotExist,
+ // and make the message more concise while we're at it.
+ err = fmt.Errorf("replacement directory %s does not exist", r.Path)
+ } else {
+ err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
+ }
+ return dir, true, module.VersionError(mod, err)
+ }
+ return dir, true, nil
+ }
+ mod = r
+ }
+
+ dir, err = modfetch.Download(mod)
+ return dir, false, err
+}
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
new file mode 100644
index 0000000000..71f68efbcc
--- /dev/null
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -0,0 +1,217 @@
+// 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/ioutil"
+ "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 := ioutil.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
+ if err != nil {
+ if !errors.Is(err, os.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 modFile.Go == nil || semver.Compare("v"+modFile.Go.Version, "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)
+ }
+ }
+
+ 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\nrun 'go mod vendor' to sync, or use -mod=mod or -mod=readonly to ignore the vendor directory", modRoot, vendErrors)
+ }
+}