2016-03-15 06:36:42 +03:00
|
|
|
package vsolver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"container/heap"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2016-03-17 19:45:36 +03:00
|
|
|
//type SolveFailure uint
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-17 19:45:36 +03:00
|
|
|
//const (
|
|
|
|
// Indicates that no version solution could be found
|
|
|
|
//NoVersionSolution SolveFailure = 1 << iota
|
|
|
|
//IncompatibleVersionType
|
|
|
|
//)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-16 23:34:09 +03:00
|
|
|
func NewSolver(sm SourceManager) Solver {
|
2016-03-15 06:36:42 +03:00
|
|
|
return &solver{
|
2016-03-17 18:13:59 +03:00
|
|
|
sm: sm,
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-16 23:34:09 +03:00
|
|
|
// solver is a backtracking-style SAT solver.
|
2016-03-15 06:36:42 +03:00
|
|
|
type solver struct {
|
2016-03-16 23:34:09 +03:00
|
|
|
sm SourceManager
|
2016-03-30 20:51:23 +03:00
|
|
|
latest map[ProjectName]struct{}
|
2016-03-15 06:36:42 +03:00
|
|
|
sel *selection
|
|
|
|
unsel *unselected
|
2016-03-17 19:45:36 +03:00
|
|
|
versions []*versionQueue
|
2016-03-17 18:13:59 +03:00
|
|
|
rp ProjectInfo
|
2016-03-16 19:35:27 +03:00
|
|
|
attempts int
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) Solve(root ProjectInfo, toUpgrade []ProjectName) Result {
|
2016-03-17 18:13:59 +03:00
|
|
|
// local overrides would need to be handled first.
|
|
|
|
// TODO local overrides! heh
|
|
|
|
s.rp = root
|
2016-03-15 06:36:42 +03:00
|
|
|
|
|
|
|
for _, v := range toUpgrade {
|
|
|
|
s.latest[v] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2016-03-17 18:13:59 +03:00
|
|
|
// Initialize queues
|
|
|
|
s.sel = &selection{
|
2016-03-30 20:51:23 +03:00
|
|
|
deps: make(map[ProjectName][]Dependency),
|
2016-03-17 18:13:59 +03:00
|
|
|
}
|
2016-03-15 06:36:42 +03:00
|
|
|
s.unsel = &unselected{
|
2016-03-30 20:51:23 +03:00
|
|
|
sl: make([]ProjectName, 0),
|
2016-03-15 06:36:42 +03:00
|
|
|
cmp: s.unselectedComparator,
|
|
|
|
}
|
|
|
|
heap.Init(s.unsel)
|
|
|
|
|
2016-03-17 18:13:59 +03:00
|
|
|
// Prime the queues with the root project
|
2016-03-30 20:51:23 +03:00
|
|
|
s.selectVersion(s.rp.pa)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-17 18:13:59 +03:00
|
|
|
// Prep is done; actually run the solver
|
|
|
|
var r Result
|
|
|
|
r.Projects, r.SolveFailure = s.solve()
|
|
|
|
return r
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) solve() ([]ProjectAtom, error) {
|
2016-03-15 06:36:42 +03:00
|
|
|
for {
|
2016-03-16 03:27:19 +03:00
|
|
|
ref, has := s.nextUnselected()
|
2016-03-17 18:13:59 +03:00
|
|
|
if !has {
|
2016-03-15 06:36:42 +03:00
|
|
|
// no more packages to select - we're done. bail out
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
queue, err := s.createVersionQueue(ref)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// Err means a failure somewhere down the line; try backtracking.
|
|
|
|
if s.backtrack() {
|
|
|
|
// backtracking succeeded, move to the next unselected ref
|
|
|
|
continue
|
|
|
|
}
|
2016-03-17 18:13:59 +03:00
|
|
|
// TODO handle different failure types appropriately, lolzies
|
2016-03-16 23:34:09 +03:00
|
|
|
return nil, err
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
2016-03-16 03:27:19 +03:00
|
|
|
|
2016-03-31 08:25:23 +03:00
|
|
|
if queue.current() == emptyVersion {
|
2016-03-24 04:25:35 +03:00
|
|
|
panic("canary - queue is empty, but flow indicates success")
|
|
|
|
}
|
|
|
|
|
2016-03-31 08:25:23 +03:00
|
|
|
s.selectVersion(ProjectAtom{
|
|
|
|
Name: queue.ref,
|
|
|
|
Version: queue.current(),
|
|
|
|
})
|
2016-03-16 03:27:19 +03:00
|
|
|
s.versions = append(s.versions, queue)
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
2016-03-16 03:27:19 +03:00
|
|
|
|
2016-03-17 18:13:59 +03:00
|
|
|
// Getting this far means we successfully found a solution
|
2016-03-30 20:51:23 +03:00
|
|
|
var projs []ProjectAtom
|
2016-03-17 18:13:59 +03:00
|
|
|
for _, p := range s.sel.projects {
|
|
|
|
projs = append(projs, p)
|
|
|
|
}
|
|
|
|
return projs, nil
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) createVersionQueue(ref ProjectName) (*versionQueue, error) {
|
|
|
|
//pretty.Printf("Creating VersionQueue for %q\n", ref)
|
2016-03-15 06:36:42 +03:00
|
|
|
// If on the root package, there's no queue to make
|
2016-03-30 20:51:23 +03:00
|
|
|
if ref == s.rp.Name() {
|
2016-03-17 19:45:36 +03:00
|
|
|
return newVersionQueue(ref, nil, s.sm)
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-16 23:34:09 +03:00
|
|
|
if !s.sm.ProjectExists(ref) {
|
2016-03-15 06:36:42 +03:00
|
|
|
// TODO this check needs to incorporate/admit the possibility that the
|
|
|
|
// upstream no longer exists, but there's something valid in vendor/
|
|
|
|
return nil, newSolveError(fmt.Sprintf("Project '%s' could not be located.", ref), cannotResolve)
|
|
|
|
}
|
|
|
|
lockv := s.getLockVersionIfValid(ref)
|
|
|
|
|
2016-03-17 19:45:36 +03:00
|
|
|
q, err := newVersionQueue(ref, lockv, s.sm)
|
2016-03-15 20:07:16 +03:00
|
|
|
if err != nil {
|
|
|
|
// TODO this particular err case needs to be improved to be ONLY for cases
|
|
|
|
// where there's absolutely nothing findable about a given project name
|
|
|
|
return nil, err
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return q, s.findValidVersion(q)
|
|
|
|
}
|
|
|
|
|
|
|
|
// findValidVersion walks through a VersionQueue until it finds a version that's
|
|
|
|
// valid, as adjudged by the current constraints.
|
2016-03-17 19:45:36 +03:00
|
|
|
func (s *solver) findValidVersion(q *versionQueue) error {
|
2016-03-15 06:36:42 +03:00
|
|
|
var err error
|
2016-03-31 08:25:23 +03:00
|
|
|
if emptyVersion == q.current() {
|
2016-03-15 06:36:42 +03:00
|
|
|
// TODO this case shouldn't be reachable, but panic here as a canary
|
|
|
|
panic("version queue is empty, should not happen")
|
|
|
|
}
|
|
|
|
|
2016-03-31 08:25:23 +03:00
|
|
|
//var name ProjectName
|
2016-03-15 06:36:42 +03:00
|
|
|
for {
|
2016-03-31 08:25:23 +03:00
|
|
|
//pretty.Printf("Checking next version for %q\n", q.ref)
|
|
|
|
err = s.checkVersion(ProjectAtom{
|
|
|
|
Name: q.ref,
|
|
|
|
Version: q.current(),
|
|
|
|
})
|
2016-03-15 06:36:42 +03:00
|
|
|
if err == nil {
|
|
|
|
// we have a good version, can return safely
|
2016-03-31 08:25:23 +03:00
|
|
|
//pretty.Printf("Found valid version %q for %q\n", q.current().Name, q.current().Version.Info)
|
2016-03-15 06:36:42 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-03-31 08:25:23 +03:00
|
|
|
// store name so we can fail on it if it turns out to be the last
|
|
|
|
// possible version in the queue
|
|
|
|
//name = q.current().Name
|
2016-03-15 20:07:16 +03:00
|
|
|
err = q.advance()
|
|
|
|
if err != nil {
|
2016-03-24 04:26:18 +03:00
|
|
|
// Error on advance, have to bail out
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if q.isExhausted() {
|
|
|
|
// Queue is empty, bail with error
|
|
|
|
err = newSolveError(fmt.Sprintf("Exhausted queue for %q without finding a satisfactory version.", q.ref), mustResolve)
|
2016-03-15 20:07:16 +03:00
|
|
|
break
|
|
|
|
}
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
s.fail(s.sel.getDependenciesOn(q.ref)[0].Depender.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) getLockVersionIfValid(ref ProjectName) *ProjectAtom {
|
|
|
|
lockver := s.rp.GetProjectAtom(ref)
|
2016-03-15 06:36:42 +03:00
|
|
|
if lockver == nil {
|
|
|
|
// Nothing in the lock about this version, so nothing to validate
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
constraint := s.sel.getConstraint(ref)
|
2016-03-30 17:39:22 +03:00
|
|
|
if !constraint.Admits(lockver.Version) {
|
2016-03-15 06:36:42 +03:00
|
|
|
// TODO msg?
|
|
|
|
return nil
|
|
|
|
//} else {
|
|
|
|
// TODO msg?
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) checkVersion(pi ProjectAtom) error {
|
|
|
|
if emptyProjectAtom == pi {
|
2016-03-15 06:36:42 +03:00
|
|
|
// TODO we should protect against this case elsewhere, but for now panic
|
|
|
|
// to canary when it's a problem
|
2016-03-30 20:51:23 +03:00
|
|
|
panic("checking version of empty ProjectAtom")
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
constraint := s.sel.getConstraint(pi.Name)
|
2016-03-30 17:39:22 +03:00
|
|
|
if !constraint.Admits(pi.Version) {
|
2016-03-30 20:51:23 +03:00
|
|
|
deps := s.sel.getDependenciesOn(pi.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
for _, dep := range deps {
|
|
|
|
// TODO grok why this check is needed
|
2016-03-30 17:39:22 +03:00
|
|
|
if !dep.Dep.Constraint.Admits(pi.Version) {
|
2016-03-30 20:51:23 +03:00
|
|
|
s.fail(dep.Depender.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO msg
|
|
|
|
return &noVersionError{
|
2016-03-30 20:51:23 +03:00
|
|
|
pn: pi.Name,
|
2016-03-15 06:36:42 +03:00
|
|
|
c: constraint,
|
|
|
|
deps: deps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
if !s.sm.ProjectExists(pi.Name) {
|
2016-03-15 06:36:42 +03:00
|
|
|
// Can get here if the lock file specifies a now-nonexistent project
|
|
|
|
// TODO this check needs to incorporate/accept the possibility that the
|
|
|
|
// upstream no longer exists, but there's something valid in vendor/
|
2016-03-30 20:51:23 +03:00
|
|
|
return newSolveError(fmt.Sprintf("Project '%s' could not be located.", pi.Name), cannotResolve)
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-17 18:13:59 +03:00
|
|
|
deps, err := s.getDependenciesOf(pi)
|
2016-03-15 06:36:42 +03:00
|
|
|
if err != nil {
|
|
|
|
// An err here would be from the package fetcher; pass it straight back
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dep := range deps {
|
|
|
|
// TODO dart skips "magic" deps here; do we need that?
|
|
|
|
|
|
|
|
// TODO maybe differentiate between the confirmed items on the list, and
|
|
|
|
// the one we're speculatively adding? or it may be fine b/c we know
|
|
|
|
// it's the last one
|
2016-03-30 20:51:23 +03:00
|
|
|
selfAndSiblings := append(s.sel.getDependenciesOn(dep.Name), Dependency{Depender: pi, Dep: dep})
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
constraint = s.sel.getConstraint(dep.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
// Ensure the constraint expressed by the dep has at least some possible
|
|
|
|
// overlap with existing constraints.
|
2016-03-30 17:39:22 +03:00
|
|
|
if !constraint.AdmitsAny(dep.Constraint) {
|
2016-03-15 06:36:42 +03:00
|
|
|
// No match - visit all siblings and identify the disagreement(s)
|
|
|
|
for _, sibling := range selfAndSiblings[:len(selfAndSiblings)-1] {
|
2016-03-30 17:39:22 +03:00
|
|
|
if !sibling.Dep.Constraint.AdmitsAny(dep.Constraint) {
|
2016-03-30 20:51:23 +03:00
|
|
|
s.fail(sibling.Depender.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO msg
|
|
|
|
return &disjointConstraintFailure{
|
2016-03-30 20:51:23 +03:00
|
|
|
pn: dep.Name,
|
2016-03-15 06:36:42 +03:00
|
|
|
deps: selfAndSiblings,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
selected, exists := s.sel.selected(dep.Name)
|
2016-03-30 17:39:22 +03:00
|
|
|
if exists && !dep.Constraint.Admits(selected.Version) {
|
2016-03-30 20:51:23 +03:00
|
|
|
s.fail(dep.Name)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
|
|
|
// TODO msg
|
|
|
|
return &noVersionError{
|
2016-03-30 20:51:23 +03:00
|
|
|
pn: dep.Name,
|
2016-03-15 06:36:42 +03:00
|
|
|
c: dep.Constraint,
|
|
|
|
deps: selfAndSiblings,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, dart/pub do things related to 'required' dependencies,
|
|
|
|
// which is about solving loops (i think) and so mostly not something we
|
|
|
|
// have to care about.
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
// getDependenciesOf returns the dependencies of the given ProjectAtom, mediated
|
2016-03-15 06:36:42 +03:00
|
|
|
// through any overrides dictated by the root project.
|
|
|
|
//
|
|
|
|
// If it's the root project, also includes dev dependencies, etc.
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) getDependenciesOf(pi ProjectAtom) ([]ProjectDep, error) {
|
2016-03-17 06:15:51 +03:00
|
|
|
info, err := s.sm.GetProjectInfo(pi)
|
2016-03-15 06:36:42 +03:00
|
|
|
if err != nil {
|
|
|
|
// TODO revisit this once a decision is made about better-formed errors;
|
|
|
|
// question is, do we expect the fetcher to pass back simple errors, or
|
|
|
|
// well-typed solver errors?
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
deps := info.GetDependencies()
|
2016-03-30 20:51:23 +03:00
|
|
|
if s.rp.Name() == pi.Name {
|
2016-03-15 06:36:42 +03:00
|
|
|
// Root package has more things to pull in
|
2016-03-16 03:27:19 +03:00
|
|
|
deps = append(deps, info.GetDevDependencies()...)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
|
|
|
// TODO add overrides here...if we impl the concept (which we should)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO we have to validate well-formedness of a project's manifest
|
|
|
|
// somewhere. this may be a good spot. alternatively, the fetcher may
|
|
|
|
// validate well-formedness, whereas here we validate availability of the
|
|
|
|
// named deps here. (the latter is sorta what pub does here)
|
|
|
|
|
|
|
|
return deps, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// backtrack works backwards from the current failed solution to find the next
|
|
|
|
// solution to try.
|
|
|
|
func (s *solver) backtrack() bool {
|
|
|
|
if len(s.versions) == 0 {
|
|
|
|
// nothing to backtrack to
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
for {
|
|
|
|
if len(s.versions) == 0 {
|
|
|
|
// no more versions, nowhere further to backtrack
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if s.versions[len(s.versions)-1].failed {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// pop last vqueue off of versions
|
|
|
|
//q, s.versions := s.versions[len(s.versions)-1], s.versions[:len(s.versions)-1]
|
|
|
|
// pub asserts here that the last in s.sel's ids is == q.current
|
|
|
|
s.versions = s.versions[:len(s.versions)-1]
|
2016-03-16 03:27:19 +03:00
|
|
|
s.unselectLast()
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-16 19:35:27 +03:00
|
|
|
// Grab the last VersionQueue off the list of queues
|
|
|
|
q := s.versions[len(s.versions)-1]
|
2016-03-15 06:36:42 +03:00
|
|
|
// another assert that the last in s.sel's ids is == q.current
|
2016-03-16 03:27:19 +03:00
|
|
|
s.unselectLast()
|
2016-03-16 19:35:27 +03:00
|
|
|
|
|
|
|
// Search for another acceptable version of this failed dep in its queue
|
|
|
|
if err := s.findValidVersion(q); err == nil {
|
|
|
|
// Found one! Put it back on the selected queue and stop
|
|
|
|
// backtracking
|
2016-03-31 08:25:23 +03:00
|
|
|
s.selectVersion(ProjectAtom{
|
|
|
|
Name: q.ref,
|
|
|
|
Version: q.current(),
|
|
|
|
})
|
2016-03-16 19:35:27 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// No solution found; continue backtracking after popping the last
|
|
|
|
// version off the list
|
|
|
|
// GC-friendly pop pointer elem in slice
|
|
|
|
s.versions, s.versions[len(s.versions)-1] = s.versions[:len(s.versions)-1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Backtracking was successful if loop ended before running out of versions
|
|
|
|
if len(s.versions) == 0 {
|
|
|
|
return false
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
2016-03-16 19:35:27 +03:00
|
|
|
s.attempts++
|
|
|
|
return true
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) nextUnselected() (ProjectName, bool) {
|
2016-03-16 03:27:19 +03:00
|
|
|
if len(s.unsel.sl) > 0 {
|
|
|
|
return s.unsel.sl[0], true
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2016-03-15 06:36:42 +03:00
|
|
|
func (s *solver) unselectedComparator(i, j int) bool {
|
|
|
|
iname, jname := s.unsel.sl[i], s.unsel.sl[j]
|
|
|
|
|
|
|
|
if iname == jname {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
rname := s.rp.Name()
|
2016-03-15 06:36:42 +03:00
|
|
|
// *always* put root project first
|
2016-03-30 20:51:23 +03:00
|
|
|
if iname == rname {
|
2016-03-15 06:36:42 +03:00
|
|
|
return true
|
|
|
|
}
|
2016-03-30 20:51:23 +03:00
|
|
|
if jname == rname {
|
2016-03-15 06:36:42 +03:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
ilock, jlock := s.rp.GetProjectAtom(iname) == nil, s.rp.GetProjectAtom(jname) == nil
|
2016-03-15 06:36:42 +03:00
|
|
|
|
|
|
|
if ilock && !jlock {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if !ilock && jlock {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
//if ilock && jlock {
|
|
|
|
//return iname < jname
|
|
|
|
//}
|
|
|
|
|
|
|
|
// TODO impl version-counting for next set of checks. but until then...
|
|
|
|
return iname < jname
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) fail(name ProjectName) {
|
2016-03-16 03:27:19 +03:00
|
|
|
// skip if the root project
|
2016-03-30 20:51:23 +03:00
|
|
|
if s.rp.Name() == name {
|
2016-03-16 03:27:19 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, vq := range s.versions {
|
2016-03-30 20:51:23 +03:00
|
|
|
if vq.ref == name {
|
2016-03-16 03:27:19 +03:00
|
|
|
vq.failed = true
|
|
|
|
// just look for the first (oldest) one; the backtracker will
|
|
|
|
// necessarily traverse through and pop off any earlier ones
|
|
|
|
// TODO ...right?
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
func (s *solver) selectVersion(pa ProjectAtom) {
|
|
|
|
s.unsel.remove(pa.Name)
|
|
|
|
s.sel.projects = append(s.sel.projects, pa)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
deps, err := s.getDependenciesOf(pa)
|
2016-03-16 03:27:19 +03:00
|
|
|
if err != nil {
|
|
|
|
// if we're choosing a package that has errors getting its deps, there's
|
|
|
|
// a bigger problem
|
|
|
|
// TODO try to create a test that hits this
|
|
|
|
panic("shouldn't be possible")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dep := range deps {
|
2016-03-30 20:51:23 +03:00
|
|
|
siblingsAndSelf := append(s.sel.getDependenciesOn(dep.Name), Dependency{Depender: pa, Dep: dep})
|
|
|
|
s.sel.deps[dep.Name] = siblingsAndSelf
|
2016-03-16 03:27:19 +03:00
|
|
|
|
|
|
|
// add project to unselected queue if this is the first dep on it -
|
|
|
|
// otherwise it's already in there, or been selected
|
|
|
|
if len(siblingsAndSelf) == 1 {
|
2016-03-30 20:51:23 +03:00
|
|
|
//pretty.Printf("pushing %q onto unselected queue\n", dep.Name)
|
|
|
|
heap.Push(s.unsel, dep.Name)
|
|
|
|
//pretty.Println("unsel after push:", s.unsel.sl)
|
2016-03-16 03:27:19 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|
|
|
|
|
2016-03-16 03:27:19 +03:00
|
|
|
func (s *solver) unselectLast() {
|
2016-03-30 20:51:23 +03:00
|
|
|
var pa ProjectAtom
|
|
|
|
pa, s.sel.projects = s.sel.projects[len(s.sel.projects)-1], s.sel.projects[:len(s.sel.projects)-1]
|
|
|
|
heap.Push(s.unsel, pa.Name)
|
|
|
|
//pretty.Println("unsel after restore:", s.unsel.sl)
|
2016-03-15 06:36:42 +03:00
|
|
|
|
2016-03-30 20:51:23 +03:00
|
|
|
deps, err := s.getDependenciesOf(pa)
|
2016-03-16 03:27:19 +03:00
|
|
|
if err != nil {
|
|
|
|
// if we're choosing a package that has errors getting its deps, there's
|
|
|
|
// a bigger problem
|
|
|
|
// TODO try to create a test that hits this
|
|
|
|
panic("shouldn't be possible")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, dep := range deps {
|
2016-03-30 20:51:23 +03:00
|
|
|
siblings := s.sel.getDependenciesOn(pa.Name)
|
|
|
|
s.sel.deps[pa.Name] = siblings[:len(siblings)-1]
|
2016-03-16 03:27:19 +03:00
|
|
|
|
|
|
|
// if no siblings, remove from unselected queue
|
|
|
|
if len(siblings) == 0 {
|
2016-03-30 20:51:23 +03:00
|
|
|
s.unsel.remove(dep.Name)
|
2016-03-16 03:27:19 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-15 06:36:42 +03:00
|
|
|
}
|