github.com/golang/dep@v0.5.4/gps/satisfy.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 // check performs constraint checks on the provided atom. The set of checks 8 // differ slightly depending on whether the atom is pkgonly, or if it's the 9 // entire project being added for the first time. 10 // 11 // The goal is to determine whether selecting the atom would result in a state 12 // where all the solver requirements are still satisfied. 13 func (s *solver) check(a atomWithPackages, pkgonly bool) error { 14 pa := a.a 15 if nilpa == pa { 16 // This shouldn't be able to happen, but if it does, it unequivocally 17 // indicates a logical bug somewhere, so blowing up is preferable 18 panic("canary - checking version of empty ProjectAtom") 19 } 20 21 s.mtr.push("satisfy") 22 var err error 23 defer func() { 24 if err != nil { 25 s.traceInfo(err) 26 } 27 s.mtr.pop() 28 }() 29 30 // If we're pkgonly, then base atom was already determined to be allowable, 31 // so we can skip the checkAtomAllowable step. 32 if !pkgonly { 33 if err = s.checkAtomAllowable(pa); err != nil { 34 return err 35 } 36 } 37 38 if err = s.checkRequiredPackagesExist(a); err != nil { 39 return err 40 } 41 42 var deps []completeDep 43 _, deps, err = s.getImportsAndConstraintsOf(a) 44 if err != nil { 45 // An err here would be from the package fetcher; pass it straight back 46 return err 47 } 48 49 // TODO(sdboyer) this deps list contains only packages not already selected 50 // from the target atom (assuming one is selected at all). It's fine for 51 // now, but won't be good enough when we get around to doing static 52 // analysis. 53 for _, dep := range deps { 54 if err = s.checkIdentMatches(a, dep); err != nil { 55 return err 56 } 57 if err = s.checkRootCaseConflicts(a, dep); err != nil { 58 return err 59 } 60 if err = s.checkDepsConstraintsAllowable(a, dep); err != nil { 61 return err 62 } 63 if err = s.checkDepsDisallowsSelected(a, dep); err != nil { 64 return err 65 } 66 if err = s.checkRevisionExists(a, dep); err != nil { 67 return err 68 } 69 if err = s.checkPackageImportsFromDepExist(a, dep); err != nil { 70 return err 71 } 72 73 // TODO(sdboyer) add check that fails if adding this atom would create a loop 74 } 75 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 constraint.Matches(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 !dep.dep.Constraint.Matches(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 constraint.MatchesAny(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 !sibling.dep.Constraint.MatchesAny(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 && !dep.Constraint.Matches(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 // checkRootCaseConflicts ensures that the ProjectRoot specified in the completeDep 225 // does not have case conflicts with any existing dependencies. 226 // 227 // We only need to check the ProjectRoot, rather than any packages therein, as 228 // the later check for package existence is case-sensitive. 229 func (s *solver) checkRootCaseConflicts(a atomWithPackages, cdep completeDep) error { 230 pr := cdep.workingConstraint.Ident.ProjectRoot 231 hasConflict, current := s.sel.findCaseConflicts(pr) 232 if !hasConflict { 233 return nil 234 } 235 236 curid, _ := s.sel.getIdentFor(current) 237 deps := s.sel.getDependenciesOn(curid) 238 for _, d := range deps { 239 s.fail(d.depender.id) 240 } 241 242 // If a project has multiple packages that import each other, we treat that 243 // as establishing a canonical case variant for the ProjectRoot. It's possible, 244 // however, that that canonical variant is not the same one that others 245 // imported it under. If that's the situation, then we'll have arrived here 246 // when visiting the project, not its dependers, having misclassified its 247 // internal imports as external. That means the atomWithPackages will 248 // be the wrong case variant induced by the importers, and the cdep will be 249 // a link pointing back at the canonical case variant. 250 // 251 // If this is the case, use a special failure, wrongCaseFailure, that 252 // makes a stronger statement as to the correctness of case variants. 253 // 254 // TODO(sdboyer) This approach to marking failure is less than great, as 255 // this will mark the current atom as failed, as well, causing the 256 // backtracker to work through it. While that could prove fruitful, it's 257 // quite likely just to be wasted effort. Addressing this - if that's a good 258 // idea - would entail creating another path back out of checking to enable 259 // backjumping directly to the incorrect importers. 260 if current == a.a.id.ProjectRoot { 261 return &wrongCaseFailure{ 262 correct: pr, 263 goal: dependency{depender: a.a, dep: cdep}, 264 badcase: deps, 265 } 266 } 267 268 return &caseMismatchFailure{ 269 goal: dependency{depender: a.a, dep: cdep}, 270 current: current, 271 failsib: deps, 272 } 273 } 274 275 // checkPackageImportsFromDepExist ensures that, if the dep is already selected, 276 // the newly-required set of packages being placed on it exist and are valid. 277 func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error { 278 sel, is := s.sel.selected(cdep.workingConstraint.Ident) 279 if !is { 280 // dep is not already selected; nothing to do 281 return nil 282 } 283 284 ptree, err := s.b.ListPackages(sel.a.id, sel.a.v) 285 if err != nil { 286 // TODO(sdboyer) handle this more gracefully 287 return err 288 } 289 290 e := &depHasProblemPackagesFailure{ 291 goal: dependency{ 292 depender: a.a, 293 dep: cdep, 294 }, 295 v: sel.a.v, 296 prob: make(map[string]error), 297 } 298 299 for _, pkg := range cdep.pl { 300 perr, has := ptree.Packages[pkg] 301 if !has || perr.Err != nil { 302 if has { 303 e.prob[pkg] = perr.Err 304 } else { 305 e.prob[pkg] = nil 306 } 307 } 308 } 309 310 if len(e.prob) > 0 { 311 return e 312 } 313 return nil 314 } 315 316 // checkRevisionExists ensures that if a dependency is constrained by a 317 // revision, that that revision actually exists. 318 func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error { 319 r, isrev := cdep.Constraint.(Revision) 320 if !isrev { 321 // Constraint is not a revision; nothing to do 322 return nil 323 } 324 325 present, _ := s.b.RevisionPresentIn(cdep.Ident, r) 326 if present { 327 return nil 328 } 329 330 return &nonexistentRevisionFailure{ 331 goal: dependency{ 332 depender: a.a, 333 dep: cdep, 334 }, 335 r: r, 336 } 337 }