* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\DependencyResolver; /** * @author Nils Adermann */ class Solver { const RULE_INTERNAL_ALLOW_UPDATE = 1; const RULE_JOB_INSTALL = 2; const RULE_JOB_REMOVE = 3; const RULE_JOB_LOCK = 4; const RULE_NOT_INSTALLABLE = 5; const RULE_NOTHING_PROVIDES_DEP = 6; const RULE_PACKAGE_CONFLICT = 7; const RULE_PACKAGE_NOT_EXIST = 8; const RULE_PACKAGE_REQUIRES = 9; const TYPE_PACKAGE = 0; const TYPE_FEATURE = 1; const TYPE_UPDATE = 2; const TYPE_JOB = 3; const TYPE_WEAK = 4; const TYPE_LEARNED = 5; protected $policy; protected $pool; protected $installed; protected $rules; protected $updateAll; protected $addedMap = array(); protected $fixMap = array(); protected $updateMap = array(); protected $watches = array(); protected $removeWatches = array(); public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) { $this->policy = $policy; $this->pool = $pool; $this->installed = $installed; $this->rules = array( // order matters here! further down => higher priority self::TYPE_LEARNED => array(), self::TYPE_WEAK => array(), self::TYPE_FEATURE => array(), self::TYPE_UPDATE => array(), self::TYPE_JOB => array(), self::TYPE_PACKAGE => array(), ); } /** * Creates a new rule for the requirements of a package * * This rule is of the form (-A|B|C), where B and C are the providers of * one requirement of the package A. * * @param Package $package The package with a requirement * @param array $providers The providers of the requirement * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the requirement name, that goes with * the reason * @return Rule The generated rule or null if tautological */ public function createRequireRule(Package $package, array $providers, $reason, $reasonData = null) { $literals = array(new Literal($package, false)); foreach ($providers as $provider) { // self fulfilling rule? if ($provider === $package) { return null; } $literals[] = new Literal($provider, true); } return new Rule($literals, $reason, $reasonData); } /** * Create a new rule for updating a package * * If package A1 can be updated to A2 or A3 the rule is (A1|A2|A3). * * @param Package $package The package to be updated * @param array $updates An array of update candidate packages * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule The generated rule or null if tautology */ protected function createUpdateRule(Package $package, array $updates, $reason, $reasonData = null) { $literals = array(new Literal($package, true)); foreach ($updates as $update) { $literals[] = new Literal($update, true); } return new Rule($literals, $reason, $reasonData); } /** * Creates a new rule for installing a package * * The rule is simply (A) for a package A to be installed. * * @param Package $package The package to be installed * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule The generated rule */ public function createInstallRule(Package $package, $reason, $reasonData = null) { return new Rule(new Literal($package, true)); } /** * Creates a rule to install at least one of a set of packages * * The rule is (A|B|C) with A, B and C different packages. If the given * set of packages is empty an impossible rule is generated. * * @param array $packages The set of packages to choose from * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule The generated rule */ public function createInstallOneOfRule(array $packages, $reason, $reasonData = null) { if (empty($packages)) { return $this->createImpossibleRule($reason, $reasonData); } $literals = array(); foreach ($packages as $package) { $literals[] = new Literal($package, true); } return new Rule($literals, $reason, $reasonData); } /** * Creates a rule to remove a package * * The rule for a package A is (-A). * * @param Package $package The package to be removed * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule The generated rule */ public function createRemoveRule(Package $package, $reason, $reasonData = null) { return new Rule(array(new Literal($package, false)), $reason, $reasonData); } /** * Creates a rule for two conflicting packages * * The rule for conflicting packages A and B is (-A|-B). A is called the issuer * and B the provider. * * @param Package $issuer The package declaring the conflict * @param Package $provider The package causing the conflict * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule The generated rule */ public function createConflictRule(Package $issuer, Package $provider, $reason, $reasonData = null) { // ignore self conflict if ($issuer === $provider) { return null; } return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData); } /** * Intentionally creates a rule impossible to solve * * The rule is an empty one so it can never be satisfied. * * @param int $reason A RULE_* constant describing the reason for * generating this rule * @param mixed $reasonData Any data, e.g. the package name, that goes with * the reason * @return Rule An empty rule */ public function createImpossibleRule($reason, $reasonData = null) { return new Rule(array(), $reason, $reasonData); } /** * Adds a rule unless it duplicates an existing one of any type * * To be able to directly pass in the result of one of the rule creation * methods the rule may also be null to indicate that no rule should be * added. * * @param int $type A TYPE_* constant defining the rule type * @param Rule $newRule The rule about to be added */ private function addRule($type, Rule $newRule = null) { if ($newRule) { foreach ($this->rules as $rules) { foreach ($rules as $rule) { if ($rule->equals($newRule)) { return; } } } $newRule->setType($type); $this->rules[$type][] = $newRule; } } public function addRulesForPackage(Package $package) { $workQueue = new \SPLQueue; $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { $package = $workQueue->dequeue(); if (isset($this->addedMap[$package->getId()])) { continue; } $this->addedMap[$package->getId()] = true; $dontFix = 0; if ($this->installed->contains($package) && !isset($this->fixMap[$package->getId()])) { $dontFix = 1; } if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installed, $package)) { $this->addRule(self::TYPE_PACKAGE, $this->createRemoveRule($package, self::RULE_NOT_INSTALLABLE, (string) $package)); continue; } foreach ($package->getRequires() as $relation) { $possibleRequires = $this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()); // the strategy here is to not insist on dependencies // that are already broken. so if we find one provider // that was already installed, we know that the // dependency was not broken before so we enforce it if ($dontFix) { $foundInstalled = false; foreach ($possibleRequires as $require) { if ($this->installed->contains($require)) { $foundInstalled = true; break; } } // no installed provider found: previously broken dependency => don't add rule if (!$foundInstalled) { continue; } } $this->addRule(self::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, self::RULE_PACKAGE_REQUIRES, (string) $relation)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); } } foreach ($package->getConflicts() as $relation) { $possibleConflicts = $this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()); foreach ($possibleConflicts as $conflict) { if ($dontfix && $this->installed->contains($conflict)) { continue; } $this->addRule(self::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, self::RULE_PACKAGE_CONFLICT, (string) $relation)); } } foreach ($package->getRecommends() as $relation) { foreach ($this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()) as $recommend) { $workQueue->enqueue($recommend); } } foreach ($package->getSuggests() as $relation) { foreach ($this->pool->whatProvides($relation->getToPackageName(), $relation->getConstraint()) as $suggest) { $workQueue->enqueue($suggest); } } } } /** * Adds all rules for all update packages of a given package * * @param Package $package Rules for this package's updates are to be added * @param bool $allowAll Whether downgrades are allowed */ private function addRulesForUpdatePackages(Package $package, $allowAll) { $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, $allowAll); $this->addRulesForPackage($package); foreach ($updates as $update) { $this->addRulesForPackage($update); } } /** * Sets up watch chains for all rules. * * Next1/2 always points to the next rule that is watching the same package. * The watches array contains rules to start from for each package * */ private function makeWatches() { foreach ($this->rules as $type => $rules) { foreach ($rules as $i => $rule) { // skip simple assertions of the form (A) or (-A) if ($rule->isAssertion()) { continue; } if (!isset($this->watches[$rule->watch1])) { $this->watches[$rule->watch1] = 0; } $rule->next1 = $this->watches[$rule->watch1]; $this->watches[$rule->watch1] = $rule; if (!isset($this->watches[$rule->watch2])) { $this->watches[$rule->watch2] = 0; } $rule->next2 = $this->watches[$rule->watch2]; $this->watches[$rule->watch2] = $rule; } } } private function findDecisionRule(Package $package) { foreach ($this->decisionQueue as $i => $literal) { if ($package === $literal->getPackage()) { return $this->decisionQueueWhy[$i]; } } return null; } private function makeAssertionRuleDecisions() { // do we need to decide a SYSTEMSOLVABLE at level 1? foreach ($this->rules as $type => $rules) { if (self::TYPE_WEAK === $type) { continue; } foreach ($rules as $rule) { if (!$rule->isAssertion() || $rule->isDisabled()) { continue; } $literals = $rule->getLiterals(); $literal = $literals[0]; if (!$this->decided($literal->getPackage())) { } if ($this->decisionsSatisfy($literal)) { continue; } // found a conflict if (self::TYPE_LEARNED === $type) { $rule->disable(); } $conflict = $this->findDecisionRule($literal->getPackage()); // todo: handle conflict with systemsolvable? if (self::TYPE_PACKAGE === $conflict->getType()) { } } } foreach ($this->rules[self::TYPE_WEAK] as $rule) { if (!$rule->isAssertion() || $rule->isDisabled()) { continue; } if ($this->decisionsSatisfy($literals[0])) { continue; } // conflict, but this is a weak rule => disable $rule->disable(); } } public function solve(Request $request) { $this->jobs = $request->getJobs(); $installedPackages = $this->installed->getPackages(); foreach ($this->jobs as $job) { switch ($job['cmd']) { case 'update-all': foreach ($installedPackages as $package) { $this->updateMap[$package->getId()] = true; } break; case 'fix-all': foreach ($installedPackages as $package) { $this->fixMap[$package->getId()] = true; } break; } foreach ($job['packages'] as $package) { switch ($job['cmd']) { case 'fix': if ($this->installed->contains($package)) { $this->fixMap[$package->getId()] = true; } break; case 'update': if ($this->installed->contains($package)) { $this->updateMap[$package->getId()] = true; } break; } } } foreach ($installedPackages as $package) { $this->addRulesForPackage($package); } foreach ($installedPackages as $package) { $this->addRulesForUpdatePackages($package, true); } foreach ($this->jobs as $job) { foreach ($job['packages'] as $package) { switch ($job['cmd']) { case 'install': $this->installCandidateMap[$package->getId()] = true; $this->addRulesForPackage($package); break; } } } // solver_addrpmrulesforweak(solv, &addedmap); /* * first pass done, we now have all the rpm rules we need. * unify existing rules before going over all job rules and * policy rules. * at this point the system is always solvable, * as an empty system (remove all packages) is a valid solution */ // solver_unifyrules(solv); /* remove duplicate rpm rules */ // no idea what this is // /* create dup maps if needed. We need the maps early to create our // * update rules */ // if (hasdupjob) // solver_createdupmaps(solv); foreach ($installedPackages as $package) { // create a feature rule which allows downgrades $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, true); $featureRule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); // create an update rule which does not allow downgrades $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installed, $package, false); $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); if ($rule->equals($featureRule)) { if ($this->policy->allowUninstall()) { $this->addRule(self::TYPE_WEAK, $featureRule); } else { $this->addRule(self::TYPE_UPDATE, $rule); } } else if ($this->policy->allowUninstall()) { $this->addRule(self::TYPE_WEAK, $featureRule); $this->addRule(self::TYPE_WEAK, $rule); } } foreach ($this->jobs as $job) { switch ($job['cmd']) { case 'install': $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']); $this->addRule(self::TYPE_JOB, $rule); //$this->ruleToJob[$rule] = $job; break; case 'remove': // remove all packages with this name including uninstalled // ones to make sure none of them are picked as replacements // todo: cleandeps foreach ($job['packages'] as $package) { $rule = $this->createRemoveRule($package, self::RULE_JOB_REMOVE); $this->addRule(self::TYPE_JOB, $rule); //$this->ruleToJob[$rule] = $job; } break; case 'lock': foreach ($job['packages'] as $package) { if ($this->installed->contains($package)) { $rule = $this->createInstallRule($package, self::RULE_JOB_LOCK); } else { $rule = $this->createRemoveRule($package, self::RULE_JOB_LOCK); } $this->addRule(self::TYPE_JOB, $rule); //$this->ruleToJob[$rule] = $job; } break; } } // solver_addchoicerules(solv); $this->makeWatches(); /* disable update rules that conflict with our job */ //solver_disablepolicyrules(solv); /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); $installRecommended = 0; $this->runSat(true, $installRecommended); //findrecommendedsuggested(solv); //solver_prepare_solutions(solv); //transaction_calculate(&solv->trans, &solv->decisionq, &solv->noobsoletes); } public function printRules() { print "\n"; foreach ($this->rules as $type => $rules) { print $type . ": "; foreach ($rules as $rule) { print $rule; } print "\n"; } } protected $decisionQueue = array(); protected $propagateIndex; protected $decisionMap = array(); protected $branches = array(); protected function addDecision(Literal $l, $level) { if ($l->isWanted()) { $this->decisionMap[$l->getPackageId()] = $level; } else { $this->decisionMap[$l->getPackageId()] = -$level; } } protected function decisionsContain(Literal $l) { return (isset($this->decisionMap[$l->getPackageId()]) && ( $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() || $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted() )); } protected function decisionsSatisfy(Literal $l) { return ($l->isWanted() && isset($this->decisionMap[$l->getPackageId()]) && $this->decisionMap[$l->getPackageId()] > 0) || (!$l->isWanted() && (!isset($this->decisionMap[$l->getPackageId()]) || $this->decisionMap[$l->getPackageId()] < 0)); } protected function decisionsConflict(Literal $l) { return (isset($this->decisionMap[$l->getPackageId()]) && ( $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() || $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted() )); } protected function decided(Package $p) { return isset($this->decisionMap[$p->getId()]); } protected function undecided(Package $p) { return !isset($this->decisionMap[$p->getId()]); } protected function decidedInstall(Package $p) { return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] > 0; } protected function decidedRemove(Package $p) { return isset($this->decisionMap[$p->getId()]) && $this->decisionMap[$p->getId()] < 0; } /** * Makes a decision and propagates it to all rules. * * Evaluates each term affected by the decision (linked through watches) * If we find unit rules we make new decisions based on them * * @return Rule|null A rule on conflict, otherwise null. */ protected function propagate($level) { while ($this->propagateIndex < count($this->decisionQueue)) { // we invert the decided literal here, example: // A was decided => (-A|B) now requires B to be true, so we look for // rules which are fulfilled by -A, rather than A. $literal = $this->decisionQueue[$this->propagateIndex]->inverted(); $this->propagateIndex++; // /* foreach rule where 'pkg' is now FALSE */ //for (rp = watches + pkg; *rp; rp = next_rp) for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) { $nextRule = $rule->getNext($literal); if ($rule->isDisabled()) { continue; } $otherWatch = $rule->getOtherWatch($literal); if ($this->decisionsContain($otherWatch)) { continue; } $ruleLiterals = $rule->getLiterals(); if (sizeof($ruleLiterals) > 2) { foreach ($ruleLiterals as $ruleLiteral) { if (!$otherWatch->equals($ruleLiteral) && !$this->decisionsConflict($ruleLiteral)) { if ($literal->equals($rule->getWatch1())) { $rule->setWatch1($ruleLiteral); $rule->setNext1($rule); } else { $rule->setWatch2($ruleLiteral); $rule->setNext2($rule); } $this->watches[$ruleLiteral->getId()] = $rule; continue 2; } } } // yay, we found a unit clause! try setting it to true if ($this->decisionsConflict($otherWatch)) { return $rule; } $this->addDecision($otherWatch, $level); $this->decisionQueue[] = $otherWatch; $this->decisionQueueWhy[] = $rule; } } return null; } private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule) { return 0; } private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule) { // choose best package to install from decisionQueue $literals = $this->policy->selectPreferedPackages($decisionQueue); // if there are multiple candidates, then branch if (count($literals) > 1) { foreach ($literals as $i => $literal) { if (0 !== $i) { $this->branches[] = array($literal, $level); } } } return $this->setPropagateLearn($level, $literals[0], $disableRules, $rule); } private function runSat($disableRules = true, $installRecommended = false) { $this->propagateIndex = 0; // /* // * here's the main loop: // * 1) propagate new decisions (only needed once) // * 2) fulfill jobs // * 3) try to keep installed packages // * 4) fulfill all unresolved rules // * 5) install recommended packages // * 6) minimalize solution if we had choices // * if we encounter a problem, we rewind to a safe level and restart // * with step 1 // */ $decisionQueue = array(); $decisionSupplementQueue = array(); $disableRules = array(); $level = 1; $systemLevel = $level + 1; $minimizationsteps = 0; $installedPos = 0; $this->installedPackages = array_values($this->installed->getPackages()); while (true) { $conflictRule = $this->propagate($level); if ($conflictRule !== null) { // if (analyze_unsolvable(solv, r, disablerules)) if ($this->analyzeUnsolvable($conflictRule, $disableRules)) { continue; } else { return; } } // handle job rules if ($level < $systemLevel) { $ruleIndex = 0; foreach ($this->rules[self::TYPE_JOB] as $ruleIndex => $rule) { if ($rule->isEnabled()) { $decisionQueue = array(); $noneSatisfied = true; foreach ($rule->getLiterals() as $literal) { if ($this->decisionsSatisfy($literal)) { $noneSatisfied = false; break; } $decisionQueue[] = $literal; } if ($noneSatisfied && count($decisionQueue)) { // prune all update packages until installed version // except for requested updates if (count($this->installed) != count($this->updateMap)) { $prunedQueue = array(); foreach ($decisionQueue as $literal) { if ($this->installed->contains($literal->getPackage())) { $prunedQueue[] = $literal; if (isset($this->updateMap[$literal->getPackageId()])) { $prunedQueue = $decisionQueue; break; } } } $decisionQueue = $prunedQueue; } } if ($noneSatisfied && count($decisionQueue)) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule); if (0 === $level) { return; } if ($level <= $oLevel) { break; } } } } $systemLevel = $level + 1; // jobs left if ($ruleIndex + 1 < count($this->rules[Solver::TYPE_JOB])) { continue; } } // handle installed packages if ($level < $systemLevel) { // use two passes if any packages are being updated // -> better user experience for ($pass = (count($this->updateMap)) ? 0 : 1; $pass < 2; $pass++) { $passLevel = $level; for ($i = $installedPos, $n = 0; $n < count($this->installedPackages); $i++, $n++) { $repeat = false; if ($i == count($this->installedPackages)) { $i = 0; } $literal = new Literal($this->installedPackages[$i], true); if ($this->decisionsContain($literal)) { continue; } // only process updates in first pass if (0 === $pass && !isset($this->updateMap[$literal->getPackageId()])) { continue; } $rule = null; if (isset($this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()])) { $rule = $this->rules[Solver::TYPE_UPDATE][$literal->getPackageId()]; } if ((!$rule || $rule->isDisabled()) && isset($this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()])) { $rule = $this->rules[Solver::TYPE_FEATURE][$literal->getPackageId()]; } if (!$rule || $rule->isDisabled()) { continue; } $decisionQueue = array(); if (!isset($this->noUpdate[$literal->getPackageId()]) && ( $this->decidedRemove($literal->getPackage()) || isset($this->updateMap[$literal->getPackageId()]) || !$literal->equals($rule->getFirstLiteral()) )) { foreach ($rule->getLiterals() as $ruleLiteral) { if ($this->decidedInstall($ruleLiteral->getPackage())) { // already fulfilled break; } if ($this->undecided($ruleLiteral->getPackage())) { $decisionQueue[] = $ruleLiteral; } } } if (sizeof($decisionQueue)) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule); if (0 === $level) { return; } if ($level <= $oLevel) { $repeat = true; } } // still undecided? keep package. if (!$repeat && $this->undecided($literal->getPackage())) { $oLevel = $level; if (isset($this->cleanDepsMap[$literal->getPackageId()])) { // clean deps removes package $level = $this->setPropagateLearn($level, $literal->invert(), $disableRules, null); } else { // ckeeping package $level = $this->setPropagateLearn($level, $literal, $disableRules, $rule); } if (0 === $level) { return; } if ($level <= $oLevel) { $repeat = true; } } if ($repeat) { if (1 === $level || $level < $passLevel) { // trouble break; } if ($level < $oLevel) { // redo all $n = 0; } // repeat $i--; $n--; continue; } } if ($n < count($this->installedPackages)) { $installedPos = $i; // retry this problem next time break; } $installedPos = 0; } $systemlevel = $level + 1; if ($pass < 2) { // had trouble => retry continue; } } if ($level < $systemLevel) { $systemLevel = $level; } foreach ($this->rules as $ruleType => $rules) { foreach ($rules as $rule) { if ($rule->isEnabled()) { $decisionQueue = array(); } } } $this->printRules(); // // /* // * decide // */ // POOL_DEBUG(SAT_DEBUG_POLICY, "deciding unresolved rules\n"); // for (i = 1, n = 1; n < solv->nrules; i++, n++) // { // if (i == solv->nrules) // i = 1; // r = solv->rules + i; // if (r->d < 0) /* ignore disabled rules */ // continue; // queue_empty(&dq); // if (r->d == 0) // { // /* binary or unary rule */ // /* need two positive undecided literals */ // if (r->p < 0 || r->w2 <= 0) // continue; // if (solv->decisionmap[r->p] || solv->decisionmap[r->w2]) // continue; // queue_push(&dq, r->p); // queue_push(&dq, r->w2); // } // else // { // /* make sure that // * all negative literals are installed // * no positive literal is installed // * i.e. the rule is not fulfilled and we // * just need to decide on the positive literals // */ // if (r->p < 0) // { // if (solv->decisionmap[-r->p] <= 0) // continue; // } // else // { // if (solv->decisionmap[r->p] > 0) // continue; // if (solv->decisionmap[r->p] == 0) // queue_push(&dq, r->p); // } // dp = pool->whatprovidesdata + r->d; // while ((p = *dp++) != 0) // { // if (p < 0) // { // if (solv->decisionmap[-p] <= 0) // break; // } // else // { // if (solv->decisionmap[p] > 0) // break; // if (solv->decisionmap[p] == 0) // queue_push(&dq, p); // } // } // if (p) // continue; // } // IF_POOLDEBUG (SAT_DEBUG_PROPAGATE) // { // POOL_DEBUG(SAT_DEBUG_PROPAGATE, "unfulfilled "); // solver_printruleclass(solv, SAT_DEBUG_PROPAGATE, r); // } // /* dq.count < 2 cannot happen as this means that // * the rule is unit */ // assert(dq.count > 1); // // olevel = level; // level = selectandinstall(solv, level, &dq, disablerules, r - solv->rules); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // if (level < systemlevel || level == 1) // break; /* trouble */ // /* something changed, so look at all rules again */ // n = 0; // } // // if (n != solv->nrules) /* ran into trouble, restart */ // continue; // // /* at this point we have a consistent system. now do the extras... */ // } } // void // solver_run_sat(Solver *solv, int disablerules, int doweak) // { // Queue dq; /* local decisionqueue */ // Queue dqs; /* local decisionqueue for supplements */ // int systemlevel; // int level, olevel; // Rule *r; // int i, j, n; // Solvable *s; // Pool *pool = solv->pool; // Id p, *dp; // int minimizationsteps; // int installedpos = solv->installed ? solv->installed->start : 0; // // IF_POOLDEBUG (SAT_DEBUG_RULE_CREATION) // { // POOL_DEBUG (SAT_DEBUG_RULE_CREATION, "number of rules: %d\n", solv->nrules); // for (i = 1; i < solv->nrules; i++) // solver_printruleclass(solv, SAT_DEBUG_RULE_CREATION, solv->rules + i); // } // // POOL_DEBUG(SAT_DEBUG_SOLVER, "initial decisions: %d\n", solv->decisionq.count); // // IF_POOLDEBUG (SAT_DEBUG_SCHUBI) // solver_printdecisions(solv); // // /* start SAT algorithm */ // level = 1; // systemlevel = level + 1; // POOL_DEBUG(SAT_DEBUG_SOLVER, "solving...\n"); // // queue_init(&dq); // queue_init(&dqs); // // /* // * here's the main loop: // * 1) propagate new decisions (only needed once) // * 2) fulfill jobs // * 3) try to keep installed packages // * 4) fulfill all unresolved rules // * 5) install recommended packages // * 6) minimalize solution if we had choices // * if we encounter a problem, we rewind to a safe level and restart // * with step 1 // */ // // minimizationsteps = 0; // for (;;) // { // /* // * initial propagation of the assertions // */ // if (level == 1) // { // POOL_DEBUG(SAT_DEBUG_PROPAGATE, "propagating (propagate_index: %d; size decisionq: %d)...\n", solv->propagate_index, solv->decisionq.count); // if ((r = propagate(solv, level)) != 0) // { // if (analyze_unsolvable(solv, r, disablerules)) // continue; // queue_free(&dq); // queue_free(&dqs); // return; // } // } // // /* // * resolve jobs first // */ // if (level < systemlevel) // { // POOL_DEBUG(SAT_DEBUG_SOLVER, "resolving job rules\n"); // for (i = solv->jobrules, r = solv->rules + i; i < solv->jobrules_end; i++, r++) // { // Id l; // if (r->d < 0) /* ignore disabled rules */ // continue; // queue_empty(&dq); // FOR_RULELITERALS(l, dp, r) // { // if (l < 0) // { // if (solv->decisionmap[-l] <= 0) // break; // } // else // { // if (solv->decisionmap[l] > 0) // break; // if (solv->decisionmap[l] == 0) // queue_push(&dq, l); // } // } // if (l || !dq.count) // continue; // /* prune to installed if not updating */ // if (dq.count > 1 && solv->installed && !solv->updatemap_all) // { // int j, k; // for (j = k = 0; j < dq.count; j++) // { // Solvable *s = pool->solvables + dq.elements[j]; // if (s->repo == solv->installed) // { // dq.elements[k++] = dq.elements[j]; // if (solv->updatemap.size && MAPTST(&solv->updatemap, dq.elements[j] - solv->installed->start)) // { // k = 0; /* package wants to be updated, do not prune */ // break; // } // } // } // if (k) // dq.count = k; // } // olevel = level; // level = selectandinstall(solv, level, &dq, disablerules, i); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // if (level <= olevel) // break; // } // systemlevel = level + 1; // if (i < solv->jobrules_end) // continue; // } // // // /* // * installed packages // */ // if (level < systemlevel && solv->installed && solv->installed->nsolvables && !solv->installed->disabled) // { // Repo *installed = solv->installed; // int pass; // // POOL_DEBUG(SAT_DEBUG_SOLVER, "resolving installed packages\n"); // /* we use two passes if we need to update packages // * to create a better user experience */ // for (pass = solv->updatemap.size ? 0 : 1; pass < 2; pass++) // { // int passlevel = level; // /* start with installedpos, the position that gave us problems last time */ // for (i = installedpos, n = installed->start; n < installed->end; i++, n++) // { // Rule *rr; // Id d; // // if (i == installed->end) // i = installed->start; // s = pool->solvables + i; // if (s->repo != installed) // continue; // // if (solv->decisionmap[i] > 0) // continue; // if (!pass && solv->updatemap.size && !MAPTST(&solv->updatemap, i - installed->start)) // continue; /* updates first */ // r = solv->rules + solv->updaterules + (i - installed->start); // rr = r; // if (!rr->p || rr->d < 0) /* disabled -> look at feature rule */ // rr -= solv->installed->end - solv->installed->start; // if (!rr->p) /* identical to update rule? */ // rr = r; // if (!rr->p) // continue; /* orpaned package */ // // /* XXX: noupdate check is probably no longer needed, as all jobs should // * already be satisfied */ // /* Actually we currently still need it because of erase jobs */ // /* if noupdate is set we do not look at update candidates */ // queue_empty(&dq); // if (!MAPTST(&solv->noupdate, i - installed->start) && (solv->decisionmap[i] < 0 || solv->updatemap_all || (solv->updatemap.size && MAPTST(&solv->updatemap, i - installed->start)) || rr->p != i)) // { // if (solv->noobsoletes.size && solv->multiversionupdaters // && (d = solv->multiversionupdaters[i - installed->start]) != 0) // { // /* special multiversion handling, make sure best version is chosen */ // queue_push(&dq, i); // while ((p = pool->whatprovidesdata[d++]) != 0) // if (solv->decisionmap[p] >= 0) // queue_push(&dq, p); // policy_filter_unwanted(solv, &dq, POLICY_MODE_CHOOSE); // p = dq.elements[0]; // if (p != i && solv->decisionmap[p] == 0) // { // rr = solv->rules + solv->featurerules + (i - solv->installed->start); // if (!rr->p) /* update rule == feature rule? */ // rr = rr - solv->featurerules + solv->updaterules; // dq.count = 1; // } // else // dq.count = 0; // } // else // { // /* update to best package */ // FOR_RULELITERALS(p, dp, rr) // { // if (solv->decisionmap[p] > 0) // { // dq.count = 0; /* already fulfilled */ // break; // } // if (!solv->decisionmap[p]) // queue_push(&dq, p); // } // } // } // /* install best version */ // if (dq.count) // { // olevel = level; // level = selectandinstall(solv, level, &dq, disablerules, rr - solv->rules); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // if (level <= olevel) // { // if (level == 1 || level < passlevel) // break; /* trouble */ // if (level < olevel) // n = installed->start; /* redo all */ // i--; // n--; // continue; // } // } // /* if still undecided keep package */ // if (solv->decisionmap[i] == 0) // { // olevel = level; // if (solv->cleandepsmap.size && MAPTST(&solv->cleandepsmap, i - installed->start)) // { // POOL_DEBUG(SAT_DEBUG_POLICY, "cleandeps erasing %s\n", solvid2str(pool, i)); // level = setpropagatelearn(solv, level, -i, disablerules, 0); // } // else // { // POOL_DEBUG(SAT_DEBUG_POLICY, "keeping %s\n", solvid2str(pool, i)); // level = setpropagatelearn(solv, level, i, disablerules, r - solv->rules); // } // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // if (level <= olevel) // { // if (level == 1 || level < passlevel) // break; /* trouble */ // if (level < olevel) // n = installed->start; /* redo all */ // i--; // n--; // continue; /* retry with learnt rule */ // } // } // } // if (n < installed->end) // { // installedpos = i; /* retry problem solvable next time */ // break; /* ran into trouble */ // } // installedpos = installed->start; /* reset installedpos */ // } // systemlevel = level + 1; // if (pass < 2) // continue; /* had trouble, retry */ // } // // if (level < systemlevel) // systemlevel = level; // // /* // * decide // */ // POOL_DEBUG(SAT_DEBUG_POLICY, "deciding unresolved rules\n"); // for (i = 1, n = 1; n < solv->nrules; i++, n++) // { // if (i == solv->nrules) // i = 1; // r = solv->rules + i; // if (r->d < 0) /* ignore disabled rules */ // continue; // queue_empty(&dq); // if (r->d == 0) // { // /* binary or unary rule */ // /* need two positive undecided literals */ // if (r->p < 0 || r->w2 <= 0) // continue; // if (solv->decisionmap[r->p] || solv->decisionmap[r->w2]) // continue; // queue_push(&dq, r->p); // queue_push(&dq, r->w2); // } // else // { // /* make sure that // * all negative literals are installed // * no positive literal is installed // * i.e. the rule is not fulfilled and we // * just need to decide on the positive literals // */ // if (r->p < 0) // { // if (solv->decisionmap[-r->p] <= 0) // continue; // } // else // { // if (solv->decisionmap[r->p] > 0) // continue; // if (solv->decisionmap[r->p] == 0) // queue_push(&dq, r->p); // } // dp = pool->whatprovidesdata + r->d; // while ((p = *dp++) != 0) // { // if (p < 0) // { // if (solv->decisionmap[-p] <= 0) // break; // } // else // { // if (solv->decisionmap[p] > 0) // break; // if (solv->decisionmap[p] == 0) // queue_push(&dq, p); // } // } // if (p) // continue; // } // IF_POOLDEBUG (SAT_DEBUG_PROPAGATE) // { // POOL_DEBUG(SAT_DEBUG_PROPAGATE, "unfulfilled "); // solver_printruleclass(solv, SAT_DEBUG_PROPAGATE, r); // } // /* dq.count < 2 cannot happen as this means that // * the rule is unit */ // assert(dq.count > 1); // // olevel = level; // level = selectandinstall(solv, level, &dq, disablerules, r - solv->rules); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // if (level < systemlevel || level == 1) // break; /* trouble */ // /* something changed, so look at all rules again */ // n = 0; // } // // if (n != solv->nrules) /* ran into trouble, restart */ // continue; // // /* at this point we have a consistent system. now do the extras... */ // // if (doweak) // { // int qcount; // // POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended packages\n"); // queue_empty(&dq); /* recommended packages */ // queue_empty(&dqs); /* supplemented packages */ // for (i = 1; i < pool->nsolvables; i++) // { // if (solv->decisionmap[i] < 0) // continue; // if (solv->decisionmap[i] > 0) // { // /* installed, check for recommends */ // Id *recp, rec, pp, p; // s = pool->solvables + i; // if (solv->ignorealreadyrecommended && s->repo == solv->installed) // continue; // /* XXX need to special case AND ? */ // if (s->recommends) // { // recp = s->repo->idarraydata + s->recommends; // while ((rec = *recp++) != 0) // { // qcount = dq.count; // FOR_PROVIDES(p, pp, rec) // { // if (solv->decisionmap[p] > 0) // { // dq.count = qcount; // break; // } // else if (solv->decisionmap[p] == 0) // { // queue_pushunique(&dq, p); // } // } // } // } // } // else // { // s = pool->solvables + i; // if (!s->supplements) // continue; // if (!pool_installable(pool, s)) // continue; // if (!solver_is_supplementing(solv, s)) // continue; // queue_push(&dqs, i); // } // } // // /* filter out all packages obsoleted by installed packages */ // /* this is no longer needed if we have reverse obsoletes */ // if ((dqs.count || dq.count) && solv->installed) // { // Map obsmap; // Id obs, *obsp, po, ppo; // // map_init(&obsmap, pool->nsolvables); // for (p = solv->installed->start; p < solv->installed->end; p++) // { // s = pool->solvables + p; // if (s->repo != solv->installed || !s->obsoletes) // continue; // if (solv->decisionmap[p] <= 0) // continue; // if (solv->noobsoletes.size && MAPTST(&solv->noobsoletes, p)) // continue; // obsp = s->repo->idarraydata + s->obsoletes; // /* foreach obsoletes */ // while ((obs = *obsp++) != 0) // FOR_PROVIDES(po, ppo, obs) // MAPSET(&obsmap, po); // } // for (i = j = 0; i < dqs.count; i++) // if (!MAPTST(&obsmap, dqs.elements[i])) // dqs.elements[j++] = dqs.elements[i]; // dqs.count = j; // for (i = j = 0; i < dq.count; i++) // if (!MAPTST(&obsmap, dq.elements[i])) // dq.elements[j++] = dq.elements[i]; // dq.count = j; // map_free(&obsmap); // } // // /* filter out all already supplemented packages if requested */ // if (solv->ignorealreadyrecommended && dqs.count) // { // /* turn off all new packages */ // for (i = 0; i < solv->decisionq.count; i++) // { // p = solv->decisionq.elements[i]; // if (p < 0) // continue; // s = pool->solvables + p; // if (s->repo && s->repo != solv->installed) // solv->decisionmap[p] = -solv->decisionmap[p]; // } // /* filter out old supplements */ // for (i = j = 0; i < dqs.count; i++) // { // p = dqs.elements[i]; // s = pool->solvables + p; // if (!s->supplements) // continue; // if (!solver_is_supplementing(solv, s)) // dqs.elements[j++] = p; // } // dqs.count = j; // /* undo turning off */ // for (i = 0; i < solv->decisionq.count; i++) // { // p = solv->decisionq.elements[i]; // if (p < 0) // continue; // s = pool->solvables + p; // if (s->repo && s->repo != solv->installed) // solv->decisionmap[p] = -solv->decisionmap[p]; // } // } // // /* multiversion doesn't mix well with supplements. // * filter supplemented packages where we already decided // * to install a different version (see bnc#501088) */ // if (dqs.count && solv->noobsoletes.size) // { // for (i = j = 0; i < dqs.count; i++) // { // p = dqs.elements[i]; // if (MAPTST(&solv->noobsoletes, p)) // { // Id p2, pp2; // s = pool->solvables + p; // FOR_PROVIDES(p2, pp2, s->name) // if (solv->decisionmap[p2] > 0 && pool->solvables[p2].name == s->name) // break; // if (p2) // continue; /* ignore this package */ // } // dqs.elements[j++] = p; // } // dqs.count = j; // } // // /* make dq contain both recommended and supplemented pkgs */ // if (dqs.count) // { // for (i = 0; i < dqs.count; i++) // queue_pushunique(&dq, dqs.elements[i]); // } // // if (dq.count) // { // Map dqmap; // int decisioncount = solv->decisionq.count; // // if (dq.count == 1) // { // /* simple case, just one package. no need to choose */ // p = dq.elements[0]; // if (dqs.count) // POOL_DEBUG(SAT_DEBUG_POLICY, "installing supplemented %s\n", solvid2str(pool, p)); // else // POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended %s\n", solvid2str(pool, p)); // queue_push(&solv->recommendations, p); // level = setpropagatelearn(solv, level, p, 0, 0); // continue; /* back to main loop */ // } // // /* filter packages, this gives us the best versions */ // policy_filter_unwanted(solv, &dq, POLICY_MODE_RECOMMEND); // // /* create map of result */ // map_init(&dqmap, pool->nsolvables); // for (i = 0; i < dq.count; i++) // MAPSET(&dqmap, dq.elements[i]); // // /* install all supplemented packages */ // for (i = 0; i < dqs.count; i++) // { // p = dqs.elements[i]; // if (solv->decisionmap[p] || !MAPTST(&dqmap, p)) // continue; // POOL_DEBUG(SAT_DEBUG_POLICY, "installing supplemented %s\n", solvid2str(pool, p)); // queue_push(&solv->recommendations, p); // olevel = level; // level = setpropagatelearn(solv, level, p, 0, 0); // if (level <= olevel) // break; // } // if (i < dqs.count || solv->decisionq.count < decisioncount) // { // map_free(&dqmap); // continue; // } // // /* install all recommended packages */ // /* more work as we want to created branches if multiple // * choices are valid */ // for (i = 0; i < decisioncount; i++) // { // Id rec, *recp, pp; // p = solv->decisionq.elements[i]; // if (p < 0) // continue; // s = pool->solvables + p; // if (!s->repo || (solv->ignorealreadyrecommended && s->repo == solv->installed)) // continue; // if (!s->recommends) // continue; // recp = s->repo->idarraydata + s->recommends; // while ((rec = *recp++) != 0) // { // queue_empty(&dq); // FOR_PROVIDES(p, pp, rec) // { // if (solv->decisionmap[p] > 0) // { // dq.count = 0; // break; // } // else if (solv->decisionmap[p] == 0 && MAPTST(&dqmap, p)) // queue_pushunique(&dq, p); // } // if (!dq.count) // continue; // if (dq.count > 1) // { // /* multiple candidates, open a branch */ // for (i = 1; i < dq.count; i++) // queue_push(&solv->branches, dq.elements[i]); // queue_push(&solv->branches, -level); // } // p = dq.elements[0]; // POOL_DEBUG(SAT_DEBUG_POLICY, "installing recommended %s\n", solvid2str(pool, p)); // queue_push(&solv->recommendations, p); // olevel = level; // level = setpropagatelearn(solv, level, p, 0, 0); // if (level <= olevel || solv->decisionq.count < decisioncount) // break; /* we had to revert some decisions */ // } // if (rec) // break; /* had a problem above, quit loop */ // } // map_free(&dqmap); // // continue; /* back to main loop so that all deps are checked */ // } // } // // if (solv->dupmap_all && solv->installed) // { // int installedone = 0; // // /* let's see if we can install some unsupported package */ // POOL_DEBUG(SAT_DEBUG_SOLVER, "deciding orphaned packages\n"); // for (i = 0; i < solv->orphaned.count; i++) // { // p = solv->orphaned.elements[i]; // if (solv->decisionmap[p]) // continue; /* already decided */ // olevel = level; // if (solv->droporphanedmap_all) // continue; // if (solv->droporphanedmap.size && MAPTST(&solv->droporphanedmap, p - solv->installed->start)) // continue; // POOL_DEBUG(SAT_DEBUG_SOLVER, "keeping orphaned %s\n", solvid2str(pool, p)); // level = setpropagatelearn(solv, level, p, 0, 0); // installedone = 1; // if (level < olevel) // break; // } // if (installedone || i < solv->orphaned.count) // continue; /* back to main loop */ // for (i = 0; i < solv->orphaned.count; i++) // { // p = solv->orphaned.elements[i]; // if (solv->decisionmap[p]) // continue; /* already decided */ // POOL_DEBUG(SAT_DEBUG_SOLVER, "removing orphaned %s\n", solvid2str(pool, p)); // olevel = level; // level = setpropagatelearn(solv, level, -p, 0, 0); // if (level < olevel) // break; // } // if (i < solv->orphaned.count) // continue; /* back to main loop */ // } // // if (solv->solution_callback) // { // solv->solution_callback(solv, solv->solution_callback_data); // if (solv->branches.count) // { // int i = solv->branches.count - 1; // int l = -solv->branches.elements[i]; // Id why; // // for (; i > 0; i--) // if (solv->branches.elements[i - 1] < 0) // break; // p = solv->branches.elements[i]; // POOL_DEBUG(SAT_DEBUG_SOLVER, "branching with %s\n", solvid2str(pool, p)); // queue_empty(&dq); // for (j = i + 1; j < solv->branches.count; j++) // queue_push(&dq, solv->branches.elements[j]); // solv->branches.count = i; // level = l; // revert(solv, level); // if (dq.count > 1) // for (j = 0; j < dq.count; j++) // queue_push(&solv->branches, dq.elements[j]); // olevel = level; // why = -solv->decisionq_why.elements[solv->decisionq_why.count]; // assert(why >= 0); // level = setpropagatelearn(solv, level, p, disablerules, why); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // continue; // } // /* all branches done, we're finally finished */ // break; // } // // /* minimization step */ // if (solv->branches.count) // { // int l = 0, lasti = -1, lastl = -1; // Id why; // // p = 0; // for (i = solv->branches.count - 1; i >= 0; i--) // { // p = solv->branches.elements[i]; // if (p < 0) // l = -p; // else if (p > 0 && solv->decisionmap[p] > l + 1) // { // lasti = i; // lastl = l; // } // } // if (lasti >= 0) // { // /* kill old solvable so that we do not loop */ // p = solv->branches.elements[lasti]; // solv->branches.elements[lasti] = 0; // POOL_DEBUG(SAT_DEBUG_SOLVER, "minimizing %d -> %d with %s\n", solv->decisionmap[p], lastl, solvid2str(pool, p)); // minimizationsteps++; // // level = lastl; // revert(solv, level); // why = -solv->decisionq_why.elements[solv->decisionq_why.count]; // assert(why >= 0); // olevel = level; // level = setpropagatelearn(solv, level, p, disablerules, why); // if (level == 0) // { // queue_free(&dq); // queue_free(&dqs); // return; // } // continue; /* back to main loop */ // } // } // /* no minimization found, we're finally finished! */ // break; // } // // POOL_DEBUG(SAT_DEBUG_STATS, "solver statistics: %d learned rules, %d unsolvable, %d minimization steps\n", solv->stats_learned, solv->stats_unsolvable, minimizationsteps); // // POOL_DEBUG(SAT_DEBUG_STATS, "done solving.\n\n"); // queue_free(&dq); // queue_free(&dqs); // #if 0 // solver_printdecisionq(solv, SAT_DEBUG_RESULT); // #endif // } }