2016-07-12 21:56:12 +03:00
|
|
|
package gps
|
2016-04-26 06:03:49 +03:00
|
|
|
|
2016-07-21 05:33:11 +03:00
|
|
|
// check performs constraint checks on the provided atom. The set of checks
|
|
|
|
// differ slightly depending on whether the atom is pkgonly, or if it's the
|
|
|
|
// entire project being added for the first time.
|
|
|
|
//
|
|
|
|
// The goal is to determine whether selecting the atom would result in a state
|
|
|
|
// where all the solver requirements are still satisfied.
|
|
|
|
func (s *solver) check(a atomWithPackages, pkgonly bool) error {
|
2016-06-30 05:57:23 +03:00
|
|
|
pa := a.a
|
2016-06-22 06:00:50 +03:00
|
|
|
if nilpa == pa {
|
2016-06-17 18:00:57 +03:00
|
|
|
// This shouldn't be able to happen, but if it does, it unequivocally
|
|
|
|
// indicates a logical bug somewhere, so blowing up is preferable
|
2016-04-26 06:03:49 +03:00
|
|
|
panic("canary - checking version of empty ProjectAtom")
|
|
|
|
}
|
|
|
|
|
2016-07-21 05:33:11 +03:00
|
|
|
// If we're pkgonly, then base atom was already determined to be allowable,
|
|
|
|
// so we can skip the checkAtomAllowable step.
|
|
|
|
if !pkgonly {
|
|
|
|
if err := s.checkAtomAllowable(pa); err != nil {
|
|
|
|
s.traceInfo(err)
|
|
|
|
return err
|
|
|
|
}
|
2016-04-26 06:03:49 +03:00
|
|
|
}
|
|
|
|
|
2016-06-22 05:56:01 +03:00
|
|
|
if err := s.checkRequiredPackagesExist(a); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
s.traceInfo(err)
|
2016-06-22 05:56:01 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-06-15 21:52:13 +03:00
|
|
|
deps, err := s.getImportsAndConstraintsOf(a)
|
2016-04-26 06:03:49 +03:00
|
|
|
if err != nil {
|
|
|
|
// An err here would be from the package fetcher; pass it straight back
|
2016-07-20 20:58:29 +03:00
|
|
|
// TODO(sdboyer) can we traceInfo this?
|
2016-04-26 06:03:49 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dep := range deps {
|
2016-06-15 21:52:13 +03:00
|
|
|
if err := s.checkIdentMatches(a, dep); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
s.traceInfo(err)
|
2016-05-03 16:47:48 +03:00
|
|
|
return err
|
|
|
|
}
|
2016-06-15 21:52:13 +03:00
|
|
|
if err := s.checkDepsConstraintsAllowable(a, dep); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
s.traceInfo(err)
|
2016-04-26 06:03:49 +03:00
|
|
|
return err
|
|
|
|
}
|
2016-06-15 21:52:13 +03:00
|
|
|
if err := s.checkDepsDisallowsSelected(a, dep); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
s.traceInfo(err)
|
2016-07-06 06:54:49 +03:00
|
|
|
return err
|
|
|
|
}
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) decide how to refactor in order to re-enable this. Checking for
|
2016-07-06 06:59:19 +03:00
|
|
|
// revision existence is important...but kinda obnoxious.
|
|
|
|
//if err := s.checkRevisionExists(a, dep); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
//s.traceInfo(err)
|
2016-07-06 06:59:19 +03:00
|
|
|
//return err
|
|
|
|
//}
|
2016-06-22 07:03:51 +03:00
|
|
|
if err := s.checkPackageImportsFromDepExist(a, dep); err != nil {
|
2016-07-20 20:58:29 +03:00
|
|
|
s.traceInfo(err)
|
2016-06-22 07:03:51 +03:00
|
|
|
return err
|
|
|
|
}
|
2016-04-26 06:03:49 +03:00
|
|
|
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) add check that fails if adding this atom would create a loop
|
2016-04-26 06:03:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkAtomAllowable ensures that an atom itself is acceptable with respect to
|
|
|
|
// the constraints established by the current solution.
|
2016-06-30 05:57:23 +03:00
|
|
|
func (s *solver) checkAtomAllowable(pa atom) error {
|
|
|
|
constraint := s.sel.getConstraint(pa.id)
|
|
|
|
if s.b.matches(pa.id, constraint, pa.v) {
|
2016-04-26 06:03:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) collect constraint failure reason (wait...aren't we, below?)
|
2016-04-26 06:03:49 +03:00
|
|
|
|
2016-06-30 05:57:23 +03:00
|
|
|
deps := s.sel.getDependenciesOn(pa.id)
|
|
|
|
var failparent []dependency
|
2016-04-26 06:03:49 +03:00
|
|
|
for _, dep := range deps {
|
2016-06-30 05:57:23 +03:00
|
|
|
if !s.b.matches(pa.id, dep.dep.Constraint, pa.v) {
|
|
|
|
s.fail(dep.depender.id)
|
2016-04-26 06:03:49 +03:00
|
|
|
failparent = append(failparent, dep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 06:55:36 +03:00
|
|
|
err := &versionNotAllowedFailure{
|
2016-04-26 06:03:49 +03:00
|
|
|
goal: pa,
|
|
|
|
failparent: failparent,
|
|
|
|
c: constraint,
|
|
|
|
}
|
2016-05-04 06:55:36 +03:00
|
|
|
|
|
|
|
return err
|
2016-04-26 06:03:49 +03:00
|
|
|
}
|
|
|
|
|
2016-06-22 05:56:01 +03:00
|
|
|
// checkRequiredPackagesExist ensures that all required packages enumerated by
|
|
|
|
// existing dependencies on this atom are actually present in the atom.
|
|
|
|
func (s *solver) checkRequiredPackagesExist(a atomWithPackages) error {
|
2016-06-30 05:57:23 +03:00
|
|
|
ptree, err := s.b.listPackages(a.a.id, a.a.v)
|
2016-06-22 05:56:01 +03:00
|
|
|
if err != nil {
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) handle this more gracefully
|
2016-06-22 05:56:01 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-06-30 05:57:23 +03:00
|
|
|
deps := s.sel.getDependenciesOn(a.a.id)
|
2016-06-22 05:56:01 +03:00
|
|
|
fp := make(map[string]errDeppers)
|
|
|
|
// We inspect these in a bit of a roundabout way, in order to incrementally
|
|
|
|
// build up the failure we'd return if there is, indeed, a missing package.
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) rechecking all of these every time is wasteful. Is there a shortcut?
|
2016-06-22 05:56:01 +03:00
|
|
|
for _, dep := range deps {
|
2016-06-30 05:57:23 +03:00
|
|
|
for _, pkg := range dep.dep.pl {
|
2016-06-22 05:56:01 +03:00
|
|
|
if errdep, seen := fp[pkg]; seen {
|
2016-06-30 05:57:23 +03:00
|
|
|
errdep.deppers = append(errdep.deppers, dep.depender)
|
2016-06-22 05:56:01 +03:00
|
|
|
fp[pkg] = errdep
|
|
|
|
} else {
|
|
|
|
perr, has := ptree.Packages[pkg]
|
|
|
|
if !has || perr.Err != nil {
|
|
|
|
fp[pkg] = errDeppers{
|
|
|
|
err: perr.Err,
|
2016-06-30 05:57:23 +03:00
|
|
|
deppers: []atom{dep.depender},
|
2016-06-22 05:56:01 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(fp) > 0 {
|
2016-07-06 06:54:49 +03:00
|
|
|
return &checkeeHasProblemPackagesFailure{
|
2016-06-30 05:57:23 +03:00
|
|
|
goal: a.a,
|
2016-06-22 05:56:01 +03:00
|
|
|
failpkg: fp,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-26 06:03:49 +03:00
|
|
|
// checkDepsConstraintsAllowable checks that the constraints of an atom on a
|
2016-06-15 21:52:13 +03:00
|
|
|
// given dep are valid with respect to existing constraints.
|
|
|
|
func (s *solver) checkDepsConstraintsAllowable(a atomWithPackages, cdep completeDep) error {
|
2016-07-09 07:18:22 +03:00
|
|
|
dep := cdep.ProjectConstraint
|
2016-04-29 01:53:42 +03:00
|
|
|
constraint := s.sel.getConstraint(dep.Ident)
|
2016-04-26 06:03:49 +03:00
|
|
|
// Ensure the constraint expressed by the dep has at least some possible
|
|
|
|
// intersection with the intersection of existing constraints.
|
2016-06-06 19:50:20 +03:00
|
|
|
if s.b.matchesAny(dep.Ident, constraint, dep.Constraint) {
|
2016-04-26 06:03:49 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-29 01:53:42 +03:00
|
|
|
siblings := s.sel.getDependenciesOn(dep.Ident)
|
2016-04-26 06:03:49 +03:00
|
|
|
// No admissible versions - visit all siblings and identify the disagreement(s)
|
2016-06-30 05:57:23 +03:00
|
|
|
var failsib []dependency
|
|
|
|
var nofailsib []dependency
|
2016-04-26 06:03:49 +03:00
|
|
|
for _, sibling := range siblings {
|
2016-06-30 05:57:23 +03:00
|
|
|
if !s.b.matchesAny(dep.Ident, sibling.dep.Constraint, dep.Constraint) {
|
|
|
|
s.fail(sibling.depender.id)
|
2016-04-26 06:03:49 +03:00
|
|
|
failsib = append(failsib, sibling)
|
|
|
|
} else {
|
|
|
|
nofailsib = append(nofailsib, sibling)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-06 06:54:49 +03:00
|
|
|
return &disjointConstraintFailure{
|
2016-06-30 05:57:23 +03:00
|
|
|
goal: dependency{depender: a.a, dep: cdep},
|
2016-04-26 06:03:49 +03:00
|
|
|
failsib: failsib,
|
|
|
|
nofailsib: nofailsib,
|
|
|
|
c: constraint,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkDepsDisallowsSelected ensures that an atom's constraints on a particular
|
|
|
|
// dep are not incompatible with the version of that dep that's already been
|
|
|
|
// selected.
|
2016-06-15 21:52:13 +03:00
|
|
|
func (s *solver) checkDepsDisallowsSelected(a atomWithPackages, cdep completeDep) error {
|
2016-07-09 07:18:22 +03:00
|
|
|
dep := cdep.ProjectConstraint
|
2016-04-29 01:53:42 +03:00
|
|
|
selected, exists := s.sel.selected(dep.Ident)
|
2016-06-30 05:57:23 +03:00
|
|
|
if exists && !s.b.matches(dep.Ident, dep.Constraint, selected.a.v) {
|
2016-05-03 05:00:06 +03:00
|
|
|
s.fail(dep.Ident)
|
2016-04-26 06:03:49 +03:00
|
|
|
|
2016-07-06 06:54:49 +03:00
|
|
|
return &constraintNotAllowedFailure{
|
2016-06-30 05:57:23 +03:00
|
|
|
goal: dependency{depender: a.a, dep: cdep},
|
|
|
|
v: selected.a.v,
|
2016-04-26 06:03:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2016-05-03 16:47:48 +03:00
|
|
|
|
2016-05-04 04:07:42 +03:00
|
|
|
// checkIdentMatches ensures that the LocalName of a dep introduced by an atom,
|
|
|
|
// has the same NetworkName as what's already been selected (assuming anything's
|
|
|
|
// been selected).
|
|
|
|
//
|
|
|
|
// In other words, this ensures that the solver never simultaneously selects two
|
2016-05-04 07:09:04 +03:00
|
|
|
// identifiers with the same local name, but that disagree about where their
|
|
|
|
// network source is.
|
2016-06-15 21:52:13 +03:00
|
|
|
func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
|
2016-07-09 07:18:22 +03:00
|
|
|
dep := cdep.ProjectConstraint
|
|
|
|
if cur, exists := s.names[dep.Ident.ProjectRoot]; exists {
|
2016-05-03 16:47:48 +03:00
|
|
|
if cur != dep.Ident.netName() {
|
2016-06-30 05:57:23 +03:00
|
|
|
deps := s.sel.getDependenciesOn(a.a.id)
|
2016-05-04 06:55:36 +03:00
|
|
|
// Fail all the other deps, as there's no way atom can ever be
|
|
|
|
// compatible with them
|
|
|
|
for _, d := range deps {
|
2016-06-30 05:57:23 +03:00
|
|
|
s.fail(d.depender.id)
|
2016-05-04 06:55:36 +03:00
|
|
|
}
|
|
|
|
|
2016-07-06 06:54:49 +03:00
|
|
|
return &sourceMismatchFailure{
|
2016-07-09 07:18:22 +03:00
|
|
|
shared: dep.Ident.ProjectRoot,
|
2016-05-03 16:47:48 +03:00
|
|
|
sel: deps,
|
|
|
|
current: cur,
|
|
|
|
mismatch: dep.Ident.netName(),
|
2016-06-30 05:57:23 +03:00
|
|
|
prob: a.a,
|
2016-05-03 16:47:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2016-06-22 07:03:51 +03:00
|
|
|
|
|
|
|
// checkPackageImportsFromDepExist ensures that, if the dep is already selected,
|
|
|
|
// the newly-required set of packages being placed on it exist and are valid.
|
|
|
|
func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error {
|
2016-07-09 07:18:22 +03:00
|
|
|
sel, is := s.sel.selected(cdep.ProjectConstraint.Ident)
|
2016-06-22 07:03:51 +03:00
|
|
|
if !is {
|
|
|
|
// dep is not already selected; nothing to do
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-06-30 05:57:23 +03:00
|
|
|
ptree, err := s.b.listPackages(sel.a.id, sel.a.v)
|
2016-06-22 07:03:51 +03:00
|
|
|
if err != nil {
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) handle this more gracefully
|
2016-06-22 07:03:51 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
e := &depHasProblemPackagesFailure{
|
2016-06-30 05:57:23 +03:00
|
|
|
goal: dependency{
|
|
|
|
depender: a.a,
|
|
|
|
dep: cdep,
|
2016-06-22 07:03:51 +03:00
|
|
|
},
|
2016-06-30 05:57:23 +03:00
|
|
|
v: sel.a.v,
|
2016-06-22 07:03:51 +03:00
|
|
|
prob: make(map[string]error),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, pkg := range cdep.pl {
|
|
|
|
perr, has := ptree.Packages[pkg]
|
|
|
|
if !has || perr.Err != nil {
|
|
|
|
if has {
|
|
|
|
e.prob[pkg] = perr.Err
|
2016-07-27 05:10:52 +03:00
|
|
|
} else {
|
|
|
|
e.prob[pkg] = nil
|
2016-06-22 07:03:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 05:10:52 +03:00
|
|
|
if len(e.prob) > 0 {
|
2016-06-22 07:03:51 +03:00
|
|
|
return e
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2016-07-06 06:54:49 +03:00
|
|
|
|
|
|
|
// checkRevisionExists ensures that if a dependency is constrained by a
|
|
|
|
// revision, that that revision actually exists.
|
|
|
|
func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error {
|
|
|
|
r, isrev := cdep.Constraint.(Revision)
|
|
|
|
if !isrev {
|
|
|
|
// Constraint is not a revision; nothing to do
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
present, _ := s.b.revisionPresentIn(cdep.Ident, r)
|
|
|
|
if present {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &nonexistentRevisionFailure{
|
|
|
|
goal: dependency{
|
|
|
|
depender: a.a,
|
|
|
|
dep: cdep,
|
|
|
|
},
|
|
|
|
r: r,
|
|
|
|
}
|
|
|
|
}
|