kcl-lang.io/kpm@v0.8.7-0.20240520061008-9fc4c5efc8c7/pkg/3rdparty/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 "reflect" 12 "sort" 13 "sync" 14 15 "golang.org/x/mod/module" 16 "kcl-lang.io/kpm/pkg/3rdparty/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 // in the module with path p. 36 // 37 // For all versions v, Max(v, "none") must be v, 38 // and for the target passed as the first argument to MVS functions, 39 // Max(target, v) must be target. 40 // 41 // Note that v1 < v2 can be written Max(v1, v2) != v1 42 // and similarly v1 <= v2 can be written Max(v1, v2) == v2. 43 Max(p, v1, v2 string) string 44 } 45 46 // An UpgradeReqs is a Reqs that can also identify available upgrades. 47 type UpgradeReqs interface { 48 Reqs 49 50 // Upgrade returns the upgraded version of m, 51 // for use during an UpgradeAll operation. 52 // If m should be kept as is, Upgrade returns m. 53 // If m is not yet used in the build, then m.Version will be "none". 54 // More typically, m.Version will be the version required 55 // by some other module in the build. 56 // 57 // If no module version is available for the given path, 58 // Upgrade returns a non-nil error. 59 // TODO(rsc): Upgrade must be able to return errors, 60 // but should "no latest version" just return m instead? 61 Upgrade(m module.Version) (module.Version, error) 62 } 63 64 // A DowngradeReqs is a Reqs that can also identify available downgrades. 65 type DowngradeReqs interface { 66 Reqs 67 68 // Previous returns the version of m.Path immediately prior to m.Version, 69 // or "none" if no such version is known. 70 Previous(m module.Version) (module.Version, error) 71 } 72 73 // BuildList returns the build list for the target module. 74 // 75 // target is the root vertex of a module requirement graph. For cmd/go, this is 76 // typically the main module, but note that this algorithm is not intended to 77 // be Go-specific: module paths and versions are treated as opaque values. 78 // 79 // reqs describes the module requirement graph and provides an opaque method 80 // for comparing versions. 81 // 82 // BuildList traverses the graph and returns a list containing the highest 83 // version for each visited module. The first element of the returned list is 84 // target itself; reqs.Max requires target.Version to compare higher than all 85 // other versions, so no other version can be selected. The remaining elements 86 // of the list are sorted by path. 87 // 88 // See https://research.swtch.com/vgo-mvs for details. 89 func BuildList(targets []module.Version, reqs Reqs) ([]module.Version, error) { 90 return buildList(targets, reqs, nil) 91 } 92 93 func buildList(targets []module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) { 94 cmp := func(p, v1, v2 string) int { 95 if reqs.Max(p, v1, v2) != v1 { 96 return -1 97 } 98 if reqs.Max(p, v2, v1) != v2 { 99 return 1 100 } 101 return 0 102 } 103 104 var ( 105 mu sync.Mutex 106 g = NewGraph(cmp, targets) 107 upgrades = map[module.Version]module.Version{} 108 errs = map[module.Version]error{} // (non-nil errors only) 109 ) 110 111 // Explore work graph in parallel in case reqs.Required 112 // does high-latency network operations. 113 var work par.Work[module.Version] 114 for _, target := range targets { 115 work.Add(target) 116 } 117 work.Do(1, func(m module.Version) { 118 119 var required []module.Version 120 var err error 121 if m.Version != "none" { 122 required, err = reqs.Required(m) 123 } 124 125 u := m 126 if upgrade != nil { 127 upgradeTo, upErr := upgrade(m) 128 if upErr == nil { 129 u = upgradeTo 130 } else if err == nil { 131 err = upErr 132 } 133 } 134 135 mu.Lock() 136 if err != nil { 137 errs[m] = err 138 } 139 if u != m { 140 upgrades[m] = u 141 required = append([]module.Version{u}, required...) 142 } 143 g.Require(m, required) 144 mu.Unlock() 145 146 for _, r := range required { 147 work.Add(r) 148 } 149 }) 150 151 // If there was an error, find the shortest path from the target to the 152 // node where the error occurred so we can report a useful error message. 153 if len(errs) > 0 { 154 errPath := g.FindPath(func(m module.Version) bool { 155 return errs[m] != nil 156 }) 157 if len(errPath) == 0 { 158 panic("internal error: could not reconstruct path to module with error") 159 } 160 161 err := errs[errPath[len(errPath)-1]] 162 isUpgrade := func(from, to module.Version) bool { 163 if u, ok := upgrades[from]; ok { 164 return u == to 165 } 166 return false 167 } 168 return nil, NewBuildListError(err, errPath, isUpgrade) 169 } 170 171 // The final list is the minimum version of each module found in the graph. 172 list := g.BuildList() 173 if vs := list[:len(targets)]; !reflect.DeepEqual(vs, targets) { 174 // target.Version will be "" for modload, the main client of MVS. 175 // "" denotes the main module, which has no version. However, MVS treats 176 // version strings as opaque, so "" is not a special value here. 177 // See golang.org/issue/31491, golang.org/issue/29773. 178 panic(fmt.Sprintf("mistake: chose versions %+v instead of targets %+v", vs, targets)) 179 } 180 return list, nil 181 } 182 183 // Req returns the minimal requirement list for the target module, 184 // with the constraint that all module paths listed in base must 185 // appear in the returned list. 186 func Req(mainModule module.Version, base []string, reqs Reqs) ([]module.Version, error) { 187 list, err := BuildList([]module.Version{mainModule}, reqs) 188 if err != nil { 189 return nil, err 190 } 191 192 // Note: Not running in parallel because we assume 193 // that list came from a previous operation that paged 194 // in all the requirements, so there's no I/O to overlap now. 195 196 max := map[string]string{} 197 for _, m := range list { 198 max[m.Path] = m.Version 199 } 200 201 // Compute postorder, cache requirements. 202 var postorder []module.Version 203 reqCache := map[module.Version][]module.Version{} 204 reqCache[mainModule] = nil 205 206 var walk func(module.Version) error 207 walk = func(m module.Version) error { 208 _, ok := reqCache[m] 209 if ok { 210 return nil 211 } 212 required, err := reqs.Required(m) 213 if err != nil { 214 return err 215 } 216 reqCache[m] = required 217 for _, m1 := range required { 218 if err := walk(m1); err != nil { 219 return err 220 } 221 } 222 postorder = append(postorder, m) 223 return nil 224 } 225 for _, m := range list { 226 if err := walk(m); err != nil { 227 return nil, err 228 } 229 } 230 231 // Walk modules in reverse post-order, only adding those not implied already. 232 have := map[module.Version]bool{} 233 walk = func(m module.Version) error { 234 if have[m] { 235 return nil 236 } 237 have[m] = true 238 for _, m1 := range reqCache[m] { 239 walk(m1) 240 } 241 return nil 242 } 243 // First walk the base modules that must be listed. 244 var min []module.Version 245 haveBase := map[string]bool{} 246 for _, path := range base { 247 if haveBase[path] { 248 continue 249 } 250 m := module.Version{Path: path, Version: max[path]} 251 min = append(min, m) 252 walk(m) 253 haveBase[path] = true 254 } 255 // Now the reverse postorder to bring in anything else. 256 for i := len(postorder) - 1; i >= 0; i-- { 257 m := postorder[i] 258 if max[m.Path] != m.Version { 259 // Older version. 260 continue 261 } 262 if !have[m] { 263 min = append(min, m) 264 walk(m) 265 } 266 } 267 sort.Slice(min, func(i, j int) bool { 268 return min[i].Path < min[j].Path 269 }) 270 return min, nil 271 } 272 273 // UpgradeAll returns a build list for the target module 274 // in which every module is upgraded to its latest version. 275 func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) { 276 return buildList([]module.Version{target}, reqs, func(m module.Version) (module.Version, error) { 277 if m.Path == target.Path { 278 return target, nil 279 } 280 281 return reqs.Upgrade(m) 282 }) 283 } 284 285 // Upgrade returns a build list for the target module 286 // in which the given additional modules are upgraded. 287 func Upgrade(target module.Version, reqs UpgradeReqs, upgrade ...module.Version) ([]module.Version, error) { 288 list, err := reqs.Required(target) 289 if err != nil { 290 return nil, err 291 } 292 293 pathInList := make(map[string]bool, len(list)) 294 for _, m := range list { 295 pathInList[m.Path] = true 296 } 297 list = append([]module.Version(nil), list...) 298 299 upgradeTo := make(map[string]string, len(upgrade)) 300 for _, u := range upgrade { 301 if !pathInList[u.Path] { 302 list = append(list, module.Version{Path: u.Path, Version: "none"}) 303 } 304 if prev, dup := upgradeTo[u.Path]; dup { 305 upgradeTo[u.Path] = reqs.Max(u.Path, prev, u.Version) 306 } else { 307 upgradeTo[u.Path] = u.Version 308 } 309 } 310 311 return buildList([]module.Version{target}, &override{target, list, reqs}, func(m module.Version) (module.Version, error) { 312 if v, ok := upgradeTo[m.Path]; ok { 313 return module.Version{Path: m.Path, Version: v}, nil 314 } 315 return m, nil 316 }) 317 } 318 319 // Downgrade returns a build list for the target module 320 // in which the given additional modules are downgraded, 321 // potentially overriding the requirements of the target. 322 // 323 // The versions to be downgraded may be unreachable from reqs.Latest and 324 // reqs.Previous, but the methods of reqs must otherwise handle such versions 325 // correctly. 326 func Downgrade(target module.Version, reqs DowngradeReqs, downgrade ...module.Version) ([]module.Version, error) { 327 // Per https://research.swtch.com/vgo-mvs#algorithm_4: 328 // “To avoid an unnecessary downgrade to E 1.1, we must also add a new 329 // requirement on E 1.2. We can apply Algorithm R to find the minimal set of 330 // new requirements to write to go.mod.” 331 // 332 // In order to generate those new requirements, we need to identify versions 333 // for every module in the build list — not just reqs.Required(target). 334 list, err := BuildList([]module.Version{target}, reqs) 335 if err != nil { 336 return nil, err 337 } 338 list = list[1:] // remove target 339 340 max := make(map[string]string) 341 for _, r := range list { 342 max[r.Path] = r.Version 343 } 344 for _, d := range downgrade { 345 if v, ok := max[d.Path]; !ok || reqs.Max(d.Path, v, d.Version) != d.Version { 346 max[d.Path] = d.Version 347 } 348 } 349 350 var ( 351 added = make(map[module.Version]bool) 352 rdeps = make(map[module.Version][]module.Version) 353 excluded = make(map[module.Version]bool) 354 ) 355 var exclude func(module.Version) 356 exclude = func(m module.Version) { 357 if excluded[m] { 358 return 359 } 360 excluded[m] = true 361 for _, p := range rdeps[m] { 362 exclude(p) 363 } 364 } 365 var add func(module.Version) 366 add = func(m module.Version) { 367 if added[m] { 368 return 369 } 370 added[m] = true 371 if v, ok := max[m.Path]; ok && reqs.Max(m.Path, m.Version, v) != v { 372 // m would upgrade an existing dependency — it is not a strict downgrade, 373 // and because it was already present as a dependency, it could affect the 374 // behavior of other relevant packages. 375 exclude(m) 376 return 377 } 378 list, err := reqs.Required(m) 379 if err != nil { 380 // If we can't load the requirements, we couldn't load the go.mod file. 381 // There are a number of reasons this can happen, but this usually 382 // means an older version of the module had a missing or invalid 383 // go.mod file. For example, if example.com/mod released v2.0.0 before 384 // migrating to modules (v2.0.0+incompatible), then added a valid go.mod 385 // in v2.0.1, downgrading from v2.0.1 would cause this error. 386 // 387 // TODO(golang.org/issue/31730, golang.org/issue/30134): if the error 388 // is transient (we couldn't download go.mod), return the error from 389 // Downgrade. Currently, we can't tell what kind of error it is. 390 exclude(m) 391 return 392 } 393 for _, r := range list { 394 add(r) 395 if excluded[r] { 396 exclude(m) 397 return 398 } 399 rdeps[r] = append(rdeps[r], m) 400 } 401 } 402 403 downgraded := make([]module.Version, 0, len(list)+1) 404 downgraded = append(downgraded, target) 405 List: 406 for _, r := range list { 407 add(r) 408 for excluded[r] { 409 p, err := reqs.Previous(r) 410 if err != nil { 411 // This is likely a transient error reaching the repository, 412 // rather than a permanent error with the retrieved version. 413 // 414 // TODO(golang.org/issue/31730, golang.org/issue/30134): 415 // decode what to do based on the actual error. 416 return nil, err 417 } 418 // If the target version is a pseudo-version, it may not be 419 // included when iterating over prior versions using reqs.Previous. 420 // Insert it into the right place in the iteration. 421 // If v is excluded, p should be returned again by reqs.Previous on the next iteration. 422 if v := max[r.Path]; reqs.Max(r.Path, v, r.Version) != v && reqs.Max(r.Path, p.Version, v) != p.Version { 423 p.Version = v 424 } 425 if p.Version == "none" { 426 continue List 427 } 428 add(p) 429 r = p 430 } 431 downgraded = append(downgraded, r) 432 } 433 434 // The downgrades we computed above only downgrade to versions enumerated by 435 // reqs.Previous. However, reqs.Previous omits some versions — such as 436 // pseudo-versions and retracted versions — that may be selected as transitive 437 // requirements of other modules. 438 // 439 // If one of those requirements pulls the version back up above the version 440 // identified by reqs.Previous, then the transitive dependencies of that that 441 // initially-downgraded version should no longer matter — in particular, we 442 // should not add new dependencies on module paths that nothing else in the 443 // updated module graph even requires. 444 // 445 // In order to eliminate those spurious dependencies, we recompute the build 446 // list with the actual versions of the downgraded modules as selected by MVS, 447 // instead of our initial downgrades. 448 // (See the downhiddenartifact and downhiddencross test cases). 449 actual, err := BuildList([]module.Version{target}, &override{ 450 target: target, 451 list: downgraded, 452 Reqs: reqs, 453 }) 454 if err != nil { 455 return nil, err 456 } 457 actualVersion := make(map[string]string, len(actual)) 458 for _, m := range actual { 459 actualVersion[m.Path] = m.Version 460 } 461 462 downgraded = downgraded[:0] 463 for _, m := range list { 464 if v, ok := actualVersion[m.Path]; ok { 465 downgraded = append(downgraded, module.Version{Path: m.Path, Version: v}) 466 } 467 } 468 469 return BuildList([]module.Version{target}, &override{ 470 target: target, 471 list: downgraded, 472 Reqs: reqs, 473 }) 474 } 475 476 type override struct { 477 target module.Version 478 list []module.Version 479 Reqs 480 } 481 482 func (r *override) Required(m module.Version) ([]module.Version, error) { 483 if m == r.target { 484 return r.list, nil 485 } 486 return r.Reqs.Required(m) 487 }