cuelang.org/go@v0.10.1/internal/mod/modload/tidy.go (about)

     1  package modload
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"log"
     9  	"maps"
    10  	"path"
    11  	"runtime"
    12  	"slices"
    13  	"strings"
    14  
    15  	"cuelang.org/go/internal/buildattr"
    16  	"cuelang.org/go/internal/mod/modimports"
    17  	"cuelang.org/go/internal/mod/modpkgload"
    18  	"cuelang.org/go/internal/mod/modrequirements"
    19  	"cuelang.org/go/internal/mod/semver"
    20  	"cuelang.org/go/internal/par"
    21  	"cuelang.org/go/mod/modfile"
    22  	"cuelang.org/go/mod/module"
    23  )
    24  
    25  const logging = false // TODO hook this up to CUE_DEBUG
    26  
    27  // Registry is modload's view of a module registry.
    28  type Registry interface {
    29  	modrequirements.Registry
    30  	modpkgload.Registry
    31  	// ModuleVersions returns all the versions for the module with the given path
    32  	// sorted in semver order.
    33  	// If mpath has a major version suffix, only versions with that major version will
    34  	// be returned.
    35  	ModuleVersions(ctx context.Context, mpath string) ([]string, error)
    36  }
    37  
    38  type loader struct {
    39  	mainModule    module.Version
    40  	mainModuleLoc module.SourceLoc
    41  	registry      Registry
    42  	checkTidy     bool
    43  }
    44  
    45  // CheckTidy checks that the module file in the given main module is considered tidy.
    46  // A module file is considered tidy when:
    47  // - it can be parsed OK by [modfile.ParseStrict].
    48  // - it contains a language version in canonical semver form
    49  // - it includes valid modules for all of its dependencies
    50  // - it does not include any unnecessary dependencies.
    51  func CheckTidy(ctx context.Context, fsys fs.FS, modRoot string, reg Registry) error {
    52  	_, err := tidy(ctx, fsys, modRoot, reg, true)
    53  	return err
    54  }
    55  
    56  // Tidy evaluates all the requirements of the given main module, using the given
    57  // registry to download requirements and returns a resolved and tidied module file.
    58  func Tidy(ctx context.Context, fsys fs.FS, modRoot string, reg Registry) (*modfile.File, error) {
    59  	return tidy(ctx, fsys, modRoot, reg, false)
    60  }
    61  
    62  func tidy(ctx context.Context, fsys fs.FS, modRoot string, reg Registry, checkTidy bool) (*modfile.File, error) {
    63  	mainModuleVersion, mf, err := readModuleFile(fsys, modRoot)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	// TODO check that module path is well formed etc
    68  	origRs := modrequirements.NewRequirements(mf.QualifiedModule(), reg, mf.DepVersions(), mf.DefaultMajorVersions())
    69  	// Note: we can ignore build tags and the fact that we might
    70  	// have _tool.cue and _test.cue files, because we want to include
    71  	// all of those, but we do need to consider @ignore() attributes.
    72  	rootPkgPaths, err := modimports.AllImports(withoutIgnoredFiles(modimports.AllModuleFiles(fsys, modRoot)))
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	ld := &loader{
    77  		mainModule: mainModuleVersion,
    78  		registry:   reg,
    79  		mainModuleLoc: module.SourceLoc{
    80  			FS:  fsys,
    81  			Dir: modRoot,
    82  		},
    83  		checkTidy: checkTidy,
    84  	}
    85  
    86  	rs, pkgs, err := ld.resolveDependencies(ctx, rootPkgPaths, origRs)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	for _, pkg := range pkgs.All() {
    91  		if pkg.Error() != nil {
    92  			return nil, fmt.Errorf("failed to resolve %q: %v", pkg.ImportPath(), pkg.Error())
    93  		}
    94  	}
    95  	// TODO check whether it's changed or not.
    96  	rs, err = ld.tidyRoots(ctx, rs, pkgs)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("cannot tidy requirements: %v", err)
    99  	}
   100  	if ld.checkTidy && !equalRequirements(origRs, rs) {
   101  		// TODO: provide a reason, perhaps in structured form rather than a string
   102  		return nil, &ErrModuleNotTidy{}
   103  	}
   104  	return modfileFromRequirements(mf, rs), nil
   105  }
   106  
   107  // ErrModuleNotTidy is returned by CheckTidy when a module is not tidy,
   108  // such as when there are missing or unnecessary dependencies listed.
   109  type ErrModuleNotTidy struct {
   110  	// Reason summarizes why the module is not tidy.
   111  	Reason string
   112  }
   113  
   114  func (e ErrModuleNotTidy) Error() string {
   115  	if e.Reason == "" {
   116  		return "module is not tidy"
   117  	}
   118  	return "module is not tidy: " + e.Reason
   119  }
   120  
   121  func equalRequirements(rs0, rs1 *modrequirements.Requirements) bool {
   122  	// Note that rs1.RootModules may include the unversioned local module
   123  	// if the current module imports any packages under cue.mod/*/.
   124  	// In such a case we want to skip over the local module when comparing,
   125  	// just like modfileFromRequirements does when filling [modfile.File.Deps].
   126  	// Note that we clone the slice to not modify rs1's internal slice in-place.
   127  	rs1RootMods := slices.DeleteFunc(slices.Clone(rs1.RootModules()), module.Version.IsLocal)
   128  	return slices.Equal(rs0.RootModules(), rs1RootMods) &&
   129  		maps.Equal(rs0.DefaultMajorVersions(), rs1.DefaultMajorVersions())
   130  }
   131  
   132  func readModuleFile(fsys fs.FS, modRoot string) (module.Version, *modfile.File, error) {
   133  	modFilePath := path.Join(modRoot, "cue.mod/module.cue")
   134  	data, err := fs.ReadFile(fsys, modFilePath)
   135  	if err != nil {
   136  		return module.Version{}, nil, fmt.Errorf("cannot read cue.mod file: %v", err)
   137  	}
   138  	mf, err := modfile.ParseNonStrict(data, modFilePath)
   139  	if err != nil {
   140  		return module.Version{}, nil, err
   141  	}
   142  	mainModuleVersion, err := module.NewVersion(mf.QualifiedModule(), "")
   143  	if err != nil {
   144  		return module.Version{}, nil, fmt.Errorf("%s: invalid module path: %v", modFilePath, err)
   145  	}
   146  	return mainModuleVersion, mf, nil
   147  }
   148  
   149  func modfileFromRequirements(old *modfile.File, rs *modrequirements.Requirements) *modfile.File {
   150  	// TODO it would be nice to have some way of automatically including new
   151  	// fields by default when they're added to modfile.File, but we don't
   152  	// want to just copy the entirety of old because that includes
   153  	// private fields too.
   154  	mf := &modfile.File{
   155  		Module:   old.Module,
   156  		Language: old.Language,
   157  		Deps:     make(map[string]*modfile.Dep),
   158  		Source:   old.Source,
   159  	}
   160  	defaults := rs.DefaultMajorVersions()
   161  	for _, v := range rs.RootModules() {
   162  		if v.IsLocal() {
   163  			continue
   164  		}
   165  		mf.Deps[v.Path()] = &modfile.Dep{
   166  			Version: v.Version(),
   167  			Default: defaults[v.BasePath()] == semver.Major(v.Version()),
   168  		}
   169  	}
   170  	return mf
   171  }
   172  
   173  // shouldIncludePkgFile reports whether a file from a package should be included
   174  // for dependency-analysis purposes.
   175  //
   176  // In general a file should always be considered unless it's a _tool.cue file
   177  // that's not in the main module.
   178  func (ld *loader) shouldIncludePkgFile(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) (_ok bool) {
   179  	if buildattr.ShouldIgnoreFile(mf.Syntax) {
   180  		// The file is marked to be explicitly ignored.
   181  		return false
   182  	}
   183  	if mod.Path() == ld.mainModule.Path() {
   184  		// All files in the main module are considered.
   185  		return true
   186  	}
   187  	if strings.HasSuffix(mf.FilePath, "_tool.cue") || strings.HasSuffix(mf.FilePath, "_test.cue") {
   188  		// tool and test files are only considered when they are part of the main module.
   189  		return false
   190  	}
   191  	ok, _, err := buildattr.ShouldBuildFile(mf.Syntax, func(key string) bool {
   192  		// Keys of build attributes are considered always false when
   193  		// outside the main module.
   194  		return false
   195  	})
   196  	if err != nil {
   197  		return false
   198  	}
   199  	return ok
   200  }
   201  
   202  func (ld *loader) resolveDependencies(ctx context.Context, rootPkgPaths []string, rs *modrequirements.Requirements) (*modrequirements.Requirements, *modpkgload.Packages, error) {
   203  	for {
   204  		logf("---- LOADING from requirements %q", rs.RootModules())
   205  		pkgs := modpkgload.LoadPackages(ctx, ld.mainModule.Path(), ld.mainModuleLoc, rs, ld.registry, rootPkgPaths, ld.shouldIncludePkgFile)
   206  		if ld.checkTidy {
   207  			for _, pkg := range pkgs.All() {
   208  				err := pkg.Error()
   209  				if err == nil {
   210  					continue
   211  				}
   212  				missingErr := new(modpkgload.ImportMissingError)
   213  				// "cannot find module providing package P" is confusing here,
   214  				// as checkTidy simply points out missing dependencies without fetching them.
   215  				if errors.As(err, &missingErr) {
   216  					err = &ErrModuleNotTidy{Reason: fmt.Sprintf(
   217  						"missing dependency providing package %s", missingErr.Path)}
   218  				}
   219  				return nil, nil, err
   220  			}
   221  			// All packages could be loaded OK so there are no new
   222  			// dependencies to be resolved and nothing to do.
   223  			// Specifically, if there are no packages in error, then
   224  			// resolveMissingImports will never return any entries
   225  			// in modAddedBy and the default major versions won't
   226  			// change.
   227  			return rs, pkgs, nil
   228  		}
   229  
   230  		// TODO the original code calls updateRequirements at this point.
   231  		// /home/rogpeppe/go/src/cmd/go/internal/modload/load.go:1124
   232  
   233  		modAddedBy, defaultMajorVersions := ld.resolveMissingImports(ctx, pkgs, rs)
   234  		if !maps.Equal(defaultMajorVersions, rs.DefaultMajorVersions()) {
   235  			rs = rs.WithDefaultMajorVersions(defaultMajorVersions)
   236  		}
   237  		if len(modAddedBy) == 0 {
   238  			// The roots are stable, and we've resolved all of the missing packages
   239  			// that we can.
   240  			logf("dependencies are stable at %q", rs.RootModules())
   241  			return rs, pkgs, nil
   242  		}
   243  		toAdd := make([]module.Version, 0, len(modAddedBy))
   244  		// TODO use maps.Keys when we can.
   245  		for m, p := range modAddedBy {
   246  			logf("added: %v (by %v)", modAddedBy, p.ImportPath())
   247  			toAdd = append(toAdd, m)
   248  		}
   249  		module.Sort(toAdd) // to make errors deterministic
   250  		oldRs := rs
   251  		var err error
   252  		rs, err = ld.updateRoots(ctx, rs, pkgs, toAdd)
   253  		if err != nil {
   254  			return nil, nil, err
   255  		}
   256  		if slices.Equal(rs.RootModules(), oldRs.RootModules()) {
   257  			// Something is deeply wrong. resolveMissingImports gave us a non-empty
   258  			// set of modules to add to the graph, but adding those modules had no
   259  			// effect — either they were already in the graph, or updateRoots did not
   260  			// add them as requested.
   261  			panic(fmt.Sprintf("internal error: adding %v to module graph had no effect on root requirements (%v)", toAdd, rs.RootModules()))
   262  		}
   263  		logf("after loading, requirements: %v", rs.RootModules())
   264  	}
   265  }
   266  
   267  // updatePrunedRoots returns a set of root requirements that maintains the
   268  // invariants of the cue.mod/module.cue file needed to support graph pruning:
   269  //
   270  //  1. The selected version of the module providing each package marked with
   271  //     either pkgInAll or pkgIsRoot is included as a root.
   272  //     Note that certain root patterns (such as '...') may explode the root set
   273  //     to contain every module that provides any package imported (or merely
   274  //     required) by any other module.
   275  //  2. Each root appears only once, at the selected version of its path
   276  //     (if rs.graph is non-nil) or at the highest version otherwise present as a
   277  //     root (otherwise).
   278  //  3. Every module path that appears as a root in rs remains a root.
   279  //  4. Every version in add is selected at its given version unless upgraded by
   280  //     (the dependencies of) an existing root or another module in add.
   281  //
   282  // The packages in pkgs are assumed to have been loaded from either the roots of
   283  // rs or the modules selected in the graph of rs.
   284  //
   285  // The above invariants together imply the graph-pruning invariants for the
   286  // go.mod file:
   287  //
   288  //  1. (The import invariant.) Every module that provides a package transitively
   289  //     imported by any package or test in the main module is included as a root.
   290  //     This follows by induction from (1) and (3) above. Transitively-imported
   291  //     packages loaded during this invocation are marked with pkgInAll (1),
   292  //     and by hypothesis any transitively-imported packages loaded in previous
   293  //     invocations were already roots in rs (3).
   294  //
   295  //  2. (The argument invariant.) Every module that provides a package matching
   296  //     an explicit package pattern is included as a root. This follows directly
   297  //     from (1): packages matching explicit package patterns are marked with
   298  //     pkgIsRoot.
   299  //
   300  //  3. (The completeness invariant.) Every module that contributed any package
   301  //     to the build is required by either the main module or one of the modules
   302  //     it requires explicitly. This invariant is left up to the caller, who must
   303  //     not load packages from outside the module graph but may add roots to the
   304  //     graph, but is facilitated by (3). If the caller adds roots to the graph in
   305  //     order to resolve missing packages, then updatePrunedRoots will retain them,
   306  //     the selected versions of those roots cannot regress, and they will
   307  //     eventually be written back to the main module's go.mod file.
   308  //
   309  // (See https://golang.org/design/36460-lazy-module-loading#invariants for more
   310  // detail.)
   311  func (ld *loader) updateRoots(ctx context.Context, rs *modrequirements.Requirements, pkgs *modpkgload.Packages, add []module.Version) (*modrequirements.Requirements, error) {
   312  	roots := rs.RootModules()
   313  	rootsUpgraded := false
   314  
   315  	spotCheckRoot := map[module.Version]bool{}
   316  
   317  	// “The selected version of the module providing each package marked with
   318  	// either pkgInAll or pkgIsRoot is included as a root.”
   319  	needSort := false
   320  	for _, pkg := range pkgs.All() {
   321  		if !pkg.Mod().IsValid() || !pkg.FromExternalModule() {
   322  			// pkg was not loaded from a module dependency, so we don't need
   323  			// to do anything special to maintain that dependency.
   324  			continue
   325  		}
   326  
   327  		switch {
   328  		case pkg.HasFlags(modpkgload.PkgInAll):
   329  			// pkg is transitively imported by a package or test in the main module.
   330  			// We need to promote the module that maintains it to a root: if some
   331  			// other module depends on the main module, and that other module also
   332  			// uses a pruned module graph, it will expect to find all of our
   333  			// transitive dependencies by reading just our go.mod file, not the go.mod
   334  			// files of everything we depend on.
   335  			//
   336  			// (This is the “import invariant” that makes graph pruning possible.)
   337  
   338  		case pkg.HasFlags(modpkgload.PkgIsRoot):
   339  			// pkg is a root of the package-import graph. (Generally this means that
   340  			// it matches a command-line argument.) We want future invocations of the
   341  			// 'go' command — such as 'go test' on the same package — to continue to
   342  			// use the same versions of its dependencies that we are using right now.
   343  			// So we need to bring this package's dependencies inside the pruned
   344  			// module graph.
   345  			//
   346  			// Making the module containing this package a root of the module graph
   347  			// does exactly that: if the module containing the package supports graph
   348  			// pruning then it should satisfy the import invariant itself, so all of
   349  			// its dependencies should be in its go.mod file, and if the module
   350  			// containing the package does not support pruning then if we make it a
   351  			// root we will load all of its (unpruned) transitive dependencies into
   352  			// the module graph.
   353  			//
   354  			// (This is the “argument invariant”, and is important for
   355  			// reproducibility.)
   356  
   357  		default:
   358  			// pkg is a dependency of some other package outside of the main module.
   359  			// As far as we know it's not relevant to the main module (and thus not
   360  			// relevant to consumers of the main module either), and its dependencies
   361  			// should already be in the module graph — included in the dependencies of
   362  			// the package that imported it.
   363  			continue
   364  		}
   365  		if _, ok := rs.RootSelected(pkg.Mod().Path()); ok {
   366  			// It is possible that the main module's go.mod file is incomplete or
   367  			// otherwise erroneous — for example, perhaps the author forgot to 'git
   368  			// add' their updated go.mod file after adding a new package import, or
   369  			// perhaps they made an edit to the go.mod file using a third-party tool
   370  			// ('git merge'?) that doesn't maintain consistency for module
   371  			// dependencies. If that happens, ideally we want to detect the missing
   372  			// requirements and fix them up here.
   373  			//
   374  			// However, we also need to be careful not to be too aggressive. For
   375  			// transitive dependencies of external tests, the go.mod file for the
   376  			// module containing the test itself is expected to provide all of the
   377  			// relevant dependencies, and we explicitly don't want to pull in
   378  			// requirements on *irrelevant* requirements that happen to occur in the
   379  			// go.mod files for these transitive-test-only dependencies. (See the test
   380  			// in mod_lazy_test_horizon.txt for a concrete example).
   381  			//
   382  			// The “goldilocks zone” seems to be to spot-check exactly the same
   383  			// modules that we promote to explicit roots: namely, those that provide
   384  			// packages transitively imported by the main module, and those that
   385  			// provide roots of the package-import graph. That will catch erroneous
   386  			// edits to the main module's go.mod file and inconsistent requirements in
   387  			// dependencies that provide imported packages, but will ignore erroneous
   388  			// or misleading requirements in dependencies that aren't obviously
   389  			// relevant to the packages in the main module.
   390  			spotCheckRoot[pkg.Mod()] = true
   391  		} else {
   392  			roots = append(roots, pkg.Mod())
   393  			rootsUpgraded = true
   394  			// The roots slice was initially sorted because rs.rootModules was sorted,
   395  			// but the root we just added could be out of order.
   396  			needSort = true
   397  		}
   398  	}
   399  
   400  	for _, m := range add {
   401  		if !m.IsValid() {
   402  			panic("add contains invalid module")
   403  		}
   404  		if v, ok := rs.RootSelected(m.Path()); !ok || semver.Compare(v, m.Version()) < 0 {
   405  			roots = append(roots, m)
   406  			rootsUpgraded = true
   407  			needSort = true
   408  		}
   409  	}
   410  	if needSort {
   411  		module.Sort(roots)
   412  	}
   413  
   414  	// "Each root appears only once, at the selected version of its path ….”
   415  	for {
   416  		var mg *modrequirements.ModuleGraph
   417  		if rootsUpgraded {
   418  			// We've added or upgraded one or more roots, so load the full module
   419  			// graph so that we can update those roots to be consistent with other
   420  			// requirements.
   421  
   422  			rs = modrequirements.NewRequirements(ld.mainModule.Path(), ld.registry, roots, rs.DefaultMajorVersions())
   423  			var err error
   424  			mg, err = rs.Graph(ctx)
   425  			if err != nil {
   426  				return rs, err
   427  			}
   428  		} else {
   429  			// Since none of the roots have been upgraded, we have no reason to
   430  			// suspect that they are inconsistent with the requirements of any other
   431  			// roots. Only look at the full module graph if we've already loaded it;
   432  			// otherwise, just spot-check the explicit requirements of the roots from
   433  			// which we loaded packages.
   434  			if rs.GraphIsLoaded() {
   435  				// We've already loaded the full module graph, which includes the
   436  				// requirements of all of the root modules — even the transitive
   437  				// requirements, if they are unpruned!
   438  				mg, _ = rs.Graph(ctx)
   439  			} else if !ld.spotCheckRoots(ctx, rs, spotCheckRoot) {
   440  				// We spot-checked the explicit requirements of the roots that are
   441  				// relevant to the packages we've loaded. Unfortunately, they're
   442  				// inconsistent in some way; we need to load the full module graph
   443  				// so that we can fix the roots properly.
   444  				var err error
   445  				mg, err = rs.Graph(ctx)
   446  				if err != nil {
   447  					return rs, err
   448  				}
   449  			}
   450  		}
   451  
   452  		roots = make([]module.Version, 0, len(rs.RootModules()))
   453  		rootsUpgraded = false
   454  		inRootPaths := map[string]bool{
   455  			ld.mainModule.Path(): true,
   456  		}
   457  		for _, m := range rs.RootModules() {
   458  			if inRootPaths[m.Path()] {
   459  				// This root specifies a redundant path. We already retained the
   460  				// selected version of this path when we saw it before, so omit the
   461  				// redundant copy regardless of its version.
   462  				//
   463  				// When we read the full module graph, we include the dependencies of
   464  				// every root even if that root is redundant. That better preserves
   465  				// reproducibility if, say, some automated tool adds a redundant
   466  				// 'require' line and then runs 'go mod tidy' to try to make everything
   467  				// consistent, since the requirements of the older version are carried
   468  				// over.
   469  				//
   470  				// So omitting a root that was previously present may *reduce* the
   471  				// selected versions of non-roots, but merely removing a requirement
   472  				// cannot *increase* the selected versions of other roots as a result —
   473  				// we don't need to mark this change as an upgrade. (This particular
   474  				// change cannot invalidate any other roots.)
   475  				continue
   476  			}
   477  
   478  			var v string
   479  			if mg == nil {
   480  				v, _ = rs.RootSelected(m.Path())
   481  			} else {
   482  				v = mg.Selected(m.Path())
   483  			}
   484  			mv, err := module.NewVersion(m.Path(), v)
   485  			if err != nil {
   486  				return nil, fmt.Errorf("internal error: cannot form module version from %q@%q", m.Path(), v)
   487  			}
   488  			roots = append(roots, mv)
   489  			inRootPaths[m.Path()] = true
   490  			if v != m.Version() {
   491  				rootsUpgraded = true
   492  			}
   493  		}
   494  		// Note that rs.rootModules was already sorted by module path and version,
   495  		// and we appended to the roots slice in the same order and guaranteed that
   496  		// each path has only one version, so roots is also sorted by module path
   497  		// and (trivially) version.
   498  
   499  		if !rootsUpgraded {
   500  			// The root set has converged: every root going into this iteration was
   501  			// already at its selected version, although we have have removed other
   502  			// (redundant) roots for the same path.
   503  			break
   504  		}
   505  	}
   506  
   507  	if slices.Equal(roots, rs.RootModules()) {
   508  		// The root set is unchanged and rs was already pruned, so keep rs to
   509  		// preserve its cached ModuleGraph (if any).
   510  		return rs, nil
   511  	}
   512  	return modrequirements.NewRequirements(ld.mainModule.Path(), ld.registry, roots, rs.DefaultMajorVersions()), nil
   513  }
   514  
   515  // resolveMissingImports returns a set of modules that could be added as
   516  // dependencies in order to resolve missing packages from pkgs.
   517  //
   518  // It returns a map from each new module version to
   519  // the first missing package that module would resolve.
   520  func (ld *loader) resolveMissingImports(ctx context.Context, pkgs *modpkgload.Packages, rs *modrequirements.Requirements) (modAddedBy map[module.Version]*modpkgload.Package, defaultMajorVersions map[string]string) {
   521  	type pkgMod struct {
   522  		pkg          *modpkgload.Package
   523  		needsDefault *bool
   524  		mods         *[]module.Version
   525  	}
   526  	var pkgMods []pkgMod
   527  	work := par.NewQueue(runtime.GOMAXPROCS(0))
   528  	for _, pkg := range pkgs.All() {
   529  		if pkg.Error() == nil {
   530  			continue
   531  		}
   532  		if !errors.As(pkg.Error(), new(*modpkgload.ImportMissingError)) {
   533  			// Leave other errors to be reported outside of the module resolution logic.
   534  			continue
   535  		}
   536  		logf("querying %q", pkg.ImportPath())
   537  		var mods []module.Version // updated asynchronously.
   538  		var needsDefault bool
   539  		work.Add(func() {
   540  			var err error
   541  			mods, needsDefault, err = ld.queryImport(ctx, pkg.ImportPath(), rs)
   542  			if err != nil {
   543  				// pkg.err was already non-nil, so we can reasonably attribute the error
   544  				// for pkg to either the original error or the one returned by
   545  				// queryImport. The existing error indicates only that we couldn't find
   546  				// the package, whereas the query error also explains why we didn't fix
   547  				// the problem — so we prefer the latter.
   548  				pkg.SetError(err)
   549  			}
   550  
   551  			// err is nil, but we intentionally leave pkg.err non-nil: we still haven't satisfied other invariants of a
   552  			// successfully-loaded package, such as scanning and loading the imports
   553  			// of that package. If we succeed in resolving the new dependency graph,
   554  			// the caller can reload pkg and update the error at that point.
   555  			//
   556  			// Even then, the package might not be loaded from the version we've
   557  			// identified here. The module may be upgraded by some other dependency,
   558  			// or by a transitive dependency of mod itself, or — less likely — the
   559  			// package may be rejected by an AllowPackage hook or rendered ambiguous
   560  			// by some other newly-added or newly-upgraded dependency.
   561  		})
   562  
   563  		pkgMods = append(pkgMods, pkgMod{pkg: pkg, mods: &mods, needsDefault: &needsDefault})
   564  	}
   565  	<-work.Idle()
   566  
   567  	modAddedBy = map[module.Version]*modpkgload.Package{}
   568  	defaultMajorVersions = make(map[string]string)
   569  	for m, v := range rs.DefaultMajorVersions() {
   570  		defaultMajorVersions[m] = v
   571  	}
   572  	for _, pm := range pkgMods {
   573  		pkg, mods, needsDefault := pm.pkg, *pm.mods, *pm.needsDefault
   574  		for _, mod := range mods {
   575  			// TODO support logging progress messages like this but without printing to stderr?
   576  			logf("cue: found potential %s in %v", pkg.ImportPath(), mod)
   577  			if modAddedBy[mod] == nil {
   578  				modAddedBy[mod] = pkg
   579  			}
   580  			if needsDefault {
   581  				defaultMajorVersions[mod.BasePath()] = semver.Major(mod.Version())
   582  			}
   583  		}
   584  	}
   585  
   586  	return modAddedBy, defaultMajorVersions
   587  }
   588  
   589  // tidyRoots returns a minimal set of root requirements that maintains the
   590  // invariants of the cue.mod/module.cue file needed to support graph pruning for the given
   591  // packages:
   592  //
   593  //  1. For each package marked with PkgInAll, the module path that provided that
   594  //     package is included as a root.
   595  //  2. For all packages, the module that provided that package either remains
   596  //     selected at the same version or is upgraded by the dependencies of a
   597  //     root.
   598  //
   599  // If any module that provided a package has been upgraded above its previous
   600  // version, the caller may need to reload and recompute the package graph.
   601  //
   602  // To ensure that the loading process eventually converges, the caller should
   603  // add any needed roots from the tidy root set (without removing existing untidy
   604  // roots) until the set of roots has converged.
   605  func (ld *loader) tidyRoots(ctx context.Context, old *modrequirements.Requirements, pkgs *modpkgload.Packages) (*modrequirements.Requirements, error) {
   606  	var (
   607  		roots      []module.Version
   608  		pathIsRoot = map[string]bool{ld.mainModule.Path(): true}
   609  	)
   610  	// We start by adding roots for every package in "all".
   611  	//
   612  	// Once that is done, we may still need to add more roots to cover upgraded or
   613  	// otherwise-missing test dependencies for packages in "all". For those test
   614  	// dependencies, we prefer to add roots for packages with shorter import
   615  	// stacks first, on the theory that the module requirements for those will
   616  	// tend to fill in the requirements for their transitive imports (which have
   617  	// deeper import stacks). So we add the missing dependencies for one depth at
   618  	// a time, starting with the packages actually in "all" and expanding outwards
   619  	// until we have scanned every package that was loaded.
   620  	var (
   621  		queue  []*modpkgload.Package
   622  		queued = map[*modpkgload.Package]bool{}
   623  	)
   624  	for _, pkg := range pkgs.All() {
   625  		if !pkg.HasFlags(modpkgload.PkgInAll) {
   626  			continue
   627  		}
   628  		if pkg.FromExternalModule() && !pathIsRoot[pkg.Mod().Path()] {
   629  			roots = append(roots, pkg.Mod())
   630  			pathIsRoot[pkg.Mod().Path()] = true
   631  		}
   632  		queue = append(queue, pkg)
   633  		queued[pkg] = true
   634  	}
   635  	module.Sort(roots)
   636  	tidy := modrequirements.NewRequirements(ld.mainModule.Path(), ld.registry, roots, old.DefaultMajorVersions())
   637  
   638  	for len(queue) > 0 {
   639  		roots = tidy.RootModules()
   640  		mg, err := tidy.Graph(ctx)
   641  		if err != nil {
   642  			return nil, err
   643  		}
   644  
   645  		prevQueue := queue
   646  		queue = nil
   647  		for _, pkg := range prevQueue {
   648  			m := pkg.Mod()
   649  			if m.Path() == "" {
   650  				continue
   651  			}
   652  			for _, dep := range pkg.Imports() {
   653  				if !queued[dep] {
   654  					queue = append(queue, dep)
   655  					queued[dep] = true
   656  				}
   657  			}
   658  			if !pathIsRoot[m.Path()] {
   659  				if s := mg.Selected(m.Path()); semver.Compare(s, m.Version()) < 0 {
   660  					roots = append(roots, m)
   661  					pathIsRoot[m.Path()] = true
   662  				}
   663  			}
   664  		}
   665  
   666  		if tidyRoots := tidy.RootModules(); len(roots) > len(tidyRoots) {
   667  			module.Sort(roots)
   668  			tidy = modrequirements.NewRequirements(ld.mainModule.Path(), ld.registry, roots, tidy.DefaultMajorVersions())
   669  		}
   670  	}
   671  
   672  	if _, err := tidy.Graph(ctx); err != nil {
   673  		return nil, err
   674  	}
   675  
   676  	// TODO the original code had some logic I don't properly understand,
   677  	// related to https://go.dev/issue/60313, that _may_ be relevant only
   678  	// to test-only dependencies, which we don't have, so leave it out for now.
   679  
   680  	return tidy, nil
   681  }
   682  
   683  // spotCheckRoots reports whether the versions of the roots in rs satisfy the
   684  // explicit requirements of the modules in mods.
   685  func (ld *loader) spotCheckRoots(ctx context.Context, rs *modrequirements.Requirements, mods map[module.Version]bool) bool {
   686  	ctx, cancel := context.WithCancel(ctx)
   687  	defer cancel()
   688  
   689  	work := par.NewQueue(runtime.GOMAXPROCS(0))
   690  	for m := range mods {
   691  		work.Add(func() {
   692  			if ctx.Err() != nil {
   693  				return
   694  			}
   695  
   696  			require, err := ld.registry.Requirements(ctx, m)
   697  			if err != nil {
   698  				cancel()
   699  				return
   700  			}
   701  
   702  			for _, r := range require {
   703  				if v, ok := rs.RootSelected(r.Path()); ok && semver.Compare(v, r.Version()) < 0 {
   704  					cancel()
   705  					return
   706  				}
   707  			}
   708  		})
   709  	}
   710  	<-work.Idle()
   711  
   712  	if ctx.Err() != nil {
   713  		// Either we failed a spot-check, or the caller no longer cares about our
   714  		// answer anyway.
   715  		return false
   716  	}
   717  
   718  	return true
   719  }
   720  
   721  func withoutIgnoredFiles(iter func(func(modimports.ModuleFile, error) bool)) func(func(modimports.ModuleFile, error) bool) {
   722  	return func(yield func(modimports.ModuleFile, error) bool) {
   723  		// TODO for mf, err := range iter {
   724  		iter(func(mf modimports.ModuleFile, err error) bool {
   725  			if err == nil && buildattr.ShouldIgnoreFile(mf.Syntax) {
   726  				return true
   727  			}
   728  			return yield(mf, err)
   729  		})
   730  	}
   731  }
   732  
   733  func logf(f string, a ...any) {
   734  	if logging {
   735  		log.Printf(f, a...)
   736  	}
   737  }