// A Requirements represents a logically-immutable set of root module requirements.
type Requirements struct {
+ // depth is the depth at which the requirement graph is computed.
+ //
+ // If eager, the graph includes all transitive requirements regardless of depth.
+ //
+ // If lazy, the graph includes only the root modules, the explicit
+ // requirements of those root modules, and the transitive requirements of only
+ // the *non-lazy* root modules.
+ depth modDepth
+
// rootModules is the set of module versions explicitly required by the main
// module, sorted and capped to length. It may contain duplicates, and may
// contain multiple versions for a given module path.
//
// If vendoring is in effect, the caller must invoke initVendor on the returned
// *Requirements before any other method.
-func newRequirements(rootModules []module.Version, direct map[string]bool) *Requirements {
+func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
for i, m := range rootModules {
if m == Target {
panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i))
g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
}
- if go117LazyTODO {
+ if rs.depth == lazy {
// The roots of a lazy module should already include every module in the
// vendor list, because the vendored modules are the same as those
// maintained as roots by the lazy loading “import invariant”.
- //
- // TODO: Double-check here that that invariant holds.
+ if go117LazyTODO {
+ // Double-check here that that invariant holds.
+ }
// So we can just treat the rest of the module graph as effectively
// “pruned out”, like a more aggressive version of lazy loading:
// returns a non-nil error of type *mvs.BuildListError.
func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) {
rs.graphOnce.Do(func() {
- mg, mgErr := readModGraph(ctx, rs.rootModules)
+ mg, mgErr := readModGraph(ctx, rs.depth, rs.rootModules)
rs.graph.Store(cachedGraph{mg, mgErr})
})
cached := rs.graph.Load().(cachedGraph)
//
// Unlike LoadModGraph, readModGraph does not attempt to diagnose or update
// inconsistent roots.
-func readModGraph(ctx context.Context, roots []module.Version) (*ModuleGraph, error) {
+func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (*ModuleGraph, error) {
var (
mu sync.Mutex // guards mg.g and hasError during loading
hasError bool
mg.g.Require(Target, roots)
var (
- loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
- loading sync.Map // module.Version → nil; the set of modules that have been or are being loaded
+ loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
+ loadingEager sync.Map // module.Version → nil; the set of modules that have been or are being loaded via eager roots
)
// loadOne synchronously loads the explicit requirements for module m.
return cached.summary, cached.err
}
- var enqueue func(m module.Version)
- enqueue = func(m module.Version) {
+ var enqueue func(m module.Version, depth modDepth)
+ enqueue = func(m module.Version, depth modDepth) {
if m.Version == "none" {
return
}
- if _, dup := loading.LoadOrStore(m, nil); dup {
- // m has already been enqueued for loading. Since the requirement graph
- // may contain cycles, we need to return early to avoid making the load
- // queue infinitely long.
- return
+ if depth == eager {
+ if _, dup := loadingEager.LoadOrStore(m, nil); dup {
+ // m has already been enqueued for loading. Since eager loading may
+ // follow cycles in the the requirement graph, we need to return early
+ // to avoid making the load queue infinitely long.
+ return
+ }
}
loadQueue.Add(func() {
// 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 !go117LazyTODO {
+ if depth == eager || summary.depth() == eager {
for _, r := range summary.require {
- enqueue(r)
+ enqueue(r, eager)
}
}
})
}
for _, m := range roots {
- enqueue(m)
+ enqueue(m, depth)
}
<-loadQueue.Idle()
// roots — but in a lazy module it may pull in previously-irrelevant
// transitive dependencies.
- newRS, rsErr := updateRoots(ctx, rs.direct, nil, rs)
+ newRS, rsErr := updateRoots(ctx, rs.depth, rs.direct, nil, rs)
if rsErr != nil {
// Failed to update roots, perhaps because of an error in a transitive
// dependency needed for the update. Return the original Requirements
direct[m.Path] = true
}
}
- return newRequirements(min, direct), changed, nil
+ return newRequirements(rs.depth, min, direct), changed, nil
}
// A ConstraintError describes inconsistent constraints in EditBuildList
// The implementation for eager modules should be factored out into a function.
}
- tidy, err := updateRoots(ctx, loaded.requirements.direct, loaded.pkgs, nil)
+ depth := index.depth()
+ if go117LazyTODO {
+ // TODO(#45094): add a -go flag to 'go mod tidy' to allow the depth to be
+ // changed after loading packages.
+ }
+
+ tidy, err := updateRoots(ctx, depth, loaded.requirements.direct, loaded.pkgs, nil)
if err != nil {
base.Fatalf("go: %v", err)
}
// 3. The selected version of the module providing each package in pkgs remains
// selected.
// 4. If rs is non-nil, every version selected in the graph of rs remains selected.
-func updateRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg, rs *Requirements) (*Requirements, error) {
+func updateRoots(ctx context.Context, depth modDepth, direct map[string]bool, pkgs []*loadPkg, rs *Requirements) (*Requirements, error) {
var (
rootPaths []string // module paths that should be included as roots
inRootPaths = map[string]bool{}
// the root set is the same as the original root set in rs and recycle its
// module graph and build list, if they have already been loaded.
- return newRequirements(min, direct), nil
+ return newRequirements(depth, min, direct), nil
}
// checkMultiplePaths verifies that a given module path is used as itself
"golang.org/x/mod/semver"
)
-// narrowAllVersionV is the Go version (plus leading "v") at which the
-// module-module "all" pattern no longer closes over the dependencies of
-// tests outside of the main module.
-const narrowAllVersionV = "v1.16"
+const (
+ // narrowAllVersionV is the Go version (plus leading "v") at which the
+ // module-module "all" pattern no longer closes over the dependencies of
+ // tests outside of the main module.
+ narrowAllVersionV = "v1.16"
+
+ // lazyLoadingVersionV is the Go version (plus leading "v") at which a
+ // module's go.mod file is expected to list explicit requirements on every
+ // module that provides any package transitively imported by that module.
+ lazyLoadingVersionV = "v1.17"
+)
+
+const (
+ // go117EnableLazyLoading toggles whether lazy-loading code paths should be
+ // active. It will be removed once the lazy loading implementation is stable
+ // and well-tested.
+ go117EnableLazyLoading = false
-// go1117LazyTODO is a constant that exists only until lazy loading is
-// implemented. Its use indicates a condition that will need to change if the
-// main module is lazy.
-const go117LazyTODO = false
+ // go1117LazyTODO is a constant that exists only until lazy loading is
+ // implemented. Its use indicates a condition that will need to change if the
+ // main module is lazy.
+ go117LazyTODO = false
+)
var modFile *modfile.File
indirect bool
}
+// A modDepth indicates which dependencies should be loaded for a go.mod file.
+type modDepth uint8
+
+const (
+ lazy modDepth = iota // load dependencies only as needed
+ eager // load all transitive dependencies eagerly
+)
+
// 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.
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,
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.