github.com/golang/dep@v0.5.4/gps/selection.go (about) 1 // Copyright 2017 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 gps 6 7 type selection struct { 8 // projects is a stack of the atoms that have currently been selected by the 9 // solver. It can also be thought of as the vertex set of the current 10 // selection graph. 11 projects []selected 12 // deps records the set of dependers on a given ProjectRoot. It is 13 // essentially an adjacency list of *inbound* edges. 14 deps map[ProjectRoot][]dependency 15 // foldRoots records a mapping from a canonical, case-folded form of 16 // ProjectRoots to the particular case variant that has currently been 17 // selected. 18 foldRoots map[string]ProjectRoot 19 } 20 21 type selected struct { 22 a atomWithPackages 23 first bool 24 } 25 26 func (s *selection) getDependenciesOn(id ProjectIdentifier) []dependency { 27 if deps, exists := s.deps[id.ProjectRoot]; exists { 28 return deps 29 } 30 31 return nil 32 } 33 34 // getIdentFor returns the ProjectIdentifier (so, the network name) currently in 35 // use for the provided ProjectRoot. 36 // 37 // If no dependencies are present yet that designate a network name for 38 // the provided root, this will return an empty ProjectIdentifier and false. 39 func (s *selection) getIdentFor(pr ProjectRoot) (ProjectIdentifier, bool) { 40 deps := s.getDependenciesOn(ProjectIdentifier{ProjectRoot: pr}) 41 if len(deps) == 0 { 42 return ProjectIdentifier{}, false 43 } 44 45 // For now, at least, the solver maintains (assumes?) the invariant that 46 // whatever is first in the deps list decides the net name to be used. 47 return deps[0].dep.Ident, true 48 } 49 50 // pushSelection pushes a new atomWithPackages onto the selection stack, along 51 // with an indicator as to whether this selection indicates a new project *and* 52 // packages, or merely some new packages on a project that was already selected. 53 func (s *selection) pushSelection(a atomWithPackages, pkgonly bool) { 54 s.projects = append(s.projects, selected{ 55 a: a, 56 first: !pkgonly, 57 }) 58 } 59 60 // popSelection removes and returns the last atomWithPackages from the selection 61 // stack, along with an indication of whether that element was the first from 62 // that project - that is, if it represented an addition of both a project and 63 // one or more packages to the overall selection. 64 func (s *selection) popSelection() (atomWithPackages, bool) { 65 var sel selected 66 sel, s.projects = s.projects[len(s.projects)-1], s.projects[:len(s.projects)-1] 67 return sel.a, sel.first 68 } 69 70 // findCaseConflicts checks to see if the given ProjectRoot has a 71 // case-insensitive overlap with another, different ProjectRoot that's already 72 // been picked. 73 func (s *selection) findCaseConflicts(pr ProjectRoot) (bool, ProjectRoot) { 74 if current, has := s.foldRoots[toFold(string(pr))]; has && pr != current { 75 return true, current 76 } 77 78 return false, "" 79 } 80 81 func (s *selection) pushDep(dep dependency) { 82 pr := dep.dep.Ident.ProjectRoot 83 deps := s.deps[pr] 84 if len(deps) == 0 { 85 s.foldRoots[toFold(string(pr))] = pr 86 } 87 88 s.deps[pr] = append(deps, dep) 89 } 90 91 func (s *selection) popDep(id ProjectIdentifier) (dep dependency) { 92 deps := s.deps[id.ProjectRoot] 93 dlen := len(deps) 94 if dlen == 1 { 95 delete(s.foldRoots, toFold(string(id.ProjectRoot))) 96 } 97 98 dep, s.deps[id.ProjectRoot] = deps[dlen-1], deps[:dlen-1] 99 return dep 100 } 101 102 func (s *selection) depperCount(id ProjectIdentifier) int { 103 return len(s.deps[id.ProjectRoot]) 104 } 105 106 // Compute a list of the unique packages within the given ProjectIdentifier that 107 // have dependers, and the number of dependers they have. 108 func (s *selection) getRequiredPackagesIn(id ProjectIdentifier) map[string]int { 109 // TODO(sdboyer) this is horribly inefficient to do on the fly; we need a method to 110 // precompute it on pushing a new dep, and preferably with an immut 111 // structure so that we can pop with zero cost. 112 uniq := make(map[string]int) 113 for _, dep := range s.deps[id.ProjectRoot] { 114 for _, pkg := range dep.dep.pl { 115 uniq[pkg]++ 116 } 117 } 118 119 return uniq 120 } 121 122 // Suppress unused linting warning. 123 var _ = (*selection)(nil).getSelectedPackagesIn 124 var _ = (*selection)(nil).getProjectImportMap 125 126 // Compute a list of the unique packages within the given ProjectIdentifier that 127 // are currently selected, and the number of times each package has been 128 // independently selected. 129 func (s *selection) getSelectedPackagesIn(id ProjectIdentifier) map[string]int { 130 // TODO(sdboyer) this is horribly inefficient to do on the fly; we need a method to 131 // precompute it on pushing a new dep, and preferably with an immut 132 // structure so that we can pop with zero cost. 133 uniq := make(map[string]int) 134 for _, p := range s.projects { 135 if p.a.a.id.eq(id) { 136 for _, pkg := range p.a.pl { 137 uniq[pkg]++ 138 } 139 } 140 } 141 142 return uniq 143 } 144 145 // getProjectImportMap extracts the set of package imports from the used 146 // packages in each selected project. 147 func (s *selection) getProjectImportMap() map[ProjectRoot]map[string]struct{} { 148 importMap := make(map[ProjectRoot]map[string]struct{}) 149 for _, edges := range s.deps { 150 for _, edge := range edges { 151 var curmap map[string]struct{} 152 if imap, has := importMap[edge.depender.id.ProjectRoot]; !has { 153 curmap = make(map[string]struct{}) 154 } else { 155 curmap = imap 156 } 157 158 for _, pl := range edge.dep.pl { 159 curmap[pl] = struct{}{} 160 } 161 importMap[edge.depender.id.ProjectRoot] = curmap 162 } 163 } 164 165 return importMap 166 } 167 168 func (s *selection) getConstraint(id ProjectIdentifier) Constraint { 169 deps, exists := s.deps[id.ProjectRoot] 170 if !exists || len(deps) == 0 { 171 return any 172 } 173 174 // TODO(sdboyer) recomputing this sucks and is quite wasteful. Precompute/cache it 175 // on changes to the constraint set, instead. 176 177 // The solver itself is expected to maintain the invariant that all the 178 // constraints kept here collectively admit a non-empty set of versions. We 179 // assume this is the case here while assembling a composite constraint. 180 181 // Start with the open set 182 var ret Constraint = any 183 for _, dep := range deps { 184 ret = ret.Intersect(dep.dep.Constraint) 185 } 186 187 return ret 188 } 189 190 // selected checks to see if the given ProjectIdentifier has been selected, and 191 // if so, returns the corresponding atomWithPackages. 192 // 193 // It walks the projects selection list from front to back and returns the first 194 // match it finds, which means it will always and only return the base selection 195 // of the project, without any additional package selections that may or may not 196 // have happened later. 197 func (s *selection) selected(id ProjectIdentifier) (atomWithPackages, bool) { 198 for _, p := range s.projects { 199 if p.a.a.id.ProjectRoot == id.ProjectRoot { 200 return p.a, true 201 } 202 } 203 204 return atomWithPackages{a: nilpa}, false 205 } 206 207 type unselected struct { 208 sl []bimodalIdentifier 209 cmp func(i, j int) bool 210 } 211 212 func (u unselected) Len() int { 213 return len(u.sl) 214 } 215 216 func (u unselected) Less(i, j int) bool { 217 return u.cmp(i, j) 218 } 219 220 func (u unselected) Swap(i, j int) { 221 u.sl[i], u.sl[j] = u.sl[j], u.sl[i] 222 } 223 224 func (u *unselected) Push(x interface{}) { 225 u.sl = append(u.sl, x.(bimodalIdentifier)) 226 } 227 228 func (u *unselected) Pop() (v interface{}) { 229 v, u.sl = u.sl[len(u.sl)-1], u.sl[:len(u.sl)-1] 230 return v 231 } 232 233 // remove takes a bimodalIdentifier out of the priority queue, if present. Only 234 // the first matching bmi will be removed. 235 // 236 // There are two events that cause this to be called: bmi selection, when the 237 // bmi at the front of the queue is removed, and backtracking, when a bmi 238 // becomes unnecessary because the dependency that induced it was backtracked 239 // and popped off. 240 // 241 // The worst case for both of these is O(n), but in practice the first case is 242 // O(1), as we iterate the queue from front to back. 243 func (u *unselected) remove(bmi bimodalIdentifier) { 244 plen := len(bmi.pl) 245 outer: 246 for i, pi := range u.sl { 247 if pi.id.eq(bmi.id) && len(pi.pl) == plen { 248 // Simple slice comparison - assume they're both sorted the same 249 for i2, pkg := range pi.pl { 250 if bmi.pl[i2] != pkg { 251 continue outer 252 } 253 } 254 255 if i == len(u.sl)-1 { 256 // if we're on the last element, just pop, no splice 257 u.sl = u.sl[:len(u.sl)-1] 258 } else { 259 u.sl = append(u.sl[:i], u.sl[i+1:]...) 260 } 261 break 262 } 263 } 264 }