mirror of
https://github.com/golang/go
synced 2024-11-11 19:21:37 -07:00
cmd/go: smooth out upgrade paths for lazy loading
This change adds two possible upgrade paths for lazy loading: 1. Run 'go mod tidy -go=1.17'. 2. Starting in a module with no existing 'go' directive, run any 'go' command that updates the go.mod file. In the latter case, commands other than 'go mod tidy' may leave the go.mod file *very* untidy if it had non-trivial dependencies. (The 'go' invocation will promote all implicit eager dependencies to explicit lazy ones, which preserves the original module graph — most of which is not actually relevant.) 'go mod tidy -go=1.17' can be used to enable lazy loading without accidentally downgrading existing transitive dependencies. 'go mod tidy -go=1.16' can be used to disable lazy loading and clear away redundant roots in a single step (if reducing the go version), or to prune away dependencies of tests-of-external-tests (if increasing the go version). 'go mod tidy -go=1.15' can be used to add dependencies of tests-of-external-tests, although there isn't much point to that. DO NOT MERGE This change still needs an explicit test and a release note. Fixes #45094 For #36460 Change-Id: I68f057e39489dfd6a667cd11dc1e320c1ee1aec1 Reviewed-on: https://go-review.googlesource.com/c/go/+/315210 Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
0e315ad79a
commit
7dedc237c5
@ -58,6 +58,17 @@ Do not send CLs removing the interior tags from such phrases.
|
||||
<!-- TODO(bcmills): replace the design-doc link with proper documentation. -->
|
||||
</p>
|
||||
|
||||
<p><!-- golang.org/issue/45094 --> To facilitate the upgrade to lazy loading,
|
||||
the <code>go</code> <code>mod</code> <code>tidy</code> subcommand now supports
|
||||
a <code>-go</code> flag to set or change the <code>go</code> version in
|
||||
the <code>go.mod</code> file. To enable lazy loading for an existing module
|
||||
without changing the selected versions of its dependencies, run:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
go mod tidy -go=1.17
|
||||
</pre>
|
||||
|
||||
<h4 id="module-deprecation-comments">Module deprecation comments</h4>
|
||||
|
||||
<p><!-- golang.org/issue/40357 -->
|
||||
|
@ -1221,7 +1221,7 @@
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go mod tidy [-e] [-v]
|
||||
// go mod tidy [-e] [-v] [-go=version]
|
||||
//
|
||||
// Tidy makes sure go.mod matches the source code in the module.
|
||||
// It adds any missing modules necessary to build the current module's
|
||||
@ -1235,6 +1235,12 @@
|
||||
// The -e flag causes tidy to attempt to proceed despite errors
|
||||
// encountered while loading packages.
|
||||
//
|
||||
// The -go flag causes tidy to update the 'go' directive in the go.mod
|
||||
// file to the given version, which may change which module dependencies
|
||||
// are retained as explicit requirements in the go.mod file.
|
||||
// (Go versions 1.17 and higher retain more requirements in order to
|
||||
// support lazy module loading.)
|
||||
//
|
||||
// See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'.
|
||||
//
|
||||
//
|
||||
|
@ -12,10 +12,12 @@ import (
|
||||
"cmd/go/internal/imports"
|
||||
"cmd/go/internal/modload"
|
||||
"context"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
)
|
||||
|
||||
var cmdTidy = &base.Command{
|
||||
UsageLine: "go mod tidy [-e] [-v]",
|
||||
UsageLine: "go mod tidy [-e] [-v] [-go=version]",
|
||||
Short: "add missing and remove unused modules",
|
||||
Long: `
|
||||
Tidy makes sure go.mod matches the source code in the module.
|
||||
@ -30,16 +32,26 @@ to standard error.
|
||||
The -e flag causes tidy to attempt to proceed despite errors
|
||||
encountered while loading packages.
|
||||
|
||||
The -go flag causes tidy to update the 'go' directive in the go.mod
|
||||
file to the given version, which may change which module dependencies
|
||||
are retained as explicit requirements in the go.mod file.
|
||||
(Go versions 1.17 and higher retain more requirements in order to
|
||||
support lazy module loading.)
|
||||
|
||||
See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'.
|
||||
`,
|
||||
Run: runTidy,
|
||||
}
|
||||
|
||||
var tidyE bool // if true, report errors but proceed anyway.
|
||||
var (
|
||||
tidyE bool // if true, report errors but proceed anyway.
|
||||
tidyGo string // go version to write to the tidied go.mod file (toggles lazy loading)
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdTidy.Flag.BoolVar(&cfg.BuildV, "v", false, "")
|
||||
cmdTidy.Flag.BoolVar(&tidyE, "e", false, "")
|
||||
cmdTidy.Flag.StringVar(&tidyGo, "go", "", "")
|
||||
base.AddModCommonFlags(&cmdTidy.Flag)
|
||||
}
|
||||
|
||||
@ -48,6 +60,12 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
|
||||
base.Fatalf("go mod tidy: no arguments allowed")
|
||||
}
|
||||
|
||||
if tidyGo != "" {
|
||||
if !modfile.GoVersionRE.MatchString(tidyGo) {
|
||||
base.Fatalf(`go mod: invalid -go option %q; expecting something like "-go 1.17"`, tidyGo)
|
||||
}
|
||||
}
|
||||
|
||||
// Tidy aims to make 'go test' reproducible for any package in 'all', so we
|
||||
// need to include test dependencies. For modules that specify go 1.15 or
|
||||
// earlier this is a no-op (because 'all' saturates transitive test
|
||||
@ -62,6 +80,7 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
|
||||
modload.RootMode = modload.NeedRoot
|
||||
|
||||
modload.LoadPackages(ctx, modload.PackageOpts{
|
||||
GoVersion: tidyGo,
|
||||
Tags: imports.AnyTags(),
|
||||
Tidy: true,
|
||||
VendorModulesInGOROOTSrc: true,
|
||||
|
@ -259,8 +259,8 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
|
||||
if m.GoVersion == "" && checksumOk("/go.mod") {
|
||||
// Load the go.mod file to determine the Go version, since it hasn't
|
||||
// already been populated from rawGoVersion.
|
||||
if summary, err := rawGoModSummary(mod); err == nil && summary.goVersionV != "" {
|
||||
m.GoVersion = summary.goVersionV[1:]
|
||||
if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
|
||||
m.GoVersion = summary.goVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +299,7 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
|
||||
// sufficient to build the packages it contains. We must load its full
|
||||
// transitive dependency graph to be sure that we see all relevant
|
||||
// dependencies.
|
||||
if depth == eager || summary.depth() == eager {
|
||||
if depth == eager || summary.depth == eager {
|
||||
for _, r := range summary.require {
|
||||
enqueue(r, eager)
|
||||
}
|
||||
@ -393,7 +393,7 @@ func LoadModGraph(ctx context.Context) *ModuleGraph {
|
||||
base.Fatalf("go: %v", err)
|
||||
}
|
||||
|
||||
commitRequirements(ctx, rs)
|
||||
commitRequirements(ctx, modFileGoVersion(), rs)
|
||||
return mg
|
||||
}
|
||||
|
||||
@ -459,7 +459,7 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
commitRequirements(ctx, rs)
|
||||
commitRequirements(ctx, modFileGoVersion(), rs)
|
||||
return changed, err
|
||||
}
|
||||
|
||||
@ -943,10 +943,40 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
if reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
|
||||
// The root set is unchanged, so keep rs to preserve its cached ModuleGraph
|
||||
// (if any).
|
||||
if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
|
||||
// The root set is unchanged and rs was already eager, so keep rs to
|
||||
// preserve its cached ModuleGraph (if any).
|
||||
return rs, nil
|
||||
}
|
||||
return newRequirements(rs.depth, min, direct), nil
|
||||
return newRequirements(eager, min, direct), nil
|
||||
}
|
||||
|
||||
// convertDepth returns a version of rs with the given depth.
|
||||
// If rs already has the given depth, convertDepth returns rs unmodified.
|
||||
func convertDepth(ctx context.Context, rs *Requirements, depth modDepth) (*Requirements, error) {
|
||||
if rs.depth == depth {
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
if depth == eager {
|
||||
// We are converting a lazy module to an eager one. The roots of an eager
|
||||
// module graph are a superset of the roots of a lazy graph, so we don't
|
||||
// need to add any new roots — we just need to prune away the ones that are
|
||||
// redundant given eager loading, which is exactly what updateEagerRoots
|
||||
// does.
|
||||
return updateEagerRoots(ctx, rs.direct, rs, nil)
|
||||
}
|
||||
|
||||
// We are converting an eager module to a lazy one. The module graph of an
|
||||
// eager module includes the transitive dependencies of every module in the
|
||||
// build list.
|
||||
//
|
||||
// Hey, we can express that as a lazy root set! “Include the transitive
|
||||
// dependencies of every module in the build list” is exactly what happens in
|
||||
// a lazy module if we promote every module in the build list to a root!
|
||||
mg, err := rs.Graph(ctx)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if summary.depth() == eager {
|
||||
if summary.depth == eager {
|
||||
// For efficiency, we'll load all of the eager upgrades as one big
|
||||
// graph, rather than loading the (potentially-overlapping) subgraph for
|
||||
// each upgrade individually.
|
||||
@ -522,7 +522,7 @@ func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
|
||||
return l.disqualify(m, dqState{err: err})
|
||||
}
|
||||
|
||||
if summary.depth() == eager {
|
||||
if summary.depth == eager {
|
||||
depth = eager
|
||||
}
|
||||
for _, r := range summary.require {
|
||||
|
@ -381,7 +381,7 @@ var errGoModDirty error = goModDirtyError{}
|
||||
func LoadModFile(ctx context.Context) *Requirements {
|
||||
rs, needCommit := loadModFile(ctx)
|
||||
if needCommit {
|
||||
commitRequirements(ctx, rs)
|
||||
commitRequirements(ctx, modFileGoVersion(), rs)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
@ -401,8 +401,9 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
|
||||
if modRoot == "" {
|
||||
Target = module.Version{Path: "command-line-arguments"}
|
||||
targetPrefix = "command-line-arguments"
|
||||
rawGoVersion.Store(Target, latestGoVersion())
|
||||
requirements = newRequirements(index.depth(), nil, nil)
|
||||
goVersion := latestGoVersion()
|
||||
rawGoVersion.Store(Target, goVersion)
|
||||
requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
|
||||
return requirements, false
|
||||
}
|
||||
|
||||
@ -432,7 +433,7 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
|
||||
}
|
||||
|
||||
setDefaultBuildMod() // possibly enable automatic vendoring
|
||||
rs = requirementsFromModFile(ctx, f)
|
||||
rs = requirementsFromModFile(ctx)
|
||||
|
||||
if cfg.BuildMod == "vendor" {
|
||||
readVendorList()
|
||||
@ -440,27 +441,23 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
|
||||
rs.initVendor(vendorList)
|
||||
}
|
||||
if index.goVersionV == "" {
|
||||
// The main module necessarily has a go.mod file, and that file lacks a
|
||||
// 'go' directive. The 'go' command has been adding that directive
|
||||
// automatically since Go 1.12, so this module either dates to Go 1.11 or
|
||||
// has been erroneously hand-edited.
|
||||
//
|
||||
// The semantics of the go.mod file are more-or-less the same from Go 1.11
|
||||
// through Go 1.16, changing at 1.17 for lazy loading. So even though a
|
||||
// go.mod file without a 'go' directive is theoretically a Go 1.11 file,
|
||||
// scripts may assume that it ends up as a Go 1.16 module. We can't go
|
||||
// higher than that, because we don't know which semantics the user intends.
|
||||
//
|
||||
// (Note that 'go mod init' always adds the latest version, so scripts that
|
||||
// use 'go mod init' will result in current-version modules instead of Go
|
||||
// 1.16 modules.)
|
||||
//
|
||||
// If we are able to modify the go.mod file, we will add a 'go' directive
|
||||
// to at least make the situation explicit going forward.
|
||||
if cfg.BuildMod == "mod" {
|
||||
addGoStmt("1.16")
|
||||
// TODO(#45551): Do something more principled instead of checking
|
||||
// cfg.CmdName directly here.
|
||||
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
|
||||
addGoStmt(latestGoVersion())
|
||||
if go117EnableLazyLoading {
|
||||
// We need to add a 'go' version to the go.mod file, but we must assume
|
||||
// that its existing contents match something between Go 1.11 and 1.16.
|
||||
// Go 1.11 through 1.16 have eager requirements, but the latest Go
|
||||
// version uses lazy requirements instead — so we need to cnvert the
|
||||
// requirements to be lazy.
|
||||
rs, err = convertDepth(ctx, rs, lazy)
|
||||
if err != nil {
|
||||
base.Fatalf("go: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rawGoVersion.Store(Target, "1.16")
|
||||
rawGoVersion.Store(Target, modFileGoVersion())
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,7 +506,7 @@ func CreateModFile(ctx context.Context, modPath string) {
|
||||
base.Fatalf("go: %v", err)
|
||||
}
|
||||
|
||||
commitRequirements(ctx, requirementsFromModFile(ctx, modFile))
|
||||
commitRequirements(ctx, modFileGoVersion(), requirementsFromModFile(ctx))
|
||||
|
||||
// Suggest running 'go mod tidy' unless the project is empty. Even if we
|
||||
// imported all the correct requirements above, we're probably missing
|
||||
@ -661,12 +658,13 @@ func initTarget(m module.Version) {
|
||||
}
|
||||
}
|
||||
|
||||
// requirementsFromModFile returns the set of non-excluded requirements from f.
|
||||
func requirementsFromModFile(ctx context.Context, f *modfile.File) *Requirements {
|
||||
roots := make([]module.Version, 0, len(f.Require))
|
||||
// requirementsFromModFile returns the set of non-excluded requirements from
|
||||
// the global modFile.
|
||||
func requirementsFromModFile(ctx context.Context) *Requirements {
|
||||
roots := make([]module.Version, 0, len(modFile.Require))
|
||||
mPathCount := map[string]int{Target.Path: 1}
|
||||
direct := map[string]bool{}
|
||||
for _, r := range f.Require {
|
||||
for _, r := range modFile.Require {
|
||||
if index != nil && index.exclude[r.Mod] {
|
||||
if cfg.BuildMod == "mod" {
|
||||
fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
|
||||
@ -683,7 +681,7 @@ func requirementsFromModFile(ctx context.Context, f *modfile.File) *Requirements
|
||||
}
|
||||
}
|
||||
module.Sort(roots)
|
||||
rs := newRequirements(index.depth(), roots, direct)
|
||||
rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct)
|
||||
|
||||
// If any module path appears more than once in the roots, we know that the
|
||||
// go.mod file needs to be updated even though we have not yet loaded any
|
||||
@ -988,12 +986,12 @@ func WriteGoMod(ctx context.Context) {
|
||||
if !allowWriteGoMod {
|
||||
panic("WriteGoMod called while disallowed")
|
||||
}
|
||||
commitRequirements(ctx, LoadModFile(ctx))
|
||||
commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx))
|
||||
}
|
||||
|
||||
// commitRequirements writes sets the global requirements variable to rs and
|
||||
// writes its contents back to the go.mod file on disk.
|
||||
func commitRequirements(ctx context.Context, rs *Requirements) {
|
||||
func commitRequirements(ctx context.Context, goVersion string, rs *Requirements) {
|
||||
requirements = rs
|
||||
|
||||
if !allowWriteGoMod {
|
||||
@ -1014,6 +1012,9 @@ func commitRequirements(ctx context.Context, rs *Requirements) {
|
||||
})
|
||||
}
|
||||
modFile.SetRequire(list)
|
||||
if goVersion != "" {
|
||||
modFile.AddGoStmt(goVersion)
|
||||
}
|
||||
modFile.Cleanup()
|
||||
|
||||
dirty := index.modFileIsDirty(modFile)
|
||||
|
@ -72,7 +72,7 @@ func ListModules(ctx context.Context, args []string, mode ListMode) ([]*modinfo.
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
commitRequirements(ctx, rs)
|
||||
commitRequirements(ctx, modFileGoVersion(), rs)
|
||||
}
|
||||
return mods, err
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ package modload
|
||||
// Because "go mod vendor" prunes out the tests of vendored packages, the
|
||||
// behavior of the "all" pattern with -mod=vendor in Go 1.11–1.15 is the same
|
||||
// as the "all" pattern (regardless of the -mod flag) in 1.16+.
|
||||
// The allClosesOverTests parameter to the loader indicates whether the "all"
|
||||
// The loader uses the GoVersion parameter to determine whether the "all"
|
||||
// pattern should close over tests (as in Go 1.11–1.15) or stop at only those
|
||||
// packages transitively imported by the packages and tests in the main module
|
||||
// ("all" in Go 1.16+ and "go mod vendor" in Go 1.11+).
|
||||
@ -121,6 +121,7 @@ import (
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// loaded is the most recently-used package loader.
|
||||
@ -133,6 +134,14 @@ var loaded *loader
|
||||
|
||||
// PackageOpts control the behavior of the LoadPackages function.
|
||||
type PackageOpts struct {
|
||||
// GoVersion is the Go version to which the go.mod file should be updated
|
||||
// after packages have been loaded.
|
||||
//
|
||||
// An empty GoVersion means to use the Go version already specified in the
|
||||
// main module's go.mod file, or the latest Go version if there is no main
|
||||
// module.
|
||||
GoVersion string
|
||||
|
||||
// Tags are the build tags in effect (as interpreted by the
|
||||
// cmd/go/internal/imports package).
|
||||
// If nil, treated as equivalent to imports.Tags().
|
||||
@ -305,12 +314,15 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
|
||||
|
||||
initialRS, _ := loadModFile(ctx) // Ignore needCommit — we're going to commit at the end regardless.
|
||||
|
||||
if opts.GoVersion == "" {
|
||||
opts.GoVersion = modFileGoVersion()
|
||||
}
|
||||
|
||||
ld := loadFromRoots(ctx, loaderParams{
|
||||
PackageOpts: opts,
|
||||
requirements: initialRS,
|
||||
|
||||
allClosesOverTests: index.allPatternClosesOverTests() && !opts.UseVendorAll,
|
||||
allPatternIsRoot: allPatternIsRoot,
|
||||
allPatternIsRoot: allPatternIsRoot,
|
||||
|
||||
listRoots: func(rs *Requirements) (roots []string) {
|
||||
updateMatches(rs, nil)
|
||||
@ -368,7 +380,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
|
||||
|
||||
// Success! Update go.mod and go.sum (if needed) and return the results.
|
||||
loaded = ld
|
||||
commitRequirements(ctx, loaded.requirements)
|
||||
commitRequirements(ctx, opts.GoVersion, loaded.requirements)
|
||||
|
||||
for _, pkg := range ld.pkgs {
|
||||
if !pkg.isTest() {
|
||||
@ -593,21 +605,22 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
|
||||
base.Fatalf("go: %v", err)
|
||||
}
|
||||
|
||||
goVersion := modFileGoVersion()
|
||||
loaded = loadFromRoots(ctx, loaderParams{
|
||||
PackageOpts: PackageOpts{
|
||||
GoVersion: goVersion,
|
||||
Tags: tags,
|
||||
ResolveMissingImports: true,
|
||||
SilencePackageErrors: true,
|
||||
},
|
||||
requirements: rs,
|
||||
allClosesOverTests: index.allPatternClosesOverTests(),
|
||||
requirements: rs,
|
||||
listRoots: func(*Requirements) (roots []string) {
|
||||
roots = append(roots, imports...)
|
||||
roots = append(roots, testImports...)
|
||||
return roots
|
||||
},
|
||||
})
|
||||
commitRequirements(ctx, loaded.requirements)
|
||||
commitRequirements(ctx, goVersion, loaded.requirements)
|
||||
}
|
||||
|
||||
// DirImportPath returns the effective import path for dir,
|
||||
@ -743,6 +756,12 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
|
||||
type loader struct {
|
||||
loaderParams
|
||||
|
||||
// allClosesOverTests indicates whether the "all" pattern includes
|
||||
// dependencies of tests outside the main module (as in Go 1.11–1.15).
|
||||
// (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages
|
||||
// transitively *imported by* the packages and tests in the main module.)
|
||||
allClosesOverTests bool
|
||||
|
||||
work *par.Queue
|
||||
|
||||
// reset on each iteration
|
||||
@ -757,8 +776,7 @@ type loaderParams struct {
|
||||
PackageOpts
|
||||
requirements *Requirements
|
||||
|
||||
allClosesOverTests bool // Does the "all" pattern include the transitive closure of tests of packages in "all"?
|
||||
allPatternIsRoot bool // Is the "all" pattern an additional root?
|
||||
allPatternIsRoot bool // Is the "all" pattern an additional root?
|
||||
|
||||
listRoots func(rs *Requirements) []string
|
||||
}
|
||||
@ -903,6 +921,22 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
|
||||
work: par.NewQueue(runtime.GOMAXPROCS(0)),
|
||||
}
|
||||
|
||||
if params.GoVersion != "" {
|
||||
if semver.Compare("v"+params.GoVersion, narrowAllVersionV) < 0 && !ld.UseVendorAll {
|
||||
// The module's go version explicitly predates the change in "all" for lazy
|
||||
// loading, so continue to use the older interpretation.
|
||||
// (If params.GoVersion is empty, we are probably not in any module at all
|
||||
// and should use the latest semantics.)
|
||||
ld.allClosesOverTests = true
|
||||
}
|
||||
|
||||
var err error
|
||||
ld.requirements, err = convertDepth(ctx, ld.requirements, modDepthFromGoVersion(params.GoVersion))
|
||||
if err != nil {
|
||||
ld.errorf("go: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ld.requirements.depth == eager {
|
||||
var err error
|
||||
ld.requirements, _, err = expandGraph(ctx, ld.requirements)
|
||||
|
@ -51,6 +51,27 @@ const (
|
||||
|
||||
var modFile *modfile.File
|
||||
|
||||
// modFileGoVersion returns the (non-empty) Go version at which the requirements
|
||||
// in modFile are intepreted, or the latest Go version if modFile is nil.
|
||||
func modFileGoVersion() string {
|
||||
if modFile == nil {
|
||||
return latestGoVersion()
|
||||
}
|
||||
if modFile.Go == nil || modFile.Go.Version == "" {
|
||||
// The main module necessarily has a go.mod file, and that file lacks a
|
||||
// 'go' directive. The 'go' command has been adding that directive
|
||||
// automatically since Go 1.12, so this module either dates to Go 1.11 or
|
||||
// has been erroneously hand-edited.
|
||||
//
|
||||
// The semantics of the go.mod file are more-or-less the same from Go 1.11
|
||||
// through Go 1.16, changing at 1.17 for lazy loading. So even though a
|
||||
// go.mod file without a 'go' directive is theoretically a Go 1.11 file,
|
||||
// scripts may assume that it ends up as a Go 1.16 module.
|
||||
return "1.16"
|
||||
}
|
||||
return modFile.Go.Version
|
||||
}
|
||||
|
||||
// A modFileIndex is an index of data corresponding to a modFile
|
||||
// at a specific point in time.
|
||||
type modFileIndex struct {
|
||||
@ -79,6 +100,16 @@ const (
|
||||
eager // load all transitive dependencies eagerly
|
||||
)
|
||||
|
||||
func modDepthFromGoVersion(goVersion string) modDepth {
|
||||
if !go117EnableLazyLoading {
|
||||
return eager
|
||||
}
|
||||
if semver.Compare("v"+goVersion, lazyLoadingVersionV) < 0 {
|
||||
return eager
|
||||
}
|
||||
return lazy
|
||||
}
|
||||
|
||||
// CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by
|
||||
// the main module's go.mod or retracted by its author. Most version queries use
|
||||
// this to filter out versions that should not be used.
|
||||
@ -344,32 +375,6 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd
|
||||
return i
|
||||
}
|
||||
|
||||
// allPatternClosesOverTests reports whether the "all" pattern includes
|
||||
// dependencies of tests outside the main module (as in Go 1.11–1.15).
|
||||
// (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages
|
||||
// transitively *imported by* the packages and tests in the main module.)
|
||||
func (i *modFileIndex) allPatternClosesOverTests() bool {
|
||||
if i != nil && i.goVersionV != "" && semver.Compare(i.goVersionV, narrowAllVersionV) < 0 {
|
||||
// The module explicitly predates the change in "all" for lazy loading, so
|
||||
// continue to use the older interpretation. (If i == nil, we not in any
|
||||
// module at all and should use the latest semantics.)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// depth reports the modDepth indicated by the indexed go.mod file,
|
||||
// or lazy if the go.mod file has not been indexed.
|
||||
func (i *modFileIndex) depth() modDepth {
|
||||
if !go117EnableLazyLoading {
|
||||
return eager
|
||||
}
|
||||
if i != nil && semver.Compare(i.goVersionV, lazyLoadingVersionV) < 0 {
|
||||
return eager
|
||||
}
|
||||
return lazy
|
||||
}
|
||||
|
||||
// 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,
|
||||
@ -396,7 +401,7 @@ func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
|
||||
return true
|
||||
}
|
||||
} else if "v"+modFile.Go.Version != i.goVersionV {
|
||||
if i.goVersionV == "" && cfg.BuildMod == "readonly" {
|
||||
if i.goVersionV == "" && cfg.BuildMod != "mod" {
|
||||
// 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.
|
||||
@ -452,7 +457,8 @@ var rawGoVersion sync.Map // map[module.Version]string
|
||||
// module.
|
||||
type modFileSummary struct {
|
||||
module module.Version
|
||||
goVersionV string // GoVersion with "v" prefix
|
||||
goVersion string
|
||||
depth modDepth
|
||||
require []module.Version
|
||||
retract []retraction
|
||||
deprecated string
|
||||
@ -465,20 +471,6 @@ type retraction struct {
|
||||
Rationale string
|
||||
}
|
||||
|
||||
func (s *modFileSummary) depth() modDepth {
|
||||
if !go117EnableLazyLoading {
|
||||
return eager
|
||||
}
|
||||
// The 'go' command fills in the 'go' directive automatically, so an empty
|
||||
// goVersionV in a dependency implies either Go 1.11 (eager loading) or no
|
||||
// explicit go.mod file at all (no difference between eager and lazy because
|
||||
// the module doesn't specify any requirements at all).
|
||||
if s.goVersionV == "" || semver.Compare(s.goVersionV, lazyLoadingVersionV) < 0 {
|
||||
return eager
|
||||
}
|
||||
return lazy
|
||||
}
|
||||
|
||||
// goModSummary returns a summary of the go.mod file for module m,
|
||||
// taking into account any replacements for m, exclusions of its dependencies,
|
||||
// and/or vendoring.
|
||||
@ -638,7 +630,10 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
|
||||
}
|
||||
if f.Go != nil && f.Go.Version != "" {
|
||||
rawGoVersion.LoadOrStore(m, f.Go.Version)
|
||||
summary.goVersionV = "v" + f.Go.Version
|
||||
summary.goVersion = f.Go.Version
|
||||
summary.depth = modDepthFromGoVersion(f.Go.Version)
|
||||
} else {
|
||||
summary.depth = eager
|
||||
}
|
||||
if len(f.Require) > 0 {
|
||||
summary.require = make([]module.Version, 0, len(f.Require))
|
||||
|
@ -30,12 +30,26 @@ cmp go.mod go.mod.orig
|
||||
stderr 'cannot find package "\." in:\n\t.*[/\\]vendor[/\\]example.com[/\\]badedit$'
|
||||
|
||||
# When we set -mod=mod, the go version should be updated immediately,
|
||||
# to Go 1.16 (not the current version).
|
||||
# to the current version, converting the requirements from eager to lazy.
|
||||
#
|
||||
# Since we don't know which requirements are actually relevant to the main
|
||||
# module, all requirements are added as roots, making the requirements untidy.
|
||||
|
||||
go list -mod=mod all
|
||||
! stdout '^example.com/testdep$'
|
||||
cmp stdout list-1.txt
|
||||
cmp go.mod go.mod.want
|
||||
cmpenv go.mod go.mod.untidy
|
||||
|
||||
go mod tidy
|
||||
cmpenv go.mod go.mod.tidy
|
||||
|
||||
# On the other hand, if we jump straight to 'go mod tidy',
|
||||
# the requirements remain tidy from the start.
|
||||
|
||||
cp go.mod.orig go.mod
|
||||
go mod tidy
|
||||
cmpenv go.mod go.mod.tidy
|
||||
|
||||
|
||||
# The updated version should have been written back to go.mod, so now the 'go'
|
||||
# directive is explicit. -mod=vendor should trigger by default, and the stronger
|
||||
@ -54,10 +68,24 @@ replace (
|
||||
example.com/dep v0.1.0 => ./dep
|
||||
example.com/testdep v0.1.0 => ./testdep
|
||||
)
|
||||
-- go.mod.want --
|
||||
-- go.mod.untidy --
|
||||
module example.com/m
|
||||
|
||||
go 1.16
|
||||
go $goversion
|
||||
|
||||
require (
|
||||
example.com/dep v0.1.0
|
||||
example.com/testdep v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.com/dep v0.1.0 => ./dep
|
||||
example.com/testdep v0.1.0 => ./testdep
|
||||
)
|
||||
-- go.mod.tidy --
|
||||
module example.com/m
|
||||
|
||||
go $goversion
|
||||
|
||||
require example.com/dep v0.1.0
|
||||
|
||||
|
@ -18,6 +18,6 @@ package use
|
||||
import _ "rsc.io/quote"
|
||||
|
||||
-- want --
|
||||
go mod download: rsc.io/quote@v1.5.2 (replaced by example.com/quote@v1.5.2): parsing go.mod:
|
||||
go: rsc.io/quote@v1.5.2 (replaced by example.com/quote@v1.5.2): parsing go.mod:
|
||||
module declares its path as: rsc.io/Quote
|
||||
but was required as: rsc.io/quote
|
||||
|
4
src/cmd/go/testdata/script/mod_retention.txt
vendored
4
src/cmd/go/testdata/script/mod_retention.txt
vendored
@ -62,6 +62,7 @@ cmp go.mod go.mod.tidy
|
||||
|
||||
# A missing "go" version directive should be added.
|
||||
# However, that should not remove other redundant requirements.
|
||||
# In fact, it may *add* redundant requirements due to activating lazy loading.
|
||||
cp go.mod.nogo go.mod
|
||||
go list -mod=mod all
|
||||
cmpenv go.mod go.mod.addedgo
|
||||
@ -136,9 +137,10 @@ require (
|
||||
-- go.mod.addedgo --
|
||||
module m
|
||||
|
||||
go 1.16
|
||||
go $goversion
|
||||
|
||||
require (
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
|
||||
rsc.io/quote v1.5.2
|
||||
rsc.io/sampler v1.3.0 // indirect
|
||||
rsc.io/testonly v1.0.0 // indirect
|
||||
|
248
src/cmd/go/testdata/script/mod_tidy_version.txt
vendored
Normal file
248
src/cmd/go/testdata/script/mod_tidy_version.txt
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
# https://golang.org/issue/45094: 'go mod tidy' now accepts a '-go' flag
|
||||
# to change the language version in use.
|
||||
#
|
||||
# The package import graph used in this test looks like:
|
||||
#
|
||||
# m --- a --- b
|
||||
# |
|
||||
# b_test --- c
|
||||
# |
|
||||
# c_test --- d
|
||||
#
|
||||
# The module diagram looks like:
|
||||
#
|
||||
# m --- a --- b
|
||||
# |
|
||||
# + --- c
|
||||
# |
|
||||
# + --- d
|
||||
#
|
||||
# Module b omits its dependency on c, and module c omits its dependency on d.
|
||||
#
|
||||
# In go 1.15, the tidy main module must require a (because it is direct),
|
||||
# c (because it is a missing test dependency of an imported package),
|
||||
# and d (because it is a missing transitive test dependency).
|
||||
#
|
||||
# In go 1.16, the tidy main module can omit d because it is no longer
|
||||
# included in "all".
|
||||
#
|
||||
# In go 1.17, the main module must explicitly require b
|
||||
# (because it is transitively imported by the main module).
|
||||
|
||||
|
||||
cp go.mod go.mod.orig
|
||||
|
||||
# An invalid argument should be rejected.
|
||||
|
||||
! go mod tidy -go=bananas
|
||||
stderr '^go mod: invalid -go option "bananas"; expecting something like "-go 1.17"$'
|
||||
cmp go.mod go.mod.orig
|
||||
|
||||
|
||||
go mod tidy -go=1.15
|
||||
cmp go.mod go.mod.115
|
||||
|
||||
go mod tidy
|
||||
cmp go.mod go.mod.115
|
||||
|
||||
|
||||
go mod tidy -go=1.16
|
||||
cmp go.mod go.mod.116
|
||||
|
||||
go mod tidy
|
||||
cmp go.mod go.mod.116
|
||||
|
||||
|
||||
go mod tidy -go=1.17
|
||||
cmp go.mod go.mod.117
|
||||
|
||||
go mod tidy
|
||||
cmp go.mod go.mod.117
|
||||
|
||||
|
||||
# If we downgrade back to 1.15, we should re-resolve d to v0.2.0 instead
|
||||
# of the original v0.1.0 (because the original requirement is lost).
|
||||
|
||||
go mod tidy -go=1.15
|
||||
cmp go.mod go.mod.115-2
|
||||
|
||||
|
||||
# -go= (with an empty argument) maintains the existing version or adds the
|
||||
# default version (just like omitting the flag).
|
||||
|
||||
go mod tidy -go=''
|
||||
cmp go.mod go.mod.115-2
|
||||
|
||||
cp go.mod.orig go.mod
|
||||
go mod tidy -go=''
|
||||
cmpenv go.mod go.mod.latest
|
||||
|
||||
|
||||
|
||||
-- go.mod --
|
||||
module example.com/m
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/c v0.1.0 // indirect
|
||||
example.net/d v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- m.go --
|
||||
package m
|
||||
|
||||
import _ "example.net/a"
|
||||
|
||||
-- go.mod.115 --
|
||||
module example.com/m
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/c v0.1.0 // indirect
|
||||
example.net/d v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- go.mod.115-2 --
|
||||
module example.com/m
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/c v0.1.0 // indirect
|
||||
example.net/d v0.2.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- go.mod.116 --
|
||||
module example.com/m
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/c v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- go.mod.117 --
|
||||
module example.com/m
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/b v0.1.0 // indirect
|
||||
example.net/c v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- go.mod.latest --
|
||||
module example.com/m
|
||||
|
||||
go $goversion
|
||||
|
||||
require (
|
||||
example.net/a v0.1.0
|
||||
example.net/b v0.1.0 // indirect
|
||||
example.net/c v0.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
example.net/a v0.1.0 => ./a
|
||||
example.net/a v0.2.0 => ./a
|
||||
example.net/b v0.1.0 => ./b
|
||||
example.net/b v0.2.0 => ./b
|
||||
example.net/c v0.1.0 => ./c
|
||||
example.net/c v0.2.0 => ./c
|
||||
example.net/d v0.1.0 => ./d
|
||||
example.net/d v0.2.0 => ./d
|
||||
)
|
||||
-- a/go.mod --
|
||||
module example.net/a
|
||||
|
||||
go 1.15
|
||||
|
||||
require example.net/b v0.1.0
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
import _ "example.net/b"
|
||||
|
||||
-- b/go.mod --
|
||||
module example.net/b
|
||||
|
||||
go 1.15
|
||||
-- b/b.go --
|
||||
package b
|
||||
-- b/b_test.go --
|
||||
package b_test
|
||||
|
||||
import _ "example.net/c"
|
||||
|
||||
-- c/go.mod --
|
||||
module example.net/c
|
||||
|
||||
go 1.15
|
||||
-- c/c.go --
|
||||
package c
|
||||
-- c/c_test.go --
|
||||
package c_test
|
||||
|
||||
import _ "example.net/d"
|
||||
|
||||
-- d/go.mod --
|
||||
module example.net/d
|
||||
|
||||
go 1.15
|
||||
-- d/d.go --
|
||||
package d
|
Loading…
Reference in New Issue
Block a user