瀏覽代碼

Turn obsolete relationships and replaced packages into conflict rules

Nils Adermann 13 年之前
父節點
當前提交
f46aa495da

+ 2 - 2
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];
     }
 
     /**

+ 173 - 10
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

+ 19 - 4
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);
     }