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