Bläddra i källkod

Make VersionSelector prefer stable by default and allow specifying a PHP version that must be matched, fixes #4318

Jordi Boggiano 9 år sedan
förälder
incheckning
507415e404

+ 6 - 5
src/Composer/Command/InitCommand.php

@@ -321,7 +321,7 @@ EOT
         return $this->repos;
     }
 
-    protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
+    protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null)
     {
         if ($requires) {
             $requires = $this->normalizeRequirements($requires);
@@ -331,7 +331,7 @@ EOT
             foreach ($requires as $requirement) {
                 if (!isset($requirement['version'])) {
                     // determine the best version automatically
-                    $version = $this->findBestVersionForPackage($input, $requirement['name']);
+                    $version = $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion);
                     $requirement['version'] = $version;
 
                     $io->writeError(sprintf(
@@ -426,7 +426,7 @@ EOT
                     );
 
                     if (false === $constraint) {
-                        $constraint = $this->findBestVersionForPackage($input, $package);
+                        $constraint = $this->findBestVersionForPackage($input, $package, $phpVersion);
 
                         $io->writeError(sprintf(
                             'Using version <info>%s</info> for <info>%s</info>',
@@ -591,14 +591,15 @@ EOT
      *
      * @param  InputInterface            $input
      * @param  string                    $name
+     * @param  string                    $phpVersion
      * @throws \InvalidArgumentException
      * @return string
      */
-    private function findBestVersionForPackage(InputInterface $input, $name)
+    private function findBestVersionForPackage(InputInterface $input, $name, $phpVersion)
     {
         // find the latest version allowed in this pool
         $versionSelector = new VersionSelector($this->getPool($input));
-        $package = $versionSelector->findBestCandidate($name);
+        $package = $versionSelector->findBestCandidate($name, null, $phpVersion);
 
         if (!$package) {
             throw new \InvalidArgumentException(sprintf(

+ 8 - 2
src/Composer/Command/RequireCommand.php

@@ -96,12 +96,18 @@ EOT
         $composer = $this->getComposer();
         $repos = $composer->getRepositoryManager()->getRepositories();
 
+        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
+        // initialize $this->repos as it is used by the parent InitCommand
         $this->repos = new CompositeRepository(array_merge(
-            array(new PlatformRepository),
+            array(new PlatformRepository(array(), $platformOverrides)),
             $repos
         ));
 
-        $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
+        $phpPackages = $this->repos->findPackages('php');
+        $phpPackage = reset($phpPackages);
+        $phpVersion = $phpPackage->getVersion();
+        unset($phpPackage, $phpPackages);
+        $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion);
 
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

+ 5 - 0
src/Composer/Package/BasePackage.php

@@ -223,6 +223,11 @@ abstract class BasePackage implements PackageInterface
         return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
     }
 
+    public function getStabilityPriority()
+    {
+        return self::$stabilities[$this->getStability()];
+    }
+
     public function __clone()
     {
         $this->repository = null;

+ 15 - 1
src/Composer/Package/Version/VersionSelector.php

@@ -17,6 +17,8 @@ use Composer\Package\PackageInterface;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Dumper\ArrayDumper;
 use Composer\Semver\VersionParser as SemverVersionParser;
+use Composer\Semver\Semver;
+use Composer\Semver\Constraint\Constraint;
 
 /**
  * Selects the best possible version for a package
@@ -41,13 +43,22 @@ class VersionSelector
      *
      * @param  string                $packageName
      * @param  string                $targetPackageVersion
+     * @param  string                $targetPhpVersion
      * @return PackageInterface|bool
      */
-    public function findBestCandidate($packageName, $targetPackageVersion = null)
+    public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferStable = true)
     {
         $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
         $candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true);
 
+        if ($targetPhpVersion) {
+            $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion));
+            $candidates = array_filter($candidates, function ($pkg) use ($phpConstraint) {
+                $reqs = $pkg->getRequires();
+                return !isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint);
+            });
+        }
+
         if (!$candidates) {
             return false;
         }
@@ -55,6 +66,9 @@ class VersionSelector
         // select highest version if we have many
         $package = reset($candidates);
         foreach ($candidates as $candidate) {
+            if ($preferStable && $package->getStabilityPriority() < $candidate->getStabilityPriority()) {
+                continue;
+            }
             if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
                 $package = $candidate;
             }

+ 73 - 12
tests/Composer/Test/Package/Version/VersionSelectorTest.php

@@ -13,6 +13,8 @@
 namespace Composer\Test\Package\Version;
 
 use Composer\Package\Version\VersionSelector;
+use Composer\Package\Package;
+use Composer\Package\Link;
 use Composer\Semver\VersionParser;
 
 class VersionSelectorTest extends \PHPUnit_Framework_TestCase
@@ -25,9 +27,9 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
     {
         $packageName = 'foobar';
 
-        $package1 = $this->createMockPackage('1.2.1');
-        $package2 = $this->createMockPackage('1.2.2');
-        $package3 = $this->createMockPackage('1.2.0');
+        $package1 = $this->createPackage('1.2.1');
+        $package2 = $this->createPackage('1.2.2');
+        $package3 = $this->createPackage('1.2.0');
         $packages = array($package1, $package2, $package3);
 
         $pool = $this->createMockPool();
@@ -40,7 +42,70 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
         $best = $versionSelector->findBestCandidate($packageName);
 
         // 1.2.2 should be returned because it's the latest of the returned versions
-        $this->assertEquals($package2, $best, 'Latest version should be 1.2.2');
+        $this->assertSame($package2, $best, 'Latest version should be 1.2.2');
+    }
+
+    public function testLatestVersionIsReturnedThatMatchesPhpRequirement()
+    {
+        $packageName = 'foobar';
+
+        $parser = new VersionParser;
+        $package1 = $this->createPackage('1.0.0');
+        $package2 = $this->createPackage('2.0.0');
+        $package1->setRequires(array('php' => new Link($packageName, 'php', $parser->parseConstraints('>=5.4'), 'requires', '>=5.4')));
+        $package2->setRequires(array('php' => new Link($packageName, 'php', $parser->parseConstraints('>=5.6'), 'requires', '>=5.6')));
+        $packages = array($package1, $package2);
+
+        $pool = $this->createMockPool();
+        $pool->expects($this->once())
+            ->method('whatProvides')
+            ->with($packageName, null, true)
+            ->will($this->returnValue($packages));
+
+        $versionSelector = new VersionSelector($pool);
+        $best = $versionSelector->findBestCandidate($packageName, null, '5.5.0');
+
+        $this->assertSame($package1, $best, 'Latest version supporting php 5.5 should be returned (1.0.0)');
+    }
+
+    public function testMostStableVersionIsReturned()
+    {
+        $packageName = 'foobar';
+
+        $package1 = $this->createPackage('1.0.0');
+        $package2 = $this->createPackage('1.1.0-beta');
+        $packages = array($package1, $package2);
+
+        $pool = $this->createMockPool();
+        $pool->expects($this->once())
+            ->method('whatProvides')
+            ->with($packageName, null, true)
+            ->will($this->returnValue($packages));
+
+        $versionSelector = new VersionSelector($pool);
+        $best = $versionSelector->findBestCandidate($packageName);
+
+        $this->assertSame($package1, $best, 'Latest most stable version should be returned (1.0.0)');
+    }
+
+    public function testHighestVersionIsReturned()
+    {
+        $packageName = 'foobar';
+
+        $package1 = $this->createPackage('1.0.0');
+        $package2 = $this->createPackage('1.1.0-beta');
+        $packages = array($package1, $package2);
+
+        $pool = $this->createMockPool();
+        $pool->expects($this->once())
+            ->method('whatProvides')
+            ->with($packageName, null, true)
+            ->will($this->returnValue($packages));
+
+        $versionSelector = new VersionSelector($pool);
+        $best = $versionSelector->findBestCandidate($packageName, null, null, false);
+
+        $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)');
     }
 
     public function testFalseReturnedOnNoPackages()
@@ -86,7 +151,7 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
         $recommended = $versionSelector->findRecommendedRequireVersion($package);
 
         // assert that the recommended version is what we expect
-        $this->assertEquals($expectedVersion, $recommended);
+        $this->assertSame($expectedVersion, $recommended);
     }
 
     public function getRecommendedRequireVersionPackages()
@@ -124,14 +189,10 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    private function createMockPackage($version)
+    private function createPackage($version)
     {
-        $package = $this->getMock('\Composer\Package\PackageInterface');
-        $package->expects($this->any())
-            ->method('getVersion')
-            ->will($this->returnValue($version));
-
-        return $package;
+        $parser = new VersionParser();
+        return new Package('foo', $parser->normalize($version), $version);
     }
 
     private function createMockPool()