Browse Source

Merge pull request #3450 from nicolas-grekas/prefer-lowest-stable

add --prefer-lowest and --prefer-stable to update command
Jordi Boggiano 10 years ago
parent
commit
901fd838f3

+ 3 - 1
doc/03-cli.md

@@ -140,7 +140,9 @@ php composer.phar update vendor/*
 * **--lock:** Only updates the lock file hash to suppress warning about the
   lock file being out of date.
 * **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
-  So all packages with their dependencies are updated recursively.
+* **--prefer-stable:** Prefer stable versions of dependencies.
+* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
+  versions of requirements, generally used with `--prefer-stable`.
 
 ## require
 

+ 4 - 0
src/Composer/Command/UpdateCommand.php

@@ -47,6 +47,8 @@ class UpdateCommand extends Command
                 new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                 new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
                 new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
+                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
+                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
             ))
             ->setHelp(<<<EOT
 The <info>update</info> command reads the composer.json file from the
@@ -121,6 +123,8 @@ EOT
             ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
             ->setWhitelistDependencies($input->getOption('with-dependencies'))
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
+            ->setPreferStable($input->getOption('prefer-stable'))
+            ->setPreferLowest($input->getOption('prefer-lowest'))
         ;
 
         if ($input->getOption('no-plugins')) {

+ 5 - 2
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
 class DefaultPolicy implements PolicyInterface
 {
     private $preferStable;
+    private $preferLowest;
 
-    public function __construct($preferStable = false)
+    public function __construct($preferStable = false, $preferLowest = false)
     {
         $this->preferStable = $preferStable;
+        $this->preferLowest = $preferLowest;
     }
 
     public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
@@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
 
     protected function pruneToBestVersion(Pool $pool, $literals)
     {
+        $operator = $this->preferLowest ? '<' : '>';
         $bestLiterals = array($literals[0]);
         $bestPackage = $pool->literalToPackage($literals[0]);
         foreach ($literals as $i => $literal) {
@@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
 
             $package = $pool->literalToPackage($literal);
 
-            if ($this->versionCompare($package, $bestPackage, '>')) {
+            if ($this->versionCompare($package, $bestPackage, $operator)) {
                 $bestPackage = $package;
                 $bestLiterals = array($literal);
             } elseif ($this->versionCompare($package, $bestPackage, '==')) {

+ 38 - 4
src/Composer/Installer.php

@@ -107,6 +107,8 @@ class Installer
     protected $update = false;
     protected $runScripts = true;
     protected $ignorePlatformReqs = false;
+    protected $preferStable = false;
+    protected $preferLowest = false;
     /**
      * Array of package names/globs flagged for update
      *
@@ -307,7 +309,8 @@ class Installer
                     $aliases,
                     $this->package->getMinimumStability(),
                     $this->package->getStabilityFlags(),
-                    $this->package->getPreferStable()
+                    $this->preferStable || $this->package->getPreferStable(),
+                    $this->preferLowest
                 );
                 if ($updatedLock) {
                     $this->io->write('<info>Writing lock file</info>');
@@ -694,16 +697,21 @@ class Installer
     private function createPolicy()
     {
         $preferStable = null;
+        $preferLowest = null;
         if (!$this->update && $this->locker->isLocked()) {
             $preferStable = $this->locker->getPreferStable();
+            $preferLowest = $this->locker->getPreferLowest();
         }
-        // old lock file without prefer stable will return null
+        // old lock file without prefer stable/lowest will return null
         // so in this case we use the composer.json info
         if (null === $preferStable) {
-            $preferStable = $this->package->getPreferStable();
+            $preferStable = $this->preferStable || $this->package->getPreferStable();
+        }
+        if (null === $preferLowest) {
+            $preferLowest = $this->preferLowest;
         }
 
-        return new DefaultPolicy($preferStable);
+        return new DefaultPolicy($preferStable, $preferLowest);
     }
 
     private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
@@ -1244,6 +1252,32 @@ class Installer
         return $this;
     }
 
+    /**
+     * Should packages be prefered in a stable version when updating?
+     *
+     * @param  boolean   $preferStable
+     * @return Installer
+     */
+    public function setPreferStable($preferStable = true)
+    {
+        $this->preferStable = (boolean) $preferStable;
+
+        return $this;
+    }
+
+    /**
+     * Should packages be prefered in a lowest version when updating?
+     *
+     * @param  boolean   $preferLowest
+     * @return Installer
+     */
+    public function setPreferLowest($preferLowest = true)
+    {
+        $this->preferLowest = (boolean) $preferLowest;
+
+        return $this;
+    }
+
     /**
      * Disables plugins.
      *

+ 12 - 1
src/Composer/Package/Locker.php

@@ -182,6 +182,15 @@ class Locker
         return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
     }
 
+    public function getPreferLowest()
+    {
+        $lockData = $this->getLockData();
+
+        // return null if not set to allow caller logic to choose the
+        // right behavior since old lock files have no prefer-lowest
+        return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
+    }
+
     public function getAliases()
     {
         $lockData = $this->getLockData();
@@ -213,10 +222,11 @@ class Locker
      * @param string $minimumStability
      * @param array  $stabilityFlags
      * @param bool   $preferStable
+     * @param bool   $preferLowest
      *
      * @return bool
      */
-    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable)
+    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest)
     {
         $lock = array(
             '_readme' => array('This file locks the dependencies of your project to a known state',
@@ -229,6 +239,7 @@ class Locker
             'minimum-stability' => $minimumStability,
             'stability-flags' => $stabilityFlags,
             'prefer-stable' => $preferStable,
+            'prefer-lowest' => $preferLowest,
         );
 
         foreach ($aliases as $package => $versions) {

+ 16 - 0
tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php

@@ -247,4 +247,20 @@ class DefaultPolicyTest extends TestCase
 
         return $map;
     }
+
+    public function testSelectLowest()
+    {
+        $policy = new DefaultPolicy(false, true);
+
+        $this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0'));
+        $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
+        $this->pool->addRepository($this->repo);
+
+        $literals = array($packageA1->getId(), $packageA2->getId());
+        $expected = array($packageA1->getId());
+
+        $selected = $policy->selectPreferedPackages($this->pool, array(), $literals);
+
+        $this->assertEquals($expected, $selected);
+    }
 }

+ 1 - 0
tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test

@@ -48,6 +48,7 @@ install --prefer-dist
         "a/a": 20
     },
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 2 - 1
tests/Composer/Test/Fixtures/installer/install-from-empty-lock.test

@@ -25,7 +25,8 @@ Requirements from the composer file are not installed if the lock file is presen
     "aliases": [],
     "minimum-stability": "stable",
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --RUN--
 install

+ 2 - 1
tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test

@@ -33,7 +33,8 @@ Installing an old alias that doesn't exist anymore from a lock is possible
     "aliases": [],
     "minimum-stability": "dev",
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --RUN--
 install

+ 2 - 0
tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test

@@ -36,6 +36,7 @@ Partial update from lock file should apply lock file and downgrade unstable pack
         "b/unstable": 15
     },
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }
@@ -59,6 +60,7 @@ update c/uptodate
     "minimum-stability": "stable",
     "stability-flags": [],
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 2 - 0
tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test

@@ -36,6 +36,7 @@ Partial update from lock file should update everything to the state of the lock,
         "b/unstable": 15
     },
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }
@@ -59,6 +60,7 @@ update b/unstable
     "minimum-stability": "stable",
     "stability-flags": [],
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 1 - 0
tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test

@@ -43,6 +43,7 @@ update b/unstable
     "minimum-stability": "stable",
     "stability-flags": [],
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 3 - 1
tests/Composer/Test/Fixtures/installer/update-alias-lock.test

@@ -39,7 +39,8 @@ Update aliased package does not mess up the lock file
     "aliases": [],
     "minimum-stability": "dev",
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --INSTALLED--
 [
@@ -66,6 +67,7 @@ update
     "minimum-stability": "dev",
     "stability-flags": [],
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 40 - 0
tests/Composer/Test/Fixtures/installer/update-prefer-lowest-stable.test

@@ -0,0 +1,40 @@
+--TEST--
+Updates packages to their lowest stable version
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0-rc1" },
+                { "name": "a/a", "version": "1.0.1" },
+                { "name": "a/a", "version": "1.1.0" },
+
+                { "name": "a/b", "version": "1.0.0" },
+                { "name": "a/b", "version": "1.0.1" },
+                { "name": "a/b", "version": "2.0.0" },
+
+                { "name": "a/c", "version": "1.0.0" },
+                { "name": "a/c", "version": "2.0.0" }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "~1.0@dev",
+        "a/c": "2.*"
+    },
+    "require-dev": {
+        "a/b": "*"
+    }
+}
+--INSTALLED--
+[
+    { "name": "a/a", "version": "1.0.0-rc1" },
+    { "name": "a/c", "version": "2.0.0" },
+    { "name": "a/b", "version": "1.0.1" }
+]
+--RUN--
+update --prefer-lowest --prefer-stable
+--EXPECT--
+Updating a/a (1.0.0-rc1) to a/a (1.0.1)
+Updating a/b (1.0.1) to a/b (1.0.0)

+ 2 - 1
tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test

@@ -32,7 +32,8 @@ Limited update takes rules from lock if available, and not from the installed re
     "aliases": [],
     "minimum-stability": "stable",
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --INSTALLED--
 [

+ 2 - 1
tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test

@@ -20,7 +20,8 @@ Installing locked dev packages should remove old dependencies
     "aliases": [],
     "minimum-stability": "dev",
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --INSTALLED--
 [

+ 3 - 1
tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test

@@ -32,7 +32,8 @@ Updating a dev package for new reference updates the url and reference
     "aliases": [],
     "minimum-stability": "dev",
     "stability-flags": {"a/a":20},
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 --INSTALLED--
 [
@@ -59,6 +60,7 @@ update
     "minimum-stability": "dev",
     "stability-flags": {"a/a":20},
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform-dev": []
 }

+ 2 - 0
tests/Composer/Test/InstallerTest.php

@@ -217,6 +217,8 @@ class InstallerTest extends TestCase
                 ->setDryRun($input->getOption('dry-run'))
                 ->setUpdateWhitelist($input->getArgument('packages'))
                 ->setWhitelistDependencies($input->getOption('with-dependencies'))
+                ->setPreferStable($input->getOption('prefer-stable'))
+                ->setPreferLowest($input->getOption('prefer-lowest'))
                 ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'));
 
             return $installer->run();

+ 3 - 2
tests/Composer/Test/Package/LockerTest.php

@@ -135,9 +135,10 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                 'platform' => array(),
                 'platform-dev' => array(),
                 'prefer-stable' => false,
+                'prefer-lowest' => false,
             ));
 
-        $locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false);
+        $locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false, false);
     }
 
     public function testLockBadPackages()
@@ -156,7 +157,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
         $this->setExpectedException('LogicException');
 
-        $locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false);
+        $locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false, false);
     }
 
     public function testIsFresh()