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