diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index bbe0e8702..71ca1f34d 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -36,7 +36,7 @@ class Pool $this->repositories[] = $repo; foreach ($repo->getPackages() as $package) { - $package->setId(count($this->packages)); + $package->setId(count($this->packages) + 1); $this->packages[] = $package; foreach ($package->getNames() as $name) { @@ -53,7 +53,7 @@ class Pool */ public function packageById($id) { - return $this->packages[$id]; + return $this->packages[$id - 1]; } /** diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 77358a74a..9edba67ca 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -29,7 +29,11 @@ class Solver const RULE_PACKAGE_CONFLICT = 7; const RULE_PACKAGE_NOT_EXIST = 8; const RULE_PACKAGE_REQUIRES = 9; - const RULE_LEARNED = 10; + const RULE_PACKAGE_OBSOLETES = 10; + const RULE_INSTALLED_PACKAGE_OBSOLETES = 11; + const RULE_PACKAGE_SAME_NAME = 12; + const RULE_PACKAGE_IMPLICIT_OBSOLETES = 13; + const RULE_LEARNED = 14; protected $policy; protected $pool; @@ -41,6 +45,7 @@ class Solver protected $addedMap = array(); protected $fixMap = array(); protected $updateMap = array(); + protected $noObsoletes = array(); protected $watches = array(); protected $removeWatches = array(); @@ -183,7 +188,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createConflictRule(PackageInterface $issuer, Package $provider, $reason, $reasonData = null) + public function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) { // ignore self conflict if ($issuer === $provider) { @@ -287,7 +292,7 @@ class Solver $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); foreach ($possibleConflicts as $conflict) { - if ($dontfix && $this->installed === $conflict->getRepository()) { + if ($dontFix && $this->installed === $conflict->getRepository()) { continue; } @@ -295,6 +300,59 @@ 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 = ($this->installed === $package->getRepository()); + + foreach ($package->getReplaces() as $link) { + $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); + + foreach ($obsoleteProviders as $provider) { + if ($provider === $package) { + continue; + } + + if ($isInstalled && $dontFix && $this->installed === $provider->getRepository()) { + continue; // don't repair installed/installed problems + } + + $reason = ($isInstalled) ? self::RULE_INSTALLED_PACKAGE_OBSOLETES : self::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 && $this->installed !== $provider->getRepository()) { + continue; + } + + // obsolete same packages even when noObsletes + if ($noObsoletes && (!$package->equals($provider))) { + continue; + } + + $reason = ($package->getName() == $provider->getName()) ? self::RULE_PACKAGE_SAME_NAME : self::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); @@ -705,6 +763,111 @@ class Solver if ($this->installed === $package->getRepository()) { $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; @@ -908,17 +1071,18 @@ class Solver $transaction = array(); - foreach ($this->decisionQueue as $literal) { + foreach ($this->decisionQueue as $i => $literal) { $package = $literal->getPackage(); // wanted & installed || !wanted & !installed - if ($literal->isWanted() == ($this->installed == $package->getRepository())) { + if ($literal->isWanted() == ($this->installed === $package->getRepository())) { continue; } $transaction[] = array( 'job' => ($literal->isWanted()) ? 'install' : 'remove', 'package' => $package, + 'why' => $this->decisionQueueWhy[$i], ); } @@ -995,7 +1159,7 @@ class Solver { $packageId = abs($literalId); return (isset($this->decisionMap[$packageId]) && ( - $this->decisionMap[$packageId] > 0 && !$literalId < 0 || + $this->decisionMap[$packageId] > 0 && !($literalId < 0) || $this->decisionMap[$packageId] < 0 && $literalId > 0 )); } @@ -1654,6 +1818,7 @@ class Solver foreach ($updateRuleLiterals as $ruleLiteral) { if ($this->decidedInstall($ruleLiteral->getPackage())) { // already fulfilled + $decisionQueue = array(); break; } if ($this->undecided($ruleLiteral->getPackage())) { @@ -1673,10 +1838,8 @@ class Solver if ($level <= $oLevel) { $repeat = true; } - } - - // still undecided? keep package. - if (!$repeat && $this->undecided($literal->getPackage())) { + } else if (!$repeat && $this->undecided($literal->getPackage())) { + // still undecided? keep package. $oLevel = $level; if (isset($this->cleanDepsMap[$literal->getPackageId()])) { // clean deps removes package diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index b7da239a5..15a1e4ab4 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -73,6 +73,16 @@ class SolverTest extends \PHPUnit_Framework_TestCase } public function testSolverInstallInstalled() + { + $this->repoInstalled->addPackage(new MemoryPackage('A', '1.0')); + $this->reposComplete(); + + $this->request->install('A'); + + $this->checkSolverResult(array()); + } + + public function testSolverInstallInstalledWithAlternative() { $this->repo->addPackage(new MemoryPackage('A', '1.0')); $this->repoInstalled->addPackage(new MemoryPackage('A', '1.0')); @@ -107,8 +117,6 @@ class SolverTest extends \PHPUnit_Framework_TestCase public function testSolverUpdateSingle() { - $this->markTestIncomplete(); - $this->repoInstalled->addPackage($packageA = new MemoryPackage('A', '1.0')); $this->repo->addPackage($newPackageA = new MemoryPackage('A', '1.1')); $this->reposComplete(); @@ -116,7 +124,9 @@ class SolverTest extends \PHPUnit_Framework_TestCase $this->request->update('A'); $this->checkSolverResult(array( - array('job' => 'update', 'package' => $newPackageA), + //array('job' => 'update', 'from' => $packageA, 'to' => $newPackageA), + array('job' => 'remove', 'package' => $packageA), + array('job' => 'install', 'package' => $newPackageA), )); } @@ -155,7 +165,7 @@ class SolverTest extends \PHPUnit_Framework_TestCase array('job' => 'install', 'package' => $packageB), array('job' => 'install', 'package' => $packageA), array('job' => 'remove', 'package' => $packageD), - array('job' => 'update', 'package' => $packageC), + array('job' => 'update', 'from' => $oldPackageC, 'to' => $packageC), )); } @@ -185,6 +195,11 @@ class SolverTest extends \PHPUnit_Framework_TestCase protected function checkSolverResult(array $expected) { $result = $this->solver->solve($this->request); + + foreach ($result as &$step) { + unset($step['why']); + } + $this->assertEquals($expected, $result); }