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