github.com/sdboyer/gps@v0.16.3/satisfy.go (about) 1 package gps 2 3 // check performs constraint checks on the provided atom. The set of checks 4 // differ slightly depending on whether the atom is pkgonly, or if it's the 5 // entire project being added for the first time. 6 // 7 // The goal is to determine whether selecting the atom would result in a state 8 // where all the solver requirements are still satisfied. 9 func (s *solver) check(a atomWithPackages, pkgonly bool) error { 10 s.mtr.push("satisfy") 11 pa := a.a 12 if nilpa == pa { 13 // This shouldn't be able to happen, but if it does, it unequivocally 14 // indicates a logical bug somewhere, so blowing up is preferable 15 panic("canary - checking version of empty ProjectAtom") 16 } 17 18 // If we're pkgonly, then base atom was already determined to be allowable, 19 // so we can skip the checkAtomAllowable step. 20 if !pkgonly { 21 if err := s.checkAtomAllowable(pa); err != nil { 22 s.traceInfo(err) 23 s.mtr.pop() 24 return err 25 } 26 } 27 28 if err := s.checkRequiredPackagesExist(a); err != nil { 29 s.traceInfo(err) 30 s.mtr.pop() 31 return err 32 } 33 34 _, deps, err := s.getImportsAndConstraintsOf(a) 35 if err != nil { 36 // An err here would be from the package fetcher; pass it straight back 37 // TODO(sdboyer) can we traceInfo this? 38 s.mtr.pop() 39 return err 40 } 41 42 // TODO(sdboyer) this deps list contains only packages not already selected 43 // from the target atom (assuming one is selected at all). It's fine for 44 // now, but won't be good enough when we get around to doing static 45 // analysis. 46 for _, dep := range deps { 47 if err := s.checkIdentMatches(a, dep); err != nil { 48 s.traceInfo(err) 49 s.mtr.pop() 50 return err 51 } 52 if err := s.checkDepsConstraintsAllowable(a, dep); err != nil { 53 s.traceInfo(err) 54 s.mtr.pop() 55 return err 56 } 57 if err := s.checkDepsDisallowsSelected(a, dep); err != nil { 58 s.traceInfo(err) 59 s.mtr.pop() 60 return err 61 } 62 if err := s.checkRevisionExists(a, dep); err != nil { 63 s.traceInfo(err) 64 return err 65 } 66 if err := s.checkPackageImportsFromDepExist(a, dep); err != nil { 67 s.traceInfo(err) 68 s.mtr.pop() 69 return err 70 } 71 72 // TODO(sdboyer) add check that fails if adding this atom would create a loop 73 } 74 75 s.mtr.pop() 76 return nil 77 } 78 79 // checkAtomAllowable ensures that an atom itself is acceptable with respect to 80 // the constraints established by the current solution. 81 func (s *solver) checkAtomAllowable(pa atom) error { 82 constraint := s.sel.getConstraint(pa.id) 83 if s.vUnify.matches(pa.id, constraint, pa.v) { 84 return nil 85 } 86 // TODO(sdboyer) collect constraint failure reason (wait...aren't we, below?) 87 88 deps := s.sel.getDependenciesOn(pa.id) 89 var failparent []dependency 90 for _, dep := range deps { 91 if !s.vUnify.matches(pa.id, dep.dep.Constraint, pa.v) { 92 s.fail(dep.depender.id) 93 failparent = append(failparent, dep) 94 } 95 } 96 97 err := &versionNotAllowedFailure{ 98 goal: pa, 99 failparent: failparent, 100 c: constraint, 101 } 102 103 return err 104 } 105 106 // checkRequiredPackagesExist ensures that all required packages enumerated by 107 // existing dependencies on this atom are actually present in the atom. 108 func (s *solver) checkRequiredPackagesExist(a atomWithPackages) error { 109 ptree, err := s.b.ListPackages(a.a.id, a.a.v) 110 if err != nil { 111 // TODO(sdboyer) handle this more gracefully 112 return err 113 } 114 115 deps := s.sel.getDependenciesOn(a.a.id) 116 fp := make(map[string]errDeppers) 117 // We inspect these in a bit of a roundabout way, in order to incrementally 118 // build up the failure we'd return if there is, indeed, a missing package. 119 // TODO(sdboyer) rechecking all of these every time is wasteful. Is there a shortcut? 120 for _, dep := range deps { 121 for _, pkg := range dep.dep.pl { 122 if errdep, seen := fp[pkg]; seen { 123 errdep.deppers = append(errdep.deppers, dep.depender) 124 fp[pkg] = errdep 125 } else { 126 perr, has := ptree.Packages[pkg] 127 if !has || perr.Err != nil { 128 fp[pkg] = errDeppers{ 129 err: perr.Err, 130 deppers: []atom{dep.depender}, 131 } 132 } 133 } 134 } 135 } 136 137 if len(fp) > 0 { 138 return &checkeeHasProblemPackagesFailure{ 139 goal: a.a, 140 failpkg: fp, 141 } 142 } 143 return nil 144 } 145 146 // checkDepsConstraintsAllowable checks that the constraints of an atom on a 147 // given dep are valid with respect to existing constraints. 148 func (s *solver) checkDepsConstraintsAllowable(a atomWithPackages, cdep completeDep) error { 149 dep := cdep.workingConstraint 150 constraint := s.sel.getConstraint(dep.Ident) 151 // Ensure the constraint expressed by the dep has at least some possible 152 // intersection with the intersection of existing constraints. 153 if s.vUnify.matchesAny(dep.Ident, constraint, dep.Constraint) { 154 return nil 155 } 156 157 siblings := s.sel.getDependenciesOn(dep.Ident) 158 // No admissible versions - visit all siblings and identify the disagreement(s) 159 var failsib []dependency 160 var nofailsib []dependency 161 for _, sibling := range siblings { 162 if !s.vUnify.matchesAny(dep.Ident, sibling.dep.Constraint, dep.Constraint) { 163 s.fail(sibling.depender.id) 164 failsib = append(failsib, sibling) 165 } else { 166 nofailsib = append(nofailsib, sibling) 167 } 168 } 169 170 return &disjointConstraintFailure{ 171 goal: dependency{depender: a.a, dep: cdep}, 172 failsib: failsib, 173 nofailsib: nofailsib, 174 c: constraint, 175 } 176 } 177 178 // checkDepsDisallowsSelected ensures that an atom's constraints on a particular 179 // dep are not incompatible with the version of that dep that's already been 180 // selected. 181 func (s *solver) checkDepsDisallowsSelected(a atomWithPackages, cdep completeDep) error { 182 dep := cdep.workingConstraint 183 selected, exists := s.sel.selected(dep.Ident) 184 if exists && !s.vUnify.matches(dep.Ident, dep.Constraint, selected.a.v) { 185 s.fail(dep.Ident) 186 187 return &constraintNotAllowedFailure{ 188 goal: dependency{depender: a.a, dep: cdep}, 189 v: selected.a.v, 190 } 191 } 192 return nil 193 } 194 195 // checkIdentMatches ensures that the LocalName of a dep introduced by an atom, 196 // has the same Source as what's already been selected (assuming anything's been 197 // selected). 198 // 199 // In other words, this ensures that the solver never simultaneously selects two 200 // identifiers with the same local name, but that disagree about where their 201 // network source is. 202 func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error { 203 dep := cdep.workingConstraint 204 if curid, has := s.sel.getIdentFor(dep.Ident.ProjectRoot); has && !curid.equiv(dep.Ident) { 205 deps := s.sel.getDependenciesOn(a.a.id) 206 // Fail all the other deps, as there's no way atom can ever be 207 // compatible with them 208 for _, d := range deps { 209 s.fail(d.depender.id) 210 } 211 212 return &sourceMismatchFailure{ 213 shared: dep.Ident.ProjectRoot, 214 sel: deps, 215 current: curid.normalizedSource(), 216 mismatch: dep.Ident.normalizedSource(), 217 prob: a.a, 218 } 219 } 220 221 return nil 222 } 223 224 // checkPackageImportsFromDepExist ensures that, if the dep is already selected, 225 // the newly-required set of packages being placed on it exist and are valid. 226 func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error { 227 sel, is := s.sel.selected(cdep.workingConstraint.Ident) 228 if !is { 229 // dep is not already selected; nothing to do 230 return nil 231 } 232 233 ptree, err := s.b.ListPackages(sel.a.id, sel.a.v) 234 if err != nil { 235 // TODO(sdboyer) handle this more gracefully 236 return err 237 } 238 239 e := &depHasProblemPackagesFailure{ 240 goal: dependency{ 241 depender: a.a, 242 dep: cdep, 243 }, 244 v: sel.a.v, 245 prob: make(map[string]error), 246 } 247 248 for _, pkg := range cdep.pl { 249 perr, has := ptree.Packages[pkg] 250 if !has || perr.Err != nil { 251 if has { 252 e.prob[pkg] = perr.Err 253 } else { 254 e.prob[pkg] = nil 255 } 256 } 257 } 258 259 if len(e.prob) > 0 { 260 return e 261 } 262 return nil 263 } 264 265 // checkRevisionExists ensures that if a dependency is constrained by a 266 // revision, that that revision actually exists. 267 func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error { 268 r, isrev := cdep.Constraint.(Revision) 269 if !isrev { 270 // Constraint is not a revision; nothing to do 271 return nil 272 } 273 274 present, _ := s.b.RevisionPresentIn(cdep.Ident, r) 275 if present { 276 return nil 277 } 278 279 return &nonexistentRevisionFailure{ 280 goal: dependency{ 281 depender: a.a, 282 dep: cdep, 283 }, 284 r: r, 285 } 286 }