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