cuelang.org/go@v0.10.1/internal/mod/mvs/graph.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package mvs 6 7 import ( 8 "fmt" 9 "slices" 10 "sort" 11 ) 12 13 // Versions is an interface that should be provided by implementations 14 // to define the mvs algorithm in terms of their own version type V, where 15 // a version type holds a (module path, module version) pair. 16 type Versions[V any] interface { 17 // New creates a new instance of V holding the 18 // given module path and version. 19 New(path, version string) (V, error) 20 // Path returns the path part of V. 21 Path(v V) string 22 // Version returns the version part of V. 23 Version(v V) string 24 } 25 26 // Graph implements an incremental version of the MVS algorithm, with the 27 // requirements pushed by the caller instead of pulled by the MVS traversal. 28 type Graph[V comparable] struct { 29 v Versions[V] 30 cmp func(v1, v2 string) int 31 roots []V 32 33 required map[V][]V 34 35 isRoot map[V]bool // contains true for roots and false for reachable non-roots 36 selected map[string]string // path → version 37 } 38 39 // NewGraph returns an incremental MVS graph containing only a set of root 40 // dependencies and using the given max function for version strings. 41 // 42 // The caller must ensure that the root slice is not modified while the Graph 43 // may be in use. 44 func NewGraph[V comparable](v Versions[V], cmp func(string, string) int, roots []V) *Graph[V] { 45 g := &Graph[V]{ 46 v: v, 47 cmp: cmp, 48 roots: slices.Clip(roots), 49 required: make(map[V][]V), 50 isRoot: make(map[V]bool), 51 selected: make(map[string]string), 52 } 53 54 for _, m := range roots { 55 g.isRoot[m] = true 56 if g.cmp(g.Selected(g.v.Path(m)), g.v.Version(m)) < 0 { 57 g.selected[g.v.Path(m)] = g.v.Version(m) 58 } 59 } 60 61 return g 62 } 63 64 // Require adds the information that module m requires all modules in reqs. 65 // The reqs slice must not be modified after it is passed to Require. 66 // 67 // m must be reachable by some existing chain of requirements from g's target, 68 // and Require must not have been called for it already. 69 // 70 // If any of the modules in reqs has the same path as g's target, 71 // the target must have higher precedence than the version in req. 72 func (g *Graph[V]) Require(m V, reqs []V) { 73 // To help catch disconnected-graph bugs, enforce that all required versions 74 // are actually reachable from the roots (and therefore should affect the 75 // selected versions of the modules they name). 76 if _, reachable := g.isRoot[m]; !reachable { 77 panic(fmt.Sprintf("%v is not reachable from any root", m)) 78 } 79 80 // Truncate reqs to its capacity to avoid aliasing bugs if it is later 81 // returned from RequiredBy and appended to. 82 reqs = slices.Clip(reqs) 83 84 if _, dup := g.required[m]; dup { 85 panic(fmt.Sprintf("requirements of %v have already been set", m)) 86 } 87 g.required[m] = reqs 88 89 for _, dep := range reqs { 90 // Mark dep reachable, regardless of whether it is selected. 91 if _, ok := g.isRoot[dep]; !ok { 92 g.isRoot[dep] = false 93 } 94 95 if g.cmp(g.Selected(g.v.Path(dep)), g.v.Version(dep)) < 0 { 96 g.selected[g.v.Path(dep)] = g.v.Version(dep) 97 } 98 } 99 } 100 101 // RequiredBy returns the slice of requirements passed to Require for m, if any, 102 // with its capacity reduced to its length. 103 // If Require has not been called for m, RequiredBy(m) returns ok=false. 104 // 105 // The caller must not modify the returned slice, but may safely append to it 106 // and may rely on it not to be modified. 107 func (g *Graph[V]) RequiredBy(m V) (reqs []V, ok bool) { 108 reqs, ok = g.required[m] 109 return reqs, ok 110 } 111 112 // Selected returns the selected version of the given module path. 113 // 114 // If no version is selected, Selected returns version "none". 115 func (g *Graph[V]) Selected(path string) (version string) { 116 v, ok := g.selected[path] 117 if !ok { 118 return "none" 119 } 120 return v 121 } 122 123 // BuildList returns the selected versions of all modules present in the Graph, 124 // beginning with the selected versions of each module path in the roots of g. 125 // 126 // The order of the remaining elements in the list is deterministic 127 // but arbitrary. 128 func (g *Graph[V]) BuildList() []V { 129 seenRoot := make(map[string]bool, len(g.roots)) 130 131 var list []V 132 for _, r := range g.roots { 133 if seenRoot[g.v.Path(r)] { 134 // Multiple copies of the same root, with the same or different versions, 135 // are a bit of a degenerate case: we will take the transitive 136 // requirements of both roots into account, but only the higher one can 137 // possibly be selected. However — especially given that we need the 138 // seenRoot map for later anyway — it is simpler to support this 139 // degenerate case than to forbid it. 140 continue 141 } 142 143 if v := g.Selected(g.v.Path(r)); v != "none" { 144 list = append(list, g.newVersion(g.v.Path(r), v)) 145 } 146 seenRoot[g.v.Path(r)] = true 147 } 148 uniqueRoots := list 149 150 for path, version := range g.selected { 151 if !seenRoot[path] { 152 list = append(list, g.newVersion(path, version)) 153 } 154 } 155 g.sortVersions(list[len(uniqueRoots):]) 156 return list 157 } 158 159 func (g *Graph[V]) sortVersions(vs []V) { 160 sort.Slice(vs, func(i, j int) bool { 161 v0, v1 := vs[i], vs[j] 162 if p0, p1 := g.v.Path(v0), g.v.Path(v1); p0 != p1 { 163 return p0 < p1 164 } 165 return g.cmp(g.v.Version(v0), g.v.Version(v1)) < 0 166 }) 167 } 168 169 func (g *Graph[V]) newVersion(path string, vers string) V { 170 v, err := g.v.New(path, vers) 171 if err != nil { 172 // Note: can't happen because all paths and versions passed to 173 // g.newVersion have already come from valid paths and versions 174 // returned from a Versions implementation. 175 panic(err) 176 } 177 return v 178 } 179 180 // WalkBreadthFirst invokes f once, in breadth-first order, for each module 181 // version other than "none" that appears in the graph, regardless of whether 182 // that version is selected. 183 func (g *Graph[V]) WalkBreadthFirst(f func(m V)) { 184 var queue []V 185 enqueued := make(map[V]bool) 186 for _, m := range g.roots { 187 if g.v.Version(m) != "none" { 188 queue = append(queue, m) 189 enqueued[m] = true 190 } 191 } 192 193 for len(queue) > 0 { 194 m := queue[0] 195 queue = queue[1:] 196 197 f(m) 198 199 reqs, _ := g.RequiredBy(m) 200 for _, r := range reqs { 201 if !enqueued[r] && g.v.Version(r) != "none" { 202 queue = append(queue, r) 203 enqueued[r] = true 204 } 205 } 206 } 207 } 208 209 // FindPath reports a shortest requirement path starting at one of the roots of 210 // the graph and ending at a module version m for which f(m) returns true, or 211 // nil if no such path exists. 212 func (g *Graph[V]) FindPath(f func(V) bool) []V { 213 // firstRequires[a] = b means that in a breadth-first traversal of the 214 // requirement graph, the module version a was first required by b. 215 firstRequires := make(map[V]V) 216 217 queue := g.roots 218 for _, m := range g.roots { 219 firstRequires[m] = *new(V) 220 } 221 222 for len(queue) > 0 { 223 m := queue[0] 224 queue = queue[1:] 225 226 if f(m) { 227 // Construct the path reversed (because we're starting from the far 228 // endpoint), then reverse it. 229 path := []V{m} 230 for { 231 m = firstRequires[m] 232 if g.v.Path(m) == "" { 233 break 234 } 235 path = append(path, m) 236 } 237 238 i, j := 0, len(path)-1 239 for i < j { 240 path[i], path[j] = path[j], path[i] 241 i++ 242 j-- 243 } 244 245 return path 246 } 247 248 reqs, _ := g.RequiredBy(m) 249 for _, r := range reqs { 250 if _, seen := firstRequires[r]; !seen { 251 queue = append(queue, r) 252 firstRequires[r] = m 253 } 254 } 255 } 256 257 return nil 258 }