Browse Source

Finalize platform override feature

- Added tests, docs
- Persist to lock file
- Add support in config command
- Added to json schema
Jordi Boggiano 10 years ago
parent
commit
a57c51e8d7

+ 4 - 4
doc/03-cli.md

@@ -82,7 +82,7 @@ resolution.
   have a proper setup.
 * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
   requirements and force the installation even if the local machine does not
-  fulfill these.
+  fulfill these. See also the `platform` [config option](04-schema.md#config).
 * **--dry-run:** If you want to run through an installation without actually
   installing a package, you can use `--dry-run`. This will simulate the
   installation and show you what would happen.
@@ -127,7 +127,7 @@ php composer.phar update vendor/*
 * **--prefer-dist:** Install packages from `dist` when available.
 * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
   requirements and force the installation even if the local machine does not
-  fulfill these.
+  fulfill these. See also the `platform` [config option](04-schema.md#config).
 * **--dry-run:** Simulate the command without actually doing anything.
 * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
 * **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
@@ -171,7 +171,7 @@ php composer.phar require vendor/package:2.* vendor/package2:dev-master
 * **--prefer-dist:** Install packages from `dist` when available.
 * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
   requirements and force the installation even if the local machine does not
-  fulfill these.
+  fulfill these. See also the `platform` [config option](04-schema.md#config).
 * **--dev:** Add packages to `require-dev`.
 * **--no-update:** Disables the automatic update of the dependencies.
 * **--no-progress:** Removes the progress display that can mess with some
@@ -195,7 +195,7 @@ uninstalled.
 ### Options
 * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
   requirements and force the installation even if the local machine does not
-  fulfill these.
+  fulfill these. See also the `platform` [config option](04-schema.md#config).
 * **--dev:** Remove packages from `require-dev`.
 * **--no-update:** Disables the automatic update of the dependencies.
 * **--no-progress:** Removes the progress display that can mess with some

+ 3 - 0
doc/04-schema.md

@@ -761,6 +761,9 @@ The following options are supported:
   against them. For example using
   `{"example.org": {"username": "alice", "password": "foo"}` as the value of this
   option will let composer authenticate against example.org.
+* **platform:** Lets you fake platform packages (PHP and extensions) so that
+  you can emulate a production env or define your target platform in the
+  config. e.g. `{"php": "5.4", "ext-something": "4.0"}`.
 * **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
   different directory if you want to. `$HOME` and `~` will be replaced by your
   home directory's path in vendor-dir and all `*-dir` options below.

+ 5 - 0
res/composer-schema.json

@@ -145,6 +145,11 @@
                     "type": ["string", "boolean"],
                     "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt."
                 },
+                "platform": {
+                    "type": "object",
+                    "description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.",
+                    "additionalProperties": true
+                },
                 "vendor-dir": {
                     "type": "string",
                     "description": "The location where all packages are installed, defaults to \"vendor\"."

+ 9 - 0
src/Composer/Command/ConfigCommand.php

@@ -410,6 +410,15 @@ EOT
             throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs http://bar.com');
         }
 
+        // handle platform
+        if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
+            if ($input->getOption('unset')) {
+                return $this->configSource->removeConfigSetting($settingKey);
+            }
+
+            return $this->configSource->addConfigSetting($settingKey, $values[0]);
+        }
+
         // handle github-oauth
         if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {

+ 2 - 2
src/Composer/Config/JsonConfigSource.php

@@ -79,7 +79,7 @@ class JsonConfigSource implements ConfigSourceInterface
     public function addConfigSetting($name, $value)
     {
         $this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) {
-            if ($key === 'github-oauth' || $key === 'http-basic') {
+            if (preg_match('{^(github-oauth|http-basic|platform)\.}', $key)) {
                 list($key, $host) = explode('.', $key, 2);
                 if ($this->authConfig) {
                     $config[$key][$host] = $val;
@@ -98,7 +98,7 @@ class JsonConfigSource implements ConfigSourceInterface
     public function removeConfigSetting($name)
     {
         $this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) {
-            if ($key === 'github-oauth' || $key === 'http-basic') {
+            if (preg_match('{^(github-oauth|http-basic|platform)\.}', $key)) {
                 list($key, $host) = explode('.', $key, 2);
                 if ($this->authConfig) {
                     unset($config[$key][$host]);

+ 8 - 4
src/Composer/Installer.php

@@ -206,9 +206,12 @@ class Installer
 
         // create installed repo, this contains all local packages + platform packages (php & extensions)
         $localRepo = $this->repositoryManager->getLocalRepository();
-        $platformOverride = $this->config->get('platform');
-        $platformOverride = is_array($platformOverride) ? $platformOverride : array();
-        $platformRepo = new PlatformRepository($platformOverride);
+        if (!$this->update && $this->locker->isLocked()) {
+            $platformOverrides = $this->locker->getPlatformOverrides();
+        } else {
+            $platformOverrides = $this->config->get('platform') ?: array();
+        }
+        $platformRepo = new PlatformRepository(array(), $platformOverrides);
         $repos = array(
             $localRepo,
             new InstalledArrayRepository(array($installedRootPackage)),
@@ -313,7 +316,8 @@ class Installer
                     $this->package->getMinimumStability(),
                     $this->package->getStabilityFlags(),
                     $this->preferStable || $this->package->getPreferStable(),
-                    $this->preferLowest
+                    $this->preferLowest,
+                    $this->config->get('platform') ?: array()
                 );
                 if ($updatedLock) {
                     $this->io->writeError('<info>Writing lock file</info>');

+ 17 - 6
src/Composer/Package/Locker.php

@@ -191,6 +191,13 @@ class Locker
         return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
     }
 
+    public function getPlatformOverrides()
+    {
+        $lockData = $this->getLockData();
+
+        return isset($lockData['platform-overrides']) ? $lockData['platform-overrides'] : array();
+    }
+
     public function getAliases()
     {
         $lockData = $this->getLockData();
@@ -214,19 +221,20 @@ class Locker
     /**
      * Locks provided data into lockfile.
      *
-     * @param array  $packages         array of packages
-     * @param mixed  $devPackages      array of dev packages or null if installed without --dev
-     * @param array  $platformReqs     array of package name => constraint for required platform packages
-     * @param mixed  $platformDevReqs  array of package name => constraint for dev-required platform packages
-     * @param array  $aliases          array of aliases
+     * @param array  $packages          array of packages
+     * @param mixed  $devPackages       array of dev packages or null if installed without --dev
+     * @param array  $platformReqs      array of package name => constraint for required platform packages
+     * @param mixed  $platformDevReqs   array of package name => constraint for dev-required platform packages
+     * @param array  $aliases           array of aliases
      * @param string $minimumStability
      * @param array  $stabilityFlags
      * @param bool   $preferStable
      * @param bool   $preferLowest
+     * @param array  $platformOverrides
      *
      * @return bool
      */
-    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest)
+    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides)
     {
         $lock = array(
             '_readme' => array('This file locks the dependencies of your project to a known state',
@@ -260,6 +268,9 @@ class Locker
 
         $lock['platform'] = $platformReqs;
         $lock['platform-dev'] = $platformDevReqs;
+        if ($platformOverrides) {
+            $lock['platform-overrides'] = $platformOverrides;
+        }
 
         if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) {
             if ($this->lockFile->exists()) {

+ 26 - 26
src/Composer/Repository/PlatformRepository.php

@@ -24,12 +24,22 @@ class PlatformRepository extends ArrayRepository
 {
     const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit)?|hhvm|(?:ext|lib)-[^/]+)$}i';
 
+    /**
+     * Defines overrides so that the platform can be mocked
+     *
+     * Should be an array of package name => version number mappings
+     *
+     * @var array
+     */
     private $overrides;
 
-    public function __construct(array $overrides = array())
+    public function __construct(array $packages = array(), array $overrides = array())
     {
-        parent::__construct(array());
-        $this->overrides = $overrides;
+        parent::__construct($packages);
+        $this->overrides = array();
+        foreach ($overrides as $name => $version) {
+            $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
+        }
     }
 
     protected function initialize()
@@ -40,19 +50,17 @@ class PlatformRepository extends ArrayRepository
 
         // Add each of the override versions as options.
         // Later we might even replace the extensions instead.
-        foreach( $this->overrides as $name => $prettyVersion ) {
+        foreach ($this->overrides as $override) {
             // Check that it's a platform package.
-            if( preg_match(self::PLATFORM_PACKAGE_REGEX, $name) ) {
-                $version = $versionParser->normalize($prettyVersion);
-                $package = new CompletePackage($name, $version, $prettyVersion);
-                $package->setDescription("Overridden virtual platform package $name.");
-                parent::addPackage($package);
-            }
-            else {
-                throw new \InvalidArgumentException('Invalid platform package '.$name);
+            if (!preg_match(self::PLATFORM_PACKAGE_REGEX, $override['name'])) {
+                throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
             }
-        }
 
+            $version = $versionParser->normalize($override['version']);
+            $package = new CompletePackage($override['name'], $version, $override['version']);
+            $package->setDescription('Overridden virtual platform package '.$override['name']);
+            parent::addPackage($package);
+        }
 
         $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
         $version = $versionParser->normalize($prettyVersion);
@@ -186,21 +194,13 @@ class PlatformRepository extends ArrayRepository
         }
     }
 
-    // TODO: Is it a good thing to redefine the public interface
-    // like this, or is it better to make the "only-add-if-no-in-platform"
-    // feature in a
-    // protected function addOverriddenPackage()
-    // instead?
+    /**
+     * {@inheritDoc}
+     */
     public function addPackage(PackageInterface $package)
     {
-        /*
-           If we can find the package in this repository,
-           in any version, it can only mean that it has been
-           added by the config key 'platform' and should
-           the real package (i.e. this one) should not be added.
-        */
-        if( count($this->findPackages($package->getName())) > 0 ) {
-            // Log a warning that we're ignoring existing package?
+        // Skip if overridden
+        if (isset($this->overrides[strtolower($package->getName())])) {
             return;
         }
         parent::addPackage($package);

+ 29 - 0
tests/Composer/Test/Fixtures/installer/install-overridden-platform-packages.test

@@ -0,0 +1,29 @@
+--TEST--
+Install overridden platform requirements works
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0", "require": { "ext-dummy2": "1.*" } }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "*",
+        "ext-dummy": "~1.0",
+        "php": "1.0"
+    },
+    "config": {
+        "platform": {
+            "php": "1.0.0",
+            "ext-dummy": "1.2.3",
+            "ext-dummy2": "1.2.3"
+        }
+    }
+}
+--RUN--
+install
+--EXPECT--
+Installing a/a (1.0.0)

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

@@ -134,11 +134,12 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                 'stability-flags' => array(),
                 'platform' => array(),
                 'platform-dev' => array(),
+                'platform-overrides' => array('foo/bar' => '1.0'),
                 'prefer-stable' => false,
                 'prefer-lowest' => false,
             ));
 
-        $locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false, false);
+        $locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false, false, array('foo/bar' => '1.0'));
     }
 
     public function testLockBadPackages()
@@ -157,7 +158,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
         $this->setExpectedException('LogicException');
 
-        $locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false, false);
+        $locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false, false, array());
     }
 
     public function testIsFresh()