Explorar o código

Merge pull request #60 from naderman/master

Implementation of the DefaultPolicy specs regarding prefered packages
Nils Adermann %!s(int64=13) %!d(string=hai) anos
pai
achega
72f69e6109

+ 137 - 8
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -63,24 +63,125 @@ class DefaultPolicy implements PolicyInterface
         return true;
     }
 
+    public function getPriority(Pool $pool, PackageInterface $package)
+    {
+        return $pool->getPriority($package->getRepository());
+    }
+
     public function selectPreferedPackages(Pool $pool, RepositoryInterface $installed, array $literals)
     {
-        $newest = $this->selectNewestPackages($installed, $literals);
+        $packages = $this->groupLiteralsByNamePreferInstalled($installed, $literals);
+
+        foreach ($packages as &$literals) {
+            $policy = $this;
+            usort($literals, function ($a, $b) use ($policy, $pool, $installed) {
+                return $policy->compareByPriorityPreferInstalled($pool, $installed, $a->getPackage(), $b->getPackage(), true);
+            });
+        }
+
+        foreach ($packages as &$literals) {
+            $literals = $this->pruneToBestVersion($literals);
+
+            $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installed, $literals);
+        }
+
+        $selected = call_user_func_array('array_merge', $packages);
+
+        // now sort the result across all packages to respect replaces across packages
+        usort($selected, function ($a, $b) use ($policy, $pool, $installed) {
+            return $policy->compareByPriorityPreferInstalled($pool, $installed, $a->getPackage(), $b->getPackage());
+        });
+
+        return $selected;
+    }
+
+    protected function groupLiteralsByNamePreferInstalled(RepositoryInterface $installed, $literals)
+    {
+        $packages = array();
+        foreach ($literals as $literal) {
+            $packageName = $literal->getPackage()->getName();
+
+            if (!isset($packages[$packageName])) {
+                $packages[$packageName] = array();
+            }
 
-        $selected = array();
-        foreach ($newest as $literal) {
             if ($literal->getPackage()->getRepository() === $installed) {
-                $selected[] = $literal;
+                array_unshift($packages[$packageName], $literal);
+            } else {
+                $packages[$packageName][] = $literal;
             }
         }
-        if (count($selected)) {
-            return $selected;
+
+        return $packages;
+    }
+
+    public function compareByPriorityPreferInstalled(Pool $pool, RepositoryInterface $installed, PackageInterface $a, PackageInterface $b, $ignoreReplace = false)
+    {
+        if ($a->getRepository() === $b->getRepository()) {
+
+            if (!$ignoreReplace) {
+                // return original, not replaced
+                if ($this->replaces($a, $b)) {
+                    return 1; // use b
+                }
+                if ($this->replaces($b, $a)) {
+                    return -1; // use a
+                }
+            }
+
+            // priority equal, sort by package id to make reproducible
+            if ($a->getId() === $b->getId()) {
+                return 0;
+            }
+
+            return ($a->getId() < $b->getId()) ? -1 : 1;
+        }
+
+        if ($a->getRepository() === $installed) {
+            return -1;
         }
 
-        return $newest;
+        if ($b->getRepository() === $installed) {
+            return 1;
+        }
+
+        return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1;
     }
 
-    public function selectNewestPackages(RepositoryInterface $installed, array $literals)
+    protected function replaces(PackageInterface $source, PackageInterface $target)
+    {
+        foreach ($source->getReplaces() as $link) {
+            if ($link->getTarget() === $target->getName() &&
+                (null === $link->getConstraint() ||
+                $link->getConstraint()->matches(new VersionConstraint('==', $target->getVersion())))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected function pruneToBestVersion($literals)
+    {
+        $bestLiterals = array($literals[0]);
+        $bestPackage = $literals[0]->getPackage();
+        foreach ($literals as $i => $literal) {
+            if (0 === $i) {
+                continue;
+            }
+
+            if ($this->versionCompare($literal->getPackage(), $bestPackage, '>')) {
+                $bestPackage = $literal->getPackage();
+                $bestLiterals = array($literal);
+            } else if ($this->versionCompare($literal->getPackage(), $bestPackage, '==')) {
+                $bestLiterals[] = $literal;
+            }
+        }
+
+        return $bestLiterals;
+    }
+
+    protected function selectNewestPackages(RepositoryInterface $installed, array $literals)
     {
         $maxLiterals = array($literals[0]);
         $maxPackage = $literals[0]->getPackage();
@@ -99,4 +200,32 @@ class DefaultPolicy implements PolicyInterface
 
         return $maxLiterals;
     }
+
+    protected function pruneToHighestPriorityOrInstalled(Pool $pool, RepositoryInterface $installed, array $literals)
+    {
+        $selected = array();
+
+        $priority = null;
+
+        foreach ($literals as $literal) {
+            $package = $literal->getPackage();
+
+            if ($package->getRepository() === $installed) {
+                $selected[] = $literal;
+                continue;
+            }
+
+            if (null === $priority) {
+                $priority = $this->getPriority($pool, $package);
+            }
+
+            if ($this->getPriority($pool, $package) != $priority) {
+                break;
+            }
+
+            $selected[] = $literal;
+        }
+
+        return $selected;
+    }
 }

+ 5 - 0
src/Composer/DependencyResolver/Pool.php

@@ -45,6 +45,11 @@ class Pool
         }
     }
 
+    public function getPriority(RepositoryInterface $repo)
+    {
+        return array_search($repo, $this->repositories, true);
+    }
+
     /**
     * Retrieves the package object for a given package id.
     *

+ 5 - 30
tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php

@@ -64,15 +64,15 @@ class DefaultPolicyTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($expected, $selected);
     }
 
-    public function testSelectInstalled()
+    public function testSelectNewestOverInstalled()
     {
-        $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
+        $this->repo->addPackage($packageA = new MemoryPackage('A', '2.0'));
         $this->repoInstalled->addPackage($packageAInstalled = new MemoryPackage('A', '1.0'));
         $this->pool->addRepository($this->repo);
         $this->pool->addRepository($this->repoInstalled);
 
         $literals = array(new Literal($packageA, true), new Literal($packageAInstalled, true));
-        $expected = array(new Literal($packageAInstalled, true));
+        $expected = array(new Literal($packageA, true));
 
         $selected = $this->policy->selectPreferedPackages($this->pool, $this->repoInstalled, $literals);
 
@@ -81,8 +81,6 @@ class DefaultPolicyTest extends \PHPUnit_Framework_TestCase
 
     public function testSelectLastRepo()
     {
-        $this->markTestIncomplete();
-
         $this->repoImportant = new ArrayRepository;
 
         $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
@@ -101,8 +99,6 @@ class DefaultPolicyTest extends \PHPUnit_Framework_TestCase
 
     public function testSelectAllProviders()
     {
-        $this->markTestIncomplete();
-
         $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
         $this->repo->addPackage($packageB = new MemoryPackage('B', '2.0'));
 
@@ -119,29 +115,8 @@ class DefaultPolicyTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($expected, $selected);
     }
 
-    public function testSelectNewestProvider()
-    {
-        $this->markTestIncomplete();
-
-        $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
-        $this->repo->addPackage($packageB = new MemoryPackage('B', '2.0'));
-
-        $packageA->setProvides(array(new Link('A', 'X', new VersionConstraint('==', '2.0'), 'provides')));
-        $packageB->setProvides(array(new Link('B', 'X', new VersionConstraint('==', '1.0'), 'provides')));
-
-        $this->pool->addRepository($this->repo);
-
-        $literals = array(new Literal($packageA, true), new Literal($packageB, true));
-        $expected = array(new Literal($packageA, true));
-
-        $selected = $this->policy->selectPreferedPackages($this->pool, $this->repoInstalled, $literals);
-
-        $this->assertEquals($expected, $selected);
-    }
-
-    public function testSelectNonReplacingFromSameRepo()
+    public function testPreferNonReplacingFromSameRepo()
     {
-        $this->markTestIncomplete();
 
         $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
         $this->repo->addPackage($packageB = new MemoryPackage('B', '2.0'));
@@ -151,7 +126,7 @@ class DefaultPolicyTest extends \PHPUnit_Framework_TestCase
         $this->pool->addRepository($this->repo);
 
         $literals = array(new Literal($packageA, true), new Literal($packageB, true));
-        $expected = array(new Literal($packageA, true));
+        $expected = array(new Literal($packageA, true), new Literal($packageB, true));
 
         $selected = $this->policy->selectPreferedPackages($this->pool, $this->repoInstalled, $literals);
 

+ 1 - 1
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -305,7 +305,7 @@ class SolverTest extends \PHPUnit_Framework_TestCase
 
     public function testInstallAlternativeWithCircularRequire()
     {
-	$this->markTestIncomplete();
+        $this->markTestIncomplete();
 
         $this->repo->addPackage($packageA = new MemoryPackage('A', '1.0'));
         $this->repo->addPackage($packageB = new MemoryPackage('B', '1.0'));