diff --git a/res/composer-schema.json b/res/composer-schema.json index 5ae75c922..3b85d9791 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -221,6 +221,10 @@ "type": ["string"], "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable." }, + "prefer-stable": { + "type": ["boolean"], + "description": "If set to true, stable packages will be prefered to dev packages when possible, even if the minimum-stability allows unstable packages." + }, "bin": { "type": ["array"], "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 9217b5ba4..d59a07d55 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -14,15 +14,28 @@ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; use Composer\Package\LinkConstraint\VersionConstraint; /** * @author Nils Adermann + * @author Jordi Boggiano */ class DefaultPolicy implements PolicyInterface { + private $preferStable; + + public function __construct($preferStable = false) + { + $this->preferStable = $preferStable; + } + public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) { + if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { + return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB]; + } + $constraint = new VersionConstraint($operator, $b->getVersion()); $version = new VersionConstraint('==', $a->getVersion()); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 47c1b6a77..0c0d1404d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -234,7 +234,7 @@ class Installer // split dev and non-dev requirements by checking what would be removed if we update without the dev requirements if ($this->devMode && $this->package->getDevRequires()) { - $policy = new DefaultPolicy(); + $policy = $this->createPolicy(); $pool = $this->createPool(); $pool->addRepository($installedRepo, $aliases); @@ -308,7 +308,7 @@ class Installer $this->io->write('Loading composer repositories with package information'); // creating repository pool - $policy = new DefaultPolicy(); + $policy = $this->createPolicy(); $pool = $this->createPool(); $pool->addRepository($installedRepo, $aliases); if ($installFromLock) { @@ -513,6 +513,11 @@ class Installer return new Pool($minimumStability, $stabilityFlags); } + private function createPolicy() + { + return new DefaultPolicy($this->package->getPreferStable()); + } + private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo) { $request = new Request($pool); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 22b443e8d..c47d715a0 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -91,6 +91,10 @@ class RootPackageLoader extends ArrayLoader $package->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); } + if (isset($config['prefer-stable'])) { + $package->setPreferStable((bool) $config['prefer-stable']); + } + $repos = Factory::createDefaultRepositories(null, $this->config, $this->manager); foreach ($repos as $repo) { $this->manager->addRepository($repo); diff --git a/src/Composer/Package/RootPackage.php b/src/Composer/Package/RootPackage.php index 798f045b7..9112e3237 100644 --- a/src/Composer/Package/RootPackage.php +++ b/src/Composer/Package/RootPackage.php @@ -20,6 +20,7 @@ namespace Composer\Package; class RootPackage extends CompletePackage implements RootPackageInterface { protected $minimumStability = 'stable'; + protected $preferStable = false; protected $stabilityFlags = array(); protected $references = array(); @@ -59,6 +60,24 @@ class RootPackage extends CompletePackage implements RootPackageInterface return $this->stabilityFlags; } + /** + * Set the preferStable + * + * @param bool $preferStable + */ + public function setPreferStable($preferStable) + { + $this->preferStable = $preferStable; + } + + /** + * {@inheritDoc} + */ + public function getPreferStable() + { + return $this->preferStable; + } + /** * Set the references * diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 7a1a6be58..8b82cc54d 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -65,6 +65,35 @@ class DefaultPolicyTest extends TestCase $this->assertEquals($expected, $selected); } + public function testSelectNewestPicksLatest() + { + $this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0.0')); + $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); + $this->pool->addRepository($this->repo); + + $literals = array($packageA1->getId(), $packageA2->getId()); + $expected = array($packageA2->getId()); + + $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); + + $this->assertEquals($expected, $selected); + } + + public function testSelectNewestPicksLatestStableWithPreferStable() + { + $this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0.0')); + $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); + $this->pool->addRepository($this->repo); + + $literals = array($packageA1->getId(), $packageA2->getId()); + $expected = array($packageA1->getId()); + + $policy = new DefaultPolicy(true); + $selected = $policy->selectPreferedPackages($this->pool, array(), $literals); + + $this->assertEquals($expected, $selected); + } + public function testSelectNewestOverInstalled() { $this->repo->addPackage($packageA = $this->getPackage('A', '2.0'));