github.com/sirkon/goproxy@v1.4.8/internal/mvs/mvs.go (about) 1 // Copyright 2018 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 implements Minimal Version Selection. 6 // See https://research.swtch.com/vgo-mvs. 7 package mvs 8 9 import ( 10 "fmt" 11 "sort" 12 "sync" 13 14 "github.com/sirkon/goproxy/internal/base" 15 "github.com/sirkon/goproxy/internal/module" 16 "github.com/sirkon/goproxy/internal/par" 17 ) 18 19 // A Reqs is the requirement graph on which Minimal Version Selection (MVS) operates. 20 // 21 // The version strings are opaque except for the special version "none" 22 // (see the documentation for module.Version). In particular, MVS does not 23 // assume that the version strings are semantic versions; instead, the Max method 24 // gives access to the comparison operation. 25 // 26 // It must be safe to call methods on a Reqs from multiple goroutines simultaneously. 27 // Because a Reqs may read the underlying graph from the network on demand, 28 // the MVS algorithms parallelize the traversal to overlap network delays. 29 type Reqs interface { 30 // Required returns the module versions explicitly required by m itself. 31 // The caller must not modify the returned list. 32 Required(m module.Version) ([]module.Version, error) 33 34 // Max returns the maximum of v1 and v2 (it returns either v1 or v2). 35 // 36 // For all versions v, Max(v, "none") must be v, 37 // and for the tanget passed as the first argument to MVS functions, 38 // Max(target, v) must be target. 39 // 40 // Note that v1 < v2 can be written Max(v1, v2) != v1 41 // and similarly v1 <= v2 can be written Max(v1, v2) == v2. 42 Max(v1, v2 string) string 43 44 // Upgrade returns the upgraded version of m, 45 // for use during an UpgradeAll operation. 46 // If m should be kept as is, Upgrade returns m. 47 // If m is not yet used in the build, then m.Version will be "none". 48 // More typically, m.Version will be the version required 49 // by some other module in the build. 50 // 51 // If no module version is available for the given path, 52 // Upgrade returns a non-nil error. 53 // TODO(rsc): Upgrade must be able to return errors, 54 // but should "no latest version" just return m instead? 55 Upgrade(m module.Version) (module.Version, error) 56 57 // Previous returns the version of m.Path immediately prior to m.Version, 58 // or "none" if no such version is known. 59 Previous(m module.Version) (module.Version, error) 60 } 61 62 type MissingModuleError struct { 63 Module module.Version 64 } 65 66 func (e *MissingModuleError) Error() string { 67 return fmt.Sprintf("missing module: %v", e.Module) 68 } 69 70 // BuildList returns the build list for the target module. 71 func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) { 72 return buildList(target, reqs, nil) 73 } 74 75 func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) module.Version) ([]module.Version, error) { 76 // Explore work graph in parallel in case reqs.Required 77 // does high-latency network operations. 78 var work par.Work 79 work.Add(target) 80 var ( 81 mu sync.Mutex 82 min = map[string]string{target.Path: target.Version} 83 firstErr error 84 ) 85 work.Do(10, func(item interface{}) { 86 m := item.(module.Version) 87 required, err := reqs.Required(m) 88 89 mu.Lock() 90 if err != nil && firstErr == nil { 91 firstErr = err 92 } 93 if firstErr != nil { 94 mu.Unlock() 95 return 96 } 97 if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v { 98 min[m.Path] = m.Version 99 } 100 mu.Unlock() 101 102 for _, r := range required { 103 if r.Path == "" { 104 base.Errorf("Required(%v) returned zero module in list", m) 105 continue 106 } 107 work.Add(r) 108 } 109 110 if upgrade != nil { 111 u := upgrade(m) 112 if u.Path == "" { 113 base.Errorf("Upgrade(%v) returned zero module", m) 114 return 115 } 116 work.Add(u) 117 } 118 }) 119 120 if firstErr != nil { 121 return nil, firstErr 122 } 123 if v := min[target.Path]; v != target.Version { 124 panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic. 125 } 126 127 list := []module.Version{target} 128 listed := map[string]bool{target.Path: true} 129 for i := 0; i < len(list); i++ { 130 m := list[i] 131 required, err := reqs.Required(m) 132 if err != nil { 133 return nil, err 134 } 135 for _, r := range required { 136 v := min[r.Path] 137 if r.Path != target.Path && reqs.Max(v, r.Version) != v { 138 panic(fmt.Sprintf("mistake: version %q does not satisfy requirement %+v", v, r)) // TODO: Don't panic. 139 } 140 if !listed[r.Path] { 141 list = append(list, module.Version{Path: r.Path, Version: v}) 142 listed[r.Path] = true 143 } 144 } 145 } 146 147 tail := list[1:] 148 sort.Slice(tail, func(i, j int) bool { 149 return tail[i].Path < tail[j].Path 150 }) 151 return list, nil 152 } 153 154 // Req returns the minimal requirement list for the target module 155 // that results in the given build list, with the constraint that all 156 // module paths listed in base must appear in the returned list. 157 func Req(target module.Version, list []module.Version, base []string, reqs Reqs) ([]module.Version, error) { 158 // Note: Not running in parallel because we assume 159 // that list came from a previous operation that paged 160 // in all the requirements, so there's no I/O to overlap now. 161 162 // Compute postorder, cache requirements. 163 var postorder []module.Version 164 reqCache := map[module.Version][]module.Version{} 165 reqCache[target] = nil 166 var walk func(module.Version) error 167 walk = func(m module.Version) error { 168 _, ok := reqCache[m] 169 if ok { 170 return nil 171 } 172 required, err := reqs.Required(m) 173 if err != nil { 174 return err 175 } 176 reqCache[m] = required 177 for _, m1 := range required { 178 if err := walk(m1); err != nil { 179 return err 180 } 181 } 182 postorder = append(postorder, m) 183 return nil 184 } 185 for _, m := range list { 186 if err := walk(m); err != nil { 187 return nil, err 188 } 189 } 190 191 // Walk modules in reverse post-order, only adding those not implied already. 192 have := map[string]string{} 193 walk = func(m module.Version) error { 194 if v, ok := have[m.Path]; ok && reqs.Max(m.Version, v) == v { 195 return nil 196 } 197 have[m.Path] = m.Version 198 for _, m1 := range reqCache[m] { 199 walk(m1) 200 } 201 return nil 202 } 203 max := map[string]string{} 204 for _, m := range list { 205 if v, ok := max[m.Path]; ok { 206 max[m.Path] = reqs.Max(m.Version, v) 207 } else { 208 max[m.Path] = m.Version 209 } 210 } 211 // First walk the base modules that must be listed. 212 var min []module.Version 213 for _, path := range base { 214 m := module.Version{Path: path, Version: max[path]} 215 min = append(min, m) 216 walk(m) 217 } 218 // Now the reverse postorder to bring in anything else. 219 for i := len(postorder) - 1; i >= 0; i-- { 220 m := postorder[i] 221 if max[m.Path] != m.Version { 222 // Older version. 223 continue 224 } 225 if have[m.Path] != m.Version { 226 min = append(min, m) 227 walk(m) 228 } 229 } 230 sort.Slice(min, func(i, j int) bool { 231 return min[i].Path < min[j].Path 232 }) 233 return min, nil 234 } 235 236 // UpgradeAll returns a build list for the target module 237 // in which every module is upgraded to its latest version. 238 func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) { 239 return buildList(target, reqs, func(m module.Version) module.Version { 240 if m.Path == target.Path { 241 return target 242 } 243 244 latest, err := reqs.Upgrade(m) 245 if err != nil { 246 panic(err) // TODO 247 } 248 m.Version = latest.Version 249 return m 250 }) 251 } 252 253 // Upgrade returns a build list for the target module 254 // in which the given additional modules are upgraded. 255 func Upgrade(target module.Version, reqs Reqs, upgrade ...module.Version) ([]module.Version, error) { 256 list, err := reqs.Required(target) 257 if err != nil { 258 panic(err) // TODO 259 } 260 // TODO: Maybe if an error is given, 261 // rerun with BuildList(upgrade[0], reqs) etc 262 // to find which ones are the buggy ones. 263 list = append([]module.Version(nil), list...) 264 list = append(list, upgrade...) 265 return BuildList(target, &override{target, list, reqs}) 266 } 267 268 // Downgrade returns a build list for the target module 269 // in which the given additional modules are downgraded. 270 // 271 // The versions to be downgraded may be unreachable from reqs.Latest and 272 // reqs.Previous, but the methods of reqs must otherwise handle such versions 273 // correctly. 274 func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([]module.Version, error) { 275 list, err := reqs.Required(target) 276 if err != nil { 277 panic(err) // TODO 278 } 279 max := make(map[string]string) 280 for _, r := range list { 281 max[r.Path] = r.Version 282 } 283 for _, d := range downgrade { 284 if v, ok := max[d.Path]; !ok || reqs.Max(v, d.Version) != d.Version { 285 max[d.Path] = d.Version 286 } 287 } 288 289 var ( 290 added = make(map[module.Version]bool) 291 rdeps = make(map[module.Version][]module.Version) 292 excluded = make(map[module.Version]bool) 293 ) 294 var exclude func(module.Version) 295 exclude = func(m module.Version) { 296 if excluded[m] { 297 return 298 } 299 excluded[m] = true 300 for _, p := range rdeps[m] { 301 exclude(p) 302 } 303 } 304 var add func(module.Version) 305 add = func(m module.Version) { 306 if added[m] { 307 return 308 } 309 added[m] = true 310 if v, ok := max[m.Path]; ok && reqs.Max(m.Version, v) != v { 311 exclude(m) 312 return 313 } 314 list, err := reqs.Required(m) 315 if err != nil { 316 panic(err) // TODO 317 } 318 for _, r := range list { 319 add(r) 320 if excluded[r] { 321 exclude(m) 322 return 323 } 324 rdeps[r] = append(rdeps[r], m) 325 } 326 } 327 328 var out []module.Version 329 out = append(out, target) 330 List: 331 for _, r := range list { 332 add(r) 333 for excluded[r] { 334 p, err := reqs.Previous(r) 335 if err != nil { 336 return nil, err // TODO 337 } 338 // If the target version is a pseudo-version, it may not be 339 // included when iterating over prior versions using reqs.Previous. 340 // Insert it into the right place in the iteration. 341 // If v is excluded, p should be returned again by reqs.Previous on the next iteration. 342 if v := max[r.Path]; reqs.Max(v, r.Version) != v && reqs.Max(p.Version, v) != p.Version { 343 p.Version = v 344 } 345 if p.Version == "none" { 346 continue List 347 } 348 add(p) 349 r = p 350 } 351 out = append(out, r) 352 } 353 354 return out, nil 355 } 356 357 type override struct { 358 target module.Version 359 list []module.Version 360 Reqs 361 } 362 363 func (r *override) Required(m module.Version) ([]module.Version, error) { 364 if m == r.target { 365 return r.list, nil 366 } 367 return r.Reqs.Required(m) 368 }