src/cmd/go/internal/modload/buildlist.go GO 1,506 lines View on github.com → Search inside
1// Copyright 2018 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package modload67import (8	"context"9	"errors"10	"fmt"11	"maps"12	"os"13	"runtime"14	"runtime/debug"15	"slices"16	"strings"17	"sync"18	"sync/atomic"1920	"cmd/go/internal/base"21	"cmd/go/internal/cfg"22	"cmd/go/internal/gover"23	"cmd/go/internal/mvs"24	"cmd/internal/par"2526	"golang.org/x/mod/module"27)2829// A Requirements represents a logically-immutable set of root module requirements.30type Requirements struct {31	// pruning is the pruning at which the requirement graph is computed.32	//33	// If unpruned, the graph includes all transitive requirements regardless34	// of whether the requiring module supports pruning.35	//36	// If pruned, the graph includes only the root modules, the explicit37	// requirements of those root modules, and the transitive requirements of only38	// the root modules that do not support pruning.39	//40	// If workspace, the graph includes only the workspace modules, the explicit41	// requirements of the workspace modules, and the transitive requirements of42	// the workspace modules that do not support pruning.43	pruning modPruning4445	// rootModules is the set of root modules of the graph, sorted and capped to46	// length. It may contain duplicates, and may contain multiple versions for a47	// given module path. The root modules of the graph are the set of main48	// modules in workspace mode, and the main module's direct requirements49	// outside workspace mode.50	//51	// The roots are always expected to contain an entry for the "go" module,52	// indicating the Go language version in use.53	rootModules    []module.Version54	maxRootVersion map[string]string5556	// direct is the set of module paths for which we believe the module provides57	// a package directly imported by a package or test in the main module.58	//59	// The "direct" map controls which modules are annotated with "// indirect"60	// comments in the go.mod file, and may impact which modules are listed as61	// explicit roots (vs. indirect-only dependencies). However, it should not62	// have a semantic effect on the build list overall.63	//64	// The initial direct map is populated from the existing "// indirect"65	// comments (or lack thereof) in the go.mod file. It is updated by the66	// package loader: dependencies may be promoted to direct if new67	// direct imports are observed, and may be demoted to indirect during68	// 'go mod tidy' or 'go mod vendor'.69	//70	// The direct map is keyed by module paths, not module versions. When a71	// module's selected version changes, we assume that it remains direct if the72	// previous version was a direct dependency. That assumption might not hold in73	// rare cases (such as if a dependency splits out a nested module, or merges a74	// nested module back into a parent module).75	direct map[string]bool7677	graphOnce sync.Once // guards writes to (but not reads from) graph78	graph     atomic.Pointer[cachedGraph]79}8081// A cachedGraph is a non-nil *ModuleGraph, together with any error discovered82// while loading that graph.83type cachedGraph struct {84	mg  *ModuleGraph85	err error // If err is non-nil, mg may be incomplete (but must still be non-nil).86}8788func mustHaveGoRoot(roots []module.Version) {89	for _, m := range roots {90		if m.Path == "go" {91			return92		}93	}94	panic("go: internal error: missing go root module")95}9697// newRequirements returns a new requirement set with the given root modules.98// The dependencies of the roots will be loaded lazily at the first call to the99// Graph method.100//101// The rootModules slice must be sorted according to gover.ModSort.102// The caller must not modify the rootModules slice or direct map after passing103// them to newRequirements.104//105// If vendoring is in effect, the caller must invoke initVendor on the returned106// *Requirements before any other method.107func newRequirements(ld *Loader, pruning modPruning, rootModules []module.Version, direct map[string]bool) *Requirements {108	mustHaveGoRoot(rootModules)109110	if pruning != workspace {111		if ld.workFilePath != "" {112			panic("in workspace mode, but pruning is not workspace in newRequirements")113		}114	}115116	if pruning != workspace {117		if ld.workFilePath != "" {118			panic("in workspace mode, but pruning is not workspace in newRequirements")119		}120		for i, m := range rootModules {121			if m.Version == "" && ld.MainModules.Contains(m.Path) {122				panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))123			}124			if m.Path == "" || m.Version == "" {125				panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))126			}127		}128	}129130	rs := &Requirements{131		pruning:        pruning,132		rootModules:    rootModules,133		maxRootVersion: make(map[string]string, len(rootModules)),134		direct:         direct,135	}136137	for i, m := range rootModules {138		if i > 0 {139			prev := rootModules[i-1]140			if prev.Path > m.Path || (prev.Path == m.Path && gover.ModCompare(m.Path, prev.Version, m.Version) > 0) {141				panic(fmt.Sprintf("newRequirements called with unsorted roots: %v", rootModules))142			}143		}144145		if v, ok := rs.maxRootVersion[m.Path]; ok && gover.ModCompare(m.Path, v, m.Version) >= 0 {146			continue147		}148		rs.maxRootVersion[m.Path] = m.Version149	}150151	if rs.maxRootVersion["go"] == "" {152		panic(`newRequirements called without a "go" version`)153	}154	return rs155}156157// String returns a string describing the Requirements for debugging.158func (rs *Requirements) String() string {159	return fmt.Sprintf("{%v %v}", rs.pruning, rs.rootModules)160}161162// initVendor initializes rs.graph from the given list of vendored module163// dependencies, overriding the graph that would normally be loaded from module164// requirements.165func (rs *Requirements) initVendor(ld *Loader, vendorList []module.Version) {166	rs.graphOnce.Do(func() {167		roots := ld.MainModules.Versions()168		if ld.inWorkspaceMode() {169			// Use rs.rootModules to pull in the go and toolchain roots170			// from the go.work file and preserve the invariant that all171			// of rs.rootModules are in mg.g.172			roots = rs.rootModules173		}174		mg := &ModuleGraph{175			g: mvs.NewGraph(cmpVersion, roots),176		}177178		if rs.pruning == pruned {179			mainModule := ld.MainModules.mustGetSingleMainModule(ld)180			// The roots of a single pruned module should already include every module in the181			// vendor list, because the vendored modules are the same as those needed182			// for graph pruning.183			//184			// Just to be sure, we'll double-check that here.185			inconsistent := false186			for _, m := range vendorList {187				if v, ok := rs.rootSelected(ld, m.Path); !ok || v != m.Version {188					base.Errorf("go: vendored module %v should be required explicitly in go.mod", m)189					inconsistent = true190				}191			}192			if inconsistent {193				base.Fatal(errGoModDirty)194			}195196			// Now we can treat the rest of the module graph as effectively “pruned197			// out”, as though we are viewing the main module from outside: in vendor198			// mode, the root requirements *are* the complete module graph.199			mg.g.Require(mainModule, rs.rootModules)200		} else {201			// The transitive requirements of the main module are not in general available202			// from the vendor directory, and we don't actually know how we got from203			// the roots to the final build list.204			//205			// Instead, we'll inject a fake "vendor/modules.txt" module that provides206			// those transitive dependencies, and mark it as a dependency of the main207			// module. That allows us to elide the actual structure of the module208			// graph, but still distinguishes between direct and indirect209			// dependencies.210			vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}211			if ld.inWorkspaceMode() {212				for _, m := range ld.MainModules.Versions() {213					reqs, _ := rootsFromModFile(ld, m, ld.MainModules.ModFile(m), omitToolchainRoot)214					mg.g.Require(m, append(reqs, vendorMod))215				}216				mg.g.Require(vendorMod, vendorList)217218			} else {219				mainModule := ld.MainModules.mustGetSingleMainModule(ld)220				mg.g.Require(mainModule, append(rs.rootModules, vendorMod))221				mg.g.Require(vendorMod, vendorList)222			}223		}224225		rs.graph.Store(&cachedGraph{mg, nil})226	})227}228229// GoVersion returns the Go language version for the Requirements.230func (rs *Requirements) GoVersion(ld *Loader) string {231	v, _ := rs.rootSelected(ld, "go")232	if v == "" {233		panic("internal error: missing go version in modload.Requirements")234	}235	return v236}237238// rootSelected returns the version of the root dependency with the given module239// path, or the zero module.Version and ok=false if the module is not a root240// dependency.241func (rs *Requirements) rootSelected(ld *Loader, path string) (version string, ok bool) {242	if ld.MainModules.Contains(path) {243		return "", true244	}245	if v, ok := rs.maxRootVersion[path]; ok {246		return v, true247	}248	return "", false249}250251// hasRedundantRoot returns true if the root list contains multiple requirements252// of the same module or a requirement on any version of the main module.253// Redundant requirements should be pruned, but they may influence version254// selection.255func (rs *Requirements) hasRedundantRoot(ld *Loader) bool {256	for i, m := range rs.rootModules {257		if ld.MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {258			return true259		}260	}261	return false262}263264// Graph returns the graph of module requirements loaded from the current265// root modules (as reported by RootModules).266//267// Graph always makes a best effort to load the requirement graph despite any268// errors, and always returns a non-nil *ModuleGraph.269//270// If the requirements of any relevant module fail to load, Graph also271// returns a non-nil error of type *mvs.BuildListError.272func (rs *Requirements) Graph(ld *Loader, ctx context.Context) (*ModuleGraph, error) {273	rs.graphOnce.Do(func() {274		mg, mgErr := readModGraph(ld, ctx, rs.pruning, rs.rootModules, nil)275		rs.graph.Store(&cachedGraph{mg, mgErr})276	})277	cached := rs.graph.Load()278	return cached.mg, cached.err279}280281// IsDirect returns whether the given module provides a package directly282// imported by a package or test in the main module.283func (rs *Requirements) IsDirect(path string) bool {284	return rs.direct[path]285}286287// A ModuleGraph represents the complete graph of module dependencies288// of a main module.289//290// If the main module supports module graph pruning, the graph does not include291// transitive dependencies of non-root (implicit) dependencies.292type ModuleGraph struct {293	g         *mvs.Graph294	loadCache par.ErrCache[module.Version, *modFileSummary]295296	buildListOnce sync.Once297	buildList     []module.Version298299	// checkPathsOnce ensures checkMultiplePaths runs at most once per graph.300	// Errors are checked and reported only on the first call.301	checkPathsOnce sync.Once302}303304var readModGraphDebugOnce sync.Once305306// readModGraph reads and returns the module dependency graph starting at the307// given roots.308//309// The requirements of the module versions found in the unprune map are included310// in the graph even if they would normally be pruned out.311//312// Unlike LoadModGraph, readModGraph does not attempt to diagnose or update313// inconsistent roots.314func readModGraph(ld *Loader, ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) {315	mustHaveGoRoot(roots)316	if pruning == pruned {317		// Enable diagnostics for lazy module loading318		// (https://golang.org/ref/mod#lazy-loading) only if the module graph is319		// pruned.320		//321		// In unpruned modules,we load the module graph much more aggressively (in322		// order to detect inconsistencies that wouldn't be feasible to spot-check),323		// so it wouldn't be useful to log when that occurs (because it happens in324		// normal operation all the time).325		readModGraphDebugOnce.Do(func() {326			for f := range strings.SplitSeq(os.Getenv("GODEBUG"), ",") {327				switch f {328				case "lazymod=log":329					debug.PrintStack()330					fmt.Fprintf(os.Stderr, "go: read full module graph.\n")331				case "lazymod=strict":332					debug.PrintStack()333					base.Fatalf("go: read full module graph (forbidden by GODEBUG=lazymod=strict).")334				}335			}336		})337	}338339	var graphRoots []module.Version340	if ld.inWorkspaceMode() {341		graphRoots = roots342	} else {343		graphRoots = ld.MainModules.Versions()344	}345	var (346		mu       sync.Mutex // guards mg.g and hasError during loading347		hasError bool348		mg       = &ModuleGraph{349			g: mvs.NewGraph(cmpVersion, graphRoots),350		}351	)352353	if pruning != workspace {354		if ld.inWorkspaceMode() {355			panic("pruning is not workspace in workspace mode")356		}357		mg.g.Require(ld.MainModules.mustGetSingleMainModule(ld), roots)358	}359360	type dedupKey struct {361		m       module.Version362		pruning modPruning363	}364	var (365		loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))366		loading   sync.Map // dedupKey → nil; the set of modules that have been or are being loaded367	)368369	// loadOne synchronously loads the explicit requirements for module m.370	// It does not load the transitive requirements of m even if the go version in371	// m's go.mod file indicates that it supports graph pruning.372	loadOne := func(m module.Version) (*modFileSummary, error) {373		return mg.loadCache.Do(m, func() (*modFileSummary, error) {374			summary, err := goModSummary(ld, m)375376			mu.Lock()377			if err == nil {378				mg.g.Require(m, summary.require)379			} else {380				hasError = true381			}382			mu.Unlock()383384			return summary, err385		})386	}387388	var enqueue func(m module.Version, pruning modPruning)389	enqueue = func(m module.Version, pruning modPruning) {390		if m.Version == "none" {391			return392		}393394		if _, dup := loading.LoadOrStore(dedupKey{m, pruning}, nil); dup {395			// m has already been enqueued for loading. Since unpruned loading may396			// follow cycles in the requirement graph, we need to return early397			// to avoid making the load queue infinitely long.398			return399		}400401		loadQueue.Add(func() {402			summary, err := loadOne(m)403			if err != nil {404				return // findError will report the error later.405			}406407			// If the version in m's go.mod file does not support pruning, then we408			// cannot assume that the explicit requirements of m (added by loadOne)409			// are sufficient to build the packages it contains. We must load its full410			// transitive dependency graph to be sure that we see all relevant411			// dependencies. In addition, we must load the requirements of any module412			// that is explicitly marked as unpruned.413			nextPruning := summary.pruning414			if pruning == unpruned {415				nextPruning = unpruned416			}417			for _, r := range summary.require {418				if pruning != pruned || summary.pruning == unpruned || unprune[r] {419					enqueue(r, nextPruning)420				}421			}422		})423	}424425	mustHaveGoRoot(roots)426	for _, m := range roots {427		enqueue(m, pruning)428	}429	<-loadQueue.Idle()430431	// Reload any dependencies of the main modules which are not432	// at their selected versions at workspace mode, because the433	// requirements don't accurately reflect the transitive imports.434	if pruning == workspace {435		// hasDepsInAll contains the set of modules that need to be loaded436		// at workspace pruning because any of their dependencies may437		// provide packages in all.438		hasDepsInAll := make(map[string]bool)439		seen := map[module.Version]bool{}440		for _, m := range roots {441			hasDepsInAll[m.Path] = true442		}443		// This loop will terminate because it will call enqueue on each version of444		// each dependency of the modules in hasDepsInAll at most once (and only445		// calls enqueue on successively increasing versions of each dependency).446		for {447			needsEnqueueing := map[module.Version]bool{}448			for p := range hasDepsInAll {449				m := module.Version{Path: p, Version: mg.g.Selected(p)}450				if !seen[m] {451					needsEnqueueing[m] = true452					continue453				}454				reqs, _ := mg.g.RequiredBy(m)455				for _, r := range reqs {456					s := module.Version{Path: r.Path, Version: mg.g.Selected(r.Path)}457					if gover.ModCompare(r.Path, s.Version, r.Version) > 0 && !seen[s] {458						needsEnqueueing[s] = true459					}460				}461			}462			// add all needs enqueueing to paths we care about463			if len(needsEnqueueing) == 0 {464				break465			}466467			for p := range needsEnqueueing {468				enqueue(p, workspace)469				seen[p] = true470				hasDepsInAll[p.Path] = true471			}472			<-loadQueue.Idle()473		}474	}475476	if hasError {477		return mg, mg.findError()478	}479	return mg, nil480}481482// RequiredBy returns the dependencies required by module m in the graph,483// or ok=false if module m's dependencies are pruned out.484//485// The caller must not modify the returned slice, but may safely append to it486// and may rely on it not to be modified.487func (mg *ModuleGraph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) {488	return mg.g.RequiredBy(m)489}490491// Selected returns the selected version of the module with the given path.492//493// If no version is selected, Selected returns version "none".494func (mg *ModuleGraph) Selected(path string) (version string) {495	return mg.g.Selected(path)496}497498// WalkBreadthFirst invokes f once, in breadth-first order, for each module499// version other than "none" that appears in the graph, regardless of whether500// that version is selected.501func (mg *ModuleGraph) WalkBreadthFirst(f func(m module.Version)) {502	mg.g.WalkBreadthFirst(f)503}504505// BuildList returns the selected versions of all modules present in the graph,506// beginning with the main modules.507//508// The order of the remaining elements in the list is deterministic509// but arbitrary.510//511// The caller must not modify the returned list, but may safely append to it512// and may rely on it not to be modified.513func (mg *ModuleGraph) BuildList() []module.Version {514	mg.buildListOnce.Do(func() {515		mg.buildList = slices.Clip(mg.g.BuildList())516	})517	return mg.buildList518}519520func (mg *ModuleGraph) findError() error {521	errStack := mg.g.FindPath(func(m module.Version) bool {522		_, err := mg.loadCache.Get(m)523		return err != nil && err != par.ErrCacheEntryNotFound524	})525	if len(errStack) > 0 {526		_, err := mg.loadCache.Get(errStack[len(errStack)-1])527		var noUpgrade func(from, to module.Version) bool528		return mvs.NewBuildListError(err, errStack, noUpgrade)529	}530531	return nil532}533534func (mg *ModuleGraph) allRootsSelected(ld *Loader) bool {535	var roots []module.Version536	if ld.inWorkspaceMode() {537		roots = ld.MainModules.Versions()538	} else {539		roots, _ = mg.g.RequiredBy(ld.MainModules.mustGetSingleMainModule(ld))540	}541	for _, m := range roots {542		if mg.Selected(m.Path) != m.Version {543			return false544		}545	}546	return true547}548549// LoadModGraph loads and returns the graph of module dependencies of the main module,550// without loading any packages.551//552// If the goVersion string is non-empty, the returned graph is the graph553// as interpreted by the given Go version (instead of the version indicated554// in the go.mod file).555//556// Modules are loaded automatically (and lazily) in LoadPackages:557// LoadModGraph need only be called if LoadPackages is not,558// typically in commands that care about modules but no particular package.559func LoadModGraph(ld *Loader, ctx context.Context, goVersion string) (*ModuleGraph, error) {560	rs, err := loadModFile(ld, ctx, nil)561	if err != nil {562		return nil, err563	}564565	if goVersion != "" {566		v, _ := rs.rootSelected(ld, "go")567		if gover.Compare(v, gover.GoStrictVersion) >= 0 && gover.Compare(goVersion, v) < 0 {568			return nil, fmt.Errorf("requested Go version %s cannot load module graph (requires Go >= %s)", goVersion, v)569		}570571		pruning := pruningForGoVersion(goVersion)572		if pruning == unpruned && rs.pruning != unpruned {573			// Use newRequirements instead of convertDepth because convertDepth574			// also updates roots; here, we want to report the unmodified roots575			// even though they may seem inconsistent.576			rs = newRequirements(ld, unpruned, rs.rootModules, rs.direct)577		}578579		return rs.Graph(ld, ctx)580	}581582	rs, mg, err := expandGraph(ld, ctx, rs)583	if err != nil {584		return nil, err585	}586	ld.requirements = rs587	return mg, nil588}589590// expandGraph loads the complete module graph from rs.591//592// If the complete graph reveals that some root of rs is not actually the593// selected version of its path, expandGraph computes a new set of roots that594// are consistent. (With a pruned module graph, this may result in upgrades to595// other modules due to requirements that were previously pruned out.)596//597// expandGraph returns the updated roots, along with the module graph loaded598// from those roots and any error encountered while loading that graph.599// expandGraph returns non-nil requirements and a non-nil graph regardless of600// errors. On error, the roots might not be updated to be consistent.601func expandGraph(ld *Loader, ctx context.Context, rs *Requirements) (*Requirements, *ModuleGraph, error) {602	mg, mgErr := rs.Graph(ld, ctx)603	if mgErr != nil {604		// Without the graph, we can't update the roots: we don't know which605		// versions of transitive dependencies would be selected.606		return rs, mg, mgErr607	}608609	if !mg.allRootsSelected(ld) {610		// The roots of rs are not consistent with the rest of the graph. Update611		// them. In an unpruned module this is a no-op for the build list as a whole —612		// it just promotes what were previously transitive requirements to be613		// roots — but in a pruned module it may pull in previously-irrelevant614		// transitive dependencies.615616		newRS, rsErr := updateRoots(ld, ctx, rs.direct, rs, nil, nil, false)617		if rsErr != nil {618			// Failed to update roots, perhaps because of an error in a transitive619			// dependency needed for the update. Return the original Requirements620			// instead.621			return rs, mg, rsErr622		}623		rs = newRS624		mg, mgErr = rs.Graph(ld, ctx)625	}626627	return rs, mg, mgErr628}629630// EditBuildList edits the global build list by first adding every module in add631// to the existing build list, then adjusting versions (and adding or removing632// requirements as needed) until every module in mustSelect is selected at the633// given version.634//635// (Note that the newly-added modules might not be selected in the resulting636// build list: they could be lower than existing requirements or conflict with637// versions in mustSelect.)638//639// If the versions listed in mustSelect are mutually incompatible (due to one of640// the listed modules requiring a higher version of another), EditBuildList641// returns a *ConstraintError and leaves the build list in its previous state.642//643// On success, EditBuildList reports whether the selected version of any module644// in the build list may have been changed (possibly to or from "none") as a645// result.646func EditBuildList(ld *Loader, ctx context.Context, add, mustSelect []module.Version) (changed bool, err error) {647	rs, changed, err := editRequirements(ld, ctx, LoadModFile(ld, ctx), add, mustSelect)648	if err != nil {649		return false, err650	}651	ld.requirements = rs652	return changed, nil653}654655func overrideRoots(ld *Loader, ctx context.Context, rs *Requirements, replace []module.Version) *Requirements {656	drop := make(map[string]bool)657	for _, m := range replace {658		drop[m.Path] = true659	}660	var roots []module.Version661	for _, m := range rs.rootModules {662		if !drop[m.Path] {663			roots = append(roots, m)664		}665	}666	roots = append(roots, replace...)667	gover.ModSort(roots)668	return newRequirements(ld, rs.pruning, roots, rs.direct)669}670671// A ConstraintError describes inconsistent constraints in EditBuildList672type ConstraintError struct {673	// Conflict lists the source of the conflict for each version in mustSelect674	// that could not be selected due to the requirements of some other version in675	// mustSelect.676	Conflicts []Conflict677}678679func (e *ConstraintError) Error() string {680	b := new(strings.Builder)681	b.WriteString("version constraints conflict:")682	for _, c := range e.Conflicts {683		fmt.Fprintf(b, "\n\t%s", c.Summary())684	}685	return b.String()686}687688// A Conflict is a path of requirements starting at a root or proposed root in689// the requirement graph, explaining why that root either causes a module passed690// in the mustSelect list to EditBuildList to be unattainable, or introduces an691// unresolvable error in loading the requirement graph.692type Conflict struct {693	// Path is a path of requirements starting at some module version passed in694	// the mustSelect argument and ending at a module whose requirements make that695	// version unacceptable. (Path always has len ≥ 1.)696	Path []module.Version697698	// If Err is nil, Constraint is a module version passed in the mustSelect699	// argument that has the same module path as, and a lower version than,700	// the last element of the Path slice.701	Constraint module.Version702703	// If Constraint is unset, Err is an error encountered when loading the704	// requirements of the last element in Path.705	Err error706}707708// UnwrapModuleError returns c.Err, but unwraps it if it is a module.ModuleError709// with a version and path matching the last entry in the Path slice.710func (c Conflict) UnwrapModuleError() error {711	me, ok := c.Err.(*module.ModuleError)712	if ok && len(c.Path) > 0 {713		last := c.Path[len(c.Path)-1]714		if me.Path == last.Path && me.Version == last.Version {715			return me.Err716		}717	}718	return c.Err719}720721// Summary returns a string that describes only the first and last modules in722// the conflict path.723func (c Conflict) Summary() string {724	if len(c.Path) == 0 {725		return "(internal error: invalid Conflict struct)"726	}727	first := c.Path[0]728	last := c.Path[len(c.Path)-1]729	if len(c.Path) == 1 {730		if c.Err != nil {731			return fmt.Sprintf("%s: %v", first, c.UnwrapModuleError())732		}733		return fmt.Sprintf("%s is above %s", first, c.Constraint.Version)734	}735736	adverb := ""737	if len(c.Path) > 2 {738		adverb = "indirectly "739	}740	if c.Err != nil {741		return fmt.Sprintf("%s %srequires %s: %v", first, adverb, last, c.UnwrapModuleError())742	}743	return fmt.Sprintf("%s %srequires %s, but %s is requested", first, adverb, last, c.Constraint.Version)744}745746// String returns a string that describes the full conflict path.747func (c Conflict) String() string {748	if len(c.Path) == 0 {749		return "(internal error: invalid Conflict struct)"750	}751	b := new(strings.Builder)752	fmt.Fprintf(b, "%v", c.Path[0])753	if len(c.Path) == 1 {754		fmt.Fprintf(b, " found")755	} else {756		for _, r := range c.Path[1:] {757			fmt.Fprintf(b, " requires\n\t%v", r)758		}759	}760	if c.Constraint != (module.Version{}) {761		fmt.Fprintf(b, ", but %v is requested", c.Constraint.Version)762	}763	if c.Err != nil {764		fmt.Fprintf(b, ": %v", c.UnwrapModuleError())765	}766	return b.String()767}768769// tidyRoots trims the root dependencies to the minimal requirements needed to770// both retain the same versions of all packages in pkgs and satisfy the771// graph-pruning invariants (if applicable).772func tidyRoots(ld *Loader, ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {773	mainModule := ld.MainModules.mustGetSingleMainModule(ld)774	if rs.pruning == unpruned {775		return tidyUnprunedRoots(ld, ctx, mainModule, rs, pkgs)776	}777	return tidyPrunedRoots(ld, ctx, mainModule, rs, pkgs)778}779780func updateRoots(ld *Loader, ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {781	switch rs.pruning {782	case unpruned:783		return updateUnprunedRoots(ld, ctx, direct, rs, add)784	case pruned:785		return updatePrunedRoots(ld, ctx, direct, rs, pkgs, add, rootsImported)786	case workspace:787		return updateWorkspaceRoots(ld, ctx, direct, rs, add)788	default:789		panic(fmt.Sprintf("unsupported pruning mode: %v", rs.pruning))790	}791}792793func updateWorkspaceRoots(ld *Loader, ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) {794	if len(add) != 0 {795		// add should be empty in workspace mode because workspace mode implies796		// -mod=readonly, which in turn implies no new requirements. The code path797		// that would result in add being non-empty returns an error before it798		// reaches this point: The set of modules to add comes from799		// resolveMissingImports, which in turn resolves each package by calling800		// queryImport. But queryImport explicitly checks for -mod=readonly, and801		// return an error.802		panic("add is not empty")803	}804	newRS := newRequirements(ld, workspace, rs.rootModules, direct)805	// The root modules are unchanged (only the direct imports change),806	// so the module graph can be reused to avoid rebuilding it from scratch.807	if cached := rs.graph.Load(); cached != nil {808		newRS.graphOnce.Do(func() { newRS.graph.Store(cached) })809	}810	return newRS, nil811}812813// tidyPrunedRoots returns a minimal set of root requirements that maintains the814// invariants of the go.mod file needed to support graph pruning for the given815// packages:816//817//  1. For each package marked with pkgInAll, the module path that provided that818//     package is included as a root.819//  2. For all packages, the module that provided that package either remains820//     selected at the same version or is upgraded by the dependencies of a821//     root.822//823// If any module that provided a package has been upgraded above its previous824// version, the caller may need to reload and recompute the package graph.825//826// To ensure that the loading process eventually converges, the caller should827// add any needed roots from the tidy root set (without removing existing untidy828// roots) until the set of roots has converged.829func tidyPrunedRoots(ld *Loader, ctx context.Context, mainModule module.Version, old *Requirements, pkgs []*loadPkg) (*Requirements, error) {830	var (831		roots      []module.Version832		pathIsRoot = map[string]bool{mainModule.Path: true}833	)834	if v, ok := old.rootSelected(ld, "go"); ok {835		roots = append(roots, module.Version{Path: "go", Version: v})836		pathIsRoot["go"] = true837	}838	if v, ok := old.rootSelected(ld, "toolchain"); ok {839		roots = append(roots, module.Version{Path: "toolchain", Version: v})840		pathIsRoot["toolchain"] = true841	}842	// We start by adding roots for every package in "all".843	//844	// Once that is done, we may still need to add more roots to cover upgraded or845	// otherwise-missing test dependencies for packages in "all". For those test846	// dependencies, we prefer to add roots for packages with shorter import847	// stacks first, on the theory that the module requirements for those will848	// tend to fill in the requirements for their transitive imports (which have849	// deeper import stacks). So we add the missing dependencies for one depth at850	// a time, starting with the packages actually in "all" and expanding outwards851	// until we have scanned every package that was loaded.852	var (853		queue  []*loadPkg854		queued = map[*loadPkg]bool{}855	)856	for _, pkg := range pkgs {857		if !pkg.flags.has(pkgInAll) {858			continue859		}860		if pkg.fromExternalModule(ld) && !pathIsRoot[pkg.mod.Path] {861			roots = append(roots, pkg.mod)862			pathIsRoot[pkg.mod.Path] = true863		}864		queue = append(queue, pkg)865		queued[pkg] = true866	}867	gover.ModSort(roots)868	tidy := newRequirements(ld, pruned, roots, old.direct)869870	for len(queue) > 0 {871		roots = tidy.rootModules872		mg, err := tidy.Graph(ld, ctx)873		if err != nil {874			return nil, err875		}876877		prevQueue := queue878		queue = nil879		for _, pkg := range prevQueue {880			m := pkg.mod881			if m.Path == "" {882				continue883			}884			for _, dep := range pkg.imports {885				if !queued[dep] {886					queue = append(queue, dep)887					queued[dep] = true888				}889			}890			if pkg.test != nil && !queued[pkg.test] {891				queue = append(queue, pkg.test)892				queued[pkg.test] = true893			}894895			if !pathIsRoot[m.Path] {896				if s := mg.Selected(m.Path); gover.ModCompare(m.Path, s, m.Version) < 0 {897					roots = append(roots, m)898					pathIsRoot[m.Path] = true899				}900			}901		}902903		if len(roots) > len(tidy.rootModules) {904			gover.ModSort(roots)905			tidy = newRequirements(ld, pruned, roots, tidy.direct)906		}907	}908909	roots = tidy.rootModules910	_, err := tidy.Graph(ld, ctx)911	if err != nil {912		return nil, err913	}914915	// We try to avoid adding explicit requirements for test-only dependencies of916	// packages in external modules. However, if we drop the explicit917	// requirements, that may change an import from unambiguous (due to lazy918	// module loading) to ambiguous (because lazy module loading no longer919	// disambiguates it). For any package that has become ambiguous, we try920	// to fix it by promoting its module to an explicit root.921	// (See https://go.dev/issue/60313.)922	q := par.NewQueue(runtime.GOMAXPROCS(0))923	for {924		var disambiguateRoot sync.Map925		for _, pkg := range pkgs {926			if pkg.mod.Path == "" || pathIsRoot[pkg.mod.Path] {927				// Lazy module loading will cause pkg.mod to be checked before any other modules928				// that are only indirectly required. It is as unambiguous as possible.929				continue930			}931			pkg := pkg932			q.Add(func() {933				skipModFile := true934				_, _, _, _, err := importFromModules(ld, ctx, pkg.path, tidy, nil, skipModFile)935				if _, ok := errors.AsType[*AmbiguousImportError](err); ok {936					disambiguateRoot.Store(pkg.mod, true)937				}938			})939		}940		<-q.Idle()941942		disambiguateRoot.Range(func(k, _ any) bool {943			m := k.(module.Version)944			roots = append(roots, m)945			pathIsRoot[m.Path] = true946			return true947		})948949		if len(roots) > len(tidy.rootModules) {950			module.Sort(roots)951			tidy = newRequirements(ld, pruned, roots, tidy.direct)952			_, err = tidy.Graph(ld, ctx)953			if err != nil {954				return nil, err955			}956			// Adding these roots may have pulled additional modules into the module957			// graph, causing additional packages to become ambiguous. Keep iterating958			// until we reach a fixed point.959			continue960		}961962		break963	}964965	return tidy, nil966}967968// updatePrunedRoots returns a set of root requirements that maintains the969// invariants of the go.mod file needed to support graph pruning:970//971//  1. The selected version of the module providing each package marked with972//     either pkgInAll or pkgIsRoot is included as a root.973//     Note that certain root patterns (such as '...') may explode the root set974//     to contain every module that provides any package imported (or merely975//     required) by any other module.976//  2. Each root appears only once, at the selected version of its path977//     (if rs.graph is non-nil) or at the highest version otherwise present as a978//     root (otherwise).979//  3. Every module path that appears as a root in rs remains a root.980//  4. Every version in add is selected at its given version unless upgraded by981//     (the dependencies of) an existing root or another module in add.982//983// The packages in pkgs are assumed to have been loaded from either the roots of984// rs or the modules selected in the graph of rs.985//986// The above invariants together imply the graph-pruning invariants for the987// go.mod file:988//989//  1. (The import invariant.) Every module that provides a package transitively990//     imported by any package or test in the main module is included as a root.991//     This follows by induction from (1) and (3) above. Transitively-imported992//     packages loaded during this invocation are marked with pkgInAll (1),993//     and by hypothesis any transitively-imported packages loaded in previous994//     invocations were already roots in rs (3).995//996//  2. (The argument invariant.) Every module that provides a package matching997//     an explicit package pattern is included as a root. This follows directly998//     from (1): packages matching explicit package patterns are marked with999//     pkgIsRoot.1000//1001//  3. (The completeness invariant.) Every module that contributed any package1002//     to the build is required by either the main module or one of the modules1003//     it requires explicitly. This invariant is left up to the caller, who must1004//     not load packages from outside the module graph but may add roots to the1005//     graph, but is facilitated by (3). If the caller adds roots to the graph in1006//     order to resolve missing packages, then updatePrunedRoots will retain them,1007//     the selected versions of those roots cannot regress, and they will1008//     eventually be written back to the main module's go.mod file.1009//1010// (See https://golang.org/design/36460-lazy-module-loading#invariants for more1011// detail.)1012func updatePrunedRoots(ld *Loader, ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {1013	roots := rs.rootModules1014	rootsUpgraded := false10151016	spotCheckRoot := map[module.Version]bool{}10171018	// “The selected version of the module providing each package marked with1019	// either pkgInAll or pkgIsRoot is included as a root.”1020	needSort := false1021	for _, pkg := range pkgs {1022		if !pkg.fromExternalModule(ld) {1023			// pkg was not loaded from a module dependency, so we don't need1024			// to do anything special to maintain that dependency.1025			continue1026		}10271028		switch {1029		case pkg.flags.has(pkgInAll):1030			// pkg is transitively imported by a package or test in the main module.1031			// We need to promote the module that maintains it to a root: if some1032			// other module depends on the main module, and that other module also1033			// uses a pruned module graph, it will expect to find all of our1034			// transitive dependencies by reading just our go.mod file, not the go.mod1035			// files of everything we depend on.1036			//1037			// (This is the “import invariant” that makes graph pruning possible.)10381039		case rootsImported && pkg.flags.has(pkgFromRoot):1040			// pkg is a transitive dependency of some root, and we are treating the1041			// roots as if they are imported by the main module (as in 'go get').10421043		case pkg.flags.has(pkgIsRoot):1044			// pkg is a root of the package-import graph. (Generally this means that1045			// it matches a command-line argument.) We want future invocations of the1046			// 'go' command — such as 'go test' on the same package — to continue to1047			// use the same versions of its dependencies that we are using right now.1048			// So we need to bring this package's dependencies inside the pruned1049			// module graph.1050			//1051			// Making the module containing this package a root of the module graph1052			// does exactly that: if the module containing the package supports graph1053			// pruning then it should satisfy the import invariant itself, so all of1054			// its dependencies should be in its go.mod file, and if the module1055			// containing the package does not support pruning then if we make it a1056			// root we will load all of its (unpruned) transitive dependencies into1057			// the module graph.1058			//1059			// (This is the “argument invariant”, and is important for1060			// reproducibility.)10611062		default:1063			// pkg is a dependency of some other package outside of the main module.1064			// As far as we know it's not relevant to the main module (and thus not1065			// relevant to consumers of the main module either), and its dependencies1066			// should already be in the module graph — included in the dependencies of1067			// the package that imported it.1068			continue1069		}10701071		if _, ok := rs.rootSelected(ld, pkg.mod.Path); ok {1072			// It is possible that the main module's go.mod file is incomplete or1073			// otherwise erroneous — for example, perhaps the author forgot to 'git1074			// add' their updated go.mod file after adding a new package import, or1075			// perhaps they made an edit to the go.mod file using a third-party tool1076			// ('git merge'?) that doesn't maintain consistency for module1077			// dependencies. If that happens, ideally we want to detect the missing1078			// requirements and fix them up here.1079			//1080			// However, we also need to be careful not to be too aggressive. For1081			// transitive dependencies of external tests, the go.mod file for the1082			// module containing the test itself is expected to provide all of the1083			// relevant dependencies, and we explicitly don't want to pull in1084			// requirements on *irrelevant* requirements that happen to occur in the1085			// go.mod files for these transitive-test-only dependencies. (See the test1086			// in mod_lazy_test_horizon.txt for a concrete example).1087			//1088			// The “goldilocks zone” seems to be to spot-check exactly the same1089			// modules that we promote to explicit roots: namely, those that provide1090			// packages transitively imported by the main module, and those that1091			// provide roots of the package-import graph. That will catch erroneous1092			// edits to the main module's go.mod file and inconsistent requirements in1093			// dependencies that provide imported packages, but will ignore erroneous1094			// or misleading requirements in dependencies that aren't obviously1095			// relevant to the packages in the main module.1096			spotCheckRoot[pkg.mod] = true1097		} else {1098			roots = append(roots, pkg.mod)1099			rootsUpgraded = true1100			// The roots slice was initially sorted because rs.rootModules was sorted,1101			// but the root we just added could be out of order.1102			needSort = true1103		}1104	}11051106	for _, m := range add {1107		if v, ok := rs.rootSelected(ld, m.Path); !ok || gover.ModCompare(m.Path, v, m.Version) < 0 {1108			roots = append(roots, m)1109			rootsUpgraded = true1110			needSort = true1111		}1112	}1113	if needSort {1114		gover.ModSort(roots)1115	}11161117	// "Each root appears only once, at the selected version of its path ….”1118	for {1119		var mg *ModuleGraph1120		if rootsUpgraded {1121			// We've added or upgraded one or more roots, so load the full module1122			// graph so that we can update those roots to be consistent with other1123			// requirements.1124			if mustHaveCompleteRequirements(ld) {1125				// Our changes to the roots may have moved dependencies into or out of1126				// the graph-pruning horizon, which could in turn change the selected1127				// versions of other modules. (For pruned modules adding or removing an1128				// explicit root is a semantic change, not just a cosmetic one.)1129				return rs, errGoModDirty1130			}11311132			rs = newRequirements(ld, pruned, roots, direct)1133			var err error1134			mg, err = rs.Graph(ld, ctx)1135			if err != nil {1136				return rs, err1137			}1138		} else {1139			// Since none of the roots have been upgraded, we have no reason to1140			// suspect that they are inconsistent with the requirements of any other1141			// roots. Only look at the full module graph if we've already loaded it;1142			// otherwise, just spot-check the explicit requirements of the roots from1143			// which we loaded packages.1144			if rs.graph.Load() != nil {1145				// We've already loaded the full module graph, which includes the1146				// requirements of all of the root modules — even the transitive1147				// requirements, if they are unpruned!1148				mg, _ = rs.Graph(ld, ctx)1149			} else if cfg.BuildMod == "vendor" {1150				// We can't spot-check the requirements of other modules because we1151				// don't in general have their go.mod files available in the vendor1152				// directory. (Fortunately this case is impossible, because mg.graph is1153				// always non-nil in vendor mode!)1154				panic("internal error: rs.graph is unexpectedly nil with -mod=vendor")1155			} else if !spotCheckRoots(ld, ctx, rs, spotCheckRoot) {1156				// We spot-checked the explicit requirements of the roots that are1157				// relevant to the packages we've loaded. Unfortunately, they're1158				// inconsistent in some way; we need to load the full module graph1159				// so that we can fix the roots properly.1160				var err error1161				mg, err = rs.Graph(ld, ctx)1162				if err != nil {1163					return rs, err1164				}1165			}1166		}11671168		roots = make([]module.Version, 0, len(rs.rootModules))1169		rootsUpgraded = false1170		inRootPaths := make(map[string]bool, len(rs.rootModules)+1)1171		for _, mm := range ld.MainModules.Versions() {1172			inRootPaths[mm.Path] = true1173		}1174		for _, m := range rs.rootModules {1175			if inRootPaths[m.Path] {1176				// This root specifies a redundant path. We already retained the1177				// selected version of this path when we saw it before, so omit the1178				// redundant copy regardless of its version.1179				//1180				// When we read the full module graph, we include the dependencies of1181				// every root even if that root is redundant. That better preserves1182				// reproducibility if, say, some automated tool adds a redundant1183				// 'require' line and then runs 'go mod tidy' to try to make everything1184				// consistent, since the requirements of the older version are carried1185				// over.1186				//1187				// So omitting a root that was previously present may *reduce* the1188				// selected versions of non-roots, but merely removing a requirement1189				// cannot *increase* the selected versions of other roots as a result —1190				// we don't need to mark this change as an upgrade. (This particular1191				// change cannot invalidate any other roots.)1192				continue1193			}11941195			var v string1196			if mg == nil {1197				v, _ = rs.rootSelected(ld, m.Path)1198			} else {1199				v = mg.Selected(m.Path)1200			}1201			roots = append(roots, module.Version{Path: m.Path, Version: v})1202			inRootPaths[m.Path] = true1203			if v != m.Version {1204				rootsUpgraded = true1205			}1206		}1207		// Note that rs.rootModules was already sorted by module path and version,1208		// and we appended to the roots slice in the same order and guaranteed that1209		// each path has only one version, so roots is also sorted by module path1210		// and (trivially) version.12111212		if !rootsUpgraded {1213			if cfg.BuildMod != "mod" {1214				// The only changes to the root set (if any) were to remove duplicates.1215				// The requirements are consistent (if perhaps redundant), so keep the1216				// original rs to preserve its ModuleGraph.1217				return rs, nil1218			}1219			// The root set has converged: every root going into this iteration was1220			// already at its selected version, although we have removed other1221			// (redundant) roots for the same path.1222			break1223		}1224	}12251226	if rs.pruning == pruned && slices.Equal(roots, rs.rootModules) && maps.Equal(direct, rs.direct) {1227		// The root set is unchanged and rs was already pruned, so keep rs to1228		// preserve its cached ModuleGraph (if any).1229		return rs, nil1230	}1231	return newRequirements(ld, pruned, roots, direct), nil1232}12331234// spotCheckRoots reports whether the versions of the roots in rs satisfy the1235// explicit requirements of the modules in mods.1236func spotCheckRoots(ld *Loader, ctx context.Context, rs *Requirements, mods map[module.Version]bool) bool {1237	ctx, cancel := context.WithCancel(ctx)1238	defer cancel()12391240	work := par.NewQueue(runtime.GOMAXPROCS(0))1241	for m := range mods {1242		m := m1243		work.Add(func() {1244			if ctx.Err() != nil {1245				return1246			}12471248			summary, err := goModSummary(ld, m)1249			if err != nil {1250				cancel()1251				return1252			}12531254			for _, r := range summary.require {1255				if v, ok := rs.rootSelected(ld, r.Path); ok && gover.ModCompare(r.Path, v, r.Version) < 0 {1256					cancel()1257					return1258				}1259			}1260		})1261	}1262	<-work.Idle()12631264	if ctx.Err() != nil {1265		// Either we failed a spot-check, or the caller no longer cares about our1266		// answer anyway.1267		return false1268	}12691270	return true1271}12721273// tidyUnprunedRoots returns a minimal set of root requirements that maintains1274// the selected version of every module that provided or lexically could have1275// provided a package in pkgs, and includes the selected version of every such1276// module in direct as a root.1277func tidyUnprunedRoots(ld *Loader, ctx context.Context, mainModule module.Version, old *Requirements, pkgs []*loadPkg) (*Requirements, error) {1278	var (1279		// keep is a set of modules that provide packages or are needed to1280		// disambiguate imports.1281		keep     []module.Version1282		keptPath = map[string]bool{}12831284		// rootPaths is a list of module paths that provide packages directly1285		// imported from the main module. They should be included as roots.1286		rootPaths   []string1287		inRootPaths = map[string]bool{}12881289		// altMods is a set of paths of modules that lexically could have provided1290		// imported packages. It may be okay to remove these from the list of1291		// explicit requirements if that removes them from the module graph. If they1292		// are present in the module graph reachable from rootPaths, they must not1293		// be at a lower version. That could cause a missing sum error or a new1294		// import ambiguity.1295		//1296		// For example, suppose a developer rewrites imports from example.com/m to1297		// example.com/m/v2, then runs 'go mod tidy'. Tidy may delete the1298		// requirement on example.com/m if there is no other transitive requirement1299		// on it. However, if example.com/m were downgraded to a version not in1300		// go.sum, when package example.com/m/v2/p is loaded, we'd get an error1301		// trying to disambiguate the import, since we can't check example.com/m1302		// without its sum. See #47738.1303		altMods = map[string]string{}1304	)1305	if v, ok := old.rootSelected(ld, "go"); ok {1306		keep = append(keep, module.Version{Path: "go", Version: v})1307		keptPath["go"] = true1308	}1309	if v, ok := old.rootSelected(ld, "toolchain"); ok {1310		keep = append(keep, module.Version{Path: "toolchain", Version: v})1311		keptPath["toolchain"] = true1312	}1313	for _, pkg := range pkgs {1314		if !pkg.fromExternalModule(ld) {1315			continue1316		}1317		if m := pkg.mod; !keptPath[m.Path] {1318			keep = append(keep, m)1319			keptPath[m.Path] = true1320			if old.direct[m.Path] && !inRootPaths[m.Path] {1321				rootPaths = append(rootPaths, m.Path)1322				inRootPaths[m.Path] = true1323			}1324		}1325		for _, m := range pkg.altMods {1326			altMods[m.Path] = m.Version1327		}1328	}13291330	// Construct a build list with a minimal set of roots.1331	// This may remove or downgrade modules in altMods.1332	reqs := &mvsReqs{ld: ld, roots: keep}1333	min, err := mvs.Req(mainModule, rootPaths, reqs)1334	if err != nil {1335		return nil, err1336	}1337	buildList, err := mvs.BuildList([]module.Version{mainModule}, reqs)1338	if err != nil {1339		return nil, err1340	}13411342	// Check if modules in altMods were downgraded but not removed.1343	// If so, add them to roots, which will retain an "// indirect" requirement1344	// in go.mod. See comment on altMods above.1345	keptAltMod := false1346	for _, m := range buildList {1347		if v, ok := altMods[m.Path]; ok && gover.ModCompare(m.Path, m.Version, v) < 0 {1348			keep = append(keep, module.Version{Path: m.Path, Version: v})1349			keptAltMod = true1350		}1351	}1352	if keptAltMod {1353		// We must run mvs.Req again instead of simply adding altMods to min.1354		// It's possible that a requirement in altMods makes some other1355		// explicit indirect requirement unnecessary.1356		reqs.roots = keep1357		min, err = mvs.Req(mainModule, rootPaths, reqs)1358		if err != nil {1359			return nil, err1360		}1361	}13621363	return newRequirements(ld, unpruned, min, old.direct), nil1364}13651366// updateUnprunedRoots returns a set of root requirements that includes the selected1367// version of every module path in direct as a root, and maintains the selected1368// version of every module selected in the graph of rs.1369//1370// The roots are updated such that:1371//1372//  1. The selected version of every module path in direct is included as a root1373//     (if it is not "none").1374//  2. Each root is the selected version of its path. (We say that such a root1375//     set is “consistent”.)1376//  3. Every version selected in the graph of rs remains selected unless upgraded1377//     by a dependency in add.1378//  4. Every version in add is selected at its given version unless upgraded by1379//     (the dependencies of) an existing root or another module in add.1380func updateUnprunedRoots(ld *Loader, ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) {1381	mg, err := rs.Graph(ld, ctx)1382	if err != nil {1383		// We can't ignore errors in the module graph even if the user passed the -e1384		// flag to try to push past them. If we can't load the complete module1385		// dependencies, then we can't reliably compute a minimal subset of them.1386		return rs, err1387	}13881389	if mustHaveCompleteRequirements(ld) {1390		// Instead of actually updating the requirements, just check that no updates1391		// are needed.1392		if rs == nil {1393			// We're being asked to reconstruct the requirements from scratch,1394			// but we aren't even allowed to modify them.1395			return rs, errGoModDirty1396		}1397		for _, m := range rs.rootModules {1398			if m.Version != mg.Selected(m.Path) {1399				// The root version v is misleading: the actual selected version is higher.1400				return rs, errGoModDirty1401			}1402		}1403		for _, m := range add {1404			if m.Version != mg.Selected(m.Path) {1405				return rs, errGoModDirty1406			}1407		}1408		for mPath := range direct {1409			if _, ok := rs.rootSelected(ld, mPath); !ok {1410				// Module m is supposed to be listed explicitly, but isn't.1411				//1412				// Note that this condition is also detected (and logged with more1413				// detail) earlier during package loading, so it shouldn't actually be1414				// possible at this point — this is just a defense in depth.1415				return rs, errGoModDirty1416			}1417		}14181419		// No explicit roots are missing and all roots are already at the versions1420		// we want to keep. Any other changes we would make are purely cosmetic,1421		// such as pruning redundant indirect dependencies. Per issue #34822, we1422		// ignore cosmetic changes when we cannot update the go.mod file.1423		return rs, nil1424	}14251426	var (1427		rootPaths   []string // module paths that should be included as roots1428		inRootPaths = map[string]bool{}1429	)1430	for _, root := range rs.rootModules {1431		// If the selected version of the root is the same as what was already1432		// listed in the go.mod file, retain it as a root (even if redundant) to1433		// avoid unnecessary churn. (See https://golang.org/issue/34822.)1434		//1435		// We do this even for indirect requirements, since we don't know why they1436		// were added and they could become direct at any time.1437		if !inRootPaths[root.Path] && mg.Selected(root.Path) == root.Version {1438			rootPaths = append(rootPaths, root.Path)1439			inRootPaths[root.Path] = true1440		}1441	}14421443	// “The selected version of every module path in direct is included as a root.”1444	//1445	// This is only for convenience and clarity for end users: in an unpruned module,1446	// the choice of explicit vs. implicit dependency has no impact on MVS1447	// selection (for itself or any other module).1448	keep := append(mg.BuildList()[ld.MainModules.Len():], add...)1449	for _, m := range keep {1450		if direct[m.Path] && !inRootPaths[m.Path] {1451			rootPaths = append(rootPaths, m.Path)1452			inRootPaths[m.Path] = true1453		}1454	}14551456	var roots []module.Version1457	for _, mainModule := range ld.MainModules.Versions() {1458		min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{ld: ld, roots: keep})1459		if err != nil {1460			return rs, err1461		}1462		roots = append(roots, min...)1463	}1464	if ld.MainModules.Len() > 1 {1465		gover.ModSort(roots)1466	}1467	if rs.pruning == unpruned && slices.Equal(roots, rs.rootModules) && maps.Equal(direct, rs.direct) {1468		// The root set is unchanged and rs was already unpruned, so keep rs to1469		// preserve its cached ModuleGraph (if any).1470		return rs, nil1471	}14721473	return newRequirements(ld, unpruned, roots, direct), nil1474}14751476// convertPruning returns a version of rs with the given pruning behavior.1477// If rs already has the given pruning, convertPruning returns rs unmodified.1478func convertPruning(ld *Loader, ctx context.Context, rs *Requirements, pruning modPruning) (*Requirements, error) {1479	if rs.pruning == pruning {1480		return rs, nil1481	} else if rs.pruning == workspace || pruning == workspace {1482		panic("attempting to convert to/from workspace pruning and another pruning type")1483	}14841485	if pruning == unpruned {1486		// We are converting a pruned module to an unpruned one. The roots of a1487		// pruned module graph are a superset of the roots of an unpruned one, so1488		// we don't need to add any new roots — we just need to drop the ones that1489		// are redundant, which is exactly what updateUnprunedRoots does.1490		return updateUnprunedRoots(ld, ctx, rs.direct, rs, nil)1491	}14921493	// We are converting an unpruned module to a pruned one.1494	//1495	// An unpruned module graph includes the transitive dependencies of every1496	// module in the build list. As it turns out, we can express that as a pruned1497	// root set! “Include the transitive dependencies of every module in the build1498	// list” is exactly what happens in a pruned module if we promote every module1499	// in the build list to a root.1500	mg, err := rs.Graph(ld, ctx)1501	if err != nil {1502		return rs, err1503	}1504	return newRequirements(ld, pruned, mg.BuildList()[ld.MainModules.Len():], rs.direct), nil1505}

Code quality findings 37

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
roots, _ = mg.g.RequiredBy(ld.MainModules.mustGetSingleMainModule(ld))
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
mg, _ = rs.Graph(ld, ctx)
Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
v, _ = rs.rootSelected(ld, m.Path)
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer cancel()
Ensure errors are handled or logged
warning correctness unhandled-error
if err != nil {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, m := range rootModules {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, m := range rootModules {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
mg.g.Require(m, append(reqs, vendorMod))
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, m := range rs.rootModules {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// Enable diagnostics for lazy module loading
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// (https://golang.org/ref/mod#lazy-loading) only if the module graph is
Manual scheduling hint; usually unnecessary and indicates deeper design issues
info correctness manual-scheduling
loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
hasDepsInAll := make(map[string]bool)
Infinite loop detected; ensure it has a proper exit condition (e.g., break, return) to avoid unintentional resource consumption or hangs
info correctness infinite-loop
for {
Map created without size hint before being populated in a loop; provide capacity hint to reduce allocations
info performance map-without-size-hint
drop := make(map[string]bool)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, module.Version{Path: "go", Version: v})
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, module.Version{Path: "toolchain", Version: v})
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, pkg.mod)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
queue = append(queue, pkg)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
queue = append(queue, dep)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
queue = append(queue, pkg.test)
Manual scheduling hint; usually unnecessary and indicates deeper design issues
info correctness manual-scheduling
q := par.NewQueue(runtime.GOMAXPROCS(0))
Infinite loop detected; ensure it has a proper exit condition (e.g., break, return) to avoid unintentional resource consumption or hangs
info correctness infinite-loop
for {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, pkg.mod)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, m)
Infinite loop detected; ensure it has a proper exit condition (e.g., break, return) to avoid unintentional resource consumption or hangs
info correctness infinite-loop
for {
Manual scheduling hint; usually unnecessary and indicates deeper design issues
info correctness manual-scheduling
work := par.NewQueue(runtime.GOMAXPROCS(0))
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
keep = append(keep, module.Version{Path: "go", Version: v})
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
keep = append(keep, module.Version{Path: "toolchain", Version: v})
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
keep = append(keep, m)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
rootPaths = append(rootPaths, m.Path)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
keep = append(keep, module.Version{Path: m.Path, Version: v})
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
rootPaths = append(rootPaths, root.Path)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
keep := append(mg.BuildList()[ld.MainModules.Len():], add...)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
rootPaths = append(rootPaths, m.Path)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
roots = append(roots, min...)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.