From 875261931cc3223120b5a21b92c7f9c6d0f3ec78 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 17:40:59 +0200 Subject: [PATCH 01/21] We do not support any options to keep obsolete packages --- src/Composer/DependencyResolver/Solver.php | 63 +++++++++------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index aa014035c..7e8554a7d 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -30,7 +30,6 @@ class Solver protected $ruleToJob = array(); protected $addedMap = array(); protected $updateMap = array(); - protected $noObsoletes = array(); protected $watches = array(); protected $removeWatches = array(); protected $decisionMap; @@ -237,50 +236,38 @@ class Solver } // 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()])); + $isInstalled = (isset($this->installedMap[$package->getId()])); - foreach ($package->getReplaces() as $link) { - $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); + foreach ($package->getReplaces() as $link) { + $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); - foreach ($obsoleteProviders as $provider) { - if ($provider === $package) { - continue; - } - - $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link)); + foreach ($obsoleteProviders as $provider) { + if ($provider === $package) { + continue; } + + $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, - // as the others are picked up when looking at the uninstalled package. - if (!$isInstalled) { - $obsoleteProviders = $this->pool->whatProvides($package->getName(), null); + // check implicit obsoletes + // for installed packages we only need to check installed/installed problems, + // as the others are picked up when looking at the uninstalled package. + if (!$isInstalled) { + $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 ($obsoleteProviders as $provider) { + if ($provider === $package) { + continue; } + + if ($isInstalled && !isset($this->installedMap[$provider->getId()])) { + 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)); } } } From 5daeacf0aceff07288ef56f90a39989709cae49f Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 17:44:42 +0200 Subject: [PATCH 02/21] We won't implement choice rules, you need to edit composer.json to choose --- src/Composer/DependencyResolver/RuleSet.php | 4 +- src/Composer/DependencyResolver/Solver.php | 174 ------------------ .../Test/DependencyResolver/RuleSetTest.php | 1 - 3 files changed, 1 insertion(+), 178 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index 1f444c788..3278b1a98 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -21,15 +21,13 @@ class RuleSet implements \IteratorAggregate, \Countable const TYPE_PACKAGE = 0; const TYPE_JOB = 1; const TYPE_FEATURE = 3; - const TYPE_CHOICE = 4; - const TYPE_LEARNED = 5; + const TYPE_LEARNED = 4; protected static $types = array( -1 => 'UNKNOWN', self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_FEATURE => 'FEATURE', self::TYPE_JOB => 'JOB', - self::TYPE_CHOICE => 'CHOICE', self::TYPE_LEARNED => 'LEARNED', ); diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 7e8554a7d..f2ba5321b 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -483,174 +483,6 @@ class Solver } } - 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 @@ -939,8 +771,6 @@ class Solver } } - $this->addChoiceRules(); - foreach ($this->rules as $rule) { $this->addWatchesToRule($rule); } @@ -1523,10 +1353,6 @@ class Solver $why = $lastWeakWhy; } - if ($lastWeakWhy->getType() == RuleSet::TYPE_CHOICE) { - $this->disableChoiceRules($lastWeakWhy); - } - $this->disableProblem($why); /** diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index 54ad88a58..f974732dc 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -31,7 +31,6 @@ class RuleSetTest extends TestCase new Rule(array(), 'update1', null), ), RuleSet::TYPE_LEARNED => array(), - RuleSet::TYPE_CHOICE => array(), ); $ruleSet = new RuleSet; From f4ae88a5d6c3464138ff226c0543497e27832deb Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 17:50:53 +0200 Subject: [PATCH 03/21] Create lookup table for installed packages in only one place --- src/Composer/DependencyResolver/Solver.php | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index f2ba5321b..15b1f46ea 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -35,6 +35,8 @@ class Solver protected $decisionMap; protected $installedMap; + protected $installedPackages; + protected $packageToFeatureRule = array(); public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) @@ -667,14 +669,20 @@ class Solver } } + protected function setupInstalledMap() + { + $this->installedPackages = $this->installed->getPackages(); + $this->installedMap = array(); + foreach ($this->installedPackages as $package) { + $this->installedMap[$package->getId()] = $package; + } + } + 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; - } + + $this->setupInstalledMap(); if (version_compare(PHP_VERSION, '5.3.4', '>=')) { $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1); @@ -695,18 +703,18 @@ class Solver switch ($job['cmd']) { case 'update-all': - foreach ($installedPackages as $package) { + foreach ($this->installedMap as $package) { $this->updateMap[$package->getId()] = true; } break; } } - foreach ($installedPackages as $package) { + foreach ($this->installedMap as $package) { $this->addRulesForPackage($package); } - foreach ($installedPackages as $package) { + foreach ($this->installedMap as $package) { $this->addRulesForUpdatePackages($package); } @@ -724,7 +732,7 @@ class Solver // solver_addrpmrulesforweak(solv, &addedmap); - foreach ($installedPackages as $package) { + foreach ($this->installedMap as $package) { $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); @@ -1467,8 +1475,6 @@ class Solver $minimizationSteps = 0; $installedPos = 0; - $this->installedPackages = $this->installed->getPackages(); - while (true) { if (1 === $level) { From c37e126e14539589c8ed0d35865fc6538b385d41 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:13:37 +0200 Subject: [PATCH 04/21] Installed packages are now always removed unless otherwise requested This means that an update request must always be accompanied by an install request, otherwise the package might be removed rather than updated. --- src/Composer/DependencyResolver/Solver.php | 135 ++---------------- .../Test/DependencyResolver/SolverTest.php | 31 +++- 2 files changed, 39 insertions(+), 127 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 15b1f46ea..0cb754284 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -730,14 +730,12 @@ class Solver } } - // solver_addrpmrulesforweak(solv, &addedmap); - foreach ($this->installedMap 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->addRule(RuleSet::TYPE_FEATURE, $rule); $this->packageToFeatureRule[$package->getId()] = $rule; } @@ -857,6 +855,18 @@ class Solver } } + foreach ($this->decisionMap as $packageId => $decision) { + if ($packageId === 0) { + continue; + } + + if (0 == $decision && isset($this->installedMap[$packageId])) { + $transaction[] = new Operation\UninstallOperation( + $this->pool->packageById($packageId), null + ); + } + } + return array_reverse($transaction); } @@ -1546,125 +1556,6 @@ class Solver } } - // 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; } diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 49355a5a4..2bc5a928a 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -54,6 +54,16 @@ class SolverTest extends TestCase )); } + public function testSolverRemoveIfNotInstalled() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->reposComplete(); + + $this->checkSolverResult(array( + array('job' => 'remove', 'package' => $packageA), + )); + } + public function testInstallNonExistingPackageFails() { $this->repo->addPackage($this->getPackage('A', '1.0')); @@ -176,6 +186,7 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1')); $this->reposComplete(); + $this->request->install('A'); $this->request->update('A'); $this->checkSolverResult(array( @@ -191,6 +202,7 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); $packageA->setRequires(array(new Link('A', 'B', null, 'requires'))); + $newPackageA->setRequires(array(new Link('A', 'B', null, 'requires'))); $this->reposComplete(); @@ -209,6 +221,7 @@ class SolverTest extends TestCase $this->repo->addPackage($this->getPackage('A', '1.0')); $this->reposComplete(); + $this->request->install('A'); $this->request->update('A'); $this->checkSolverResult(array()); @@ -223,6 +236,8 @@ class SolverTest extends TestCase $this->reposComplete(); + $this->request->install('A'); + $this->request->install('B'); $this->request->update('A'); $this->checkSolverResult(array( @@ -267,7 +282,7 @@ class SolverTest extends TestCase public function testSolverUpdateFullyConstrainedPrunesInstalledPackages() { $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); - $this->repoInstalled->addPackage($this->getPackage('B', '1.0')); + $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0')); $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.2')); $this->repo->addPackage($this->getPackage('A', '2.0')); $this->reposComplete(); @@ -275,10 +290,15 @@ class SolverTest extends TestCase $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0')); $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0')); - $this->checkSolverResult(array(array( - 'job' => 'update', - 'from' => $packageA, - 'to' => $newPackageA, + $this->checkSolverResult(array( + array( + 'job' => 'remove', + 'package' => $packageB, + ), + array( + 'job' => 'update', + 'from' => $packageA, + 'to' => $newPackageA, ))); } @@ -297,6 +317,7 @@ class SolverTest extends TestCase $this->reposComplete(); $this->request->install('A'); + $this->request->install('C'); $this->request->update('C'); $this->request->remove('D'); From 5feff954a4121fbcd41afe8526e55f2bacdcb198 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:16:34 +0200 Subject: [PATCH 05/21] Remove commented out debug output --- src/Composer/DependencyResolver/Solver.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 0cb754284..6eb57b636 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -789,9 +789,6 @@ class Solver $installRecommended = 0; $this->runSat(true, $installRecommended); - //$this->printDecisionMap(); - //findrecommendedsuggested(solv); - //solver_prepare_solutions(solv); if ($this->problems) { throw new SolverProblemsException($this->problems); From c51fedef8cdfa304de659a0f6d0c90d7118a9c48 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:21:25 +0200 Subject: [PATCH 06/21] Policy rules cannot conflict with jobs anymore As we no longer need special feature rules for updating installed packages we don't have policy rules which might conflict with job rules anymore. Everything is driven by jobs now. --- src/Composer/DependencyResolver/Solver.php | 196 --------------------- 1 file changed, 196 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 6eb57b636..f692a2d22 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -481,191 +481,6 @@ class Solver } $this->disableProblem($why); - /** TODO solver_reenablepolicyrules(solv, -(v + 1)); */ - } - } - -/*********************************************************************** - *** - *** 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']); - } } } @@ -734,8 +549,6 @@ class Solver $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; } @@ -781,9 +594,6 @@ class Solver $this->addWatchesToRule($rule); } - /* disable update rules that conflict with our job */ - $this->disablePolicyRules(); - /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); @@ -1369,12 +1179,6 @@ class Solver } $this->disableProblem($why); - - /** -@TODO what does v < 0 mean here? ($why == v) - if (v < 0) - solver_reenablepolicyrules(solv, -(v + 1)); -*/ $this->resetSolver(); return true; From cf5d14e2e1244539ec43b40cbb3b91565009e6da Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:22:55 +0200 Subject: [PATCH 07/21] Move all solver members to top of the file --- src/Composer/DependencyResolver/Solver.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index f692a2d22..b6cbdf4e0 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -39,6 +39,15 @@ class Solver protected $packageToFeatureRule = array(); + protected $decisionQueue = array(); + protected $decisionQueueWhy = array(); + protected $decisionQueueFree = array(); + protected $propagateIndex; + protected $branches = array(); + protected $problems = array(); + protected $learnedPool = array(); + protected $recommendsIndex; + public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) { $this->policy = $policy; @@ -677,15 +686,6 @@ class Solver 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)); From 0bd6fa1f37d0f71240d74ecd84cef909254b98f8 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:24:17 +0200 Subject: [PATCH 08/21] An array of installed packages is no longer needed in the solver --- src/Composer/DependencyResolver/Solver.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index b6cbdf4e0..d51bbd55d 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -35,8 +35,6 @@ class Solver protected $decisionMap; protected $installedMap; - protected $installedPackages; - protected $packageToFeatureRule = array(); protected $decisionQueue = array(); @@ -495,9 +493,8 @@ class Solver protected function setupInstalledMap() { - $this->installedPackages = $this->installed->getPackages(); $this->installedMap = array(); - foreach ($this->installedPackages as $package) { + foreach ($this->installed->getPackages() as $package) { $this->installedMap[$package->getId()] = $package; } } @@ -1269,9 +1266,7 @@ class Solver // * 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 From 69b55f12ae1546715c0a52bfd6f40c9893bb9478 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:25:38 +0200 Subject: [PATCH 09/21] Call feature rules update rules, as there is no difference in composer --- src/Composer/DependencyResolver/Solver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index d51bbd55d..5990fcc59 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -35,7 +35,7 @@ class Solver protected $decisionMap; protected $installedMap; - protected $packageToFeatureRule = array(); + protected $packageToUpdateRule = array(); protected $decisionQueue = array(); protected $decisionQueueWhy = array(); @@ -555,7 +555,7 @@ class Solver $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); - $this->packageToFeatureRule[$package->getId()] = $rule; + $this->packageToUpdateRule[$package->getId()] = $rule; } foreach ($this->jobs as $job) { @@ -625,8 +625,8 @@ class Solver 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()); + if (isset($this->packageToUpdateRule[$package->getId()])) { + $literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals()); } foreach ($literals as $updateLiteral) { From 15f43571b97c3049a2a7b8e8a9fceea7ff6e0fa6 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 18:28:18 +0200 Subject: [PATCH 10/21] Rules of type feature are no longer needed --- src/Composer/DependencyResolver/RuleSet.php | 2 -- src/Composer/DependencyResolver/Solver.php | 2 +- .../RuleSetIteratorTest.php | 6 +++--- .../Test/DependencyResolver/RuleSetTest.php | 19 +++++++++---------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index 3278b1a98..9e8ecf29b 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -20,13 +20,11 @@ class RuleSet implements \IteratorAggregate, \Countable // highest priority => lowest number const TYPE_PACKAGE = 0; const TYPE_JOB = 1; - const TYPE_FEATURE = 3; const TYPE_LEARNED = 4; protected static $types = array( -1 => 'UNKNOWN', self::TYPE_PACKAGE => 'PACKAGE', - self::TYPE_FEATURE => 'FEATURE', self::TYPE_JOB => 'JOB', self::TYPE_LEARNED => 'LEARNED', ); diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 5990fcc59..afa8f6996 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -427,7 +427,7 @@ class Solver // 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) { + foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) { if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) { continue; } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index 28db18131..56084f32a 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase new Rule(array(), 'job1', null), new Rule(array(), 'job2', null), ), - RuleSet::TYPE_FEATURE => array( + RuleSet::TYPE_LEARNED => array( new Rule(array(), 'update1', null), ), RuleSet::TYPE_PACKAGE => array(), @@ -46,7 +46,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $expected = array( $this->rules[RuleSet::TYPE_JOB][0], $this->rules[RuleSet::TYPE_JOB][1], - $this->rules[RuleSet::TYPE_FEATURE][0], + $this->rules[RuleSet::TYPE_LEARNED][0], ); $this->assertEquals($expected, $result); @@ -64,7 +64,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $expected = array( RuleSet::TYPE_JOB, RuleSet::TYPE_JOB, - RuleSet::TYPE_FEATURE, + RuleSet::TYPE_LEARNED, ); $this->assertEquals($expected, $result); diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index f974732dc..f319651ae 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -27,16 +27,15 @@ class RuleSetTest extends TestCase new Rule(array(), 'job1', null), new Rule(array(), 'job2', null), ), - RuleSet::TYPE_FEATURE => array( + RuleSet::TYPE_LEARNED => array( new Rule(array(), 'update1', null), ), - RuleSet::TYPE_LEARNED => array(), ); $ruleSet = new RuleSet; $ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB); - $ruleSet->add($rules[RuleSet::TYPE_FEATURE][0], RuleSet::TYPE_FEATURE); + $ruleSet->add($rules[RuleSet::TYPE_LEARNED][0], RuleSet::TYPE_LEARNED); $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB); $this->assertEquals($rules, $ruleSet->getRules()); @@ -79,7 +78,7 @@ class RuleSetTest extends TestCase $rule1 = new Rule(array(), 'job1', null); $rule2 = new Rule(array(), 'job1', null); $ruleSet->add($rule1, RuleSet::TYPE_JOB); - $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + $ruleSet->add($rule2, RuleSet::TYPE_LEARNED); $iterator = $ruleSet->getIterator(); @@ -95,9 +94,9 @@ class RuleSetTest extends TestCase $rule2 = new Rule(array(), 'job1', null); $ruleSet->add($rule1, RuleSet::TYPE_JOB); - $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + $ruleSet->add($rule2, RuleSet::TYPE_LEARNED); - $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_FEATURE); + $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_LEARNED); $this->assertSame($rule2, $iterator->current()); } @@ -109,7 +108,7 @@ class RuleSetTest extends TestCase $rule2 = new Rule(array(), 'job1', null); $ruleSet->add($rule1, RuleSet::TYPE_JOB); - $ruleSet->add($rule2, RuleSet::TYPE_FEATURE); + $ruleSet->add($rule2, RuleSet::TYPE_LEARNED); $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB); @@ -141,7 +140,7 @@ class RuleSetTest extends TestCase ->method('equal') ->will($this->returnValue(false)); - $ruleSet->add($rule, RuleSet::TYPE_FEATURE); + $ruleSet->add($rule, RuleSet::TYPE_LEARNED); $this->assertTrue($ruleSet->containsEqual($rule)); $this->assertFalse($ruleSet->containsEqual($rule2)); @@ -154,9 +153,9 @@ class RuleSetTest extends TestCase $literal = new Literal($this->getPackage('foo', '2.1'), true); $rule = new Rule(array($literal), 'job1', null); - $ruleSet->add($rule, RuleSet::TYPE_FEATURE); + $ruleSet->add($rule, RuleSet::TYPE_JOB); - $this->assertContains('FEATURE : (+foo-2.1.0.0)', $ruleSet->__toString()); + $this->assertContains('JOB : (+foo-2.1.0.0)', $ruleSet->__toString()); } private function getRuleMock() From b84bcd84d6c8c73db42d5fa3804ec1018c90f065 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 27 Apr 2012 19:23:35 +0200 Subject: [PATCH 11/21] Let the user know a package is being removed --- src/Composer/Downloader/FileDownloader.php | 1 + src/Composer/Downloader/VcsDownloader.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 356934514..b11bf7ba6 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -101,6 +101,7 @@ class FileDownloader implements DownloaderInterface */ public function remove(PackageInterface $package, $path) { + $this->io->write(" - Removing package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 2d19bba40..7d3c90c7f 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -77,6 +77,7 @@ abstract class VcsDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $this->enforceCleanDirectory($path); + $this->io->write(" - Removing package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } From 90732fd03fd0a10d5861d31c7bee185232ef530d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 27 Apr 2012 21:23:07 +0200 Subject: [PATCH 12/21] Clean up vendor dir after a package was removed --- src/Composer/Installer/LibraryInstaller.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 0da923654..9be2e1d1d 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -127,6 +127,13 @@ class LibraryInstaller implements InstallerInterface $this->downloadManager->remove($package, $downloadPath); $this->removeBinaries($package); $repo->removePackage($package); + + if (strpos($package->getName(), '/')) { + $packageVendorDir = dirname($downloadPath); + if (is_dir($packageVendorDir) && !glob($packageVendorDir.'/*')) { + @rmdir($packageVendorDir); + } + } } /** From dd17a1bbd70f2cd44b307ec73f82b06604264451 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 27 Apr 2012 21:24:07 +0200 Subject: [PATCH 13/21] Force platform packages to remain installed --- src/Composer/Installer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f27f687b8..772c7bc3c 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -259,9 +259,11 @@ class Installer } } - // fix the version all installed packages that are not in the current local repo to prevent rogue updates + // fix the version of all installed packages (+ platform) that are not + // in the current local repo to prevent rogue updates (e.g. non-dev + // updating when in dev) foreach ($installedRepo->getPackages() as $package) { - if ($package->getRepository() === $localRepo || $package->getRepository() instanceof PlatformRepository) { + if ($package->getRepository() === $localRepo) { continue; } From d74eec9bd556255cca1bb28f4c8459bf03492136 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 27 Apr 2012 21:40:46 +0200 Subject: [PATCH 14/21] Clarify output --- src/Composer/Downloader/FileDownloader.php | 4 ++-- src/Composer/Downloader/VcsDownloader.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index b11bf7ba6..3e15acf80 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -64,7 +64,7 @@ class FileDownloader implements DownloaderInterface $fileName = $this->getFileName($package, $path); - $this->io->write(" - Package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); + $this->io->write(" - Installing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); $processUrl = $this->processUrl($url); @@ -101,7 +101,7 @@ class FileDownloader implements DownloaderInterface */ public function remove(PackageInterface $package, $path) { - $this->io->write(" - Removing package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); + $this->io->write(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 7d3c90c7f..cacc74fc4 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -50,7 +50,7 @@ abstract class VcsDownloader implements DownloaderInterface throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } - $this->io->write(" - Package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); + $this->io->write(" - Installing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); $this->filesystem->removeDirectory($path); $this->doDownload($package, $path); $this->io->write(''); @@ -65,7 +65,7 @@ abstract class VcsDownloader implements DownloaderInterface throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information'); } - $this->io->write(" - Package " . $target->getName() . " (" . $target->getPrettyVersion() . ")"); + $this->io->write(" - Installing " . $target->getName() . " (" . $target->getPrettyVersion() . ")"); $this->enforceCleanDirectory($path); $this->doUpdate($initial, $target, $path); $this->io->write(''); @@ -77,7 +77,7 @@ abstract class VcsDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $this->enforceCleanDirectory($path); - $this->io->write(" - Removing package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); + $this->io->write(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } From 0c1944a9d0d20c6204cc746199dfc7cf535ca49b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 27 Apr 2012 19:41:53 +0200 Subject: [PATCH 15/21] Let the solver handle aliases instead of the installer --- .../Operation/MarkAliasInstalledOperation.php | 66 ++++++++ .../MarkAliasUninstalledOperation.php | 66 ++++++++ src/Composer/DependencyResolver/Rule.php | 3 + src/Composer/DependencyResolver/Solver.php | 141 +++++++++++++----- src/Composer/Installer.php | 23 +-- .../Installer/InstallationManager.php | 57 ++++--- src/Composer/Package/Dumper/ArrayDumper.php | 3 +- src/Composer/Package/Locker.php | 14 +- src/Composer/Package/MemoryPackage.php | 19 --- src/Composer/Repository/ArrayRepository.php | 4 +- .../Repository/FilesystemRepository.php | 39 +++-- .../Test/DependencyResolver/SolverTest.php | 49 ++++++ tests/Composer/Test/TestCase.php | 7 + 13 files changed, 382 insertions(+), 109 deletions(-) create mode 100644 src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php create mode 100644 src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php diff --git a/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php new file mode 100644 index 000000000..3846dd52c --- /dev/null +++ b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Konstantin Kudryashov + */ +class MarkAliasInstalledOperation extends SolverOperation +{ + protected $package; + + /** + * Initializes operation. + * + * @param PackageInterface $package package instance + * @param string $reason operation reason + */ + public function __construct(AliasPackage $package, $reason = null) + { + parent::__construct($reason); + + $this->package = $package; + } + + /** + * Returns package instance. + * + * @return PackageInterface + */ + public function getPackage() + { + return $this->package; + } + + /** + * Returns job type. + * + * @return string + */ + public function getJobType() + { + return 'markAliasInstalled'; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getPrettyVersion().')'; + } +} diff --git a/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php b/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php new file mode 100644 index 000000000..648356df5 --- /dev/null +++ b/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\AliasPackage; + +/** + * Solver install operation. + * + * @author Nils Adermann + */ +class MarkAliasUninstalledOperation extends SolverOperation +{ + protected $package; + + /** + * Initializes operation. + * + * @param PackageInterface $package package instance + * @param string $reason operation reason + */ + public function __construct(AliasPackage $package, $reason = null) + { + parent::__construct($reason); + + $this->package = $package; + } + + /** + * Returns package instance. + * + * @return PackageInterface + */ + public function getPackage() + { + return $this->package; + } + + /** + * Returns job type. + * + * @return string + */ + public function getJobType() + { + return 'markAliasUninstalled'; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getPrettyVersion().')'; + } +} diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index cd674ef0f..3b9ffcc28 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -29,6 +29,7 @@ class Rule const RULE_PACKAGE_SAME_NAME = 10; const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11; const RULE_LEARNED = 12; + const RULE_PACKAGE_ALIAS = 13; protected $disabled; protected $literals; @@ -235,6 +236,8 @@ class Rule return $ruleText; case self::RULE_LEARNED: return 'learned: '.$ruleText; + case self::RULE_PACKAGE_ALIAS: + return $ruleText; } } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index afa8f6996..66c17f7fa 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -14,6 +14,7 @@ namespace Composer\DependencyResolver; use Composer\Repository\RepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\DependencyResolver\Operation; /** @@ -255,8 +256,10 @@ class Solver continue; } - $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link)); + if (!$this->obsoleteImpossibleForAlias($package, $provider)) { + $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link)); + } } } @@ -271,17 +274,31 @@ class Solver continue; } - if ($isInstalled && !isset($this->installedMap[$provider->getId()])) { - continue; + if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { + $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, (string) $package)); + } else if (!$this->obsoleteImpossibleForAlias($package, $provider)) { + $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)); } - - $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)); } } } } + protected function obsoleteImpossibleForAlias($package, $provider) + { + $packageIsAlias = $package instanceof AliasPackage; + $providerIsAlias = $provider instanceof AliasPackage; + + $impossible = ( + ($packageIsAlias && $package->getAliasOf() === $provider) || + ($providerIsAlias && $provider->getAliasOf() === $package) || + ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf()) + ); + + return $impossible; + } + /** * Adds all rules for all update packages of a given package * @@ -646,7 +663,15 @@ class Solver } if ($literal->isWanted()) { + if ($package instanceof AliasPackage) { + $transaction[] = new Operation\MarkAliasInstalledOperation( + $package, $this->decisionQueueWhy[$i] + ); + continue; + } + if (isset($installMeansUpdateMap[$literal->getPackageId()])) { + $source = $installMeansUpdateMap[$literal->getPackageId()]; $transaction[] = new Operation\UpdateOperation( @@ -662,9 +687,47 @@ class Solver ); } } else if (!isset($ignoreRemove[$package->getId()])) { - $transaction[] = new Operation\UninstallOperation( - $package, $this->decisionQueueWhy[$i] - ); + if ($package instanceof AliasPackage) { + $transaction[] = new Operation\MarkAliasInstalledOperation( + $package, $this->decisionQueueWhy[$i] + ); + } else { + $transaction[] = new Operation\UninstallOperation( + $package, $this->decisionQueueWhy[$i] + ); + } + } + } + + $allDecidedMap = $this->decisionMap; + foreach ($this->decisionMap as $packageId => $decision) { + if ($decision != 0) { + $package = $this->pool->packageById($packageId); + if ($package instanceof AliasPackage) { + $allDecidedMap[$package->getAliasOf()->getId()] = $decision; + } + } + } + + foreach ($allDecidedMap as $packageId => $decision) { + if ($packageId === 0) { + continue; + } + + if (0 == $decision && isset($this->installedMap[$packageId])) { + $package = $this->pool->packageById($packageId); + + if ($package instanceof AliasPackage) { + $transaction[] = new Operation\MarkAliasInstalledOperation( + $package, null + ); + } else { + $transaction[] = new Operation\UninstallOperation( + $package, null + ); + } + + $this->decisionMap[$packageId] = -1; } } @@ -674,9 +737,19 @@ class Solver } if (0 == $decision && isset($this->installedMap[$packageId])) { - $transaction[] = new Operation\UninstallOperation( - $this->pool->packageById($packageId), null - ); + $package = $this->pool->packageById($packageId); + + if ($package instanceof AliasPackage) { + $transaction[] = new Operation\MarkAliasInstalledOperation( + $package, null + ); + } else { + $transaction[] = new Operation\UninstallOperation( + $package, null + ); + } + + $this->decisionMap[$packageId] = -1; } } @@ -733,7 +806,7 @@ class Solver protected function decisionsSatisfy(Literal $l) { return ($l->isWanted() && $this->decisionMap[$l->getPackageId()] > 0) || - (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] <= 0); + (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] < 0); } protected function decisionsConflict(Literal $l) @@ -876,11 +949,6 @@ class Solver 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); @@ -1060,14 +1128,19 @@ class Solver $l1num++; $l1retry = true; } - - $rule = $this->decisionQueueWhy[$decisionId]; } + + $rule = $this->decisionQueueWhy[$decisionId]; } $why = count($this->learnedPool) - 1; - assert($learnedLiterals[0] !== null); - $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why); + + if (count($learnedLiterals) === 1 && $learnedLiterals[0] === null) { + $newRule = new Rule(array(), Rule::RULE_LEARNED, $why); + } else { + assert($learnedLiterals[0] !== null); + $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why); + } return array($learnedLiterals[0], $ruleLevel, $newRule, $why); } @@ -1178,7 +1251,7 @@ class Solver $this->disableProblem($why); $this->resetSolver(); - return true; + return 1; } if ($disableRules) { @@ -1191,10 +1264,10 @@ class Solver } $this->resetSolver(); - return true; + return 1; } - return false; + return 0; } private function disableProblem($why) @@ -1266,8 +1339,8 @@ class Solver // * here's the main loop: // * 1) propagate new decisions (only needed once) // * 2) fulfill jobs - // * 4) fulfill all unresolved rules - // * 6) minimalize solution if we had choices + // * 3) fulfill all unresolved rules + // * 4) minimalize solution if we had choices // * if we encounter a problem, we rewind to a safe level and restart // * with step 1 // */ @@ -1403,18 +1476,14 @@ class Solver 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; } + if ($level < $systemLevel) { + continue; + } + // minimization step if (count($this->branches)) { diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 772c7bc3c..08d56819d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -326,14 +326,6 @@ class Installer } } - // anti-alias local repository to allow updates to work fine - foreach ($localRepo->getPackages() as $package) { - if ($package instanceof AliasPackage) { - $package->getRepository()->addPackage(clone $package->getAliasOf()); - $package->getRepository()->removePackage($package); - } - } - // execute operations if (!$operations) { $this->io->write('Nothing to install or update'); @@ -356,7 +348,10 @@ class Installer } if (!$this->dryRun) { - $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation); + $event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType()); + if (defined($event)) { + $this->eventDispatcher->dispatchPackageEvent(constant($event), $operation); + } // if installing from lock, restore dev packages' references to their locked state if ($installFromLock) { @@ -378,17 +373,15 @@ class Installer } $this->installationManager->execute($localRepo, $operation); - $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation); + $event = 'Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType()); + if (defined($event)) { + $this->eventDispatcher->dispatchPackageEvent(constant($event), $operation); + } $localRepo->write(); } } - // reload local repository for the dev pass to work ok with aliases since it was anti-aliased above - if (!$devMode) { - $localRepo->reload(); - } - return true; } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 32587a8db..81891b5e1 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -21,6 +21,8 @@ use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; +use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\Util\Filesystem; /** @@ -104,6 +106,10 @@ class InstallationManager */ public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { + if ($package instanceof AliasPackage) { + return $repo->hasPackage($package); + } + return $this->getInstaller($package->getType())->isInstalled($repo, $package); } @@ -127,7 +133,7 @@ class InstallationManager */ public function install(RepositoryInterface $repo, InstallOperation $operation) { - $package = $this->antiAlias($operation->getPackage()); + $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $installer->install($repo, $package); $this->notifyInstall($package); @@ -141,8 +147,8 @@ class InstallationManager */ public function update(RepositoryInterface $repo, UpdateOperation $operation) { - $initial = $this->antiAlias($operation->getInitialPackage()); - $target = $this->antiAlias($operation->getTargetPackage()); + $initial = $operation->getInitialPackage(); + $target = $operation->getTargetPackage(); $initialType = $initial->getType(); $targetType = $target->getType(); @@ -165,11 +171,41 @@ class InstallationManager */ public function uninstall(RepositoryInterface $repo, UninstallOperation $operation) { - $package = $this->antiAlias($operation->getPackage()); + $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $installer->uninstall($repo, $package); } + /** + * Executes markAliasInstalled operation. + * + * @param RepositoryInterface $repo repository in which to check + * @param MarkAliasInstalledOperation $operation operation instance + */ + public function markAliasInstalled(RepositoryInterface $repo, MarkAliasInstalledOperation $operation) + { + $package = $operation->getPackage(); + + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + + $this->notifyInstall($package); + } + + /** + * Executes markAlias operation. + * + * @param RepositoryInterface $repo repository in which to check + * @param MarkAliasUninstalledOperation $operation operation instance + */ + public function markAliasUninstalled(RepositoryInterface $repo, MarkAliasUninstalledOperation $operation) + { + $package = $operation->getPackage(); + + $repo->removePackage($package); + } + /** * Returns the installation path of a package * @@ -203,17 +239,4 @@ class InstallationManager $package->getRepository()->notifyInstall($package); } } - - private function antiAlias(PackageInterface $package) - { - if ($package instanceof AliasPackage) { - $alias = $package; - $package = $package->getAliasOf(); - $package->setInstalledAsAlias(true); - $package->setAlias($alias->getVersion()); - $package->setPrettyAlias($alias->getPrettyVersion()); - } - - return $package; - } } diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index ed25e656b..08eb9c32e 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -41,8 +41,9 @@ class ArrayDumper $data = array(); $data['name'] = $package->getPrettyName(); + $data['version'] = $package->getPrettyVersion(); - $data['version_normalized'] = $package->getVersion(); + $data['version_normalized'] = $package->getVersion();; if ($package->getTargetDir()) { $data['target-dir'] = $package->getTargetDir(); diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index a7b5bdcd1..44247c5b8 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -89,7 +89,7 @@ class Locker $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository(); foreach ($lockedPackages as $info) { - $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version']; + $resolvedVersion = !empty($info['alias-version']) ? $info['alias-version'] : $info['version']; // try to find the package in the local repo (best match) $package = $repo->findPackage($info['package'], $resolvedVersion); @@ -100,10 +100,10 @@ class Locker } // try to find the package in any repo (second pass without alias + rebuild alias since it disappeared) - if (!$package && !empty($info['alias'])) { + if (!$package && !empty($info['alias-version'])) { $package = $this->repositoryManager->findPackage($info['package'], $info['version']); if ($package) { - $alias = new AliasPackage($package, $info['alias'], $info['alias']); + $alias = new AliasPackage($package, $info['alias-version'], $info['alias-pretty-version']); $package->getRepository()->addPackage($alias); $package = $alias; } @@ -179,7 +179,10 @@ class Locker $locked = array(); foreach ($packages as $package) { + $alias = null; + if ($package instanceof AliasPackage) { + $alias = $package; $package = $package->getAliasOf(); } @@ -198,8 +201,9 @@ class Locker $spec['source-reference'] = $package->getSourceReference(); } - if ($package->getAlias() && $package->isInstalledAsAlias()) { - $spec['alias'] = $package->getAlias(); + if ($alias) { + $spec['alias-pretty-version'] = $alias->getPrettyVersion(); + $spec['alias-version'] = $alias->getVersion(); } $locked[] = $spec; diff --git a/src/Composer/Package/MemoryPackage.php b/src/Composer/Package/MemoryPackage.php index 4a8f55cde..147f94ed7 100644 --- a/src/Composer/Package/MemoryPackage.php +++ b/src/Composer/Package/MemoryPackage.php @@ -46,7 +46,6 @@ class MemoryPackage extends BasePackage protected $aliases = array(); protected $alias; protected $prettyAlias; - protected $installedAsAlias; protected $dev; protected $requires = array(); @@ -211,24 +210,6 @@ class MemoryPackage extends BasePackage return $this->prettyAlias; } - /** - * Enabled if the package is installed from its alias package - * - * @param string $installedAsAlias - */ - public function setInstalledAsAlias($installedAsAlias) - { - $this->installedAsAlias = $installedAsAlias; - } - - /** - * @return string - */ - public function isInstalledAsAlias() - { - return $this->installedAsAlias; - } - /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 7a6336393..e75e3b18a 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -103,8 +103,8 @@ class ArrayRepository implements RepositoryInterface $package->setRepository($this); $this->packages[] = $package; - // create alias package on the fly if needed (installed repos manage aliases themselves) - if ($package->getAlias() && !$this instanceof InstalledRepositoryInterface) { + // create alias package on the fly if needed + if ($package->getAlias()) { $this->addPackage($this->createAliasPackage($package)); } } diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index 8bbeb6ad3..973e56062 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -14,6 +14,7 @@ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; @@ -54,21 +55,32 @@ class FilesystemRepository extends ArrayRepository implements WritableRepository throw new \UnexpectedValueException('Could not parse package list from the '.$this->file->getPath().' repository'); } + $aliases = array(); + $loader = new ArrayLoader(); foreach ($packages as $packageData) { $package = $loader->load($packageData); - // package was installed as alias, so we only add the alias - if ($this instanceof InstalledRepositoryInterface && !empty($packageData['installed-as-alias'])) { - $alias = $packageData['installed-as-alias']; - $package->setAlias($alias); - $package->setPrettyAlias($alias); - $package->setInstalledAsAlias(true); - $this->addPackage($this->createAliasPackage($package, $alias, $alias)); - } else { - // only add regular package - if it's not an installed repo the alias will be created on the fly - $this->addPackage($package); + // aliases need to be looked up in the end to set up references correctly + if ($this instanceof InstalledRepositoryInterface && !empty($packageData['alias'])) { + $aliases[] = array( + 'package' => $package, + 'alias' => $packageData['alias'], + 'alias_pretty' => $packageData['alias_pretty'] + ); } + + $this->addPackage($package); + } + + foreach ($aliases as $aliasData) { + $temporaryPackage = $aliasData['package']; + + $package = $this->findPackage($package->getName(), $package->getVersion()); + + $package->setAlias($aliasData['alias']); + $package->setPrettyAlias($aliasData['alias_pretty']); + $this->addPackage($this->createAliasPackage($package, $aliasData['alias'], $aliasData['alias_pretty'])); } } @@ -86,11 +98,10 @@ class FilesystemRepository extends ArrayRepository implements WritableRepository $packages = array(); $dumper = new ArrayDumper(); foreach ($this->getPackages() as $package) { - $data = $dumper->dump($package); - if ($this instanceof InstalledRepositoryInterface && $package->isInstalledAsAlias()) { - $data['installed-as-alias'] = $package->getAlias(); + if (!$package instanceof AliasPackage) { + $data = $dumper->dump($package); + $packages[] = $data; } - $packages[] = $data; } $this->file->write($packages); diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 2bc5a928a..d361b66b0 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -670,6 +670,55 @@ class SolverTest extends TestCase )); } + public function testInstallRecursiveAliasDependencies() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '2.0')); + $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); + + $packageA2->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('==', '2.0'), 'requires', '== 2.0'), + )); + $packageB->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'), + )); + + $this->repo->addPackage($packageA2Alias = $this->getAliasPackage($packageA2, '1.1')); + + $this->reposComplete(); + + $this->request->install('A', $this->getVersionConstraint('==', '1.1.0.0')); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageB), + array('job' => 'install', 'package' => $packageA2), + array('job' => 'install', 'package' => $packageA2Alias), + )); + } + + public function testInstallDevAlias() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '2.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + + $packageB->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('<', '2.0'), 'requires'), + )); + + $this->repo->addPackage($packageAAlias = $this->getAliasPackage($packageA, '1.1')); + + $this->reposComplete(); + + $this->request->install('A', $this->getVersionConstraint('==', '2.0')); + $this->request->install('B'); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageAAlias), + array('job' => 'install', 'package' => $packageB), + array('job' => 'install', 'package' => $packageA), + )); + } + protected function reposComplete() { $this->pool->addRepository($this->repoInstalled); diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index 3756e9fa3..bbceb3875 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -14,6 +14,7 @@ namespace Composer\Test; use Composer\Package\Version\VersionParser; use Composer\Package\MemoryPackage; +use Composer\Package\AliasPackage; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Util\Filesystem; @@ -45,6 +46,12 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase return new MemoryPackage($name, $normVersion, $version); } + protected function getAliasPackage($package, $version) + { + $normVersion = self::getVersionParser()->normalize($version); + return new AliasPackage($package, $normVersion, $version); + } + protected function ensureDirectoryExistsAndClear($directory) { $fs = new Filesystem(); From 863bb59b73a62d8038924b52f883a61f767ee5ac Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 9 May 2012 16:03:04 +0200 Subject: [PATCH 16/21] Fix authorship info --- .../Operation/MarkAliasInstalledOperation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php index 3846dd52c..4638709ed 100644 --- a/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php +++ b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php @@ -17,7 +17,7 @@ use Composer\Package\AliasPackage; /** * Solver install operation. * - * @author Konstantin Kudryashov + * @author Nils Adermann */ class MarkAliasInstalledOperation extends SolverOperation { From 74da398f21ed0c04fc23b36c58eb9582b6f413fb Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 9 May 2012 16:23:38 +0200 Subject: [PATCH 17/21] Request install of normalized alias version in lock file --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 08d56819d..4af17f074 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -242,7 +242,7 @@ class Installer $version = $package->getVersion(); foreach ($aliases as $alias) { if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) { - $version = $alias['alias']; + $version = $alias['alias_normalized']; break; } } From bca91677acae2ca6c419d51e2613fcad202edfb9 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 9 May 2012 16:24:06 +0200 Subject: [PATCH 18/21] Keep original packages in installed repository when adding aliases --- src/Composer/Installer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 4af17f074..2f7a3b137 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -405,7 +405,6 @@ class Installer $package->setAlias($alias['alias_normalized']); $package->setPrettyAlias($alias['alias']); $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); - $package->getRepository()->removePackage($package); $aliasPackage->setRootPackageAlias(true); } } From bd9aa6a288da114094a26e2f151860d49d965c3b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 9 May 2012 16:49:48 +0200 Subject: [PATCH 19/21] Undo pointless changes in ArrayDumper --- src/Composer/Package/Dumper/ArrayDumper.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index 08eb9c32e..ed25e656b 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -41,9 +41,8 @@ class ArrayDumper $data = array(); $data['name'] = $package->getPrettyName(); - $data['version'] = $package->getPrettyVersion(); - $data['version_normalized'] = $package->getVersion();; + $data['version_normalized'] = $package->getVersion(); if ($package->getTargetDir()) { $data['target-dir'] = $package->getTargetDir(); From fafd09a7baa41bdf4b89329a94c1b7a42f095d6f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 9 May 2012 17:31:12 +0200 Subject: [PATCH 20/21] Improve output of operations to show details about dev versions --- .../DependencyResolver/Operation/InstallOperation.php | 2 +- .../DependencyResolver/Operation/SolverOperation.php | 9 +++++++++ .../DependencyResolver/Operation/UninstallOperation.php | 2 +- .../DependencyResolver/Operation/UpdateOperation.php | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/Operation/InstallOperation.php b/src/Composer/DependencyResolver/Operation/InstallOperation.php index 0a16629f4..4fd3bcba1 100644 --- a/src/Composer/DependencyResolver/Operation/InstallOperation.php +++ b/src/Composer/DependencyResolver/Operation/InstallOperation.php @@ -61,6 +61,6 @@ class InstallOperation extends SolverOperation */ public function __toString() { - return 'Installing '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')'; + return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')'; } } diff --git a/src/Composer/DependencyResolver/Operation/SolverOperation.php b/src/Composer/DependencyResolver/Operation/SolverOperation.php index a0071641e..5fb796d17 100644 --- a/src/Composer/DependencyResolver/Operation/SolverOperation.php +++ b/src/Composer/DependencyResolver/Operation/SolverOperation.php @@ -42,4 +42,13 @@ abstract class SolverOperation implements OperationInterface { return $this->reason; } + + protected function formatVersion(PackageInterface $package) + { + if (!$package->isDev() || !in_array($package->getSourceType(), array('hg', 'git'))) { + return $package->getPrettyVersion(); + } + + return $package->getPrettyVersion().' '.substr($package->getSourceReference(), 0, 6); + } } diff --git a/src/Composer/DependencyResolver/Operation/UninstallOperation.php b/src/Composer/DependencyResolver/Operation/UninstallOperation.php index 018df26c5..1412d2f85 100644 --- a/src/Composer/DependencyResolver/Operation/UninstallOperation.php +++ b/src/Composer/DependencyResolver/Operation/UninstallOperation.php @@ -61,6 +61,6 @@ class UninstallOperation extends SolverOperation */ public function __toString() { - return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')'; + return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')'; } } diff --git a/src/Composer/DependencyResolver/Operation/UpdateOperation.php b/src/Composer/DependencyResolver/Operation/UpdateOperation.php index 5f0bae2e2..96b12d35e 100644 --- a/src/Composer/DependencyResolver/Operation/UpdateOperation.php +++ b/src/Composer/DependencyResolver/Operation/UpdateOperation.php @@ -74,7 +74,7 @@ class UpdateOperation extends SolverOperation */ public function __toString() { - return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->initialPackage->getPrettyVersion().') to '. - $this->targetPackage->getPrettyName(). ' ('.$this->targetPackage->getPrettyVersion().')'; + return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '. + $this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')'; } } From 45a2070feebdaf9ead7eccf5cd679cb92f0d24c9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 9 May 2012 17:31:27 +0200 Subject: [PATCH 21/21] Add BC warning for older lock files --- src/Composer/Package/Locker.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 44247c5b8..7f6b738f6 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -89,6 +89,12 @@ class Locker $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository(); foreach ($lockedPackages as $info) { + // TODO BC remove this after June 10th + if (isset($info['alias']) && empty($warned)) { + $warned = true; + echo 'BC warning: your lock file appears to be of an older format than this composer version, it is recommended to run composer update'.PHP_EOL; + } + $resolvedVersion = !empty($info['alias-version']) ? $info['alias-version'] : $info['version']; // try to find the package in the local repo (best match)