1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071 |
- <?php
- /*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- * Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Composer\DependencyResolver;
- use Composer\Repository\RepositoryInterface;
- use Composer\Package\PackageInterface;
- use Composer\DependencyResolver\Operation;
- /**
- * @author Nils Adermann <naderman@naderman.de>
- */
- class Solver
- {
- protected $policy;
- protected $pool;
- protected $installed;
- protected $rules;
- protected $updateAll;
- protected $ruleToJob = array();
- protected $addedMap = array();
- protected $fixMap = array();
- protected $updateMap = array();
- protected $noObsoletes = array();
- protected $watches = array();
- protected $removeWatches = array();
- protected $decisionMap;
- protected $installedMap;
- protected $packageToFeatureRule = array();
- public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
- {
- $this->policy = $policy;
- $this->pool = $pool;
- $this->installed = $installed;
- $this->rules = new RuleSet;
- }
- /**
- * 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 PackageInterface $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
- */
- protected function createRequireRule(PackageInterface $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 PackageInterface $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(PackageInterface $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 PackageInterface $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
- */
- protected function createInstallRule(PackageInterface $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
- */
- protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
- {
- $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 PackageInterface $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
- */
- protected function createRemoveRule(PackageInterface $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 PackageInterface $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
- */
- protected function createConflictRule(PackageInterface $issuer, PackageInterface $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);
- }
- /**
- * 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) {
- if ($this->rules->containsEqual($newRule)) {
- return;
- }
- $this->rules->add($newRule, $type);
- }
- }
- protected function addRulesForPackage(PackageInterface $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 (isset($this->installedMap[$package->getId()]) && !isset($this->fixMap[$package->getId()])) {
- $dontFix = 1;
- }
- if (!$dontFix && !$this->policy->installable($this, $this->pool, $this->installedMap, $package)) {
- $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, Rule::RULE_NOT_INSTALLABLE, (string) $package));
- continue;
- }
- foreach ($package->getRequires() as $link) {
- $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->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 (isset($this->installedMap[$require->getId()])) {
- $foundInstalled = true;
- break;
- }
- }
- // no installed provider found: previously broken dependency => don't add rule
- if (!$foundInstalled) {
- continue;
- }
- }
- $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
- foreach ($possibleRequires as $require) {
- $workQueue->enqueue($require);
- }
- }
- foreach ($package->getConflicts() as $link) {
- $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
- foreach ($possibleConflicts as $conflict) {
- if ($dontFix && isset($this->installedMap[$conflict->getId()])) {
- continue;
- }
- $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
- }
- }
- // check obsoletes and implicit obsoletes of a package
- // if ignoreinstalledsobsoletes is not set, we're also checking
- // obsoletes of installed packages (like newer rpm versions)
- //
- /** TODO if ($this->noInstalledObsoletes) */
- if (true) {
- $noObsoletes = isset($this->noObsoletes[$package->getId()]);
- $isInstalled = (isset($this->installedMap[$package->getId()]));
- foreach ($package->getReplaces() as $link) {
- $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
- foreach ($obsoleteProviders as $provider) {
- if ($provider === $package) {
- continue;
- }
- if ($isInstalled && $dontFix && isset($this->installedMap[$provider->getId()])) {
- continue; // don't repair installed/installed problems
- }
- $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
- $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
- }
- }
- // check implicit obsoletes
- // for installed packages we only need to check installed/installed problems (and
- // only when dontFix is not set), as the others are picked up when looking at the
- // uninstalled package.
- if (!$isInstalled || !$dontFix) {
- $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
- foreach ($obsoleteProviders as $provider) {
- if ($provider === $package) {
- continue;
- }
- if ($isInstalled && !isset($this->installedMap[$provider->getId()])) {
- continue;
- }
- // obsolete same packages even when noObsoletes
- if ($noObsoletes && (!$package->equals($provider))) {
- continue;
- }
- $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
- $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
- }
- }
- }
- foreach ($package->getRecommends() as $link) {
- foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $recommend) {
- $workQueue->enqueue($recommend);
- }
- }
- foreach ($package->getSuggests() as $link) {
- foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $suggest) {
- $workQueue->enqueue($suggest);
- }
- }
- }
- }
- /**
- * Adds all rules for all update packages of a given package
- *
- * @param PackageInterface $package Rules for this package's updates are to
- * be added
- * @param bool $allowAll Whether downgrades are allowed
- */
- private function addRulesForUpdatePackages(PackageInterface $package)
- {
- $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
- $this->addRulesForPackage($package);
- foreach ($updates as $update) {
- $this->addRulesForPackage($update);
- }
- }
- /**
- * Alters watch chains for a rule.
- *
- * 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 addWatchesToRule(Rule $rule)
- {
- // skip simple assertions of the form (A) or (-A)
- if ($rule->isAssertion()) {
- return;
- }
- if (!isset($this->watches[$rule->watch1])) {
- $this->watches[$rule->watch1] = null;
- }
- $rule->next1 = $this->watches[$rule->watch1];
- $this->watches[$rule->watch1] = $rule;
- if (!isset($this->watches[$rule->watch2])) {
- $this->watches[$rule->watch2] = null;
- }
- $rule->next2 = $this->watches[$rule->watch2];
- $this->watches[$rule->watch2] = $rule;
- }
- /**
- * Put watch2 on rule's literal with highest level
- */
- private function watch2OnHighest(Rule $rule)
- {
- $literals = $rule->getLiterals();
- // if there are only 2 elements, both are being watched anyway
- if ($literals < 3) {
- return;
- }
- $watchLevel = 0;
- foreach ($literals as $literal) {
- $level = abs($this->decisionMap[$literal->getPackageId()]);
- if ($level > $watchLevel) {
- $rule->watch2 = $literal->getId();
- $watchLevel = $level;
- }
- }
- }
- private function findDecisionRule(PackageInterface $package)
- {
- foreach ($this->decisionQueue as $i => $literal) {
- if ($package === $literal->getPackage()) {
- return $this->decisionQueueWhy[$i];
- }
- }
- return null;
- }
- // aka solver_makeruledecisions
- private function makeAssertionRuleDecisions()
- {
- // do we need to decide a SYSTEMSOLVABLE at level 1?
- $decisionStart = count($this->decisionQueue);
- for ($ruleIndex = 0; $ruleIndex < count($this->rules); $ruleIndex++) {
- $rule = $this->rules->ruleById($ruleIndex);
- if ($rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
- continue;
- }
- $literals = $rule->getLiterals();
- $literal = $literals[0];
- if (!$this->decided($literal->getPackage())) {
- $this->decisionQueue[] = $literal;
- $this->decisionQueueWhy[] = $rule;
- $this->addDecision($literal, 1);
- continue;
- }
- if ($this->decisionsSatisfy($literal)) {
- continue;
- }
- // found a conflict
- if (RuleSet::TYPE_LEARNED === $rule->getType()) {
- $rule->disable();
- continue;
- }
- $conflict = $this->findDecisionRule($literal->getPackage());
- /** TODO: handle conflict with systemsolvable? */
- if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
- $problem = new Problem;
- if ($rule->getType() == RuleSet::TYPE_JOB) {
- $job = $this->ruleToJob[$rule->getId()];
- $problem->addJobRule($job, $rule);
- $problem->addRule($conflict);
- $this->disableProblem($job);
- } else {
- $problem->addRule($rule);
- $problem->addRule($conflict);
- $this->disableProblem($rule);
- }
- $this->problems[] = $problem;
- continue;
- }
- // conflict with another job or update/feature rule
- $problem = new Problem;
- $problem->addRule($rule);
- $problem->addRule($conflict);
- // push all of our rules (can only be feature or job rules)
- // asserting this literal on the problem stack
- foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
- if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
- continue;
- }
- $assertRuleLiterals = $assertRule->getLiterals();
- $assertRuleLiteral = $assertRuleLiterals[0];
- if ($literal->getPackageId() !== $assertRuleLiteral->getPackageId()) {
- continue;
- }
- if ($assertRule->getType() === RuleSet::TYPE_JOB) {
- $job = $this->ruleToJob[$assertRule->getId()];
- $problem->addJobRule($job, $assertRule);
- $this->disableProblem($job);
- } else {
- $problem->addRule($assertRule);
- $this->disableProblem($assertRule);
- }
- }
- $this->problems[] = $problem;
- // start over
- while (count($this->decisionQueue) > $decisionStart) {
- $decisionLiteral = array_pop($this->decisionQueue);
- array_pop($this->decisionQueueWhy);
- unset($this->decisionQueueFree[count($this->decisionQueue)]);
- $this->decisionMap[$decisionLiteral->getPackageId()] = 0;
- }
- $ruleIndex = -1;
- }
- foreach ($this->rules as $rule) {
- if (!$rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
- continue;
- }
- $literals = $rule->getLiterals();
- $literal = $literals[0];
- if ($this->decisionMap[$literal->getPackageId()] == 0) {
- $this->decisionQueue[] = $literal;
- $this->decisionQueueWhy[] = $rule;
- $this->addDecision($literal, 1);
- continue;
- }
- if ($this->decisionsSatisfy($literals[0])) {
- continue;
- }
- // conflict, but this is a weak rule => disable
- if ($rule->getType() == RuleSet::TYPE_JOB) {
- $why = $this->ruleToJob[$rule->getId()];
- } else {
- $why = $rule;
- }
- $this->disableProblem($why);
- /** TODO solver_reenablepolicyrules(solv, -(v + 1)); */
- }
- }
- protected function addChoiceRules()
- {
- // void
- // solver_addchoicerules(Solver *solv)
- // {
- // Pool *pool = solv->pool;
- // Map m, mneg;
- // Rule *r;
- // Queue q, qi;
- // int i, j, rid, havechoice;
- // Id p, d, *pp;
- // Id p2, pp2;
- // Solvable *s, *s2;
- //
- // solv->choicerules = solv->nrules;
- // if (!pool->installed)
- // {
- // solv->choicerules_end = solv->nrules;
- // return;
- // }
- // solv->choicerules_ref = sat_calloc(solv->rpmrules_end, sizeof(Id));
- // queue_init(&q);
- // queue_init(&qi);
- // map_init(&m, pool->nsolvables);
- // map_init(&mneg, pool->nsolvables);
- // /* set up negative assertion map from infarch and dup rules */
- // for (rid = solv->infarchrules, r = solv->rules + rid; rid < solv->infarchrules_end; rid++, r++)
- // if (r->p < 0 && !r->w2 && (r->d == 0 || r->d == -1))
- // MAPSET(&mneg, -r->p);
- // for (rid = solv->duprules, r = solv->rules + rid; rid < solv->duprules_end; rid++, r++)
- // if (r->p < 0 && !r->w2 && (r->d == 0 || r->d == -1))
- // MAPSET(&mneg, -r->p);
- // for (rid = 1; rid < solv->rpmrules_end ; rid++)
- // {
- // r = solv->rules + rid;
- // if (r->p >= 0 || ((r->d == 0 || r->d == -1) && r->w2 < 0))
- // continue; /* only look at requires rules */
- // // solver_printrule(solv, SAT_DEBUG_RESULT, r);
- // queue_empty(&q);
- // queue_empty(&qi);
- // havechoice = 0;
- // FOR_RULELITERALS(p, pp, r)
- // {
- // if (p < 0)
- // continue;
- // s = pool->solvables + p;
- // if (!s->repo)
- // continue;
- // if (s->repo == pool->installed)
- // {
- // queue_push(&q, p);
- // continue;
- // }
- // /* check if this package is "blocked" by a installed package */
- // s2 = 0;
- // FOR_PROVIDES(p2, pp2, s->name)
- // {
- // s2 = pool->solvables + p2;
- // if (s2->repo != pool->installed)
- // continue;
- // if (!pool->implicitobsoleteusesprovides && s->name != s2->name)
- // continue;
- // if (pool->obsoleteusescolors && !pool_colormatch(pool, s, s2))
- // continue;
- // break;
- // }
- // if (p2)
- // {
- // /* found installed package p2 that we can update to p */
- // if (MAPTST(&mneg, p))
- // continue;
- // if (policy_is_illegal(solv, s2, s, 0))
- // continue;
- // queue_push(&qi, p2);
- // queue_push(&q, p);
- // continue;
- // }
- // if (s->obsoletes)
- // {
- // Id obs, *obsp = s->repo->idarraydata + s->obsoletes;
- // s2 = 0;
- // while ((obs = *obsp++) != 0)
- // {
- // FOR_PROVIDES(p2, pp2, obs)
- // {
- // s2 = pool->solvables + p2;
- // if (s2->repo != pool->installed)
- // continue;
- // if (!pool->obsoleteusesprovides && !pool_match_nevr(pool, pool->solvables + p2, obs))
- // continue;
- // if (pool->obsoleteusescolors && !pool_colormatch(pool, s, s2))
- // continue;
- // break;
- // }
- // if (p2)
- // break;
- // }
- // if (obs)
- // {
- // /* found installed package p2 that we can update to p */
- // if (MAPTST(&mneg, p))
- // continue;
- // if (policy_is_illegal(solv, s2, s, 0))
- // continue;
- // queue_push(&qi, p2);
- // queue_push(&q, p);
- // continue;
- // }
- // }
- // /* package p is independent of the installed ones */
- // havechoice = 1;
- // }
- // if (!havechoice || !q.count)
- // continue; /* no choice */
- //
- // /* now check the update rules of the installed package.
- // * if all packages of the update rules are contained in
- // * the dependency rules, there's no need to set up the choice rule */
- // map_empty(&m);
- // FOR_RULELITERALS(p, pp, r)
- // if (p > 0)
- // MAPSET(&m, p);
- // for (i = 0; i < qi.count; i++)
- // {
- // if (!qi.elements[i])
- // continue;
- // Rule *ur = solv->rules + solv->updaterules + (qi.elements[i] - pool->installed->start);
- // if (!ur->p)
- // ur = solv->rules + solv->featurerules + (qi.elements[i] - pool->installed->start);
- // if (!ur->p)
- // continue;
- // FOR_RULELITERALS(p, pp, ur)
- // if (!MAPTST(&m, p))
- // break;
- // if (p)
- // break;
- // for (j = i + 1; j < qi.count; j++)
- // if (qi.elements[i] == qi.elements[j])
- // qi.elements[j] = 0;
- // }
- // if (i == qi.count)
- // {
- // #if 0
- // printf("skipping choice ");
- // solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + rid);
- // #endif
- // continue;
- // }
- // d = q.count ? pool_queuetowhatprovides(pool, &q) : 0;
- // solver_addrule(solv, r->p, d);
- // queue_push(&solv->weakruleq, solv->nrules - 1);
- // solv->choicerules_ref[solv->nrules - 1 - solv->choicerules] = rid;
- // #if 0
- // printf("OLD ");
- // solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + rid);
- // printf("WEAK CHOICE ");
- // solver_printrule(solv, SAT_DEBUG_RESULT, solv->rules + solv->nrules - 1);
- // #endif
- // }
- // queue_free(&q);
- // queue_free(&qi);
- // map_free(&m);
- // map_free(&mneg);
- // solv->choicerules_end = solv->nrules;
- // }
- }
- /***********************************************************************
- ***
- *** Policy rule disabling/reenabling
- ***
- *** Disable all policy rules that conflict with our jobs. If a job
- *** gets disabled later on, reenable the involved policy rules again.
- ***
- *** /
- #define DISABLE_UPDATE 1
- #define DISABLE_INFARCH 2
- #define DISABLE_DUP 3
- */
- protected function jobToDisableQueue(array $job, array $disableQueue)
- {
- switch ($job['cmd']) {
- case 'install':
- foreach ($job['packages'] as $package) {
- if (isset($this->installedMap[$package->getId()])) {
- $disableQueue[] = array('type' => 'update', 'package' => $package);
- }
- /* all job packages obsolete * /
- qstart = q->count;
- pass = 0;
- memset(&omap, 0, sizeof(omap));
- FOR_JOB_SELECT(p, pp, select, what)
- {
- Id p2, pp2;
- if (pass == 1)
- map_grow(&omap, installed->end - installed->start);
- s = pool->solvables + p;
- if (s->obsoletes)
- {
- Id obs, *obsp;
- obsp = s->repo->idarraydata + s->obsoletes;
- while ((obs = *obsp++) != 0)
- FOR_PROVIDES(p2, pp2, obs)
- {
- Solvable *ps = pool->solvables + p2;
- if (ps->repo != installed)
- continue;
- if (!pool->obsoleteusesprovides && !pool_match_nevr(pool, ps, obs))
- continue;
- if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
- continue;
- if (pass)
- MAPSET(&omap, p2 - installed->start);
- else
- queue_push2(q, DISABLE_UPDATE, p2);
- }
- }
- FOR_PROVIDES(p2, pp2, s->name)
- {
- Solvable *ps = pool->solvables + p2;
- if (ps->repo != installed)
- continue;
- if (!pool->implicitobsoleteusesprovides && ps->name != s->name)
- continue;
- if (pool->obsoleteusescolors && !pool_colormatch(pool, s, ps))
- continue;
- if (pass)
- MAPSET(&omap, p2 - installed->start);
- else
- queue_push2(q, DISABLE_UPDATE, p2);
- }
- if (pass)
- {
- for (i = j = qstart; i < q->count; i += 2)
- {
- if (MAPTST(&omap, q->elements[i + 1] - installed->start))
- {
- MAPCLR(&omap, q->elements[i + 1] - installed->start);
- q->elements[j + 1] = q->elements[i + 1];
- j += 2;
- }
- }
- queue_truncate(q, j);
- }
- if (q->count == qstart)
- break;
- pass++;
- }
- if (omap.size)
- map_free(&omap);
- if (qstart == q->count)
- return; /* nothing to prune * /
- if ((set & (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR)) == (SOLVER_SETEVR | SOLVER_SETARCH | SOLVER_SETVENDOR))
- return; /* all is set */
- /* now that we know which installed packages are obsoleted check each of them * /
- for (i = j = qstart; i < q->count; i += 2)
- {
- Solvable *is = pool->solvables + q->elements[i + 1];
- FOR_JOB_SELECT(p, pp, select, what)
- {
- int illegal = 0;
- s = pool->solvables + p;
- if ((set & SOLVER_SETEVR) != 0)
- illegal |= POLICY_ILLEGAL_DOWNGRADE; /* ignore * /
- if ((set & SOLVER_SETARCH) != 0)
- illegal |= POLICY_ILLEGAL_ARCHCHANGE; /* ignore * /
- if ((set & SOLVER_SETVENDOR) != 0)
- illegal |= POLICY_ILLEGAL_VENDORCHANGE; /* ignore * /
- illegal = policy_is_illegal(solv, is, s, illegal);
- if (illegal && illegal == POLICY_ILLEGAL_DOWNGRADE && (set & SOLVER_SETEV) != 0)
- {
- /* it's ok if the EV is different * /
- if (evrcmp(pool, is->evr, s->evr, EVRCMP_COMPARE_EVONLY) != 0)
- illegal = 0;
- }
- if (illegal)
- break;
- }
- if (!p)
- {
- /* no package conflicts with the update rule * /
- /* thus keep the DISABLE_UPDATE * /
- q->elements[j + 1] = q->elements[i + 1];
- j += 2;
- }
- }
- queue_truncate(q, j);
- return;*/
- }
- break;
- case 'remove':
- foreach ($job['packages'] as $package) {
- if (isset($this->installedMap[$package->getId()])) {
- $disableQueue[] = array('type' => 'update', 'package' => $package);
- }
- }
- break;
- }
- return $disableQueue;
- }
- protected function disableUpdateRule($package)
- {
- if (isset($this->packageToFeatureRule[$package->getId()])) {
- $this->packageToFeatureRule[$package->getId()]->disable();
- }
- }
- /**
- * Disables all policy rules that conflict with jobs
- */
- protected function disablePolicyRules()
- {
- $lastJob = null;
- $allQueue = array();
- $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
- foreach ($iterator as $rule) {
- if ($rule->isDisabled()) {
- continue;
- }
- $job = $this->ruleToJob[$rule->getId()];
- if ($job === $lastJob) {
- continue;
- }
- $lastJob = $job;
- $allQueue = $this->jobToDisableQueue($job, $allQueue);
- }
- foreach ($allQueue as $disable) {
- switch ($disable['type']) {
- case 'update':
- $this->disableUpdateRule($disable['package']);
- break;
- default:
- throw new \RuntimeException("Unsupported disable type: " . $disable['type']);
- }
- }
- }
- public function solve(Request $request)
- {
- $this->jobs = $request->getJobs();
- $installedPackages = $this->installed->getPackages();
- $this->installedMap = array();
- foreach ($installedPackages as $package) {
- $this->installedMap[$package->getId()] = $package;
- }
- if (version_compare(PHP_VERSION, '5.3.4', '>=')) {
- $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1);
- } else {
- $this->decisionMap = array_fill(0, $this->pool->getMaxId() + 1, 0);
- }
- foreach ($this->jobs as $job) {
- foreach ($job['packages'] as $package) {
- switch ($job['cmd']) {
- case 'fix':
- if (isset($this->installedMap[$package->getId()])) {
- $this->fixMap[$package->getId()] = true;
- }
- break;
- case 'update':
- if (isset($this->installedMap[$package->getId()])) {
- $this->updateMap[$package->getId()] = true;
- }
- break;
- }
- }
- switch ($job['cmd']) {
- case 'update-all':
- foreach ($installedPackages as $package) {
- $this->updateMap[$package->getId()] = true;
- }
- break;
- }
- }
- foreach ($installedPackages as $package) {
- $this->addRulesForPackage($package);
- }
- foreach ($installedPackages as $package) {
- $this->addRulesForUpdatePackages($package);
- }
- 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);
- foreach ($installedPackages as $package) {
- $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
- $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
- $rule->setWeak(true);
- $this->addRule(RuleSet::TYPE_FEATURE, $rule);
- $this->packageToFeatureRule[$package->getId()] = $rule;
- }
- foreach ($this->jobs as $job) {
- switch ($job['cmd']) {
- case 'install':
- if (empty($job['packages'])) {
- $problem = new Problem();
- $problem->addJobRule($job);
- $this->problems[] = $problem;
- } else {
- $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job['packageName']);
- $this->addRule(RuleSet::TYPE_JOB, $rule);
- $this->ruleToJob[$rule->getId()] = $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, Rule::RULE_JOB_REMOVE);
- $this->addRule(RuleSet::TYPE_JOB, $rule);
- $this->ruleToJob[$rule->getId()] = $job;
- }
- break;
- case 'lock':
- foreach ($job['packages'] as $package) {
- if (isset($this->installedMap[$package->getId()])) {
- $rule = $this->createInstallRule($package, Rule::RULE_JOB_LOCK);
- } else {
- $rule = $this->createRemoveRule($package, Rule::RULE_JOB_LOCK);
- }
- $this->addRule(RuleSet::TYPE_JOB, $rule);
- $this->ruleToJob[$rule->getId()] = $job;
- }
- break;
- }
- }
- $this->addChoiceRules();
- foreach ($this->rules as $rule) {
- $this->addWatchesToRule($rule);
- }
- /* disable update rules that conflict with our job */
- $this->disablePolicyRules();
- /* make decisions based on job/update assertions */
- $this->makeAssertionRuleDecisions();
- $installRecommended = 0;
- $this->runSat(true, $installRecommended);
- //$this->printDecisionMap();
- //findrecommendedsuggested(solv);
- //solver_prepare_solutions(solv);
- if ($this->problems) {
- throw new SolverProblemsException($this->problems);
- }
- return $this->createTransaction();
- }
- protected function createTransaction()
- {
- $transaction = array();
- $installMeansUpdateMap = array();
- foreach ($this->decisionQueue as $i => $literal) {
- $package = $literal->getPackage();
- // !wanted & installed
- if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
- $literals = array();
- if (isset($this->packageToFeatureRule[$package->getId()])) {
- $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
- }
- foreach ($literals as $updateLiteral) {
- if (!$updateLiteral->equals($literal)) {
- $installMeansUpdateMap[$updateLiteral->getPackageId()] = $package;
- }
- }
- }
- }
- foreach ($this->decisionQueue as $i => $literal) {
- $package = $literal->getPackage();
- // wanted & installed || !wanted & !installed
- if ($literal->isWanted() == (isset($this->installedMap[$package->getId()]))) {
- continue;
- }
- if ($literal->isWanted()) {
- if (isset($installMeansUpdateMap[$literal->getPackageId()])) {
- $source = $installMeansUpdateMap[$literal->getPackageId()];
- $transaction[] = new Operation\UpdateOperation(
- $source, $package, $this->decisionQueueWhy[$i]
- );
- // avoid updates to one package from multiple origins
- unset($installMeansUpdateMap[$literal->getPackageId()]);
- $ignoreRemove[$source->getId()] = true;
- } else {
- $transaction[] = new Operation\InstallOperation(
- $package, $this->decisionQueueWhy[$i]
- );
- }
- } else if (!isset($ignoreRemove[$package->getId()])) {
- $transaction[] = new Operation\UninstallOperation(
- $package, $this->decisionQueueWhy[$i]
- );
- }
- }
- return array_reverse($transaction);
- }
- protected $decisionQueue = array();
- protected $decisionQueueWhy = array();
- protected $decisionQueueFree = array();
- protected $propagateIndex;
- protected $branches = array();
- protected $problems = array();
- protected $learnedPool = array();
- protected $recommendsIndex;
- protected function literalFromId($id)
- {
- $package = $this->pool->packageById(abs($id));
- return new Literal($package, $id > 0);
- }
- protected function addDecision(Literal $l, $level)
- {
- assert($this->decisionMap[$l->getPackageId()] == 0);
- if ($l->isWanted()) {
- $this->decisionMap[$l->getPackageId()] = $level;
- } else {
- $this->decisionMap[$l->getPackageId()] = -$level;
- }
- }
- protected function addDecisionId($literalId, $level)
- {
- $packageId = abs($literalId);
- assert($this->decisionMap[$packageId] == 0);
- if ($literalId > 0) {
- $this->decisionMap[$packageId] = $level;
- } else {
- $this->decisionMap[$packageId] = -$level;
- }
- }
- protected function decisionsContain(Literal $l)
- {
- return (
- $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() ||
- $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted()
- );
- }
- protected function decisionsContainId($literalId)
- {
- $packageId = abs($literalId);
- return (
- $this->decisionMap[$packageId] > 0 && $literalId > 0 ||
- $this->decisionMap[$packageId] < 0 && $literalId < 0
- );
- }
- protected function decisionsSatisfy(Literal $l)
- {
- return ($l->isWanted() && $this->decisionMap[$l->getPackageId()] > 0) ||
- (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] <= 0);
- }
- protected function decisionsConflict(Literal $l)
- {
- return (
- $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() ||
- $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted()
- );
- }
- protected function decisionsConflictId($literalId)
- {
- $packageId = abs($literalId);
- return (
- ($this->decisionMap[$packageId] > 0 && $literalId < 0) ||
- ($this->decisionMap[$packageId] < 0 && $literalId > 0)
- );
- }
- protected function decided(PackageInterface $p)
- {
- return $this->decisionMap[$p->getId()] != 0;
- }
- protected function undecided(PackageInterface $p)
- {
- return $this->decisionMap[$p->getId()] == 0;
- }
- protected function decidedInstall(PackageInterface $p) {
- return $this->decisionMap[$p->getId()] > 0;
- }
- protected function decidedRemove(PackageInterface $p) {
- return $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)
- if (!isset($this->watches[$literal->getId()])) {
- continue;
- }
- $prevRule = null;
- for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
- $nextRule = $rule->getNext($literal);
- if ($rule->isDisabled()) {
- continue;
- }
- $otherWatch = $rule->getOtherWatch($literal);
- if ($this->decisionsContainId($otherWatch)) {
- continue;
- }
- $ruleLiterals = $rule->getLiterals();
- if (sizeof($ruleLiterals) > 2) {
- foreach ($ruleLiterals as $ruleLiteral) {
- if ($otherWatch !== $ruleLiteral->getId() &&
- !$this->decisionsConflict($ruleLiteral)) {
- if ($literal->getId() === $rule->watch1) {
- $rule->watch1 = $ruleLiteral->getId();
- $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
- } else {
- $rule->watch2 = $ruleLiteral->getId();
- $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
- }
- if ($prevRule) {
- if ($prevRule->next1 == $rule) {
- $prevRule->next1 = $nextRule;
- } else {
- $prevRule->next2 = $nextRule;
- }
- } else {
- $this->watches[$literal->getId()] = $nextRule;
- }
- $this->watches[$ruleLiteral->getId()] = $rule;
- $rule = $prevRule;
- continue 2;
- }
- }
- }
- // yay, we found a unit clause! try setting it to true
- if ($this->decisionsConflictId($otherWatch)) {
- return $rule;
- }
- $this->addDecisionId($otherWatch, $level);
- $this->decisionQueue[] = $this->literalFromId($otherWatch);
- $this->decisionQueueWhy[] = $rule;
- }
- }
- return null;
- }
- /**
- * Reverts a decision at the given level.
- */
- private function revert($level)
- {
- while (!empty($this->decisionQueue)) {
- $literal = $this->decisionQueue[count($this->decisionQueue) - 1];
- if (!$this->decisionMap[$literal->getPackageId()]) {
- break;
- }
- $decisionLevel = abs($this->decisionMap[$literal->getPackageId()]);
- if ($decisionLevel <= $level) {
- break;
- }
- /** TODO: implement recommendations
- *if (v > 0 && solv->recommendations.count && v == solv->recommendations.elements[solv->recommendations.count - 1])
- * solv->recommendations.count--;
- */
- $this->decisionMap[$literal->getPackageId()] = 0;
- array_pop($this->decisionQueue);
- array_pop($this->decisionQueueWhy);
- $this->propagateIndex = count($this->decisionQueue);
- }
- while (!empty($this->branches)) {
- list($literals, $branchLevel) = $this->branches[count($this->branches) - 1];
- if ($branchLevel >= $level) {
- break;
- }
- array_pop($this->branches);
- }
- $this->recommendsIndex = -1;
- }
- /**-------------------------------------------------------------------
- *
- * setpropagatelearn
- *
- * add free decision (solvable to install) to decisionq
- * increase level and propagate decision
- * return if no conflict.
- *
- * in conflict case, analyze conflict rule, add resulting
- * rule to learnt rule set, make decision from learnt
- * rule (always unit) and re-propagate.
- *
- * returns the new solver level or 0 if unsolvable
- *
- */
- private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule)
- {
- assert($rule != null);
- assert($literal != null);
- $level++;
- $this->addDecision($literal, $level);
- $this->decisionQueue[] = $literal;
- $this->decisionQueueWhy[] = $rule;
- $this->decisionQueueFree[count($this->decisionQueueWhy) - 1] = true;
- while (true) {
- $rule = $this->propagate($level);
- if (!$rule) {
- break;
- }
- if ($level == 1) {
- return $this->analyzeUnsolvable($rule, $disableRules);
- }
- // conflict
- list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);
- assert($newLevel > 0);
- assert($newLevel < $level);
- $level = $newLevel;
- $this->revert($level);
- assert($newRule != null);
- $this->addRule(RuleSet::TYPE_LEARNED, $newRule);
- $this->learnedWhy[$newRule->getId()] = $why;
- $this->watch2OnHighest($newRule);
- $this->addWatchesToRule($newRule);
- $this->addDecision($learnLiteral, $level);
- $this->decisionQueue[] = $learnLiteral;
- $this->decisionQueueWhy[] = $newRule;
- }
- return $level;
- }
- private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
- {
- // choose best package to install from decisionQueue
- $literals = $this->policy->selectPreferedPackages($this->pool, $this->installedMap, $decisionQueue);
- $selectedLiteral = array_shift($literals);
- // if there are multiple candidates, then branch
- if (count($literals)) {
- $this->branches[] = array($literals, $level);
- }
- return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
- }
- protected function analyze($level, $rule)
- {
- $ruleLevel = 1;
- $num = 0;
- $l1num = 0;
- $seen = array();
- $learnedLiterals = array(null);
- $decisionId = count($this->decisionQueue);
- $this->learnedPool[] = array();
- while(true) {
- $this->learnedPool[count($this->learnedPool) - 1][] = $rule;
- foreach ($rule->getLiterals() as $literal) {
- // skip the one true literal
- if ($this->decisionsSatisfy($literal)) {
- continue;
- }
- if (isset($seen[$literal->getPackageId()])) {
- continue;
- }
- $seen[$literal->getPackageId()] = true;
- $l = abs($this->decisionMap[$literal->getPackageId()]);
- if (1 === $l) {
- $l1num++;
- } else if ($level === $l) {
- $num++;
- } else {
- // not level1 or conflict level, add to new rule
- $learnedLiterals[] = $literal;
- if ($l > $ruleLevel) {
- $ruleLevel = $l;
- }
- }
- }
- $l1retry = true;
- while ($l1retry) {
- $l1retry = false;
- if (!$num && !--$l1num) {
- // all level 1 literals done
- break 2;
- }
- while (true) {
- assert($decisionId > 0);
- $decisionId--;
- $literal = $this->decisionQueue[$decisionId];
- if (isset($seen[$literal->getPackageId()])) {
- break;
- }
- }
- unset($seen[$literal->getPackageId()]);
- if ($num && 0 === --$num) {
- $learnedLiterals[0] = $this->literalFromId(-$literal->getPackageId());
- if (!$l1num) {
- break 2;
- }
- foreach ($learnedLiterals as $i => $learnedLiteral) {
- if ($i !== 0) {
- unset($seen[$literal->getPackageId()]);
- }
- }
- // only level 1 marks left
- $l1num++;
- $l1retry = true;
- }
- $rule = $this->decisionQueueWhy[$decisionId];
- }
- }
- $why = count($this->learnedPool) - 1;
- assert($learnedLiterals[0] !== null);
- $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
- return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
- }
- private function analyzeUnsolvableRule($problem, $conflictRule, &$lastWeakWhy)
- {
- $why = $conflictRule->getId();
- if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
- $learnedWhy = $this->learnedWhy[$why];
- $problemRules = $this->learnedPool[$learnedWhy];
- foreach ($problemRules as $problemRule) {
- $this->analyzeUnsolvableRule($problem, $problemRule, $lastWeakWhy);
- }
- return;
- }
- if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
- // package rules cannot be part of a problem
- return;
- }
- if ($conflictRule->isWeak()) {
- /** TODO why > or < lastWeakWhy? */
- if (!$lastWeakWhy || $why > $lastWeakWhy->getId()) {
- $lastWeakWhy = $conflictRule;
- }
- }
- if ($conflictRule->getType() == RuleSet::TYPE_JOB) {
- $job = $this->ruleToJob[$conflictRule->getId()];
- $problem->addJobRule($job, $conflictRule);
- } else {
- $problem->addRule($conflictRule);
- }
- }
- private function analyzeUnsolvable($conflictRule, $disableRules)
- {
- $lastWeakWhy = null;
- $problem = new Problem;
- $problem->addRule($conflictRule);
- $this->analyzeUnsolvableRule($problem, $conflictRule, $lastWeakWhy);
- $this->problems[] = $problem;
- $seen = array();
- $literals = $conflictRule->getLiterals();
- /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
- if (sizeof($literals) == 2) {
- $literals[1] = $this->literalFromId($conflictRule->watch2);
- }
- */
- foreach ($literals as $literal) {
- // skip the one true literal
- if ($this->decisionsSatisfy($literal)) {
- continue;
- }
- $seen[$literal->getPackageId()] = true;
- }
- $decisionId = count($this->decisionQueue);
- while ($decisionId > 0) {
- $decisionId--;
- $literal = $this->decisionQueue[$decisionId];
- // skip literals that are not in this rule
- if (!isset($seen[$literal->getPackageId()])) {
- continue;
- }
- $why = $this->decisionQueueWhy[$decisionId];
- $problem->addRule($why);
- $this->analyzeUnsolvableRule($problem, $why, $lastWeakWhy);
- $literals = $why->getLiterals();
- /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
- if (sizeof($literals) == 2) {
- $literals[1] = $this->literalFromId($why->watch2);
- }
- */
- foreach ($literals as $literal) {
- // skip the one true literal
- if ($this->decisionsSatisfy($literal)) {
- continue;
- }
- $seen[$literal->getPackageId()] = true;
- }
- }
- if ($lastWeakWhy) {
- array_pop($this->problems);
- if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) {
- $why = $this->ruleToJob[$lastWeakWhy];
- } else {
- $why = $lastWeakWhy;
- }
- if ($lastWeakWhy->getType() == RuleSet::TYPE_CHOICE) {
- $this->disableChoiceRules($lastWeakWhy);
- }
- $this->disableProblem($why);
- /**
- @TODO what does v < 0 mean here? ($why == v)
- if (v < 0)
- solver_reenablepolicyrules(solv, -(v + 1));
- */
- $this->resetSolver();
- return true;
- }
- if ($disableRules) {
- foreach ($this->problems[count($this->problems) - 1] as $reason) {
- if ($reason['job']) {
- $this->disableProblem($reason['job']);
- } else {
- $this->disableProblem($reason['rule']);
- }
- }
- $this->resetSolver();
- return true;
- }
- return false;
- }
- private function disableProblem($why)
- {
- if ($why instanceof Rule) {
- $why->disable();
- } else if (is_array($why)) {
- // disable all rules of this job
- foreach ($this->ruleToJob as $ruleId => $job) {
- if ($why === $job) {
- $this->rules->ruleById($ruleId)->disable();
- }
- }
- }
- }
- private function resetSolver()
- {
- while ($literal = array_pop($this->decisionQueue)) {
- $this->decisionMap[$literal->getPackageId()] = 0;
- }
- $this->decisionQueueWhy = array();
- $this->decisionQueueFree = array();
- $this->recommendsIndex = -1;
- $this->propagateIndex = 0;
- $this->recommendations = array();
- $this->branches = array();
- $this->enableDisableLearnedRules();
- $this->makeAssertionRuleDecisions();
- }
- /*-------------------------------------------------------------------
- * enable/disable learnt rules
- *
- * we have enabled or disabled some of our rules. We now reenable all
- * of our learnt rules except the ones that were learnt from rules that
- * are now disabled.
- */
- private function enableDisableLearnedRules()
- {
- foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
- $why = $this->learnedWhy[$rule->getId()];
- $problemRules = $this->learnedPool[$why];
- $foundDisabled = false;
- foreach ($problemRules as $problemRule) {
- if ($problemRule->disabled()) {
- $foundDisabled = true;
- break;
- }
- }
- if ($foundDisabled && $rule->isEnabled()) {
- $rule->disable();
- } else if (!$foundDisabled && $rule->isDisabled()) {
- $rule->enable();
- }
- }
- }
- 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 = $this->installed->getPackages();
- while (true) {
- if (1 === $level) {
- $conflictRule = $this->propagate($level);
- if ($conflictRule !== null) {
- if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
- continue;
- } else {
- return;
- }
- }
- }
- // handle job rules
- if ($level < $systemLevel) {
- $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
- foreach ($iterator as $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 (isset($this->installedMap[$literal->getPackageId()])) {
- $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
- $iterator->next();
- if ($iterator->valid()) {
- 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
- /** TODO: && or || ? **/
- if (0 === $pass && !isset($this->updateMap[$literal->getPackageId()])) {
- continue;
- }
- $rule = null;
- if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
- $rule = $this->packageToFeatureRule[$literal->getPackageId()];
- }
- if (!$rule || $rule->isDisabled()) {
- continue;
- }
- $updateRuleLiterals = $rule->getLiterals();
- $decisionQueue = array();
- if (!isset($this->noUpdate[$literal->getPackageId()]) && (
- $this->decidedRemove($literal->getPackage()) ||
- isset($this->updateMap[$literal->getPackageId()]) ||
- !$literal->equals($updateRuleLiterals[0])
- )) {
- foreach ($updateRuleLiterals as $ruleLiteral) {
- if ($this->decidedInstall($ruleLiteral->getPackage())) {
- // already fulfilled
- $decisionQueue = array();
- 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;
- }
- } else if (!$repeat && $this->undecided($literal->getPackage())) {
- // still undecided? keep package.
- $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;
- }
- for ($i = 0, $n = 0; $n < count($this->rules); $i++, $n++) {
- if ($i == count($this->rules)) {
- $i = 0;
- }
- $rule = $this->rules->ruleById($i);
- $literals = $rule->getLiterals();
- if ($rule->isDisabled()) {
- continue;
- }
- $decisionQueue = array();
- // 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
- //
- foreach ($literals as $literal) {
- if (!$literal->isWanted()) {
- if (!$this->decidedInstall($literal->getPackage())) {
- continue 2; // next rule
- }
- } else {
- if ($this->decidedInstall($literal->getPackage())) {
- continue 2; // next rule
- }
- if ($this->undecided($literal->getPackage())) {
- $decisionQueue[] = $literal;
- }
- }
- }
- // need to have at least 2 item to pick from
- if (count($decisionQueue) < 2) {
- continue;
- }
- $oLevel = $level;
- $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
- if (0 === $level) {
- return;
- }
- // open suse sat-solver uses this, but why is $level == 1 trouble?
- // SYSTEMSOLVABLE related? we don't have that, so should work
- //if ($level < $systemLevel || $level == 1) {
- if ($level < $systemLevel) {
- break; // trouble
- }
- // something changed, so look at all rules again
- $n = -1;
- }
- // minimization step
- if (count($this->branches)) {
- $lastLiteral = null;
- $lastLevel = null;
- $lastBranchIndex = 0;
- $lastBranchOffset = 0;
- for ($i = count($this->branches) - 1; $i >= 0; $i--) {
- list($literals, $level) = $this->branches[$i];
- foreach ($literals as $offset => $literal) {
- if ($literal && $literal->isWanted() && $this->decisionMap[$literal->getPackageId()] > $level + 1) {
- $lastLiteral = $literal;
- $lastBranchIndex = $i;
- $lastBranchOffset = $offset;
- $lastLevel = $level;
- }
- }
- }
- if ($lastLiteral) {
- $this->branches[$lastBranchIndex][$lastBranchOffset] = null;
- $minimizationSteps++;
- $level = $lastLevel;
- $this->revert($level);
- $why = $this->decisionQueueWhy[count($this->decisionQueueWhy) - 1];
- $oLevel = $level;
- $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
- if ($level == 0) {
- return;
- }
- continue;
- }
- }
- break;
- }
- }
- private function printDecisionMap()
- {
- echo "\nDecisionMap: \n";
- foreach ($this->decisionMap as $packageId => $level) {
- if ($packageId === 0) {
- continue;
- }
- if ($level > 0) {
- echo ' +' . $this->pool->packageById($packageId)."\n";
- } elseif ($level < 0) {
- echo ' -' . $this->pool->packageById($packageId)."\n";
- } else {
- echo ' ?' . $this->pool->packageById($packageId)."\n";
- }
- }
- echo "\n";
- }
- private function printDecisionQueue()
- {
- echo "DecisionQueue: \n";
- foreach ($this->decisionQueue as $i => $literal) {
- echo ' ' . $literal . ' ' . $this->decisionQueueWhy[$i]." level ".$this->decisionMap[$literal->getPackageId()]."\n";
- }
- echo "\n";
- }
- private function printWatches()
- {
- echo "\nWatches:\n";
- foreach ($this->watches as $literalId => $watch) {
- echo ' '.$this->literalFromId($literalId)."\n";
- $queue = array(array(' ', $watch));
- while (!empty($queue)) {
- list($indent, $watch) = array_pop($queue);
- echo $indent.$watch;
- if ($watch) {
- echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]";
- }
- echo "\n";
- if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) {
- if ($watch->next1 == $watch) {
- echo $indent." 1 *RECURSION*";
- }
- if ($watch->next2 == $watch) {
- echo $indent." 2 *RECURSION*";
- }
- } elseif ($watch && ($watch->next1 || $watch->next2)) {
- $indent = str_replace(array('1', '2'), ' ', $indent);
- array_push($queue, array($indent.' 2 ', $watch->next2));
- array_push($queue, array($indent.' 1 ', $watch->next1));
- }
- }
- echo "\n";
- }
- }
- }
|