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