cuelang.org/go@v0.13.0/internal/mod/modrequirements/requirements.go (about) 1 package modrequirements 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "slices" 8 "sync" 9 "sync/atomic" 10 11 "cuelang.org/go/cue/ast" 12 "cuelang.org/go/internal/mod/mvs" 13 "cuelang.org/go/internal/mod/semver" 14 "cuelang.org/go/internal/par" 15 "cuelang.org/go/mod/module" 16 ) 17 18 type majorVersionDefault struct { 19 version string 20 explicitDefault bool 21 ambiguousDefault bool 22 } 23 24 // Requirements holds a set of module requirements. It does not 25 // initially load the full module graph, as that can be expensive. 26 // Instead the [Registry.Graph] method can be used to lazily construct 27 // that. 28 type Requirements struct { 29 registry Registry 30 mainModuleVersion module.Version 31 32 // rootModules is the set of root modules of the graph, sorted and capped to 33 // length. It may contain duplicates, and may contain multiple versions for a 34 // given module path. The root modules are the main module's direct requirements. 35 rootModules []module.Version 36 maxRootVersion map[string]string 37 38 // origDefaultMajorVersions holds the original passed to New. 39 origDefaultMajorVersions map[string]string 40 41 // defaultMajorVersions is derived from the above information, 42 // also holding modules that have a default due to being unique 43 // in the roots. 44 defaultMajorVersions map[string]majorVersionDefault 45 46 graphOnce sync.Once // guards writes to (but not reads from) graph 47 graph atomic.Pointer[cachedGraph] 48 } 49 50 // Registry holds the contents of a registry. It's expected that this will 51 // cache any results that it returns. 52 type Registry interface { 53 Requirements(ctx context.Context, m module.Version) ([]module.Version, error) 54 } 55 56 // A cachedGraph is a non-nil *ModuleGraph, together with any error discovered 57 // while loading that graph. 58 type cachedGraph struct { 59 mg *ModuleGraph 60 err error // If err is non-nil, mg may be incomplete (but must still be non-nil). 61 } 62 63 // NewRequirements returns a new requirement set with the given root modules. 64 // The dependencies of the roots will be loaded lazily from the given 65 // Registry value at the first call to the Graph method. 66 // 67 // The rootModules slice must be sorted according to [module.Sort]. 68 // 69 // The defaultMajorVersions slice holds the default major version for (major-version-less) 70 // mdule paths, if any have been specified. For example {"foo.com/bar": "v0"} specifies 71 // that the default major version for the module `foo.com/bar` is `v0`. 72 // 73 // The caller must not modify rootModules or defaultMajorVersions after passing 74 // them to NewRequirements. 75 func NewRequirements(mainModulePath string, reg Registry, rootModules []module.Version, defaultMajorVersions map[string]string) *Requirements { 76 mainModuleVersion := module.MustNewVersion(mainModulePath, "") 77 // TODO add direct, so we can tell which modules are directly used by the 78 // main module. 79 for i, v := range rootModules { 80 if v.Path() == mainModulePath { 81 panic(fmt.Sprintf("NewRequirements called with untrimmed build list: rootModules[%v] is a main module", i)) 82 } 83 if !v.IsValid() { 84 panic("NewRequirements with invalid zero version") 85 } 86 } 87 rs := &Requirements{ 88 registry: reg, 89 mainModuleVersion: mainModuleVersion, 90 rootModules: rootModules, 91 maxRootVersion: make(map[string]string, len(rootModules)), 92 } 93 for i, m := range rootModules { 94 if i > 0 { 95 prev := rootModules[i-1] 96 if prev.Path() > m.Path() || (prev.Path() == m.Path() && semver.Compare(prev.Version(), m.Version()) > 0) { 97 panic(fmt.Sprintf("NewRequirements called with unsorted roots: %v", rootModules)) 98 } 99 } 100 if v, ok := rs.maxRootVersion[m.Path()]; !ok || semver.Compare(v, m.Version()) < 0 { 101 rs.maxRootVersion[m.Path()] = m.Version() 102 } 103 } 104 rs.initDefaultMajorVersions(defaultMajorVersions) 105 return rs 106 } 107 108 // WithDefaultMajorVersions returns rs but with the given default major versions. 109 // The caller should not modify the map after calling this method. 110 func (rs *Requirements) WithDefaultMajorVersions(defaults map[string]string) *Requirements { 111 rs1 := &Requirements{ 112 registry: rs.registry, 113 mainModuleVersion: rs.mainModuleVersion, 114 rootModules: rs.rootModules, 115 maxRootVersion: rs.maxRootVersion, 116 } 117 // Initialize graph and graphOnce in rs1 to mimic their state in rs. 118 // We can't copy the sync.Once, so if it's already triggered, we'll 119 // run the Once with a no-op function to get the same effect. 120 rs1.graph.Store(rs.graph.Load()) 121 if rs1.GraphIsLoaded() { 122 rs1.graphOnce.Do(func() {}) 123 } 124 rs1.initDefaultMajorVersions(defaults) 125 return rs1 126 } 127 128 func (rs *Requirements) initDefaultMajorVersions(defaultMajorVersions map[string]string) { 129 rs.origDefaultMajorVersions = defaultMajorVersions 130 rs.defaultMajorVersions = make(map[string]majorVersionDefault) 131 for mpath, v := range defaultMajorVersions { 132 if _, _, ok := ast.SplitPackageVersion(mpath); ok { 133 panic(fmt.Sprintf("NewRequirements called with major version in defaultMajorVersions %q", mpath)) 134 } 135 if semver.Major(v) != v { 136 panic(fmt.Sprintf("NewRequirements called with invalid major version %q for module %q", v, mpath)) 137 } 138 rs.defaultMajorVersions[mpath] = majorVersionDefault{ 139 version: v, 140 explicitDefault: true, 141 } 142 } 143 // Add defaults for all modules that have exactly one major version 144 // and no existing default. 145 for _, m := range rs.rootModules { 146 if m.IsLocal() { 147 continue 148 } 149 mpath := m.BasePath() 150 d, ok := rs.defaultMajorVersions[mpath] 151 if !ok { 152 rs.defaultMajorVersions[mpath] = majorVersionDefault{ 153 version: semver.Major(m.Version()), 154 } 155 continue 156 } 157 if d.explicitDefault { 158 continue 159 } 160 d.ambiguousDefault = true 161 rs.defaultMajorVersions[mpath] = d 162 } 163 } 164 165 // RootSelected returns the version of the root dependency with the given module 166 // path, or the zero module.Version and ok=false if the module is not a root 167 // dependency. 168 func (rs *Requirements) RootSelected(mpath string) (version string, ok bool) { 169 if mpath == rs.mainModuleVersion.Path() { 170 return "", true 171 } 172 if v, ok := rs.maxRootVersion[mpath]; ok { 173 return v, true 174 } 175 return "", false 176 } 177 178 // DefaultMajorVersions returns the defaults that the requirements was 179 // created with. The returned map should not be modified. 180 func (rs *Requirements) DefaultMajorVersions() map[string]string { 181 return rs.origDefaultMajorVersions 182 } 183 184 type MajorVersionDefaultStatus byte 185 186 const ( 187 ExplicitDefault MajorVersionDefaultStatus = iota 188 NonExplicitDefault 189 NoDefault 190 AmbiguousDefault 191 ) 192 193 // DefaultMajorVersion returns the default major version for the given 194 // module path (which should not itself contain a major version). 195 // 196 // It also returns information about the default. 197 func (rs *Requirements) DefaultMajorVersion(mpath string) (string, MajorVersionDefaultStatus) { 198 d, ok := rs.defaultMajorVersions[mpath] 199 switch { 200 case !ok: 201 return "", NoDefault 202 case d.ambiguousDefault: 203 return "", AmbiguousDefault 204 case d.explicitDefault: 205 return d.version, ExplicitDefault 206 default: 207 return d.version, NonExplicitDefault 208 } 209 } 210 211 // rootModules returns the set of root modules of the graph, sorted and capped to 212 // length. It may contain duplicates, and may contain multiple versions for a 213 // given module path. 214 func (rs *Requirements) RootModules() []module.Version { 215 return slices.Clip(rs.rootModules) 216 } 217 218 // Graph returns the graph of module requirements loaded from the current 219 // root modules (as reported by RootModules). 220 // 221 // Graph always makes a best effort to load the requirement graph despite any 222 // errors, and always returns a non-nil *ModuleGraph. 223 // 224 // If the requirements of any relevant module fail to load, Graph also 225 // returns a non-nil error of type *mvs.BuildListError. 226 func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) { 227 rs.graphOnce.Do(func() { 228 mg, mgErr := rs.readModGraph(ctx) 229 rs.graph.Store(&cachedGraph{mg, mgErr}) 230 }) 231 cached := rs.graph.Load() 232 return cached.mg, cached.err 233 } 234 235 // GraphIsLoaded reports whether Graph has been called previously. 236 func (rs *Requirements) GraphIsLoaded() bool { 237 return rs.graph.Load() != nil 238 } 239 240 // A ModuleGraph represents the complete graph of module dependencies 241 // of a main module. 242 // 243 // If the main module supports module graph pruning, the graph does not include 244 // transitive dependencies of non-root (implicit) dependencies. 245 type ModuleGraph struct { 246 g *mvs.Graph[module.Version] 247 248 buildListOnce sync.Once 249 buildList []module.Version 250 } 251 252 // cueModSummary returns a summary of the cue.mod/module.cue file for module m, 253 // taking into account any replacements for m, exclusions of its dependencies, 254 // and/or vendoring. 255 // 256 // m must be a version in the module graph, reachable from the Target module. 257 // cueModSummary must not be called for the Target module 258 // itself, as its requirements may change. 259 // 260 // The caller must not modify the returned summary. 261 func (rs *Requirements) cueModSummary(ctx context.Context, m module.Version) (*modFileSummary, error) { 262 require, err := rs.registry.Requirements(ctx, m) 263 if err != nil { 264 return nil, err 265 } 266 // TODO account for replacements, exclusions, etc. 267 return &modFileSummary{ 268 module: m, 269 require: require, 270 }, nil 271 } 272 273 type modFileSummary struct { 274 module module.Version 275 require []module.Version 276 } 277 278 // readModGraph reads and returns the module dependency graph starting at the 279 // given roots. 280 // 281 // readModGraph does not attempt to diagnose or update inconsistent roots. 282 func (rs *Requirements) readModGraph(ctx context.Context) (*ModuleGraph, error) { 283 var ( 284 mu sync.Mutex // guards mg.g and hasError during loading 285 hasError bool 286 mg = &ModuleGraph{ 287 g: mvs.NewGraph[module.Version](module.Versions{}, cmpVersion, []module.Version{rs.mainModuleVersion}), 288 } 289 ) 290 291 mg.g.Require(rs.mainModuleVersion, rs.rootModules) 292 293 var ( 294 loadQueue = par.NewQueue(runtime.GOMAXPROCS(0)) 295 loading sync.Map // module.Version → nil; the set of modules that have been or are being loaded 296 loadCache par.ErrCache[module.Version, *modFileSummary] 297 ) 298 299 // loadOne synchronously loads the explicit requirements for module m. 300 // It does not load the transitive requirements of m. 301 loadOne := func(m module.Version) (*modFileSummary, error) { 302 return loadCache.Do(m, func() (*modFileSummary, error) { 303 summary, err := rs.cueModSummary(ctx, m) 304 305 mu.Lock() 306 if err == nil { 307 mg.g.Require(m, summary.require) 308 } else { 309 hasError = true 310 } 311 mu.Unlock() 312 313 return summary, err 314 }) 315 } 316 317 for _, m := range rs.rootModules { 318 m := m 319 if !m.IsValid() { 320 panic("root module version is invalid") 321 } 322 if m.IsLocal() || m.Version() == "none" { 323 continue 324 } 325 326 if _, dup := loading.LoadOrStore(m, nil); dup { 327 // m has already been enqueued for loading. Since unpruned loading may 328 // follow cycles in the requirement graph, we need to return early 329 // to avoid making the load queue infinitely long. 330 continue 331 } 332 333 loadQueue.Add(func() { 334 loadOne(m) 335 // If there's an error, findError will report it later. 336 }) 337 } 338 <-loadQueue.Idle() 339 340 if hasError { 341 return mg, mg.findError(&loadCache) 342 } 343 return mg, nil 344 } 345 346 // RequiredBy returns the dependencies required by module m in the graph, 347 // or ok=false if module m's dependencies are pruned out. 348 // 349 // The caller must not modify the returned slice, but may safely append to it 350 // and may rely on it not to be modified. 351 func (mg *ModuleGraph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) { 352 return mg.g.RequiredBy(m) 353 } 354 355 // Selected returns the selected version of the module with the given path. 356 // 357 // If no version is selected, Selected returns version "none". 358 func (mg *ModuleGraph) Selected(path string) (version string) { 359 return mg.g.Selected(path) 360 } 361 362 // WalkBreadthFirst invokes f once, in breadth-first order, for each module 363 // version other than "none" that appears in the graph, regardless of whether 364 // that version is selected. 365 func (mg *ModuleGraph) WalkBreadthFirst(f func(m module.Version)) { 366 mg.g.WalkBreadthFirst(f) 367 } 368 369 // BuildList returns the selected versions of all modules present in the graph, 370 // beginning with the main modules. 371 // 372 // The order of the remaining elements in the list is deterministic 373 // but arbitrary. 374 // 375 // The caller must not modify the returned list, but may safely append to it 376 // and may rely on it not to be modified. 377 func (mg *ModuleGraph) BuildList() []module.Version { 378 mg.buildListOnce.Do(func() { 379 mg.buildList = slices.Clip(mg.g.BuildList()) 380 }) 381 return mg.buildList 382 } 383 384 func (mg *ModuleGraph) findError(loadCache *par.ErrCache[module.Version, *modFileSummary]) error { 385 errStack := mg.g.FindPath(func(m module.Version) bool { 386 _, err := loadCache.Get(m) 387 return err != nil && err != par.ErrCacheEntryNotFound 388 }) 389 if len(errStack) > 0 { 390 // TODO it seems that this stack can never be more than one 391 // element long, becasuse readModGraph never goes more 392 // than one depth level below the root requirements. 393 // Given that the top of the stack will always be the main 394 // module and that BuildListError elides the first element 395 // in this case, is it really worth using FindPath? 396 _, err := loadCache.Get(errStack[len(errStack)-1]) 397 var noUpgrade func(from, to module.Version) bool 398 return mvs.NewBuildListError[module.Version](err, errStack, module.Versions{}, noUpgrade) 399 } 400 401 return nil 402 } 403 404 // cmpVersion implements the comparison for versions in the module loader. 405 // 406 // It is consistent with semver.Compare except that as a special case, 407 // the version "" is considered higher than all other versions. 408 // The main module (also known as the target) has no version and must be chosen 409 // over other versions of the same module in the module dependency graph. 410 func cmpVersion(v1, v2 string) int { 411 if v2 == "" { 412 if v1 == "" { 413 return 0 414 } 415 return -1 416 } 417 if v1 == "" { 418 return 1 419 } 420 return semver.Compare(v1, v2) 421 }