...

Source file src/cuelang.org/go/internal/mod/modload/tidy.go

Documentation: cuelang.org/go/internal/mod/modload

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

View as plain text