]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go/internal/modload: factor smaller files out of load.go and init.go
authorBryan C. Mills <bcmills@google.com>
Fri, 6 Mar 2020 22:18:16 +0000 (17:18 -0500)
committerBryan C. Mills <bcmills@google.com>
Mon, 9 Mar 2020 19:17:27 +0000 (19:17 +0000)
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>
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/modfile.go [new file with mode: 0644]
src/cmd/go/internal/modload/mvs.go [new file with mode: 0644]
src/cmd/go/internal/modload/vendor.go [new file with mode: 0644]

index 61cbdf2c543264f3f5b37bb61df709bcf104cfa3..8b57d8005b2659766670b77dcbc0cf36cc1aa2e6 100644 (file)
@@ -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
-}
index 6ea7d8c69b376cf25defd5eab39a2f127c683f02..21601cb13e61b3c6cd48d2ec4aa74905d9e1a731 100644 (file)
@@ -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 (file)
index 0000000..9f4ec5a
--- /dev/null
@@ -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 (file)
index 0000000..50620e3
--- /dev/null
@@ -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 (file)
index 0000000..71f68ef
--- /dev/null
@@ -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)
+       }
+}