diff options
author | Bryan C. Mills <bcmills@google.com> | 2020-03-06 17:18:16 -0500 |
---|---|---|
committer | Bryan C. Mills <bcmills@google.com> | 2020-03-09 19:17:27 +0000 |
commit | 2818a11b1824ca52803a3a335354970fbccfcf2e (patch) | |
tree | b75a94adfd714040e5e0ab3e109bb21f58a9706d | |
parent | b559a173f9f187e1185f8de00a9cc1f5b05aceef (diff) | |
download | go-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.go | 226 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/load.go | 355 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/modfile.go | 164 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/mvs.go | 253 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/vendor.go | 217 |
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) + } +} |