1
0
Fork 0

Finalize platform override feature

- Added tests, docs
- Persist to lock file
- Add support in config command
- Added to json schema
pull/3982/head
Jordi Boggiano 2015-04-29 22:38:07 +01:00
parent 80b0a35a68
commit a57c51e8d7
10 changed files with 106 additions and 44 deletions

View File

@ -82,7 +82,7 @@ resolution.
have a proper setup. have a proper setup.
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not 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 * **--dry-run:** If you want to run through an installation without actually
installing a package, you can use `--dry-run`. This will simulate the installing a package, you can use `--dry-run`. This will simulate the
installation and show you what would happen. installation and show you what would happen.
@ -127,7 +127,7 @@ php composer.phar update vendor/*
* **--prefer-dist:** Install packages from `dist` when available. * **--prefer-dist:** Install packages from `dist` when available.
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not 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. * **--dry-run:** Simulate the command without actually doing anything.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior). * **--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. * **--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. * **--prefer-dist:** Install packages from `dist` when available.
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not 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`. * **--dev:** Add packages to `require-dev`.
* **--no-update:** Disables the automatic update of the dependencies. * **--no-update:** Disables the automatic update of the dependencies.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some
@ -195,7 +195,7 @@ uninstalled.
### Options ### Options
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not 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`. * **--dev:** Remove packages from `require-dev`.
* **--no-update:** Disables the automatic update of the dependencies. * **--no-update:** Disables the automatic update of the dependencies.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some

View File

@ -761,6 +761,9 @@ The following options are supported:
against them. For example using against them. For example using
`{"example.org": {"username": "alice", "password": "foo"}` as the value of this `{"example.org": {"username": "alice", "password": "foo"}` as the value of this
option will let composer authenticate against example.org. 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 * **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
different directory if you want to. `$HOME` and `~` will be replaced by your 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. home directory's path in vendor-dir and all `*-dir` options below.

View File

@ -145,6 +145,11 @@
"type": ["string", "boolean"], "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." "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": { "vendor-dir": {
"type": "string", "type": "string",
"description": "The location where all packages are installed, defaults to \"vendor\"." "description": "The location where all packages are installed, defaults to \"vendor\"."

View File

@ -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'); 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 // handle github-oauth
if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) { if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {

View File

@ -79,7 +79,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function addConfigSetting($name, $value) public function addConfigSetting($name, $value)
{ {
$this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) { $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); list($key, $host) = explode('.', $key, 2);
if ($this->authConfig) { if ($this->authConfig) {
$config[$key][$host] = $val; $config[$key][$host] = $val;
@ -98,7 +98,7 @@ class JsonConfigSource implements ConfigSourceInterface
public function removeConfigSetting($name) public function removeConfigSetting($name)
{ {
$this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) { $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); list($key, $host) = explode('.', $key, 2);
if ($this->authConfig) { if ($this->authConfig) {
unset($config[$key][$host]); unset($config[$key][$host]);

View File

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

View File

@ -191,6 +191,13 @@ class Locker
return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null; 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() public function getAliases()
{ {
$lockData = $this->getLockData(); $lockData = $this->getLockData();
@ -223,10 +230,11 @@ class Locker
* @param array $stabilityFlags * @param array $stabilityFlags
* @param bool $preferStable * @param bool $preferStable
* @param bool $preferLowest * @param bool $preferLowest
* @param array $platformOverrides
* *
* @return bool * @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( $lock = array(
'_readme' => array('This file locks the dependencies of your project to a known state', '_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'] = $platformReqs;
$lock['platform-dev'] = $platformDevReqs; $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 (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) {
if ($this->lockFile->exists()) { if ($this->lockFile->exists()) {

View File

@ -24,12 +24,22 @@ class PlatformRepository extends ArrayRepository
{ {
const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit)?|hhvm|(?:ext|lib)-[^/]+)$}i'; 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; private $overrides;
public function __construct(array $overrides = array()) public function __construct(array $packages = array(), array $overrides = array())
{ {
parent::__construct(array()); parent::__construct($packages);
$this->overrides = $overrides; $this->overrides = array();
foreach ($overrides as $name => $version) {
$this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
}
} }
protected function initialize() protected function initialize()
@ -40,19 +50,17 @@ class PlatformRepository extends ArrayRepository
// Add each of the override versions as options. // Add each of the override versions as options.
// Later we might even replace the extensions instead. // 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. // Check that it's a platform package.
if( preg_match(self::PLATFORM_PACKAGE_REGEX, $name) ) { if (!preg_match(self::PLATFORM_PACKAGE_REGEX, $override['name'])) {
$version = $versionParser->normalize($prettyVersion); throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
$package = new CompletePackage($name, $version, $prettyVersion);
$package->setDescription("Overridden virtual platform package $name.");
parent::addPackage($package);
}
else {
throw new \InvalidArgumentException('Invalid platform package '.$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; $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
$version = $versionParser->normalize($prettyVersion); $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" * {@inheritDoc}
// feature in a */
// protected function addOverriddenPackage()
// instead?
public function addPackage(PackageInterface $package) public function addPackage(PackageInterface $package)
{ {
/* // Skip if overridden
If we can find the package in this repository, if (isset($this->overrides[strtolower($package->getName())])) {
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?
return; return;
} }
parent::addPackage($package); parent::addPackage($package);

View File

@ -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)

View File

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