1
2
3
4
5
6
7 package mvs
8
9 import (
10 "fmt"
11 "slices"
12 "sort"
13 "sync"
14
15 "cmd/internal/par"
16
17 "golang.org/x/mod/module"
18 )
19
20
21
22
23
24
25
26
27
28
29
30 type Reqs interface {
31
32
33 Required(m module.Version) ([]module.Version, error)
34
35
36
37
38
39
40
41
42
43
44 Max(p, v1, v2 string) string
45 }
46
47
48 type UpgradeReqs interface {
49 Reqs
50
51
52
53
54
55
56
57
58
59
60
61
62 Upgrade(m module.Version) (module.Version, error)
63 }
64
65
66 type DowngradeReqs interface {
67 Reqs
68
69
70
71 Previous(m module.Version) (module.Version, error)
72 }
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 func BuildList(targets []module.Version, reqs Reqs) ([]module.Version, error) {
91 return buildList(targets, reqs, nil)
92 }
93
94 func buildList(targets []module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) {
95 cmp := func(p, v1, v2 string) int {
96 if reqs.Max(p, v1, v2) != v1 {
97 return -1
98 }
99 if reqs.Max(p, v2, v1) != v2 {
100 return 1
101 }
102 return 0
103 }
104
105 var (
106 mu sync.Mutex
107 g = NewGraph(cmp, targets)
108 upgrades = map[module.Version]module.Version{}
109 errs = map[module.Version]error{}
110 )
111
112
113
114 var work par.Work[module.Version]
115 for _, target := range targets {
116 work.Add(target)
117 }
118 work.Do(10, func(m module.Version) {
119
120 var required []module.Version
121 var err error
122 if m.Version != "none" {
123 required, err = reqs.Required(m)
124 }
125
126 u := m
127 if upgrade != nil {
128 upgradeTo, upErr := upgrade(m)
129 if upErr == nil {
130 u = upgradeTo
131 } else if err == nil {
132 err = upErr
133 }
134 }
135
136 mu.Lock()
137 if err != nil {
138 errs[m] = err
139 }
140 if u != m {
141 upgrades[m] = u
142 required = append([]module.Version{u}, required...)
143 }
144 g.Require(m, required)
145 mu.Unlock()
146
147 for _, r := range required {
148 work.Add(r)
149 }
150 })
151
152
153
154 if len(errs) > 0 {
155 errPath := g.FindPath(func(m module.Version) bool {
156 return errs[m] != nil
157 })
158 if len(errPath) == 0 {
159 panic("internal error: could not reconstruct path to module with error")
160 }
161
162 err := errs[errPath[len(errPath)-1]]
163 isUpgrade := func(from, to module.Version) bool {
164 if u, ok := upgrades[from]; ok {
165 return u == to
166 }
167 return false
168 }
169 return nil, NewBuildListError(err, errPath, isUpgrade)
170 }
171
172
173 list := g.BuildList()
174 if vs := list[:len(targets)]; !slices.Equal(vs, targets) {
175
176
177
178
179 panic(fmt.Sprintf("mistake: chose versions %+v instead of targets %+v", vs, targets))
180 }
181 return list, nil
182 }
183
184
185
186
187 func Req(mainModule module.Version, base []string, reqs Reqs) ([]module.Version, error) {
188 list, err := BuildList([]module.Version{mainModule}, reqs)
189 if err != nil {
190 return nil, err
191 }
192
193
194
195
196
197 max := map[string]string{}
198 for _, m := range list {
199 max[m.Path] = m.Version
200 }
201
202
203 var postorder []module.Version
204 reqCache := map[module.Version][]module.Version{}
205 reqCache[mainModule] = nil
206
207 var walk func(module.Version) error
208 walk = func(m module.Version) error {
209 _, ok := reqCache[m]
210 if ok {
211 return nil
212 }
213 required, err := reqs.Required(m)
214 if err != nil {
215 return err
216 }
217 reqCache[m] = required
218 for _, m1 := range required {
219 if err := walk(m1); err != nil {
220 return err
221 }
222 }
223 postorder = append(postorder, m)
224 return nil
225 }
226 for _, m := range list {
227 if err := walk(m); err != nil {
228 return nil, err
229 }
230 }
231
232
233 have := map[module.Version]bool{}
234 walk = func(m module.Version) error {
235 if have[m] {
236 return nil
237 }
238 have[m] = true
239 for _, m1 := range reqCache[m] {
240 walk(m1)
241 }
242 return nil
243 }
244
245 var min []module.Version
246 haveBase := map[string]bool{}
247 for _, path := range base {
248 if haveBase[path] {
249 continue
250 }
251 m := module.Version{Path: path, Version: max[path]}
252 min = append(min, m)
253 walk(m)
254 haveBase[path] = true
255 }
256
257 for i := len(postorder) - 1; i >= 0; i-- {
258 m := postorder[i]
259 if max[m.Path] != m.Version {
260
261 continue
262 }
263 if !have[m] {
264 min = append(min, m)
265 walk(m)
266 }
267 }
268 sort.Slice(min, func(i, j int) bool {
269 return min[i].Path < min[j].Path
270 })
271 return min, nil
272 }
273
274
275
276 func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) {
277 return buildList([]module.Version{target}, reqs, func(m module.Version) (module.Version, error) {
278 if m.Path == target.Path {
279 return target, nil
280 }
281
282 return reqs.Upgrade(m)
283 })
284 }
285
286
287
288 func Upgrade(target module.Version, reqs UpgradeReqs, upgrade ...module.Version) ([]module.Version, error) {
289 list, err := reqs.Required(target)
290 if err != nil {
291 return nil, err
292 }
293
294 pathInList := make(map[string]bool, len(list))
295 for _, m := range list {
296 pathInList[m.Path] = true
297 }
298 list = append([]module.Version(nil), list...)
299
300 upgradeTo := make(map[string]string, len(upgrade))
301 for _, u := range upgrade {
302 if !pathInList[u.Path] {
303 list = append(list, module.Version{Path: u.Path, Version: "none"})
304 }
305 if prev, dup := upgradeTo[u.Path]; dup {
306 upgradeTo[u.Path] = reqs.Max(u.Path, prev, u.Version)
307 } else {
308 upgradeTo[u.Path] = u.Version
309 }
310 }
311
312 return buildList([]module.Version{target}, &override{target, list, reqs}, func(m module.Version) (module.Version, error) {
313 if v, ok := upgradeTo[m.Path]; ok {
314 return module.Version{Path: m.Path, Version: v}, nil
315 }
316 return m, nil
317 })
318 }
319
320
321
322
323
324
325
326
327 func Downgrade(target module.Version, reqs DowngradeReqs, downgrade ...module.Version) ([]module.Version, error) {
328
329
330
331
332
333
334
335 list, err := BuildList([]module.Version{target}, reqs)
336 if err != nil {
337 return nil, err
338 }
339 list = list[1:]
340
341 max := make(map[string]string)
342 for _, r := range list {
343 max[r.Path] = r.Version
344 }
345 for _, d := range downgrade {
346 if v, ok := max[d.Path]; !ok || reqs.Max(d.Path, v, d.Version) != d.Version {
347 max[d.Path] = d.Version
348 }
349 }
350
351 var (
352 added = make(map[module.Version]bool)
353 rdeps = make(map[module.Version][]module.Version)
354 excluded = make(map[module.Version]bool)
355 )
356 var exclude func(module.Version)
357 exclude = func(m module.Version) {
358 if excluded[m] {
359 return
360 }
361 excluded[m] = true
362 for _, p := range rdeps[m] {
363 exclude(p)
364 }
365 }
366 var add func(module.Version)
367 add = func(m module.Version) {
368 if added[m] {
369 return
370 }
371 added[m] = true
372 if v, ok := max[m.Path]; ok && reqs.Max(m.Path, m.Version, v) != v {
373
374
375
376 exclude(m)
377 return
378 }
379 list, err := reqs.Required(m)
380 if err != nil {
381
382
383
384
385
386
387
388
389
390
391 exclude(m)
392 return
393 }
394 for _, r := range list {
395 add(r)
396 if excluded[r] {
397 exclude(m)
398 return
399 }
400 rdeps[r] = append(rdeps[r], m)
401 }
402 }
403
404 downgraded := make([]module.Version, 0, len(list)+1)
405 downgraded = append(downgraded, target)
406 List:
407 for _, r := range list {
408 add(r)
409 for excluded[r] {
410 p, err := reqs.Previous(r)
411 if err != nil {
412
413
414
415
416
417 return nil, err
418 }
419
420
421
422
423 if v := max[r.Path]; reqs.Max(r.Path, v, r.Version) != v && reqs.Max(r.Path, p.Version, v) != p.Version {
424 p.Version = v
425 }
426 if p.Version == "none" {
427 continue List
428 }
429 add(p)
430 r = p
431 }
432 downgraded = append(downgraded, r)
433 }
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450 actual, err := BuildList([]module.Version{target}, &override{
451 target: target,
452 list: downgraded,
453 Reqs: reqs,
454 })
455 if err != nil {
456 return nil, err
457 }
458 actualVersion := make(map[string]string, len(actual))
459 for _, m := range actual {
460 actualVersion[m.Path] = m.Version
461 }
462
463 downgraded = downgraded[:0]
464 for _, m := range list {
465 if v, ok := actualVersion[m.Path]; ok {
466 downgraded = append(downgraded, module.Version{Path: m.Path, Version: v})
467 }
468 }
469
470 return BuildList([]module.Version{target}, &override{
471 target: target,
472 list: downgraded,
473 Reqs: reqs,
474 })
475 }
476
477 type override struct {
478 target module.Version
479 list []module.Version
480 Reqs
481 }
482
483 func (r *override) Required(m module.Version) ([]module.Version, error) {
484 if m == r.target {
485 return r.list, nil
486 }
487 return r.Reqs.Required(m)
488 }
489
View as plain text