1
2
3
4
5 package mvs
6
7 import (
8 "fmt"
9
10 "golang.org/x/mod/module"
11 )
12
13
14
15 type Graph struct {
16 cmp func(v1, v2 string) int
17 roots []module.Version
18
19 required map[module.Version][]module.Version
20
21 isRoot map[module.Version]bool
22 selected map[string]string
23 }
24
25
26
27
28
29
30 func NewGraph(cmp func(v1, v2 string) int, roots []module.Version) *Graph {
31 g := &Graph{
32 cmp: cmp,
33 roots: roots[:len(roots):len(roots)],
34 required: make(map[module.Version][]module.Version),
35 isRoot: make(map[module.Version]bool),
36 selected: make(map[string]string),
37 }
38
39 for _, m := range roots {
40 g.isRoot[m] = true
41 if g.cmp(g.Selected(m.Path), m.Version) < 0 {
42 g.selected[m.Path] = m.Version
43 }
44 }
45
46 return g
47 }
48
49
50
51
52
53
54
55
56
57 func (g *Graph) Require(m module.Version, reqs []module.Version) {
58
59
60
61 if _, reachable := g.isRoot[m]; !reachable {
62 panic(fmt.Sprintf("%v is not reachable from any root", m))
63 }
64
65
66
67 reqs = reqs[:len(reqs):len(reqs)]
68
69 if _, dup := g.required[m]; dup {
70 panic(fmt.Sprintf("requirements of %v have already been set", m))
71 }
72 g.required[m] = reqs
73
74 for _, dep := range reqs {
75
76 if _, ok := g.isRoot[dep]; !ok {
77 g.isRoot[dep] = false
78 }
79
80 if g.cmp(g.Selected(dep.Path), dep.Version) < 0 {
81 g.selected[dep.Path] = dep.Version
82 }
83 }
84 }
85
86
87
88
89
90
91
92 func (g *Graph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) {
93 reqs, ok = g.required[m]
94 return reqs, ok
95 }
96
97
98
99
100 func (g *Graph) Selected(path string) (version string) {
101 v, ok := g.selected[path]
102 if !ok {
103 return "none"
104 }
105 return v
106 }
107
108
109
110
111
112
113 func (g *Graph) BuildList() []module.Version {
114 seenRoot := make(map[string]bool, len(g.roots))
115
116 var list []module.Version
117 for _, r := range g.roots {
118 if seenRoot[r.Path] {
119
120
121
122
123
124
125 continue
126 }
127
128 if v := g.Selected(r.Path); v != "none" {
129 list = append(list, module.Version{Path: r.Path, Version: v})
130 }
131 seenRoot[r.Path] = true
132 }
133 uniqueRoots := list
134
135 for path, version := range g.selected {
136 if !seenRoot[path] {
137 list = append(list, module.Version{Path: path, Version: version})
138 }
139 }
140 module.Sort(list[len(uniqueRoots):])
141
142 return list
143 }
144
145
146
147
148 func (g *Graph) WalkBreadthFirst(f func(m module.Version)) {
149 var queue []module.Version
150 enqueued := make(map[module.Version]bool)
151 for _, m := range g.roots {
152 if m.Version != "none" {
153 queue = append(queue, m)
154 enqueued[m] = true
155 }
156 }
157
158 for len(queue) > 0 {
159 m := queue[0]
160 queue = queue[1:]
161
162 f(m)
163
164 reqs, _ := g.RequiredBy(m)
165 for _, r := range reqs {
166 if !enqueued[r] && r.Version != "none" {
167 queue = append(queue, r)
168 enqueued[r] = true
169 }
170 }
171 }
172 }
173
174
175
176
177 func (g *Graph) FindPath(f func(module.Version) bool) []module.Version {
178
179
180 firstRequires := make(map[module.Version]module.Version)
181
182 queue := g.roots
183 for _, m := range g.roots {
184 firstRequires[m] = module.Version{}
185 }
186
187 for len(queue) > 0 {
188 m := queue[0]
189 queue = queue[1:]
190
191 if f(m) {
192
193
194 path := []module.Version{m}
195 for {
196 m = firstRequires[m]
197 if m.Path == "" {
198 break
199 }
200 path = append(path, m)
201 }
202
203 i, j := 0, len(path)-1
204 for i < j {
205 path[i], path[j] = path[j], path[i]
206 i++
207 j--
208 }
209
210 return path
211 }
212
213 reqs, _ := g.RequiredBy(m)
214 for _, r := range reqs {
215 if _, seen := firstRequires[r]; !seen {
216 queue = append(queue, r)
217 firstRequires[r] = m
218 }
219 }
220 }
221
222 return nil
223 }
224
View as plain text