From 354bd7f7e29a1f0b9c81422abe9d20572ea6efc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=AB=E5=85=B4=E8=8C=82?= Date: Mon, 18 Dec 2017 14:55:03 +0800 Subject: [PATCH 001/580] Update parse name version parirs name to lower --- src/Composer/Package/Version/VersionParser.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 54d7f09c0..5edca4777 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -54,10 +54,10 @@ class VersionParser extends SemverVersionParser } if (strpos($pair, ' ')) { - list($name, $version) = explode(" ", $pair, 2); - $result[] = array('name' => $name, 'version' => $version); + list($name, $version) = explode(' ', $pair, 2); + $result[] = array('name' => strtolower($name), 'version' => $version); } else { - $result[] = array('name' => $pair); + $result[] = array('name' => strtolower($pair)); } } From 1c0a494c727d1e064bece7346172a12feeac69ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=AB=E5=85=B4=E8=8C=82?= Date: Tue, 19 Dec 2017 22:17:24 +0800 Subject: [PATCH 002/580] Update findBestVersionForPackage method to support return package name --- src/Composer/Command/InitCommand.php | 21 +++++++++++++------ .../Package/Version/VersionParser.php | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 9a8d542de..7e16d035b 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -385,9 +385,12 @@ EOT foreach ($requires as $requirement) { if (!isset($requirement['version'])) { // determine the best version automatically - $version = $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion, $preferredStability); + list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability); $requirement['version'] = $version; + // replace package name from packagist.org + $requirement['name'] = $name; + $io->writeError(sprintf( 'Using version %s for %s', $requirement['version'], @@ -395,7 +398,10 @@ EOT )); } else { // check that the specified version/constraint exists before we proceed - $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $requirement['version'], 'dev'); + list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $requirement['version'], 'dev'); + + // replace package name from packagist.org + $requirement['name'] = $name; } $result[] = $requirement['name'] . ' ' . $requirement['version']; @@ -493,7 +499,7 @@ EOT ); if (false === $constraint) { - $constraint = $this->findBestVersionForPackage($input, $package, $phpVersion, $preferredStability); + list($name, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $phpVersion, $preferredStability); $io->writeError(sprintf( 'Using version %s for %s', @@ -662,9 +668,9 @@ EOT * @param string $preferredStability * @param string $minimumStability * @throws \InvalidArgumentException - * @return string + * @return array name version */ - private function findBestVersionForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) + private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) { // find the latest version allowed in this pool $versionSelector = new VersionSelector($this->getPool($input, $minimumStability)); @@ -705,7 +711,10 @@ EOT )); } - return $versionSelector->findRecommendedRequireVersion($package); + return [ + $package->getPrettyName(), + $versionSelector->findRecommendedRequireVersion($package) + ]; } private function findSimilar($package) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 5edca4777..22b6dad50 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -55,9 +55,9 @@ class VersionParser extends SemverVersionParser if (strpos($pair, ' ')) { list($name, $version) = explode(' ', $pair, 2); - $result[] = array('name' => strtolower($name), 'version' => $version); + $result[] = array('name' => $name, 'version' => $version); } else { - $result[] = array('name' => strtolower($pair)); + $result[] = array('name' => $pair); } } From e4bb306dfca5d702c83ae87880acfd00267edc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=AB=E5=85=B4=E8=8C=82?= Date: Tue, 19 Dec 2017 22:32:10 +0800 Subject: [PATCH 003/580] Fix for CI --- src/Composer/Command/InitCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 7e16d035b..88c04373e 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -711,10 +711,10 @@ EOT )); } - return [ + return array( $package->getPrettyName(), $versionSelector->findRecommendedRequireVersion($package) - ]; + ); } private function findSimilar($package) From 5e68566ce6387452d29092e9dff3ca7b5d93a947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Mon, 25 Dec 2017 21:33:39 +0100 Subject: [PATCH 004/580] Fix: Typo / wording --- src/Composer/Package/Locker.php | 2 +- src/Composer/Plugin/Capability/CommandProvider.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 93e5ca655..fe2616cb4 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -102,7 +102,7 @@ class Locker } /** - * Checks whether locker were been locked (lockfile found). + * Checks whether locker has been locked (lockfile found). * * @return bool */ diff --git a/src/Composer/Plugin/Capability/CommandProvider.php b/src/Composer/Plugin/Capability/CommandProvider.php index 0debd2009..0f94bf37c 100644 --- a/src/Composer/Plugin/Capability/CommandProvider.php +++ b/src/Composer/Plugin/Capability/CommandProvider.php @@ -25,7 +25,7 @@ namespace Composer\Plugin\Capability; interface CommandProvider extends Capability { /** - * Retreives an array of commands + * Retrieves an array of commands * * @return \Composer\Command\BaseCommand[] */ From d034f1e23f6d5ff5d8257c7e112caed2330cc7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Thu, 28 Dec 2017 21:05:21 +0100 Subject: [PATCH 005/580] Fix: Add 'sort-packages' to composer-schema.json --- res/composer-schema.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/composer-schema.json b/res/composer-schema.json index 80a8312b4..1150cef64 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -278,6 +278,10 @@ "htaccess-protect": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." + }, + "sort-packages": { + "type": "boolean", + "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." } } }, From 79828f7543f355180c6225442404dd833a0252a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 27 Dec 2017 19:43:31 +0100 Subject: [PATCH 006/580] Enhancement: Assert that key is removed when value is null --- tests/Composer/Test/Json/JsonManipulatorTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index e66e9aac2..05de454ca 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -2310,6 +2310,22 @@ class JsonManipulatorTest extends TestCase ', $manipulator->getContents()); } + public function testRemoveMainKeyRemovesKeyWhereValueIsNull() + { + $manipulator = new JsonManipulator(json_encode(array( + 'foo' => 9000, + 'bar' => null, + ))); + + $manipulator->removeMainKey('bar'); + + $expected = json_encode(array( + 'foo' => 9000, + )); + + $this->assertJsonStringEqualsJsonString($expected, $manipulator->getContents()); + } + public function testIndentDetection() { $manipulator = new JsonManipulator('{ From de07f588c14452e69e0ec2e557739657f3b95892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 27 Dec 2017 19:49:32 +0100 Subject: [PATCH 007/580] Fix: Use array_key_exists() instead of isset() --- src/Composer/Json/JsonManipulator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index f929732fe..cdd22c817 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -417,7 +417,7 @@ class JsonManipulator { $decoded = JsonFile::parseJson($this->contents); - if (!isset($decoded[$key])) { + if (!array_key_exists($key, $decoded)) { return true; } From 58a62dceab4d85653334a43bf589d8965883245a Mon Sep 17 00:00:00 2001 From: Martin Hujer Date: Fri, 29 Dec 2017 11:33:19 +0100 Subject: [PATCH 008/580] Change http to https where possible --- doc/00-intro.md | 4 ++-- doc/01-basic-usage.md | 2 +- doc/03-cli.md | 2 +- doc/04-schema.md | 4 ++-- doc/05-repositories.md | 4 ++-- doc/articles/handling-private-packages-with-satis.md | 2 +- doc/articles/plugins.md | 2 +- doc/articles/versions.md | 2 +- doc/faqs/why-are-unbound-version-constraints-a-bad-idea.md | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index 0399ae51a..efc5c4c00 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -14,7 +14,7 @@ manager. It does however support a "global" project for convenience via the [global](03-cli.md#global) command. This idea is not new and Composer is strongly inspired by node's -[npm](https://npmjs.org/) and ruby's [bundler](http://bundler.io/). +[npm](https://npmjs.org/) and ruby's [bundler](https://bundler.io/). Suppose: @@ -135,7 +135,7 @@ C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat Add the directory to your PATH environment variable if it isn't already. For information on changing your PATH variable, please see -[this article](http://www.computerhope.com/issues/ch000549.htm) and/or +[this article](https://www.computerhope.com/issues/ch000549.htm) and/or use Google. Close your current terminal. Test usage with a new terminal: diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index de5c85f43..8740c59db 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -56,7 +56,7 @@ you to require certain versions of server software. See ### Package Version Constraints In our example, we are requesting the Monolog package with the version constraint -[`1.0.*`](http://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*). +[`1.0.*`](https://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*). This means any version in the `1.0` development branch, or any version that is greater than or equal to 1.0 and less than 1.1 (`>=1.0 <1.1`). diff --git a/doc/03-cli.md b/doc/03-cli.md index 3d279242f..e0b60da30 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -854,7 +854,7 @@ all projects. By default it points to `C:\Users\\AppData\Roaming\Composer` on Windows and `/Users//.composer` on OSX. On *nix systems that follow the [XDG Base -Directory Specifications](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html), +Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), it points to `$XDG_CONFIG_HOME/composer`. On other *nix systems, it points to `/home//.composer`. diff --git a/doc/04-schema.md b/doc/04-schema.md index 0f01f684d..8e9b55e49 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -214,7 +214,7 @@ An example: { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be", + "homepage": "https://seld.be", "role": "Developer" } ] @@ -768,7 +768,7 @@ Example: "name": "smarty/smarty", "version": "3.1.7", "dist": { - "url": "http://www.smarty.net/files/Smarty-3.1.7.zip", + "url": "https://www.smarty.net/files/Smarty-3.1.7.zip", "type": "zip" }, "source": { diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 1aaadf4c8..39a1a490b 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -93,7 +93,7 @@ Here is a minimal package definition: "name": "smarty/smarty", "version": "3.1.7", "dist": { - "url": "http://www.smarty.net/files/Smarty-3.1.7.zip", + "url": "https://www.smarty.net/files/Smarty-3.1.7.zip", "type": "zip" } } @@ -488,7 +488,7 @@ Here is an example for the smarty template engine: "name": "smarty/smarty", "version": "3.1.7", "dist": { - "url": "http://www.smarty.net/files/Smarty-3.1.7.zip", + "url": "https://www.smarty.net/files/Smarty-3.1.7.zip", "type": "zip" }, "source": { diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index 7307c02bf..c875edbc4 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -339,6 +339,6 @@ is set to true. [ssh2 context options]: https://secure.php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-options [ssl context options]: https://secure.php.net/manual/en/context.ssl.php -[Twig]: http://twig.sensiolabs.org/ +[Twig]: https://twig.sensiolabs.org/ [config schema]: https://getcomposer.org/doc/04-schema.md#config [notify-batch]: https://getcomposer.org/doc/05-repositories.md#notify-batch diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 83f9994c8..ab54015c9 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -282,4 +282,4 @@ local project plugins are loaded. [7]: ../01-basic-usage.md#package-versions [8]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capable.php [9]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capability/CommandProvider.php -[10]: http://symfony.com/doc/current/components/console/introduction.html +[10]: https://symfony.com/doc/current/components/console.html diff --git a/doc/articles/versions.md b/doc/articles/versions.md index 5cf02f940..7ed725d7f 100644 --- a/doc/articles/versions.md +++ b/doc/articles/versions.md @@ -140,7 +140,7 @@ Example: `1.0.*` The `~` operator is best explained by example: `~1.2` is equivalent to `>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see it is mostly useful for projects respecting [semantic -versioning](http://semver.org/). A common usage would be to mark the minimum +versioning](https://semver.org/). A common usage would be to mark the minimum minor version you depend on, like `~1.2` (which allows anything up to, but not including, 2.0). Since in theory there should be no backwards compatibility breaks until 2.0, that works well. Another way of looking at it is that using diff --git a/doc/faqs/why-are-unbound-version-constraints-a-bad-idea.md b/doc/faqs/why-are-unbound-version-constraints-a-bad-idea.md index 015ac92da..9aef970f9 100644 --- a/doc/faqs/why-are-unbound-version-constraints-a-bad-idea.md +++ b/doc/faqs/why-are-unbound-version-constraints-a-bad-idea.md @@ -14,7 +14,7 @@ compatible with the new major version of your dependency. For example instead of using `>=3.4` you should use `~3.4` which allows all versions up to `3.999` but does not include `4.0` and above. The `^` operator -works very well with libraries following [semantic versioning](http://semver.org). +works very well with libraries following [semantic versioning](https://semver.org). **Note:** As a package maintainer, you can make the life of your users easier by providing an [alias version](../articles/aliases.md) for your development From 149a5e9401f4afcdc9d0b190a318d8668373ee6c Mon Sep 17 00:00:00 2001 From: Martin Hujer Date: Fri, 29 Dec 2017 11:37:42 +0100 Subject: [PATCH 009/580] Change links to prevent unnecessary redirect --- CHANGELOG.md | 2 +- doc/00-intro.md | 2 +- doc/04-schema.md | 2 +- doc/07-community.md | 2 +- doc/articles/troubleshooting.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c34ad2ef..c853340ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -578,7 +578,7 @@ * Added autoloading support for root packages that use target-dir * Added awareness of the root package presence and support for it's provide/replace/conflict keys * Added IOInterface::isDecorated to test for colored output support - * Added validation of licenses based on the [SPDX registry](http://www.spdx.org/licenses/) + * Added validation of licenses based on the [SPDX registry](https://spdx.org/licenses/) * Improved repository protocol to have large cacheable parts * Fixed various bugs relating to package aliasing, proxy configuration, binaries * Various bug fixes and docs improvements diff --git a/doc/00-intro.md b/doc/00-intro.md index efc5c4c00..e0a81b05b 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -14,7 +14,7 @@ manager. It does however support a "global" project for convenience via the [global](03-cli.md#global) command. This idea is not new and Composer is strongly inspired by node's -[npm](https://npmjs.org/) and ruby's [bundler](https://bundler.io/). +[npm](https://www.npmjs.com/) and ruby's [bundler](https://bundler.io/). Suppose: diff --git a/doc/04-schema.md b/doc/04-schema.md index 8e9b55e49..0dc1c0f30 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -152,7 +152,7 @@ The recommended notation for the most common licenses is (alphabetical): - MIT Optional, but it is highly recommended to supply this. More identifiers are -listed at the [SPDX Open Source License Registry](https://www.spdx.org/licenses/). +listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/). For closed-source software, you may use `"proprietary"` as the license identifier. diff --git a/doc/07-community.md b/doc/07-community.md index b79515d8c..4296f90cd 100644 --- a/doc/07-community.md +++ b/doc/07-community.md @@ -7,7 +7,7 @@ contributing. If you would like to contribute to Composer, please read the [README](https://github.com/composer/composer) and -[CONTRIBUTING](https://github.com//composer/composer/blob/master/.github/CONTRIBUTING.md) +[CONTRIBUTING](https://github.com/composer/composer/blob/master/.github/CONTRIBUTING.md) documents. The most important guidelines are described as follows: diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 7bce67d29..9142f33c6 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -146,7 +146,7 @@ Or, you can increase the limit with a command-line argument: php -d memory_limit=-1 composer.phar <...> ``` -This issue can also happen on cPanel instances, when the shell fork bomb protection is activated. For more information, see the [documentation](https://documentation.cpanel.net/display/ALD/Shell+Fork+Bomb+Protection) of the fork bomb feature on the cPanel site. +This issue can also happen on cPanel instances, when the shell fork bomb protection is activated. For more information, see the [documentation](https://documentation.cpanel.net/display/68Docs/Shell+Fork+Bomb+Protection) of the fork bomb feature on the cPanel site. ## Xdebug impact on Composer From 9bb4cce0b8b02696cebfef202d71c8d27c5d6c49 Mon Sep 17 00:00:00 2001 From: Martin Hujer Date: Fri, 29 Dec 2017 12:44:51 +0100 Subject: [PATCH 010/580] Fixes typos in docs --- doc/01-basic-usage.md | 4 ++-- doc/03-cli.md | 8 ++++---- doc/04-schema.md | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 8740c59db..3119fd015 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -192,7 +192,7 @@ includes PHP itself, PHP extensions and some system libraries. require the `php-64bit` package. * `hhvm` represents the version of the HHVM runtime and allows you to apply - a constraint, e.g., '>=2.3.3'. + a constraint, e.g., `>=2.3.3`. * `ext-` allows you to require PHP extensions (includes core extensions). Versioning can be quite inconsistent here, so it's often @@ -255,7 +255,7 @@ In addition to PSR-4 autoloading, Composer also supports PSR-0, classmap and files autoloading. See the [`autoload`](04-schema.md#autoload) reference for more information. -See also the docs on [`optimizing the autoloader`](articles/autoloader-optimization.md). +See also the docs on [optimizing the autoloader](articles/autoloader-optimization.md). > **Note:** Composer provides its own autoloader. If you don't want to use that > one, you can just include `vendor/composer/autoload_*.php` files, which return diff --git a/doc/03-cli.md b/doc/03-cli.md index e0b60da30..ca8d906a8 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -625,7 +625,7 @@ would set `"extra": { "foo": { "bar": "value" } }`. ## create-project You can use Composer to create new projects from an existing package. This is -the equivalent of doing a git clone/svn checkout followed by a "composer install" +the equivalent of doing a git clone/svn checkout followed by a `composer install` of the vendors. There are several applications for this: @@ -635,7 +635,7 @@ There are several applications for this: 3. Projects with multiple developers can use this feature to bootstrap the initial application for development. -To create a new project using Composer you can use the "create-project" command. +To create a new project using Composer you can use the `create-project` command. Pass it a package name, and the directory to create the project in. You can also provide a version as third argument, otherwise the latest version is used. @@ -677,7 +677,7 @@ By default the command checks for the packages on packagist.org. ## dump-autoload (dumpautoload) If you need to update the autoloader because of new classes in a classmap -package for example, you can use "dump-autoload" to do that without having to +package for example, you can use `dump-autoload` to do that without having to go through an install or update. Additionally, it can dump an optimized autoloader that converts PSR-0/4 packages @@ -688,7 +688,7 @@ using this option you can still use PSR-0/4 for convenience and classmaps for performance. ### Options -* **--no-scripts:** Skips the execution of all scripts defined in composer.json file. +* **--no-scripts:** Skips the execution of all scripts defined in `composer.json` file. * **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but can take a bit of time to run so it is currently not done by default. diff --git a/doc/04-schema.md b/doc/04-schema.md index 0dc1c0f30..24dc15135 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -571,7 +571,7 @@ Example: #### Files If you want to require certain files explicitly on every request then you can use -the 'files' autoloading mechanism. This is useful if your package includes PHP functions +the `files` autoloading mechanism. This is useful if your package includes PHP functions that cannot be autoloaded by PHP. Example: @@ -586,7 +586,7 @@ Example: #### Exclude files from classmaps -If you want to exclude some files or folders from the classmap you can use the 'exclude-from-classmap' property. +If you want to exclude some files or folders from the classmap you can use the `exclude-from-classmap` property. This might be useful to exclude test classes in your live environment, for example, as those will be skipped from the classmap even when building an optimized autoloader. @@ -608,7 +608,7 @@ Example: The autoloader can have quite a substantial impact on your request time (50-100ms per request in large frameworks using a lot of classes). See the -[`article about optimizing the autoloader`](articles/autoloader-optimization.md) +[article about optimizing the autoloader](articles/autoloader-optimization.md) for more details on how to reduce this impact. ### autoload-dev ([root-only](04-schema.md#root-package)) @@ -883,7 +883,7 @@ but the same branch is installed (in the example: latest-testing). An example: If you have a testing branch, that is heavily maintained during a testing phase and is -deployed to your staging environment, normally "composer show -s" will give you `versions : * dev-master`. +deployed to your staging environment, normally `composer show -s` will give you `versions : * dev-master`. If you configure `latest-.*` as a pattern for non-feature-branches like this: @@ -893,7 +893,7 @@ If you configure `latest-.*` as a pattern for non-feature-branches like this: } ``` -Then "composer show -s" will give you `versions : * dev-latest-testing`. +Then `composer show -s` will give you `versions : * dev-latest-testing`. Optional. From 486e580db294b5fe2e8ef887efacb7f2500268c9 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sat, 30 Dec 2017 18:06:14 -0200 Subject: [PATCH 011/580] Clean up documentation --- doc/03-cli.md | 1 - doc/04-schema.md | 1 - doc/05-repositories.md | 11 +++++------ doc/articles/aliases.md | 4 ++-- doc/articles/autoloader-optimization.md | 2 -- doc/articles/handling-private-packages-with-satis.md | 1 - doc/articles/plugins.md | 4 ++-- doc/articles/scripts.md | 1 - doc/articles/troubleshooting.md | 10 +++++----- doc/articles/vendor-binaries.md | 5 ----- doc/articles/versions.md | 4 ++-- doc/faqs/how-to-install-untrusted-packages-safely.md | 2 +- doc/fixtures/fixtures.md | 1 - 13 files changed, 17 insertions(+), 30 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index ca8d906a8..4880d5406 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -220,7 +220,6 @@ If you do not specify a package, composer will prompt you to search for a packag Implicitly enables `--optimize-autoloader`. * **--apcu-autoloader:** Use APCu to cache found/not-found classes. - ## remove The `remove` command removes packages from the `composer.json` file from diff --git a/doc/04-schema.md b/doc/04-schema.md index 24dc15135..c338b4673 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -356,7 +356,6 @@ Example: > use and require. Alternatively you may use third party tools to analyze > your project for the list of extensions used. - #### require Lists packages required by this package. The package will not be installed diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 39a1a490b..99ba5f36a 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -205,7 +205,7 @@ project to use the patched version. If the library is on GitHub (this is the case most of the time), you can simply fork it there and push your changes to your fork. After that you update the project's `composer.json`. All you have to do is add your fork as a repository and update the version constraint to -point to your custom branch. In `composer.json`, you should prefix your custom +point to your custom branch. In `composer.json`, you should prefix your custom branch name with `"dev-"`. For version constraint naming conventions see [Libraries](02-libraries.md) for more information. @@ -300,18 +300,17 @@ Please note: The BitBucket driver uses OAuth to access your private repositories via the BitBucket REST APIs and you will need to create an OAuth consumer to use the driver, please refer to [Atlassian's Documentation](https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html). You will need to fill the callback url with something to satisfy BitBucket, but the address does not need to go anywhere and is not used by Composer. -After creating an OAuth consumer in the BitBucket control panel, you need to setup your auth.json file with +After creating an OAuth consumer in the BitBucket control panel, you need to setup your auth.json file with the credentials like this (more info [here](https://getcomposer.org/doc/06-config.md#bitbucket-oauth)): ```json { "config": { "bitbucket-oauth": { "bitbucket.org": { - "consumer-key": "myKey", + "consumer-key": "myKey", "consumer-secret": "mySecret" } } - } } ``` @@ -518,7 +517,7 @@ Typically you would leave the source part off, as you don't really need it. > reference you will have to delete the package to force an update, and will > have to deal with an unstable lock file. -The `"package"` key in a `package` repository may be set to an array to define multiple versions of a package: +The `"package"` key in a `package` repository may be set to an array to define multiple versions of a package: ```json { @@ -708,7 +707,7 @@ You can disable the default Packagist.org repository by adding this to your You can disable Packagist.org globally by using the global config flag: -``` +```bash composer config -g repo.packagist false ``` diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index c340e7053..bda6f79a8 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -65,8 +65,8 @@ is a dependency of your local project. For this reason, you can alias packages in your `require` and `require-dev` fields. Let's say you found a bug in the `monolog/monolog` package. You cloned -[Monolog](https://github.com/Seldaek/monolog) on GitHub and fixed the issue in -a branch named `bugfix`. Now you want to install that version of monolog in your +[Monolog](https://github.com/Seldaek/monolog) on GitHub and fixed the issue in +a branch named `bugfix`. Now you want to install that version of monolog in your local project. You are using `symfony/monolog-bundle` which requires `monolog/monolog` version diff --git a/doc/articles/autoloader-optimization.md b/doc/articles/autoloader-optimization.md index 0a8dab406..41bd4a6cb 100644 --- a/doc/articles/autoloader-optimization.md +++ b/doc/articles/autoloader-optimization.md @@ -53,7 +53,6 @@ result in slow filesystem checks. To solve this issue two Level 2 optimization options exist, and you can decide to enable either if you have a lot of class_exists checks that are done for classes that do not exist in your project. - ## Optimization Level 2/A: Authoritative class maps ### How to run it? @@ -82,7 +81,6 @@ then you might experience "class not found" issues in production. Enable this wi > Note: This can not be combined with Level 2/B optimizations. You have to choose one as > they address the same issue in different ways. - ## Optimization Level 2/B: APCu cache ### How to run it? diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index c875edbc4..fb799a720 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -336,7 +336,6 @@ is set to true. * `notify-batch`: optional, specify a URL that will be called every time a user installs a package. See [notify-batch]. - [ssh2 context options]: https://secure.php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-options [ssl context options]: https://secure.php.net/manual/en/context.ssl.php [Twig]: https://twig.sensiolabs.org/ diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index ab54015c9..228cbac9e 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -191,8 +191,8 @@ with [`Composer\Composer`][4]'s internal state, by providing explicit extension for common plugin requirements. Capable Plugins classes must implement the [`Composer\Plugin\Capable`][8] interface -and declare their capabilities in the `getCapabilities()` method. -This method must return an array, with the _key_ as a Composer Capability class name, +and declare their capabilities in the `getCapabilities()` method. +This method must return an array, with the _key_ as a Composer Capability class name, and the _value_ as the Plugin's own implementation class name of said Capability: ```php diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 00eae08b4..a93f2f205 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -15,7 +15,6 @@ the Composer execution process. > executed. If a dependency of the root package specifies its own scripts, > Composer does not execute those additional scripts. - ## Event names Composer fires the following named events during its execution process: diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 9142f33c6..80e639a63 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -244,7 +244,7 @@ following workarounds: On linux, it seems that running this command helps to make ipv4 traffic have a higher prio than ipv6, which is a better alternative than disabling ipv6 entirely: -```Bash +```bash sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf" ``` @@ -256,13 +256,13 @@ On windows the only way is to disable ipv6 entirely I am afraid (either in windo Get name of your network device: -``` +```bash networksetup -listallnetworkservices ``` Disable IPv6 on that device (in this case "Wi-Fi"): -``` +```bash networksetup -setv6off Wi-Fi ``` @@ -270,7 +270,7 @@ Run composer ... You can enable IPv6 again with: -``` +```bash networksetup -setv6automatic Wi-Fi ``` @@ -288,7 +288,7 @@ The reason for this is a SSH Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1 As a workaround, open a SSH connection to your Git host before running Composer: -``` +```bash ssh -t git@mygitserver.tld composer update ``` diff --git a/doc/articles/vendor-binaries.md b/doc/articles/vendor-binaries.md index b65b6bcf4..9e678a950 100644 --- a/doc/articles/vendor-binaries.md +++ b/doc/articles/vendor-binaries.md @@ -13,7 +13,6 @@ If a package contains other scripts that are not needed by the package users (like build or compile scripts) that code should not be listed as a vendor binary. - ## How is it defined? It is defined by adding the `bin` key to a project's `composer.json`. @@ -34,12 +33,10 @@ for any project that **depends** on that project. This is a convenient way to expose useful scripts that would otherwise be hidden deep in the `vendor/` directory. - ## What happens when Composer is run on a composer.json that defines vendor binaries? For the binaries that a package defines directly, nothing happens. - ## What happens when Composer is run on a composer.json that has dependencies with vendor binaries listed? Composer looks for the binaries defined in all of the dependencies. A @@ -75,7 +72,6 @@ In this case, Composer will make `vendor/my-vendor/project-a/bin/project-a-bin` available as `vendor/bin/project-a-bin`. On a Unix-like platform this is accomplished by creating a symlink. - ## What about Windows and .bat files? Packages managed entirely by Composer do not *need* to contain any @@ -90,7 +86,6 @@ Packages that need to support workflows that may not include Composer are welcome to maintain custom `.bat` files. In this case, the package should **not** list the `.bat` file as a binary as it is not needed. - ## Can vendor binaries be installed somewhere other than vendor/bin? Yes, there are two ways an alternate vendor binary location can be specified: diff --git a/doc/articles/versions.md b/doc/articles/versions.md index 7ed725d7f..a584d3e88 100644 --- a/doc/articles/versions.md +++ b/doc/articles/versions.md @@ -74,7 +74,7 @@ correct location in your `vendor` directory. ### Branches -If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it just copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).) +If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it just copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).) In the above example, if you wanted to check out the `my-feature` branch, you would specify `dev-my-feature` as the version constraint in your `require` clause. This would result in Composer cloning the `my-library` repository into my `vendor` directory and checking out the `my-feature` branch. @@ -201,7 +201,7 @@ setting. All available stability flags are listed on the minimum-stability section of the [schema page](../04-schema.md#minimum-stability). ## Summary -``` +```json "require": { "vendor/package": "1.3.2", // exactly 1.3.2 diff --git a/doc/faqs/how-to-install-untrusted-packages-safely.md b/doc/faqs/how-to-install-untrusted-packages-safely.md index df9eb7321..1b3e9d383 100644 --- a/doc/faqs/how-to-install-untrusted-packages-safely.md +++ b/doc/faqs/how-to-install-untrusted-packages-safely.md @@ -1,6 +1,6 @@ # How do I install untrusted packages safely? Is it safe to run Composer as superuser or root? -Certain Composer commands, including `exec`, `install`, and `update` allow third party code to +Certain Composer commands, including `exec`, `install`, and `update` allow third party code to execute on your system. This is from its "plugins" and "scripts" features. Plugins and scripts have full access to the user account which runs Composer. For this reason, it is strongly advised to **avoid running Composer as super-user/root**. diff --git a/doc/fixtures/fixtures.md b/doc/fixtures/fixtures.md index e689bfbd5..d67edb220 100644 --- a/doc/fixtures/fixtures.md +++ b/doc/fixtures/fixtures.md @@ -20,4 +20,3 @@ All these repositories contain the following packages. * `bar/baz` has a 1.0.0 version and 1.0.x-dev as well as dev-default branches. Additionally, 1.1.x-dev is a branch alias for dev-default. - From dbea8258711bcdcc3ddf620c7e2e9783ea19f00a Mon Sep 17 00:00:00 2001 From: William Sandin Date: Sun, 31 Dec 2017 19:35:09 +0700 Subject: [PATCH 012/580] Raise a warning if current user and owner of deploy dir doesn't match --- src/Composer/Command/SelfUpdateCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 055f8af7f..1f86e7ed9 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -93,7 +93,9 @@ EOT $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $home = $config->get('home'); + $homeOwner = posix_getpwuid(fileowner($home)); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; + $composeUser = posix_getpwuid(posix_geteuid()); if ($input->getOption('update-keys')) { return $this->fetchKeys($io, $config); @@ -107,6 +109,11 @@ EOT throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); } + // check if composer is running as the same user that owns the directory root + if ($composeUser !== $homeOwner) { + $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); + } + if ($input->getOption('rollback')) { return $this->rollback($output, $rollbackDir, $localFilename); } From 7aad20cb30221f8e4d6996bba74adf01fd16c5c8 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Tue, 2 Jan 2018 01:24:27 -0200 Subject: [PATCH 013/580] [SvnDownloader] Improve plurals and singulars --- src/Composer/Downloader/SvnDownloader.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index b3c8c33d8..9cde1537b 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -126,10 +126,17 @@ class SvnDownloader extends VcsDownloader $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); - $this->io->writeError(' The package has modified files:'); + $countChanges = count($changes); + $this->io->writeError(sprintf(' The package has modified file%s:', $countChanges === 1 ? '' : 's')); $this->io->writeError(array_slice($changes, 0, 10)); - if (count($changes) > 10) { - $this->io->writeError(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); + if ($countChanges > 10) { + $remaingChanges = $countChanges - 10; + $this->io->writeError( + sprintf( + ' '.$remaingChanges.' more file%s modified, choose "v" to view the full list', + $remaingChanges === 1 ? '' : 's' + ) + ); } while (true) { From 2ad6f611d7fb57b06f98365e0d0c856048075800 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Tue, 2 Jan 2018 06:55:41 -0200 Subject: [PATCH 014/580] Allow bin key to receive string --- res/composer-schema.json | 8 ++++---- src/Composer/Package/Loader/ArrayLoader.php | 7 ++----- src/Composer/Package/Loader/ValidatingArrayLoader.php | 10 +++++++++- tests/Composer/Test/Json/Fixtures/composer.json | 2 +- .../Test/Package/Loader/ValidatingArrayLoaderTest.php | 8 +++++++- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index 1150cef64..8c61a6240 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -368,8 +368,8 @@ "description": "If set to true, stable packages will be preferred 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).", + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } @@ -776,8 +776,8 @@ } }, "bin": { - "type": ["array"], - "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", + "type": ["string", "array"], + "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index f7e607085..303cc3c13 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -65,13 +65,10 @@ class ArrayLoader implements LoaderInterface } if (isset($config['bin'])) { - if (!is_array($config['bin'])) { - throw new \UnexpectedValueException('Package '.$config['name'].'\'s bin key should be an array, '.gettype($config['bin']).' given.'); - } - foreach ($config['bin'] as $key => $bin) { + foreach ((array) $config['bin'] as $key => $bin) { $config['bin'][$key] = ltrim($bin, '/'); } - $package->setBinaries($config['bin']); + $package->setBinaries((array) $config['bin']); } if (isset($config['installation-source'])) { diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index b0c254351..f98db5955 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -77,7 +77,15 @@ class ValidatingArrayLoader implements LoaderInterface $this->validateRegex('type', '[A-Za-z0-9-]+'); $this->validateString('target-dir'); $this->validateArray('extra'); - $this->validateFlatArray('bin'); + + if (isset($this->config['bin'])) { + if (is_string($this->config['bin'])) { + $this->validateString('bin'); + } else { + $this->validateFlatArray('bin'); + } + } + $this->validateArray('scripts'); // TODO validate event names & listener syntax $this->validateString('description'); $this->validateUrl('homepage'); diff --git a/tests/Composer/Test/Json/Fixtures/composer.json b/tests/Composer/Test/Json/Fixtures/composer.json index 1664d8048..66e35a7e7 100644 --- a/tests/Composer/Test/Json/Fixtures/composer.json +++ b/tests/Composer/Test/Json/Fixtures/composer.json @@ -47,7 +47,7 @@ "autoload-dev": { "psr-0": { "Composer\\Test": "tests/" } }, - "bin": ["bin/composer"], + "bin": "bin/composer", "extra": { "branch-alias": { "dev-master": "1.0-dev" diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 574f116e3..c1115becd 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -151,12 +151,18 @@ class ValidatingArrayLoaderTest extends TestCase 'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')), ), ), - array( // test as array + array( // test licenses as array array( 'name' => 'foo/bar', 'license' => array('MIT', 'WTFPL'), ), ), + array( // test bin as string + array( + 'name' => 'foo/bar', + 'bin' => 'bin1', + ), + ), ); } From b02aa2e7451975dbe801d232b7bd8d4388a001a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Tue, 2 Jan 2018 21:54:00 +0200 Subject: [PATCH 015/580] changelog: fix typo in 1.5.6 entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c853340ec..2b30c50ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ### [1.5.6] - 2017-12-18 * Fixed root package version guessed when a tag is checked out - * Fixed support for GitLab reposhosted on non-standard ports + * Fixed support for GitLab repos hosted on non-standard ports * Fixed regression in require command when requiring unstable packages, part 3 ### [1.5.5] - 2017-12-01 From 8a50345df791c3dddd5ec73792f45fc8bc84af42 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Jan 2018 15:02:32 +0100 Subject: [PATCH 016/580] Follow ignore-platform-reqs when checking for package requirements, fixes #6859 --- src/Composer/Command/InitCommand.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index b84c8dde0..577b73979 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -660,8 +660,9 @@ EOT * * @param InputInterface $input * @param string $name - * @param string $phpVersion + * @param string|null $phpVersion * @param string $preferredStability + * @param string|null $requiredVersion * @param string $minimumStability * @throws \InvalidArgumentException * @return string @@ -672,6 +673,12 @@ EOT $versionSelector = new VersionSelector($this->getPool($input, $minimumStability)); $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); + // retry without phpVersion if platform requirements are ignored in case nothing was found + if ($input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs')) { + $phpVersion = null; + $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); + } + if (!$package) { // Check whether the PHP version was the problem if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) { From 3be9591930688dc8be146095a321cdb280098e2b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 3 Jan 2018 16:24:22 +0100 Subject: [PATCH 017/580] Simplify some ClassLoader code, minor memory improvement, fixes #6937 --- src/Composer/Autoload/AutoloadGenerator.php | 8 ++++++++ src/Composer/Autoload/ClassLoader.php | 18 ++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 9b9710d9e..dca6a7553 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -783,6 +783,14 @@ HEADER; } } + // BC handling when converting to a new ClassLoader + if (isset($maps['prefixLengthsPsr4'])) { + $maps['firstCharsPsr4'] = array_map(function () { + return true; + }, $maps['prefixLengthsPsr4']); + unset($maps['prefixLengthsPsr4']); + } + foreach ($maps as $prop => $value) { if (count($value) > 32767) { // Static arrays are limited to 32767 values on PHP 5.6 diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 6e8d09f43..bebe83706 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -43,7 +43,7 @@ namespace Composer\Autoload; class ClassLoader { // PSR-4 - private $prefixLengthsPsr4 = array(); + private $firstCharsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); @@ -170,11 +170,10 @@ class ClassLoader } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { + if ('\\' !== substr($prefix, -1)) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->firstCharsPsr4[$prefix[0]] = true; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. @@ -221,11 +220,10 @@ class ClassLoader if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { + if ('\\' !== substr($prefix, -1)) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->firstCharsPsr4[$prefix[0]] = true; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } @@ -373,15 +371,15 @@ class ClassLoader $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; - if (isset($this->prefixLengthsPsr4[$first])) { + if (isset($this->firstCharsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; if (isset($this->prefixDirsPsr4[$search])) { - $length = $this->prefixLengthsPsr4[$first][$search]; + $pathEnd = substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $pathEnd)) { return $file; } } From 5cd0fef7ff75dbe32c06a8d36b4f837852a6d004 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 10:42:31 +0100 Subject: [PATCH 018/580] Upgrade to SPDX License 3.0 and handle deprecations more gracefully, fixes #6951 --- composer.lock | 12 +++--- .../Package/Loader/ValidatingArrayLoader.php | 43 +++++++++++++++++++ src/Composer/Util/ConfigValidator.php | 27 +----------- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/composer.lock b/composer.lock index 858234009..23915cf61 100644 --- a/composer.lock +++ b/composer.lock @@ -126,23 +126,23 @@ }, { "name": "composer/spdx-licenses", - "version": "1.1.6", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c" + "reference": "2d899e9b33023c631854f36c39ef9f8317a7ab33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2603a0d7ddc00a015deb576fa5297ca43dee6b1c", - "reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2d899e9b33023c631854f36c39ef9f8317a7ab33", + "reference": "2d899e9b33023c631854f36c39ef9f8317a7ab33", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" }, "type": "library", @@ -183,7 +183,7 @@ "spdx", "validator" ], - "time": "2017-04-03T19:08:52+00:00" + "time": "2018-01-03T16:37:06+00:00" }, { "name": "justinrainbow/json-schema", diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index f98db5955..68279d539 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -17,6 +17,7 @@ use Composer\Package\BasePackage; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; use Composer\Repository\PlatformRepository; +use Composer\Spdx\SpdxLicenses; /** * @author Jordi Boggiano @@ -97,6 +98,48 @@ class ValidatingArrayLoader implements LoaderInterface } else { $this->validateFlatArray('license', '[A-Za-z0-9+. ()-]+'); } + + if (is_array($this->config['license']) || is_string($this->config['license'])) { + $licenses = (array) $this->config['license']; + + // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer + foreach ($licenses as $key => $license) { + if ('proprietary' === $license) { + unset($licenses[$key]); + } + } + + $licenseValidator = new SpdxLicenses(); + if (count($licenses) === 1 && !$licenseValidator->validate($licenses) && $licenseValidator->validate(trim($licenses[0]))) { + $this->warnings[] = sprintf( + 'License %s must not contain extra spaces, make sure to trim it.', + json_encode($this->config['license']) + ); + } elseif (array() !== $licenses && !$licenseValidator->validate($licenses)) { + $this->warnings[] = sprintf( + 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . + 'If the software is closed-source, you may use "proprietary" as license.', + json_encode($this->config['license']) + ); + } else { + foreach ($licenses as $license) { + $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); + if ($spdxLicense && $spdxLicense[3]) { + if (preg_match('{^[AL]?GPL-[123](\.[01])?\+?$}i', $license)) { + $this->warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', + $license + ); + } else { + $this->warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', + $license + ); + } + } + } + } + } } $this->validateString('time'); diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php index 9953e92d4..e5f64ec23 100644 --- a/src/Composer/Util/ConfigValidator.php +++ b/src/Composer/Util/ConfigValidator.php @@ -18,7 +18,6 @@ use Composer\Package\Loader\InvalidPackageException; use Composer\Json\JsonValidationException; use Composer\IO\IOInterface; use Composer\Json\JsonFile; -use Composer\Spdx\SpdxLicenses; /** * Validates a composer configuration. @@ -73,31 +72,7 @@ class ConfigValidator } // validate actual data - if (!empty($manifest['license'])) { - // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer - if (is_array($manifest['license'])) { - foreach ($manifest['license'] as $key => $license) { - if ('proprietary' === $license) { - unset($manifest['license'][$key]); - } - } - } - - $licenseValidator = new SpdxLicenses(); - if ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license']) && $licenseValidator->validate(trim($manifest['license']))) { - $warnings[] = sprintf( - 'License %s must not contain extra spaces, make sure to trim it.', - json_encode($manifest['license']) - ); - } elseif ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license'])) { - $warnings[] = sprintf( - 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' - . PHP_EOL . - 'If the software is closed-source, you may use "proprietary" as license.', - json_encode($manifest['license']) - ); - } - } else { + if (empty($manifest['license'])) { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; } From 882b82d542b45435bae1d95b33e225427950c59c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 10:49:41 +0100 Subject: [PATCH 019/580] Add COMPOSER_MEMORY_LIMIT env var to force a given memory_limit, fixes #6931 --- bin/composer | 4 ++++ doc/03-cli.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bin/composer b/bin/composer index 88566e170..0664e04ce 100755 --- a/bin/composer +++ b/bin/composer @@ -45,6 +45,10 @@ if (function_exists('ini_set')) { if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { @ini_set('memory_limit', '1536M'); } + // Set user defined memory limit + if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) { + @ini_set('memory_limit', $memoryLimit); + } unset($memoryInBytes, $memoryLimit); } diff --git a/doc/03-cli.md b/doc/03-cli.md index 4880d5406..04ae1d0ae 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -909,6 +909,10 @@ If set to 1, this env disables the warning about running commands as root/super It also disables automatic clearing of sudo sessions, so you should really only set this if you use Composer as super user at all times like in docker containers. +### COMPOSER_MEMORY_LIMIT + +If set, the value is used as php's memory_limit. + ### COMPOSER_MIRROR_PATH_REPOS If set to 1, this env changes the default path repository strategy to `mirror` instead From c962be2f4123434b4843b1d945724a633237ce00 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 11:53:41 +0100 Subject: [PATCH 020/580] Update changelog for 1.6.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b30c50ad..23766d835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [1.6.0] 2018-01-04 + + * Added support for SPDX license identifiers v3.0, deprecates GPL/LGPL/AGPL identifiers, which should now have a `-only` or `-or-later` suffix added. + * Added support for COMPOSER_MEMORY_LIMIT env var to make Composer set the PHP memory limit explicitly + * Added support for simple strings for the `bin` + * Fixed `check-platform-reqs` bug in version checking + ### [1.6.0-RC] 2017-12-19 * Improved performance of installs and updates from git clones when checking out known commits @@ -602,6 +609,7 @@ * Initial release +[1.6.0]: https://github.com/composer/composer/compare/1.6.0-RC...1.6.0 [1.6.0-RC]: https://github.com/composer/composer/compare/1.5.6...1.6.0-RC [1.5.6]: https://github.com/composer/composer/compare/1.5.5...1.5.6 [1.5.5]: https://github.com/composer/composer/compare/1.5.4...1.5.5 From 749b4c46b74f9702fc4f6abde6480fb6f0373ed6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 12:22:11 +0100 Subject: [PATCH 021/580] Bump branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 30d9f91dd..9b47cad96 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "bin": ["bin/composer"], "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.7-dev" } }, "scripts": { From ce70e0e9dd63e4f3fcdd0eb2d960873d160a3a2f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 Jan 2018 13:33:31 +0100 Subject: [PATCH 022/580] Fix BC of generated static map --- src/Composer/Autoload/AutoloadGenerator.php | 5 +---- src/Composer/Autoload/ClassLoader.php | 7 ++++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index dca6a7553..87b250617 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -785,10 +785,7 @@ HEADER; // BC handling when converting to a new ClassLoader if (isset($maps['prefixLengthsPsr4'])) { - $maps['firstCharsPsr4'] = array_map(function () { - return true; - }, $maps['prefixLengthsPsr4']); - unset($maps['prefixLengthsPsr4']); + $maps['firstCharsPsr4'] = array_map('is_array', $maps['prefixLengthsPsr4']); } foreach ($maps as $prop => $value) { diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index bebe83706..c6f6d2322 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -44,6 +44,7 @@ class ClassLoader { // PSR-4 private $firstCharsPsr4 = array(); + private $prefixLengthsPsr4 = array(); // For BC with legacy static maps private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); @@ -371,15 +372,15 @@ class ClassLoader $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; - if (isset($this->firstCharsPsr4[$first])) { + if (isset($this->firstCharsPsr4[$first]) || isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath.'\\'; if (isset($this->prefixDirsPsr4[$search])) { - $pathEnd = substr($logicalPathPsr4, $lastPos + 1); + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $pathEnd)) { + if (file_exists($file = $dir . $pathEnd)) { return $file; } } From b0fa2bd5e20b5300cdfe977678eea315e96df9a3 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 4 Jan 2018 13:49:44 +0100 Subject: [PATCH 023/580] Fix dependency on seld/jsonlint 1.4 As deprecated flag is used. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 30d9f91dd..74030a47b 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", - "composer/spdx-licenses": "^1.0", + "composer/spdx-licenses": "^1.2", "seld/jsonlint": "^1.4", "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", From 5e223dae61b7b04ad4cf8361cb60ab76d07a63f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 14:27:50 +0100 Subject: [PATCH 024/580] Update jsonlint dep --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 23915cf61..1ab5c6c33 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "d3c8dbadf8d41e2c7933e274b2fe1327", + "content-hash": "8c8fe8c8c57c958b318515f636a6839e", "packages": [ { "name": "composer/ca-bundle", @@ -348,23 +348,23 @@ }, { "name": "seld/jsonlint", - "version": "1.6.2", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19" + "reference": "9b355654ea99460397b89c132b5c1087b6bf4473" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", - "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9b355654ea99460397b89c132b5c1087b6bf4473", + "reference": "9b355654ea99460397b89c132b5c1087b6bf4473", "shasum": "" }, "require": { "php": "^5.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "bin": [ "bin/jsonlint" @@ -393,7 +393,7 @@ "parser", "validator" ], - "time": "2017-11-30T15:34:22+00:00" + "time": "2018-01-03T12:13:57+00:00" }, { "name": "seld/phar-utils", From 8c7b13e9beb7f82de9645f9d71f8c5204a0495fd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 14:38:40 +0100 Subject: [PATCH 025/580] Update content hash --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 1ab5c6c33..5d008a143 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "8c8fe8c8c57c958b318515f636a6839e", + "content-hash": "a248442611fb58177b28432be1af692c", "packages": [ { "name": "composer/ca-bundle", From 95d5e8dd947f4ec1402e96ddfcc8f6a7bb7bc0a6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jan 2018 14:44:42 +0100 Subject: [PATCH 026/580] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23766d835..f9929546f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### [1.6.1] 2018-01-04 + + * Fixed upgrade regression due to some autoloader cleanups + * Fixed some overly loose version constraints + ### [1.6.0] 2018-01-04 * Added support for SPDX license identifiers v3.0, deprecates GPL/LGPL/AGPL identifiers, which should now have a `-only` or `-or-later` suffix added. @@ -609,6 +614,7 @@ * Initial release +[1.6.1]: https://github.com/composer/composer/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/composer/composer/compare/1.6.0-RC...1.6.0 [1.6.0-RC]: https://github.com/composer/composer/compare/1.5.6...1.6.0-RC [1.5.6]: https://github.com/composer/composer/compare/1.5.5...1.5.6 From 0c912d6eee60505333479c71e5708ce263ae65ae Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 Jan 2018 15:29:54 +0100 Subject: [PATCH 027/580] Fix generated static map... --- src/Composer/Autoload/AutoloadGenerator.php | 5 ----- src/Composer/Autoload/ClassLoader.php | 15 ++++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 87b250617..9b9710d9e 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -783,11 +783,6 @@ HEADER; } } - // BC handling when converting to a new ClassLoader - if (isset($maps['prefixLengthsPsr4'])) { - $maps['firstCharsPsr4'] = array_map('is_array', $maps['prefixLengthsPsr4']); - } - foreach ($maps as $prop => $value) { if (count($value) > 32767) { // Static arrays are limited to 32767 values on PHP 5.6 diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index c6f6d2322..dc02dfb11 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -43,8 +43,7 @@ namespace Composer\Autoload; class ClassLoader { // PSR-4 - private $firstCharsPsr4 = array(); - private $prefixLengthsPsr4 = array(); // For BC with legacy static maps + private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); @@ -171,10 +170,11 @@ class ClassLoader } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. - if ('\\' !== substr($prefix, -1)) { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } - $this->firstCharsPsr4[$prefix[0]] = true; + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. @@ -221,10 +221,11 @@ class ClassLoader if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { - if ('\\' !== substr($prefix, -1)) { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } - $this->firstCharsPsr4[$prefix[0]] = true; + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } @@ -372,7 +373,7 @@ class ClassLoader $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; - if (isset($this->firstCharsPsr4[$first]) || isset($this->prefixLengthsPsr4[$first])) { + if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); From e6114b2ca7b2eb75920fd03957070045a1ac1bc1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 5 Jan 2018 15:20:30 +0100 Subject: [PATCH 028/580] Fix support for replacing dist refs in gitlab URLs and add support for gitlab/github enterprise too --- src/Composer/Downloader/ArchiveDownloader.php | 26 -------- src/Composer/Downloader/FileDownloader.php | 5 ++ src/Composer/Util/Url.php | 56 +++++++++++++++++ tests/Composer/Test/Util/UrlTest.php | 60 +++++++++++++++++++ 4 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 src/Composer/Util/Url.php create mode 100644 tests/Composer/Test/Util/UrlTest.php diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 1b93724ba..24256c81f 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -101,32 +101,6 @@ abstract class ArchiveDownloader extends FileDownloader return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); } - /** - * {@inheritdoc} - */ - protected function processUrl(PackageInterface $package, $url) - { - if ($package->getDistReference() && strpos($url, 'github.com')) { - if (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { - // update legacy github archives to API calls with the proper reference - $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); - } elseif ($package->getDistReference() && preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) { - // update current github web archives to API calls with the proper reference - $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); - } elseif ($package->getDistReference() && preg_match('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { - // update api archives to the proper reference - $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); - } - } elseif ($package->getDistReference() && strpos($url, 'bitbucket.org')) { - if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { - // update Bitbucket archives to the proper reference - $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $package->getDistReference() . '.' . $match[4]; - } - } - - return parent::processUrl($package, $url); - } - /** * Extract file to directory * diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8bdf0a519..0cc531704 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -22,6 +22,7 @@ use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\RemoteFilesystem; +use Composer\Util\Url as UrlUtil; /** * Base downloader for files @@ -260,6 +261,10 @@ class FileDownloader implements DownloaderInterface throw new \RuntimeException('You must enable the openssl extension to download files via https'); } + if ($package->getDistReference()) { + $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); + } + return $url; } diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php new file mode 100644 index 000000000..2fe68c3f1 --- /dev/null +++ b/src/Composer/Util/Url.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; + +/** + * @author Jordi Boggiano + */ +class Url +{ + public static function updateDistReference(Config $config, $url, $ref) + { + $host = parse_url($url, PHP_URL_HOST); + + if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') { + if (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) { + // update legacy github archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) { + // update current github web archives to API calls with the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } elseif (preg_match('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) { + // update api archives to the proper reference + $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref; + } + } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') { + if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { + // update Bitbucket archives to the proper reference + $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4]; + } + } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') { + if (preg_match('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) { + // update Gitlab archives to the proper reference + $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref; + } + } elseif (in_array($host, $config->get('github-domains'), true)) { + $url = preg_replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url); + } elseif (in_array($host, $config->get('gitlab-domains'), true)) { + $url = preg_replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '$1'.$ref, $url); + } + + return $url; + } +} diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php new file mode 100644 index 000000000..1a4ccda3b --- /dev/null +++ b/tests/Composer/Test/Util/UrlTest.php @@ -0,0 +1,60 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Url; +use PHPUnit\Framework\TestCase; +use Composer\Config; + +class UrlTest extends TestCase +{ + /** + * @dataProvider distRefsProvider + */ + public function testUpdateDistReference($url, $expectedUrl, $conf = array()) + { + $config = new Config(); + $config->merge(array('config' => $conf)); + + $this->assertSame($expectedUrl, Url::updateDistReference($config, $url, 'newref')); + } + + public static function distRefsProvider() + { + return array( + // github + array('https://github.com/foo/bar/zipball/abcd', 'https://api.github.com/repos/foo/bar/zipball/newref'), + array('https://www.github.com/foo/bar/zipball/abcd', 'https://api.github.com/repos/foo/bar/zipball/newref'), + array('https://github.com/foo/bar/archive/abcd.zip', 'https://api.github.com/repos/foo/bar/zipball/newref'), + array('https://github.com/foo/bar/archive/abcd.tar.gz', 'https://api.github.com/repos/foo/bar/tarball/newref'), + array('https://api.github.com/repos/foo/bar/tarball', 'https://api.github.com/repos/foo/bar/tarball/newref'), + array('https://api.github.com/repos/foo/bar/tarball/abcd', 'https://api.github.com/repos/foo/bar/tarball/newref'), + + // github enterprise + array('https://mygithub.com/api/v3/repos/foo/bar/tarball/abcd', 'https://mygithub.com/api/v3/repos/foo/bar/tarball/newref', array('github-domains' => array('mygithub.com'))), + + // bitbucket + array('https://bitbucket.org/foo/bar/get/abcd.zip', 'https://bitbucket.org/foo/bar/get/newref.zip'), + array('https://www.bitbucket.org/foo/bar/get/abcd.tar.bz2', 'https://bitbucket.org/foo/bar/get/newref.tar.bz2'), + + // gitlab + array('https://gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.zip?sha=abcd', 'https://gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.zip?sha=newref'), + array('https://www.gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.zip?sha=abcd', 'https://gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.zip?sha=newref'), + array('https://gitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.gz?sha=abcd', 'https://gitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=newref'), + + // gitlab enterprise + array('https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=abcd', 'https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), + array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), + ); + } +} From 20699905abf2613d4d91c03e80ca2a7f9ab8f0f1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 5 Jan 2018 15:28:38 +0100 Subject: [PATCH 029/580] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9929546f..e1abff21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### [1.6.2] 2018-01-05 + + * Fixed more autoloader regressions + * Fixed support for updating dist refs in gitlab URLs + ### [1.6.1] 2018-01-04 * Fixed upgrade regression due to some autoloader cleanups @@ -614,6 +619,7 @@ * Initial release +[1.6.2]: https://github.com/composer/composer/compare/1.6.1...1.6.2 [1.6.1]: https://github.com/composer/composer/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/composer/composer/compare/1.6.0-RC...1.6.0 [1.6.0-RC]: https://github.com/composer/composer/compare/1.5.6...1.6.0-RC From 251a347efbae4dbedb5c45f1571265e0a8705230 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 Jan 2018 17:18:42 +0100 Subject: [PATCH 030/580] Fix CTRL+C handling during create-project --- src/Composer/Command/CreateProjectCommand.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 4f58a4a77..16960ac64 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -319,13 +319,16 @@ EOT } // handler Ctrl+C for unix-like systems - if (function_exists('pcntl_signal')) { - declare(ticks=100); - pcntl_signal(SIGINT, function () use ($directory) { - $fs = new Filesystem(); - $fs->removeDirectory($directory); - exit(130); - }); + if (function_exists('pcntl_async_signals')) { + @mkdir($directory, 0777, true); + if ($realDir = realpath($directory)) { + pcntl_async_signals(true); + pcntl_signal(SIGINT, function () use ($realDir) { + $fs = new Filesystem(); + $fs->removeDirectory($realDir); + exit(130); + }); + } } $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); From 4296fe657f3e0568f3e820473831536f254813e0 Mon Sep 17 00:00:00 2001 From: William Sandin Date: Sat, 6 Jan 2018 19:02:14 +0700 Subject: [PATCH 031/580] Adding check to confirm POSIX is defined and callable --- src/Composer/Command/SelfUpdateCommand.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 1f86e7ed9..147d65c02 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -93,9 +93,7 @@ EOT $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $home = $config->get('home'); - $homeOwner = posix_getpwuid(fileowner($home)); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; - $composeUser = posix_getpwuid(posix_geteuid()); if ($input->getOption('update-keys')) { return $this->fetchKeys($io, $config); @@ -109,9 +107,13 @@ EOT throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); } - // check if composer is running as the same user that owns the directory root - if ($composeUser !== $homeOwner) { - $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); + // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $composeUser = posix_getpwuid(posix_geteuid()); + $homeOwner = posix_getpwuid(fileowner($home)); + if ($composeUser !== $homeOwner) { + $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); + } } if ($input->getOption('rollback')) { From 5601f07bfadc2359c53f9fa92f96d5249120188d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 7 Jan 2018 19:00:12 +0100 Subject: [PATCH 032/580] Add RemoteFilesystem::getRemoteContents() extension point --- src/Composer/Util/RemoteFilesystem.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 6bc1505dd..9f662e795 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -297,7 +297,7 @@ class RemoteFilesystem $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); }); try { - $result = file_get_contents($fileUrl, false, $ctx); + list($http_response_header, $result) = $this->getRemoteContents($originUrl, $fileUrl, $ctx); $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { @@ -538,6 +538,22 @@ class RemoteFilesystem return $result; } + /** + * Get contents of remote URL. + * + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param resource $context The stream context + * + * @return array The response headers and the contents + */ + protected function getRemoteContents($originUrl, $fileUrl, $context) + { + $contents = file_get_contents($fileUrl, false, $context); + + return array($http_response_header, $contents); + } + /** * Get notification action. * From 188b3a35c8d2a2a32a23f18b6b628696cb0eeff6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Jan 2018 17:29:30 +0100 Subject: [PATCH 033/580] Tweak license deprecation text to handle + more gracefully, fixes #6981 --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 68279d539..0713a2fe5 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -125,7 +125,12 @@ class ValidatingArrayLoader implements LoaderInterface foreach ($licenses as $license) { $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); if ($spdxLicense && $spdxLicense[3]) { - if (preg_match('{^[AL]?GPL-[123](\.[01])?\+?$}i', $license)) { + if (preg_match('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { + $this->warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', + $license + ); + } elseif (preg_match('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { $this->warnings[] = sprintf( 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', $license From 60106edd324f2297f09baa7d11c0f32ebb700764 Mon Sep 17 00:00:00 2001 From: Tomas Klinkenberg Date: Tue, 9 Jan 2018 16:48:15 +0100 Subject: [PATCH 034/580] Added a test to confirm issue #6994. Added a encapsulated group to the replacement parameter of the `preg_replace` for GitLab in `\Composer\Util\Url::updateDistReference()`. This fixes #6994. --- src/Composer/Util/Url.php | 2 +- tests/Composer/Test/Util/UrlTest.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 2fe68c3f1..f8d4b446d 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -48,7 +48,7 @@ class Url } elseif (in_array($host, $config->get('github-domains'), true)) { $url = preg_replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url); } elseif (in_array($host, $config->get('gitlab-domains'), true)) { - $url = preg_replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '$1'.$ref, $url); + $url = preg_replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url); } return $url; diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php index 1a4ccda3b..96e71d10e 100644 --- a/tests/Composer/Test/Util/UrlTest.php +++ b/tests/Composer/Test/Util/UrlTest.php @@ -21,12 +21,12 @@ class UrlTest extends TestCase /** * @dataProvider distRefsProvider */ - public function testUpdateDistReference($url, $expectedUrl, $conf = array()) + public function testUpdateDistReference($url, $expectedUrl, $conf = array(), $ref = 'newref') { $config = new Config(); $config->merge(array('config' => $conf)); - $this->assertSame($expectedUrl, Url::updateDistReference($config, $url, 'newref')); + $this->assertSame($expectedUrl, Url::updateDistReference($config, $url, $ref)); } public static function distRefsProvider() @@ -55,6 +55,7 @@ class UrlTest extends TestCase // gitlab enterprise array('https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=abcd', 'https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), + array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'), ); } } From 6ae8e340c1b0e49b007ad7255dfb9f8babe1b242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Pal=C3=B3cz?= Date: Fri, 12 Jan 2018 13:37:42 +0100 Subject: [PATCH 035/580] Update how-do-i-install-a-package-to-a-custom-path-for-my-framework.md Title changed --- ...-do-i-install-a-package-to-a-custom-path-for-my-framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faqs/how-do-i-install-a-package-to-a-custom-path-for-my-framework.md b/doc/faqs/how-do-i-install-a-package-to-a-custom-path-for-my-framework.md index bd38d1e40..20a2e83ae 100644 --- a/doc/faqs/how-do-i-install-a-package-to-a-custom-path-for-my-framework.md +++ b/doc/faqs/how-do-i-install-a-package-to-a-custom-path-for-my-framework.md @@ -23,7 +23,7 @@ WordPress theme: Now when your theme is installed with Composer it will be placed into `wp-content/themes/themename/` folder. Check the -[current supported types](https://github.com/composer/installers#current-supported-types) +[current supported types](https://github.com/composer/installers#current-supported-package-types) for your package. As a **package consumer** you can set or override the install path for a package From 3d262bd637dbf96285766835edf1ce0ba5ed9cec Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sat, 6 Jan 2018 12:05:39 -0200 Subject: [PATCH 036/580] Fixes from PHPStan level 0 More fixes from PHPStan level 0 --- src/Composer/Command/DiagnoseCommand.php | 2 +- src/Composer/Command/InitCommand.php | 2 +- src/Composer/DependencyResolver/Rule.php | 1 + src/Composer/DependencyResolver/Solver.php | 2 +- src/Composer/Factory.php | 2 +- src/Composer/Installer/PluginInstaller.php | 3 +-- src/Composer/Json/JsonManipulator.php | 2 +- src/Composer/Package/Archiver/BaseExcludeFilter.php | 2 +- src/Composer/Repository/RepositoryInterface.php | 2 +- .../Test/DependencyResolver/RuleSetIteratorTest.php | 1 + tests/Composer/Test/Installer/InstallationManagerTest.php | 2 ++ tests/Composer/Test/Mock/RemoteFilesystemMock.php | 2 ++ tests/Composer/Test/Util/PerforceTest.php | 7 ++++--- 13 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index d8977493d..3fecf52c4 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -34,7 +34,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DiagnoseCommand extends BaseCommand { - /** @var RemoteFileSystem */ + /** @var RemoteFilesystem */ protected $rfs; /** @var ProcessExecutor */ diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 33df93a8b..197c951c7 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -279,7 +279,7 @@ EOT $minimumStability = $input->getOption('stability') ?: null; $minimumStability = $io->askAndValidate( 'Minimum Stability ['.$minimumStability.']: ', - function ($value) use ($self, $minimumStability) { + function ($value) use ($minimumStability) { if (null === $value) { return $minimumStability; } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index b2dd7708d..0829c2c7e 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -41,6 +41,7 @@ abstract class Rule const BITFIELD_DISABLED = 16; protected $bitfield; + protected $job; protected $reasonData; /** diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 520109d66..1775cb5ce 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -30,7 +30,7 @@ class Solver protected $pool; /** @var RepositoryInterface */ protected $installed; - /** @var Ruleset */ + /** @var RuleSet */ protected $rules; /** @var RuleSetGenerator */ protected $ruleSetGenerator; diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index a68586fbb..d0cf6ee8a 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -546,7 +546,7 @@ class Factory $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library')); $im->addInstaller(new Installer\PluginInstaller($io, $composer)); - $im->addInstaller(new Installer\MetapackageInstaller($io)); + $im->addInstaller(new Installer\MetapackageInstaller()); } /** diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index e2ae07956..c400ca4a6 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -32,9 +32,8 @@ class PluginInstaller extends LibraryInstaller * * @param IOInterface $io * @param Composer $composer - * @param string $type */ - public function __construct(IOInterface $io, Composer $composer, $type = 'library') + public function __construct(IOInterface $io, Composer $composer) { parent::__construct($io, $composer, 'composer-plugin'); $this->installationManager = $composer->getInstallationManager(); diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index cdd22c817..52834527b 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -229,7 +229,7 @@ class JsonManipulator // child exists $childRegex = '{'.self::$DEFINES.'(?P"'.preg_quote($name).'"\s*:\s*)(?P(?&json))(?P,?)}x'; if ($this->pregMatch($childRegex, $children, $matches)) { - $children = preg_replace_callback($childRegex, function ($matches) use ($name, $subName, $value, $that) { + $children = preg_replace_callback($childRegex, function ($matches) use ($subName, $value, $that) { if ($subName !== null) { $curVal = json_decode($matches['content'], true); if (!is_array($curVal)) { diff --git a/src/Composer/Package/Archiver/BaseExcludeFilter.php b/src/Composer/Package/Archiver/BaseExcludeFilter.php index 99ba24431..18fa05404 100644 --- a/src/Composer/Package/Archiver/BaseExcludeFilter.php +++ b/src/Composer/Package/Archiver/BaseExcludeFilter.php @@ -71,7 +71,7 @@ abstract class BaseExcludeFilter * Processes a file containing exclude rules of different formats per line * * @param array $lines A set of lines to be parsed - * @param callback $lineParser The parser to be used on each line + * @param callable $lineParser The parser to be used on each line * * @return array Exclude patterns to be used in filter() */ diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 4e8e478f4..9a2aaf3b5 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -68,7 +68,7 @@ interface RepositoryInterface extends \Countable * @param string $query search query * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only * - * @return \array[] an array of array('name' => '...', 'description' => '...') + * @return array[] an array of array('name' => '...', 'description' => '...') */ public function search($query, $mode = 0); } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index 37fa26512..7789881df 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -22,6 +22,7 @@ use PHPUnit\Framework\TestCase; class RuleSetIteratorTest extends TestCase { protected $rules; + protected $pool; protected function setUp() { diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index f8de7ba0f..bded74c1c 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -20,6 +20,8 @@ use PHPUnit\Framework\TestCase; class InstallationManagerTest extends TestCase { + protected $repository; + public function setUp() { $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); diff --git a/tests/Composer/Test/Mock/RemoteFilesystemMock.php b/tests/Composer/Test/Mock/RemoteFilesystemMock.php index 79a25e550..5d4f52e54 100644 --- a/tests/Composer/Test/Mock/RemoteFilesystemMock.php +++ b/tests/Composer/Test/Mock/RemoteFilesystemMock.php @@ -20,6 +20,8 @@ use Composer\Downloader\TransportException; */ class RemoteFilesystemMock extends RemoteFilesystem { + protected $contentMap; + /** * @param array $contentMap associative array of locations and content */ diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 54b058805..9c25adaa1 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -22,6 +22,7 @@ class PerforceTest extends TestCase { protected $perforce; protected $processExecutor; + protected $repoConfig; protected $io; const TEST_DEPOT = 'depot'; @@ -224,7 +225,7 @@ class PerforceTest extends TestCase 'p4user' => 'user', 'p4password' => 'TEST_PASSWORD', ); - $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, $this->getMockIOInterface(), 'TEST'); + $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, $this->getMockIOInterface()); $password = $this->perforce->queryP4Password(); $this->assertEquals('TEST_PASSWORD', $password); } @@ -289,7 +290,7 @@ class PerforceTest extends TestCase $this->assertStringStartsWith($expected, fgets($stream)); } $this->assertFalse(fgets($stream)); - } catch (Exception $e) { + } catch (\Exception $e) { fclose($stream); throw $e; } @@ -310,7 +311,7 @@ class PerforceTest extends TestCase $this->assertStringStartsWith($expected, fgets($stream)); } $this->assertFalse(fgets($stream)); - } catch (Exception $e) { + } catch (\Exception $e) { fclose($stream); throw $e; } From 1a6e3ee8c75579d179dd440b1a61d3c900372457 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 16 Jan 2018 09:19:44 +0100 Subject: [PATCH 037/580] Show script description for custom commands in run-script --list, fixes #7009 --- src/Composer/Command/RunScriptCommand.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index 929056946..d78662e23 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Helper\Table; /** * @author Fabien Potencier @@ -68,7 +69,7 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('list')) { - return $this->listScripts(); + return $this->listScripts($output); } elseif (!$input->getArgument('script')) { throw new \RuntimeException('Missing required argument "script"'); } @@ -101,7 +102,7 @@ EOT return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); } - protected function listScripts() + protected function listScripts(OutputInterface $output) { $scripts = $this->getComposer()->getPackage()->getScripts(); @@ -111,10 +112,22 @@ EOT $io = $this->getIO(); $io->writeError('scripts:'); + $table = array(); foreach ($scripts as $name => $script) { - $io->write(' ' . $name); + $cmd = $this->getApplication()->find($name); + $description = ''; + if ($cmd instanceof ScriptAliasCommand) { + $description = $cmd->getDescription(); + } + $table[] = array(' '.$name, $description); } + $renderer = new Table($output); + $renderer->setStyle('compact'); + $renderer->getStyle()->setVerticalBorderChar(''); + $renderer->getStyle()->setCellRowContentFormat('%s '); + $renderer->setRows($table)->render(); + return 0; } } From bbee0d7c6c4441b49085e1853ed9e7c927163b14 Mon Sep 17 00:00:00 2001 From: Martin Hujer Date: Wed, 17 Jan 2018 19:15:06 +0100 Subject: [PATCH 038/580] Validation warns if script description for nonexistent script is present Fixes #7010 --- src/Composer/Util/ConfigValidator.php | 12 ++++++++++++ tests/Composer/Test/Util/ConfigValidatorTest.php | 11 +++++++++++ .../Util/Fixtures/composer_scripts-descriptions.json | 10 ++++++++++ 3 files changed, 33 insertions(+) create mode 100644 tests/Composer/Test/Util/Fixtures/composer_scripts-descriptions.json diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php index e5f64ec23..407819b58 100644 --- a/src/Composer/Util/ConfigValidator.php +++ b/src/Composer/Util/ConfigValidator.php @@ -118,6 +118,18 @@ class ConfigValidator } } + // report scripts-descriptions for non-existent scripts + $scriptsDescriptions = isset($manifest['scripts-descriptions']) ? $manifest['scripts-descriptions'] : array(); + $scripts = isset($manifest['scripts']) ? $manifest['scripts'] : array(); + foreach ($scriptsDescriptions as $scriptName => $scriptDescription) { + if (!array_key_exists($scriptName, $scripts)) { + $warnings[] = sprintf( + 'Description for non-existent script "%s" found in "scripts-descriptions"', + $scriptName + ); + } + } + // check for empty psr-0/psr-4 namespace prefixes if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; diff --git a/tests/Composer/Test/Util/ConfigValidatorTest.php b/tests/Composer/Test/Util/ConfigValidatorTest.php index 3d0260832..157eba92e 100644 --- a/tests/Composer/Test/Util/ConfigValidatorTest.php +++ b/tests/Composer/Test/Util/ConfigValidatorTest.php @@ -34,4 +34,15 @@ class ConfigValidatorTest extends TestCase $warnings ); } + + public function testConfigValidatorWarnsOnScriptDescriptionForNonexistentScript() + { + $configValidator = new ConfigValidator(new NullIO()); + list(, , $warnings) = $configValidator->validate(__DIR__ . '/Fixtures/composer_scripts-descriptions.json'); + + $this->assertContains( + 'Description for non-existent script "phpcsxxx" found in "scripts-descriptions"', + $warnings + ); + } } diff --git a/tests/Composer/Test/Util/Fixtures/composer_scripts-descriptions.json b/tests/Composer/Test/Util/Fixtures/composer_scripts-descriptions.json new file mode 100644 index 000000000..8cf3bd501 --- /dev/null +++ b/tests/Composer/Test/Util/Fixtures/composer_scripts-descriptions.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "test": "phpunit", + "phpcs": "phpcs --standard=PSR2 src" + }, + "scripts-descriptions": { + "test": "Launches the preconfigured PHPUnit", + "phpcsxxx": "Checks that the application code conforms to coding standard" + } +} From 5a1765c8385d0dd421de67a78ca786c4893bcb23 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 21 Jan 2018 17:40:02 +0100 Subject: [PATCH 039/580] Only warn for license deprecations for new releases/branches --- .../Package/Loader/ValidatingArrayLoader.php | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 0713a2fe5..83b27aa55 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -92,6 +92,17 @@ class ValidatingArrayLoader implements LoaderInterface $this->validateUrl('homepage'); $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+'); + $releaseDate = null; + $this->validateString('time'); + if (!empty($this->config['time'])) { + try { + $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); + } catch (\Exception $e) { + $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); + unset($this->config['time']); + } + } + if (isset($this->config['license'])) { if (is_string($this->config['license'])) { $this->validateRegex('license', '[A-Za-z0-9+. ()-]+'); @@ -121,7 +132,7 @@ class ValidatingArrayLoader implements LoaderInterface 'If the software is closed-source, you may use "proprietary" as license.', json_encode($this->config['license']) ); - } else { + } else if (!$releaseDate || $releaseDate->format('Y-m-d H:i:s') >= '2018-01-20 00:00:00') { // only warn for deprecations for releases/branches that follow the introduction of deprecated licenses foreach ($licenses as $license) { $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); if ($spdxLicense && $spdxLicense[3]) { @@ -147,16 +158,6 @@ class ValidatingArrayLoader implements LoaderInterface } } - $this->validateString('time'); - if (!empty($this->config['time'])) { - try { - $date = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); - } catch (\Exception $e) { - $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); - unset($this->config['time']); - } - } - if ($this->validateArray('authors') && !empty($this->config['authors'])) { foreach ($this->config['authors'] as $key => $author) { if (!is_array($author)) { From 2e0a25e73f4ba0c9e1784e87a675a9805c4fab15 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 09:37:05 +0100 Subject: [PATCH 040/580] Update license docs --- doc/04-schema.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index c338b4673..f3d85e742 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -145,10 +145,10 @@ The recommended notation for the most common licenses is (alphabetical): - BSD-2-Clause - BSD-3-Clause - BSD-4-Clause -- GPL-2.0 -- GPL-3.0 -- LGPL-2.1 -- LGPL-3.0 +- GPL-2.0-only / GPL-2.0-or-later +- GPL-3.0-only / GPL-3.0-or-later +- LGPL-2.1-only / LGPL-2.1-or-later +- LGPL-3.0-only / LGPL-3.0-or-later - MIT Optional, but it is highly recommended to supply this. More identifiers are From 9b30d83c23c6246ff44b682a54a56bdc3ec46b9e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 09:42:50 +0100 Subject: [PATCH 041/580] More license docs fixes --- doc/04-schema.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index f3d85e742..9e846168a 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -172,8 +172,8 @@ An Example for disjunctive licenses: ```json { "license": [ - "LGPL-2.1", - "GPL-3.0+" + "LGPL-2.1-only", + "GPL-3.0-or-later" ] } ``` @@ -182,7 +182,7 @@ Alternatively they can be separated with "or" and enclosed in parenthesis; ```json { - "license": "(LGPL-2.1 or GPL-3.0+)" + "license": "(LGPL-2.1-only or GPL-3.0-or-later)" } ``` From 471b012e3ae6ec93c5ad439c01538ffa984c5b9b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 13:41:32 +0100 Subject: [PATCH 042/580] Fix problem report when requiring "ext-zend opcache", refs #2509 --- src/Composer/DependencyResolver/Problem.php | 4 ++++ src/Composer/Repository/PlatformRepository.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 15815be59..ba9eb9774 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -108,6 +108,10 @@ class Problem // handle php extensions if (0 === stripos($job['packageName'], 'ext-')) { + if (false !== strpos($job['packageName'], ' ')) { + return "\n - The requested PHP extension ".$job['packageName'].' should be required as '.str_replace(' ', '-', $job['packageName']).'.'; + } + $ext = substr($job['packageName'], 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 02d40e9a4..da5c9ab73 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -24,7 +24,7 @@ use Composer\Util\Silencer; */ class PlatformRepository extends ArrayRepository { - const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/]+)$}i'; + const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/ ]+)$}i'; private $versionParser; From 621a9d845cd5b571146298214ad27d5fff6cc670 Mon Sep 17 00:00:00 2001 From: Jean Baptiste Noblot Date: Fri, 19 Jan 2018 10:28:05 +0100 Subject: [PATCH 043/580] Add 'git-bitbucket' in RepositoryFactory Add 'git-bitbucket' and 'hg-bitbucket' in RepositoryFactory help to call good driver in vcs Cause if you config your repository type with 'git'. the GitDriver is instantiate and not GitBitbucketDriver Fix #5389 --- src/Composer/Repository/RepositoryFactory.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 5e38f961a..ca479a7fd 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -119,12 +119,14 @@ class RepositoryFactory $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('hg-bitbucket', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); From 595cf4432c42a0c98042866ff21107ea90b2c523 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Fri, 19 Jan 2018 10:51:32 +0100 Subject: [PATCH 044/580] Fix Test RepositoryFactory --- tests/Composer/Test/Repository/RepositoryFactoryTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Composer/Test/Repository/RepositoryFactoryTest.php b/tests/Composer/Test/Repository/RepositoryFactoryTest.php index 6611abbb6..8ce66cfd5 100644 --- a/tests/Composer/Test/Repository/RepositoryFactoryTest.php +++ b/tests/Composer/Test/Repository/RepositoryFactoryTest.php @@ -34,12 +34,14 @@ class RepositoryFactoryTest extends TestCase 'package', 'pear', 'git', + 'git-bitbucket', 'github', 'gitlab', 'svn', 'fossil', 'perforce', 'hg', + 'hg-bitbucket', 'artifact', 'path', ), array_keys($repositoryClasses)); From f28feedf58ea5defbacfbcc9e03b06b97adfd068 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 14:09:36 +0100 Subject: [PATCH 045/580] Update list of explicit VCS repo types, refs #7023 --- doc/05-repositories.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 99ba5f36a..c28f34141 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -284,8 +284,9 @@ VCS repository provides `dist`s for them that fetch the packages as zips. * **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial) The VCS driver to be used is detected automatically based on the URL. However, -should you need to specify one for whatever reason, you can use `fossil`, `git`, -`svn` or `hg` as the repository type instead of `vcs`. +should you need to specify one for whatever reason, you can use `git-bitbucket`, +`hg-bitbucket`, `github`, `gitlab`, `perforce`, `fossil`, `git`, `svn` or `hg` +as the repository type instead of `vcs`. If you set the `no-api` key to `true` on a github repository it will clone the repository as it would with any other git repository instead of using the From 6a7e9322333c72a629bef9a86b9b889bcf72e3e5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 16 Jan 2018 15:13:36 +0100 Subject: [PATCH 046/580] Mention that also a antivirus software might corrupt file contents --- src/Composer/Repository/ComposerRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 2f0d19db9..60ab0dfbd 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -677,7 +677,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } // TODO use scarier wording once we know for sure it doesn't do false positives anymore - throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack. Try running composer again and report this if you think it is a mistake.'); + throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); } $data = JsonFile::parseJson($json, $filename); From a5e35b9e8939e116b0bcbd677179d26bc490e6a1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 15:17:30 +0100 Subject: [PATCH 047/580] Add --remove-vcs flag to create-project command to allow removing user prompts, fixes #7002 --- src/Composer/Command/CreateProjectCommand.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 16960ac64..b1e711f3a 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -75,7 +75,8 @@ class CreateProjectCommand extends BaseCommand new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), - new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), + new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'), + new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), )) @@ -140,11 +141,12 @@ EOT $input->getOption('no-progress'), $input->getOption('no-install'), $input->getOption('ignore-platform-reqs'), - !$input->getOption('no-secure-http') + !$input->getOption('no-secure-http'), + $input->getOption('remove-vcs') ); } - public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true) + public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true, $removeVcs = false) { $oldCwd = getcwd(); @@ -195,9 +197,12 @@ EOT } $hasVcs = $installedFromVcs; - if (!$keepVcs && $installedFromVcs + if ( + !$keepVcs + && $installedFromVcs && ( - !$io->isInteractive() + $removeVcs + || !$io->isInteractive() || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? ', true) ) ) { From 842a7ea922f680203285df91898edefe84b897d0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Jan 2018 16:44:17 +0100 Subject: [PATCH 048/580] Fix dev-master not being normalized correctly for the root package, fixes #7007 --- src/Composer/Package/Version/VersionGuesser.php | 5 +---- tests/Composer/Test/Package/Version/VersionGuesserTest.php | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 060bf26dd..02297a1e6 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -88,7 +88,7 @@ class VersionGuesser private function postprocess(array $versionData) { - if ('-dev' === substr($versionData['version'], -4)) { + if ('-dev' === substr($versionData['version'], -4) && preg_match('{\.9{7}}', $versionData['version'])) { $versionData['pretty_version'] = preg_replace('{(\.9{7})+}', '.x', $versionData['version']); } @@ -120,9 +120,6 @@ class VersionGuesser $version = $this->versionParser->normalizeBranch($match[1]); $prettyVersion = 'dev-' . $match[1]; $isFeatureBranch = 0 === strpos($version, 'dev-'); - if ('9999999-dev' === $version) { - $version = $prettyVersion; - } } if ($match[2]) { diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index d8db7e599..c0b6346c8 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -123,7 +123,8 @@ class VersionGuesserTest extends TestCase $guesser = new VersionGuesser($config, $executor, new VersionParser()); $versionArray = $guesser->guessVersion(array(), 'dummy/path'); - $this->assertEquals("dev-master", $versionArray['version']); + $this->assertEquals("9999999-dev", $versionArray['version']); + $this->assertEquals("dev-master", $versionArray['pretty_version']); $this->assertEquals($commitHash, $versionArray['commit']); } From fa5a94143a5158383bae92068f78caa23ee30105 Mon Sep 17 00:00:00 2001 From: Vic Metcalfe Date: Mon, 15 Jan 2018 09:50:22 -0500 Subject: [PATCH 049/580] Show reason for php version package mismatch due to config.platform --- src/Composer/DependencyResolver/Problem.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index ba9eb9774..7fa40641b 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -11,6 +11,7 @@ */ namespace Composer\DependencyResolver; +use Composer\Package\CompletePackageInterface; /** * Represents a problem detected while solving dependencies @@ -91,7 +92,13 @@ class Problem // handle php/hhvm if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { $available = $this->pool->whatProvides($job['packageName']); - $version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); + $firstAvailable = reset($available); + + $version = count($available) ? $firstAvailable->getPrettyVersion() : phpversion(); + if (count($available) && $firstAvailable instanceof CompletePackageInterface) { + $version .= '; ' . $firstAvailable->getDescription(); + } + $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; From 766ed9555e21ca6cd3019a69b14311279889e304 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 09:02:51 +0100 Subject: [PATCH 050/580] Only show override description if there is one, refs #7011 --- src/Composer/DependencyResolver/Problem.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 7fa40641b..debc8678c 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -91,15 +91,18 @@ class Problem // handle php/hhvm if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { + $version = phpversion(); $available = $this->pool->whatProvides($job['packageName']); - $firstAvailable = reset($available); - $version = count($available) ? $firstAvailable->getPrettyVersion() : phpversion(); - if (count($available) && $firstAvailable instanceof CompletePackageInterface) { - $version .= '; ' . $firstAvailable->getDescription(); + if (count($available)) { + $firstAvailable = reset($available); + $version = $firstAvailable->getPrettyVersion(); + $extra = $firstAvailable->getExtra(); + if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) { + $version .= '; ' . $firstAvailable->getDescription(); + } } - $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; if (defined('HHVM_VERSION')) { From fd61a21bffb191a00980c64266ced43a820376e7 Mon Sep 17 00:00:00 2001 From: Narration SD Date: Wed, 24 Jan 2018 10:04:09 +0100 Subject: [PATCH 051/580] Fix detection of junction points, fixes #7025 --- src/Composer/Util/Filesystem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 39c953a50..9e0e37279 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -665,6 +665,7 @@ class Filesystem * Stat cache should be cleared before to avoid accidentally reading wrong information from previous installs. */ clearstatcache(true, $junction); + clearstatcache(false); $stat = lstat($junction); return !($stat['mode'] & 0xC000); From a567501e58e0fc138daa50cbc29a07a0e4ea3e3d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 10:19:21 +0100 Subject: [PATCH 052/580] Fix ConsoleIO::select regression in which 1.5.3-1.6.2 returned the selected values instead of index keys, fixes #7000 --- src/Composer/IO/ConsoleIO.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index f3a99edf2..bef7cea9f 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -289,7 +289,16 @@ class ConsoleIO extends BaseIO $question->setErrorMessage($errorMessage); $question->setMultiselect($multiselect); - return $helper->ask($this->input, $this->getErrorOutput(), $question); + $result = $helper->ask($this->input, $this->getErrorOutput(), $question); + + $results = array(); + foreach ($choices as $index => $choice) { + if (in_array($choice, $result, true)) { + $results[] = (string) $index; + } + } + + return $results; } /** From da9e00066c480dc0deccac356af6b02051f6f52f Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Wed, 24 Jan 2018 10:19:46 +0100 Subject: [PATCH 053/580] SCA: reduced repetitive methods references, used specialized PhpUnit assertions --- src/Composer/Command/ArchiveCommand.php | 5 +++-- src/Composer/Command/BaseDependencyCommand.php | 5 +++-- .../Command/CheckPlatformReqsCommand.php | 5 +++-- src/Composer/Command/LicensesCommand.php | 5 +++-- src/Composer/Command/RunScriptCommand.php | 5 +++-- src/Composer/Util/StreamContextFactory.php | 2 +- tests/Composer/Test/Util/FilesystemTest.php | 16 ++++++++-------- 7 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index bf64ca47e..4ab1e4905 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -66,8 +66,9 @@ EOT $composer = $this->getComposer(false); if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); - $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); - $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); + $eventDispatcher = $composer->getEventDispatcher(); + $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); + $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); } if (null === $input->getOption('format')) { diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index e36663558..980ca9282 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -180,8 +180,9 @@ class BaseDependencyCommand extends BaseCommand // Render table $renderer = new Table($output); $renderer->setStyle('compact'); - $renderer->getStyle()->setVerticalBorderChar(''); - $renderer->getStyle()->setCellRowContentFormat('%s '); + $rendererStyle = $renderer->getStyle(); + $rendererStyle->setVerticalBorderChar(''); + $rendererStyle->setCellRowContentFormat('%s '); $renderer->setRows($table)->render(); } diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 8b59c28ea..6a75ab4dc 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -142,8 +142,9 @@ EOT // Render table $renderer = new Table($output); $renderer->setStyle('compact'); - $renderer->getStyle()->setVerticalBorderChar(''); - $renderer->getStyle()->setCellRowContentFormat('%s '); + $rendererStyle = $renderer->getStyle(); + $rendererStyle->setVerticalBorderChar(''); + $rendererStyle->setCellRowContentFormat('%s '); $renderer->setRows($table)->render(); } } diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index 0c4537be1..f3a71eb39 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -74,8 +74,9 @@ EOT $table = new Table($output); $table->setStyle('compact'); - $table->getStyle()->setVerticalBorderChar(''); - $table->getStyle()->setCellRowContentFormat('%s '); + $tableStyle = $table->getStyle(); + $tableStyle->setVerticalBorderChar(''); + $tableStyle->setCellRowContentFormat('%s '); $table->setHeaders(array('Name', 'Version', 'License')); foreach ($packages as $package) { $table->addRow(array( diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index d78662e23..ff0c79361 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -124,8 +124,9 @@ EOT $renderer = new Table($output); $renderer->setStyle('compact'); - $renderer->getStyle()->setVerticalBorderChar(''); - $renderer->getStyle()->setCellRowContentFormat('%s '); + $rendererStyle = $renderer->getStyle(); + $rendererStyle->setVerticalBorderChar(''); + $rendererStyle->setCellRowContentFormat('%s '); $renderer->setRows($table)->render(); return 0; diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index b8290086c..6b16c8a18 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -169,7 +169,7 @@ final class StreamContextFactory $header = explode("\r\n", $header); } uasort($header, function ($el) { - return preg_match('{^content-type}i', $el) ? 1 : -1; + return stripos($el, 'content-type') === 0 ? 1 : -1; }); return $header; diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index ef3ab5bbe..5a9adbae7 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -307,9 +307,9 @@ class FilesystemTest extends TestCase $this->assertTrue($fs->isJunction($junction . '/../junction')); // Remove junction - $this->assertTrue(is_dir($junction)); + $this->assertDirectoryExists($junction); $this->assertTrue($fs->removeJunction($junction)); - $this->assertFalse(is_dir($junction)); + $this->assertDirectoryNotExists($junction); } public function testCopy() @@ -325,9 +325,9 @@ class FilesystemTest extends TestCase $result1 = $fs->copy($this->workingDir . '/foo', $this->workingDir . '/foop'); $this->assertTrue($result1, 'Copying directory failed.'); - $this->assertTrue(is_dir($this->workingDir . '/foop'), 'Not a directory: ' . $this->workingDir . '/foop'); - $this->assertTrue(is_dir($this->workingDir . '/foop/bar'), 'Not a directory: ' . $this->workingDir . '/foop/bar'); - $this->assertTrue(is_dir($this->workingDir . '/foop/baz'), 'Not a directory: ' . $this->workingDir . '/foop/baz'); + $this->assertDirectoryExists($this->workingDir . '/foop', 'Not a directory: ' . $this->workingDir . '/foop'); + $this->assertDirectoryExists($this->workingDir . '/foop/bar', 'Not a directory: ' . $this->workingDir . '/foop/bar'); + $this->assertDirectoryExists($this->workingDir . '/foop/baz', 'Not a directory: ' . $this->workingDir . '/foop/baz'); $this->assertTrue(is_file($this->workingDir . '/foop/foo.file'), 'Not a file: ' . $this->workingDir . '/foop/foo.file'); $this->assertTrue(is_file($this->workingDir . '/foop/bar/foobar.file'), 'Not a file: ' . $this->workingDir . '/foop/bar/foobar.file'); $this->assertTrue(is_file($this->workingDir . '/foop/baz/foobaz.file'), 'Not a file: ' . $this->workingDir . '/foop/baz/foobaz.file'); @@ -355,8 +355,8 @@ class FilesystemTest extends TestCase $this->assertFalse(is_file($this->workingDir . '/foo/baz/foobaz.file'), 'Still a file: ' . $this->workingDir . '/foo/baz/foobaz.file'); $this->assertFalse(is_file($this->workingDir . '/foo/bar/foobar.file'), 'Still a file: ' . $this->workingDir . '/foo/bar/foobar.file'); $this->assertFalse(is_file($this->workingDir . '/foo/foo.file'), 'Still a file: ' . $this->workingDir . '/foo/foo.file'); - $this->assertFalse(is_dir($this->workingDir . '/foo/baz'), 'Still a directory: ' . $this->workingDir . '/foo/baz'); - $this->assertFalse(is_dir($this->workingDir . '/foo/bar'), 'Still a directory: ' . $this->workingDir . '/foo/bar'); - $this->assertFalse(is_dir($this->workingDir . '/foo'), 'Still a directory: ' . $this->workingDir . '/foo'); + $this->assertDirectoryNotExists($this->workingDir . '/foo/baz', 'Still a directory: ' . $this->workingDir . '/foo/baz'); + $this->assertDirectoryNotExists($this->workingDir . '/foo/bar', 'Still a directory: ' . $this->workingDir . '/foo/bar'); + $this->assertDirectoryNotExists($this->workingDir . '/foo', 'Still a directory: ' . $this->workingDir . '/foo'); } } From a1bf6890d5204d1bd671f8ebf599ac0ecf8d0d27 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 10:28:39 +0100 Subject: [PATCH 054/580] Fix ConsoleIO tests --- tests/Composer/Test/IO/ConsoleIOTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index c02a2eae5..9c9ca8902 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -240,6 +240,7 @@ class ConsoleIOTest extends TestCase $this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), $this->isInstanceOf('Symfony\Component\Console\Question\Question') ) + ->will($this->returnValue(array('item2'))); ; $setMock @@ -250,7 +251,8 @@ class ConsoleIOTest extends TestCase ; $consoleIO = new ConsoleIO($inputMock, $outputMock, $setMock); - $consoleIO->select('Select item', array("item1", "item2"), null, false, "Error message", true); + $result = $consoleIO->select('Select item', array("item1", "item2"), null, false, "Error message", true); + $this->assertEquals(array('1'), $result); } public function testSetAndgetAuthentication() From db0cae04773377e678c600ad20fc4e0f0da3bd53 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Wed, 24 Jan 2018 10:31:39 +0100 Subject: [PATCH 055/580] SCA: revert assertions tweaks --- tests/Composer/Test/Util/FilesystemTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index 5a9adbae7..ef3ab5bbe 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -307,9 +307,9 @@ class FilesystemTest extends TestCase $this->assertTrue($fs->isJunction($junction . '/../junction')); // Remove junction - $this->assertDirectoryExists($junction); + $this->assertTrue(is_dir($junction)); $this->assertTrue($fs->removeJunction($junction)); - $this->assertDirectoryNotExists($junction); + $this->assertFalse(is_dir($junction)); } public function testCopy() @@ -325,9 +325,9 @@ class FilesystemTest extends TestCase $result1 = $fs->copy($this->workingDir . '/foo', $this->workingDir . '/foop'); $this->assertTrue($result1, 'Copying directory failed.'); - $this->assertDirectoryExists($this->workingDir . '/foop', 'Not a directory: ' . $this->workingDir . '/foop'); - $this->assertDirectoryExists($this->workingDir . '/foop/bar', 'Not a directory: ' . $this->workingDir . '/foop/bar'); - $this->assertDirectoryExists($this->workingDir . '/foop/baz', 'Not a directory: ' . $this->workingDir . '/foop/baz'); + $this->assertTrue(is_dir($this->workingDir . '/foop'), 'Not a directory: ' . $this->workingDir . '/foop'); + $this->assertTrue(is_dir($this->workingDir . '/foop/bar'), 'Not a directory: ' . $this->workingDir . '/foop/bar'); + $this->assertTrue(is_dir($this->workingDir . '/foop/baz'), 'Not a directory: ' . $this->workingDir . '/foop/baz'); $this->assertTrue(is_file($this->workingDir . '/foop/foo.file'), 'Not a file: ' . $this->workingDir . '/foop/foo.file'); $this->assertTrue(is_file($this->workingDir . '/foop/bar/foobar.file'), 'Not a file: ' . $this->workingDir . '/foop/bar/foobar.file'); $this->assertTrue(is_file($this->workingDir . '/foop/baz/foobaz.file'), 'Not a file: ' . $this->workingDir . '/foop/baz/foobaz.file'); @@ -355,8 +355,8 @@ class FilesystemTest extends TestCase $this->assertFalse(is_file($this->workingDir . '/foo/baz/foobaz.file'), 'Still a file: ' . $this->workingDir . '/foo/baz/foobaz.file'); $this->assertFalse(is_file($this->workingDir . '/foo/bar/foobar.file'), 'Still a file: ' . $this->workingDir . '/foo/bar/foobar.file'); $this->assertFalse(is_file($this->workingDir . '/foo/foo.file'), 'Still a file: ' . $this->workingDir . '/foo/foo.file'); - $this->assertDirectoryNotExists($this->workingDir . '/foo/baz', 'Still a directory: ' . $this->workingDir . '/foo/baz'); - $this->assertDirectoryNotExists($this->workingDir . '/foo/bar', 'Still a directory: ' . $this->workingDir . '/foo/bar'); - $this->assertDirectoryNotExists($this->workingDir . '/foo', 'Still a directory: ' . $this->workingDir . '/foo'); + $this->assertFalse(is_dir($this->workingDir . '/foo/baz'), 'Still a directory: ' . $this->workingDir . '/foo/baz'); + $this->assertFalse(is_dir($this->workingDir . '/foo/bar'), 'Still a directory: ' . $this->workingDir . '/foo/bar'); + $this->assertFalse(is_dir($this->workingDir . '/foo'), 'Still a directory: ' . $this->workingDir . '/foo'); } } From 49068c579ea5ace2e1b105da19ec81f5fa88c26b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 11:14:40 +0100 Subject: [PATCH 056/580] Fix indenting --- src/Composer/Command/SelfUpdateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 147d65c02..3ed72bdbc 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -112,7 +112,7 @@ EOT $composeUser = posix_getpwuid(posix_geteuid()); $homeOwner = posix_getpwuid(fileowner($home)); if ($composeUser !== $homeOwner) { - $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); + $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); } } From 9bc83d698e48dff2cd5559134c9c0877ca6977a4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 12 Jan 2018 10:42:46 +0100 Subject: [PATCH 057/580] Pass auth credentials to svn log while retrieving commit logs --- src/Composer/Downloader/SvnDownloader.php | 23 +++++++++++---- src/Composer/Util/Svn.php | 36 +++++++++++++++++++++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 9cde1537b..9ce39c4b2 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -15,6 +15,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\Svn as SvnUtil; use Composer\Repository\VcsRepository; +use Composer\Util\ProcessExecutor; /** * @author Ben Bieker @@ -171,22 +172,34 @@ class SvnDownloader extends VcsDownloader protected function getCommitLogs($fromReference, $toReference, $path) { if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) { + // retrieve the svn base url from the checkout folder + $command = sprintf('svn info %s | grep ^URL:', ProcessExecutor::escape($path)); + if (0 !== $this->process->execute($command, $output, $path)) { + throw new \RuntimeException( + 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() + ); + } + // parse info line like 'URL: https://example.com/my/svn/url' + list ($prefix, $baseUrl) = explode(" ", $output, 2); + // strip paths from references and only keep the actual revision $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference); $command = sprintf('svn log -r%s:%s --incremental', $fromRevision, $toRevision); - if (0 !== $this->process->execute($command, $output, $path)) { + $util = new SvnUtil($baseUrl, $this->io, $this->config); + $util->setCacheCredentials($this->cacheCredentials); + try { + return $util->executeLocal($command, $path, null, $this->io->isVerbose()); + } catch (\RuntimeException $e) { throw new \RuntimeException( - 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() + 'Failed to execute ' . $command . "\n\n".$e->getMessage() ); } - } else { - $output = "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; } - return $output; + return "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; } protected function discardChanges($path) diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 80f70faf6..082ece691 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -85,7 +85,7 @@ class Svn } /** - * Execute an SVN command and try to fix up the process with credentials + * Execute an SVN remote command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run @@ -103,6 +103,36 @@ class Svn $this->config->prohibitUrlByConfig($url, $this->io); $svnCommand = $this->getCommand($command, $url, $path); + + return $this->executeWithAuthRetry($svnCommand, $cwd, $path, $verbose); + } + + /** + * Execute an SVN local command and try to fix up the process with credentials + * if necessary. + * + * @param string $command SVN command to run + * @param string $path Path argument passed thru to the command + * @param string $cwd Working directory + * @param bool $verbose Output all output to the user + * + * @throws \RuntimeException + * @return string + */ + public function executeLocal($command, $path, $cwd = null, $verbose = false) + { + $svnCommand = sprintf('%s %s%s %s', + $command, + '--non-interactive ', + $this->getCredentialString(), + ProcessExecutor::escape($path) + ); + + return $this->executeWithAuthRetry($svnCommand, $cwd, $path, $verbose); + } + + private function executeWithAuthRetry($command, $cwd, $path, $verbose) + { $output = null; $io = $this->io; $handler = function ($type, $buffer) use (&$output, $io, $verbose) { @@ -117,7 +147,7 @@ class Svn $io->writeError($buffer, false); } }; - $status = $this->process->execute($svnCommand, $handler, $cwd); + $status = $this->process->execute($command, $handler, $cwd); if (0 === $status) { return $output; } @@ -140,7 +170,7 @@ class Svn // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process - return $this->execute($command, $url, $cwd, $path, $verbose); + return $this->executeWithAuthRetry($command, $cwd, $path, $verbose); } throw new \RuntimeException( From e67a559db972a54c2534048e45c4953ef4981851 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 15 Jan 2018 09:35:49 +0100 Subject: [PATCH 058/580] make sure "svn info" output is parsed in a x-platform way --- src/Composer/Downloader/SvnDownloader.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 9ce39c4b2..54d9718be 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -173,14 +173,21 @@ class SvnDownloader extends VcsDownloader { if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) { // retrieve the svn base url from the checkout folder - $command = sprintf('svn info %s | grep ^URL:', ProcessExecutor::escape($path)); + $command = sprintf('svn info --non-interactive --xml %s', ProcessExecutor::escape($path)); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException( 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() ); } - // parse info line like 'URL: https://example.com/my/svn/url' - list ($prefix, $baseUrl) = explode(" ", $output, 2); + + $urlPattern = '#(.*)#'; + if (preg_match($urlPattern, $output, $matches)) { + $baseUrl = $matches[1]; + } else { + throw new \RuntimeException( + 'Unable to determine svn url for path '. $path + ); + } // strip paths from references and only keep the actual revision $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); From b1bfb9bb65641d35434209c2c60169a92e99d1e3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 15:21:46 +0100 Subject: [PATCH 059/580] Add PRE_COMMAND_RUN event, fixes #7002 --- doc/articles/scripts.md | 3 ++ src/Composer/Command/BaseCommand.php | 11 +++++ src/Composer/Plugin/PluginEvents.php | 10 +++++ src/Composer/Plugin/PreCommandRunEvent.php | 51 ++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 src/Composer/Plugin/PreCommandRunEvent.php diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index a93f2f205..79ea01519 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -63,6 +63,9 @@ Composer fires the following named events during its execution process: - **pre-file-download**: occurs before files are downloaded and allows you to manipulate the `RemoteFilesystem` object prior to downloading files based on the URL to be downloaded. +- **pre-command-run**: occurs before a command is executed and allows you to + manipulate the `InputInterface` object's options and arguments to tweak + a command's behavior. > **Note:** Composer makes no assumptions about the state of your dependencies > prior to `install` or `update`. Therefore, you should not specify scripts diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index d6b695ca8..052c17213 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -15,8 +15,11 @@ namespace Composer\Command; use Composer\Composer; use Composer\Config; use Composer\Console\Application; +use Composer\Factory; use Composer\IO\IOInterface; use Composer\IO\NullIO; +use Composer\Plugin\PreCommandRunEvent; +use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; @@ -123,6 +126,14 @@ abstract class BaseCommand extends Command */ protected function initialize(InputInterface $input, OutputInterface $output) { + // initialize a plugin-enabled Composer instance, either local or global + $composer = $this->getComposer(false, false); + if (null === $composer) { + $composer = Factory::createGlobal($this->getIO(), false); + } + $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input); + $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); + if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { $input->setOption('no-progress', true); } diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php index 0304ebb9a..1fb368baf 100644 --- a/src/Composer/Plugin/PluginEvents.php +++ b/src/Composer/Plugin/PluginEvents.php @@ -48,4 +48,14 @@ class PluginEvents * @var string */ const PRE_FILE_DOWNLOAD = 'pre-file-download'; + + /** + * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options + * + * The event listener method receives a + * Composer\Plugin\PreCommandRunEvent instance. + * + * @var string + */ + const PRE_COMMAND_RUN = 'pre-command-run'; } diff --git a/src/Composer/Plugin/PreCommandRunEvent.php b/src/Composer/Plugin/PreCommandRunEvent.php new file mode 100644 index 000000000..ab2b035a1 --- /dev/null +++ b/src/Composer/Plugin/PreCommandRunEvent.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Plugin; + +use Composer\EventDispatcher\Event; +use Symfony\Component\Console\Input\InputInterface; + +/** + * The pre command run event. + * + * @author Jordi Boggiano + */ +class PreCommandRunEvent extends Event +{ + /** + * @var InputInterface + */ + private $input; + + /** + * Constructor. + * + * @param string $name The event name + * @param InputInterface $input + */ + public function __construct($name, InputInterface $input) + { + parent::__construct($name); + $this->input = $input; + } + + /** + * Returns the console input + * + * @return InputInterface + */ + public function getInput() + { + return $this->input; + } +} From 352aefe48cda64c6982c15dc4e964b7b9823c2a0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 15:27:36 +0100 Subject: [PATCH 060/580] Add command name to the PreCommandRun event --- src/Composer/Command/BaseCommand.php | 2 +- src/Composer/Plugin/PreCommandRunEvent.php | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index 052c17213..9e8aacb7d 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -131,7 +131,7 @@ abstract class BaseCommand extends Command if (null === $composer) { $composer = Factory::createGlobal($this->getIO(), false); } - $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input); + $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { diff --git a/src/Composer/Plugin/PreCommandRunEvent.php b/src/Composer/Plugin/PreCommandRunEvent.php index ab2b035a1..60ad05b4a 100644 --- a/src/Composer/Plugin/PreCommandRunEvent.php +++ b/src/Composer/Plugin/PreCommandRunEvent.php @@ -27,16 +27,23 @@ class PreCommandRunEvent extends Event */ private $input; + /** + * @var string + */ + private $command; + /** * Constructor. * - * @param string $name The event name + * @param string $name The event name * @param InputInterface $input + * @param string $command The command about to be executed */ - public function __construct($name, InputInterface $input) + public function __construct($name, InputInterface $input, $command) { parent::__construct($name); $this->input = $input; + $this->command = $command; } /** @@ -48,4 +55,14 @@ class PreCommandRunEvent extends Event { return $this->input; } + + /** + * Returns the command about to be executed + * + * @return string + */ + public function getCommand() + { + return $this->command; + } } From ccbbbccadbb18a9056d89d347cd72eb77e03a349 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 15:36:00 +0100 Subject: [PATCH 061/580] CAvoid calling PRE_COMMAND_RUN if no composer could be initialized --- src/Composer/Command/BaseCommand.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index 9e8aacb7d..bd7c8a9fb 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -131,8 +131,10 @@ abstract class BaseCommand extends Command if (null === $composer) { $composer = Factory::createGlobal($this->getIO(), false); } - $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); - $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); + if ($composer) { + $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); + $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); + } if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { $input->setOption('no-progress', true); From 1933532ec5a436191e43c6fb43e34200c21536b2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 16:07:02 +0100 Subject: [PATCH 062/580] Fix github authentication request, fixes #5767 --- src/Composer/Repository/Vcs/GitHubDriver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 916150fa3..5c5c08cf2 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -361,7 +361,9 @@ class GitHubDriver extends VcsDriver } } $scopesFailed = array_diff($scopesNeeded, $scopesIssued); - if (!$headers || count($scopesFailed)) { + // non-authenticated requests get no scopesNeeded, so ask for credentials + // authenticated requests which failed some scopes should ask for new credentials too + if (!$headers || !count($scopesNeeded) || count($scopesFailed)) { $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); } From a29ad2bfd68e4e692f960c7f25675595080692d1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 24 Jan 2018 16:19:28 +0100 Subject: [PATCH 063/580] Add some more debugging info --- src/Composer/Downloader/DownloadManager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 77b41c5eb..0b1affc1d 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -160,8 +160,8 @@ class DownloadManager if ($installationSource !== $downloader->getInstallationSource()) { throw new \LogicException(sprintf( - 'Downloader "%s" is a %s type downloader and can not be used to download %s', - get_class($downloader), $downloader->getInstallationSource(), $installationSource + 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', + get_class($downloader), $downloader->getInstallationSource(), $installationSource, $package )); } From 71896b07770730a88dbee5d3063042f05970cd16 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jan 2018 14:06:54 +0100 Subject: [PATCH 064/580] Fix self-update regression, fixes #7045 --- src/Composer/Command/SelfUpdateCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 3ed72bdbc..52f7b87f0 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -111,8 +111,8 @@ EOT if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { $composeUser = posix_getpwuid(posix_geteuid()); $homeOwner = posix_getpwuid(fileowner($home)); - if ($composeUser !== $homeOwner) { - $io->writeError('You are running composer as "'.$composeUser.'", while "'.$home.'" is owned by "'.$homeOwner.'"'); + if (isset($composeUser['name']) && isset($homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) { + $io->writeError('You are running composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"'); } } From 9b3ab4896d8dec27cb7427d57c7f553539c3a0ff Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jan 2018 14:20:29 +0100 Subject: [PATCH 065/580] Update SPDX licenses to 1.3.0, fixes #7039 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 1ab5c6c33..1a0d1b046 100644 --- a/composer.lock +++ b/composer.lock @@ -126,16 +126,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "2d899e9b33023c631854f36c39ef9f8317a7ab33" + "reference": "7e111c50db92fa2ced140f5ba23b4e261bc77a30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2d899e9b33023c631854f36c39ef9f8317a7ab33", - "reference": "2d899e9b33023c631854f36c39ef9f8317a7ab33", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7e111c50db92fa2ced140f5ba23b4e261bc77a30", + "reference": "7e111c50db92fa2ced140f5ba23b4e261bc77a30", "shasum": "" }, "require": { @@ -183,7 +183,7 @@ "spdx", "validator" ], - "time": "2018-01-03T16:37:06+00:00" + "time": "2018-01-31T13:17:27+00:00" }, { "name": "justinrainbow/json-schema", From cd27cb1a95e94d5cfa4507bde18da48316445c9f Mon Sep 17 00:00:00 2001 From: Kat Date: Wed, 31 Jan 2018 14:38:48 +0000 Subject: [PATCH 066/580] Correct a tiny typo `project b's` should be `project a's` in the example given --- doc/articles/vendor-binaries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/vendor-binaries.md b/doc/articles/vendor-binaries.md index 9e678a950..ded1aa5f8 100644 --- a/doc/articles/vendor-binaries.md +++ b/doc/articles/vendor-binaries.md @@ -66,7 +66,7 @@ Say project `my-vendor/project-b` has requirements setup like this: ``` Running `composer install` for this `composer.json` will look at -all of project-b's dependencies and install them to `vendor/bin`. +all of project-a's dependencies and install them to `vendor/bin`. In this case, Composer will make `vendor/my-vendor/project-a/bin/project-a-bin` available as `vendor/bin/project-a-bin`. On a Unix-like platform From a61a8d78b13e2bb2eee7178552ab55168406b213 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jan 2018 16:11:37 +0100 Subject: [PATCH 067/580] Fix warning for packages not existing while they exist but not at the required stability, fixes #7044 --- src/Composer/Command/InitCommand.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 33df93a8b..a17efe6b9 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -705,8 +705,18 @@ EOT )); } + // Check for similar names/typos $similar = $this->findSimilar($name); if ($similar) { + // Check whether the minimum stability was the problem but the package exists + if ($requiredVersion === null && in_array($name, $similar, true)) { + throw new \InvalidArgumentException(sprintf( + 'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.', + $name, + $this->getMinimumStability($input) + )); + } + throw new \InvalidArgumentException(sprintf( "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", $name, From 056ac0258dbfebb8aedd63ef9e45e46281fd61ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jan 2018 16:22:54 +0100 Subject: [PATCH 068/580] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1abff21f..4a9a026a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### [1.6.3] 2018-01-31 + + * Fixed GitLab downloads failing in some edge cases + * Fixed ctrl-C handling during create-project + * Fixed GitHub VCS repositories not prompting for a token in some conditions + * Fixed SPDX license identifiers being case sensitive + * Fixed and clarified a few dependency resolution error reporting strings + * Fixed SVN commit log fetching in verbose mode when using private repositories + ### [1.6.2] 2018-01-05 * Fixed more autoloader regressions @@ -619,6 +628,7 @@ * Initial release +[1.6.3]: https://github.com/composer/composer/compare/1.6.2...1.6.3 [1.6.2]: https://github.com/composer/composer/compare/1.6.1...1.6.2 [1.6.1]: https://github.com/composer/composer/compare/1.6.0...1.6.1 [1.6.0]: https://github.com/composer/composer/compare/1.6.0-RC...1.6.0 From 1f3c52d3af82da40c63d592ded1bf4802b0d55e2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jan 2018 16:23:01 +0100 Subject: [PATCH 069/580] Doc tweak --- doc/03-cli.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 04ae1d0ae..31d0b6b50 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -668,6 +668,7 @@ By default the command checks for the packages on packagist.org. * **--keep-vcs:** Skip the deletion of the VCS metadata for the created project. This is mostly useful if you run the command in non-interactive mode. +* **--remove-vcs:** Force-remove the VCS metadata without prompting. * **--no-install:** Disables installation of the vendors. * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` requirements and force the installation even if the local machine does not From 9a3c0f7edac1e148f0faf1d70a3f242a2a3f803e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 1 Feb 2018 11:03:56 +0100 Subject: [PATCH 070/580] Tweak platform config docs, fixes #7067 --- doc/06-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/06-config.md b/doc/06-config.md index 2980e1c31..6b10ed634 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -120,7 +120,7 @@ value of this option will let Composer authenticate against example.org. Lets you fake platform packages (PHP and extensions) so that you can emulate a production env or define your target platform in the config. Example: `{"php": -"5.4", "ext-something": "4.0"}`. +"7.0.3", "ext-something": "4.0.3"}`. ## vendor-dir From a34f88cb018488e5b10501692a2babd3e61564d8 Mon Sep 17 00:00:00 2001 From: Kat Date: Thu, 1 Feb 2018 14:43:38 +0000 Subject: [PATCH 071/580] Change `dependencies` to `binaries` --- doc/articles/vendor-binaries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/vendor-binaries.md b/doc/articles/vendor-binaries.md index ded1aa5f8..0022b90b9 100644 --- a/doc/articles/vendor-binaries.md +++ b/doc/articles/vendor-binaries.md @@ -66,7 +66,7 @@ Say project `my-vendor/project-b` has requirements setup like this: ``` Running `composer install` for this `composer.json` will look at -all of project-a's dependencies and install them to `vendor/bin`. +all of project-a's binaries and install them to `vendor/bin`. In this case, Composer will make `vendor/my-vendor/project-a/bin/project-a-bin` available as `vendor/bin/project-a-bin`. On a Unix-like platform From 3b391191b963d963eab958b3cf238834368fc2d9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 1 Feb 2018 23:02:25 +0100 Subject: [PATCH 072/580] Fix RemoteFilesystem::getRemoteContents() on-failure behavior --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 9f662e795..06fb8b80b 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -551,7 +551,7 @@ class RemoteFilesystem { $contents = file_get_contents($fileUrl, false, $context); - return array($http_response_header, $contents); + return array(isset($http_response_header) ? $http_response_header : null, $contents); } /** From f722f952e7c0989d7b2f3bd6b2aadccdb2c920bb Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sun, 4 Feb 2018 22:41:28 -0200 Subject: [PATCH 073/580] Fix misspelling Signed-off-by: Gabriel Caruso --- CHANGELOG.md | 2 +- src/Composer/Repository/Vcs/GitLabDriver.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9a026a3..12960efe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -169,7 +169,7 @@ * Added `COMPOSER_MIRROR_PATH_REPOS` env var to force mirroring of path repositories vs symlinking * Added `COMPOSER_DEV_MODE` env var that is set by Composer to forward the dev mode to script handlers * Fixed support for git 2.11 - * Fixed output from zip and rar leaking out when an error occured + * Fixed output from zip and rar leaking out when an error occurred * Removed `hash` from composer.lock, only `content-hash` is now used which should reduce conflicts * Minor fixes and performance improvements diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index dcaf646ec..2044ff702 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -129,7 +129,7 @@ class GitLabDriver extends VcsDriver return $this->gitDriver->getFileContent($file, $identifier); } - // Convert the root identifier to a cachable commit id + // Convert the root identifier to a cacheable commit id if (!preg_match('{[a-f0-9]{40}}i', $identifier)) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { From 0fc6fb56a0463fbb3f4f5fa5bcd2c8ff7cc0f216 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 5 Feb 2018 10:17:52 +0100 Subject: [PATCH 074/580] Fix validation of license field --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 83b27aa55..b228460d8 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -104,12 +104,6 @@ class ValidatingArrayLoader implements LoaderInterface } if (isset($this->config['license'])) { - if (is_string($this->config['license'])) { - $this->validateRegex('license', '[A-Za-z0-9+. ()-]+'); - } else { - $this->validateFlatArray('license', '[A-Za-z0-9+. ()-]+'); - } - if (is_array($this->config['license']) || is_string($this->config['license'])) { $licenses = (array) $this->config['license']; From f857da7c295bad9146e1705ba4eb325e7cb2039a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 5 Feb 2018 10:34:41 +0100 Subject: [PATCH 075/580] Remove deprecated license check from ValidatingArrayLoader, fixes #7026, fixes #7073 --- .../Package/Loader/ValidatingArrayLoader.php | 22 ------------- src/Composer/Util/ConfigValidator.php | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index b228460d8..4ee444f02 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -126,28 +126,6 @@ class ValidatingArrayLoader implements LoaderInterface 'If the software is closed-source, you may use "proprietary" as license.', json_encode($this->config['license']) ); - } else if (!$releaseDate || $releaseDate->format('Y-m-d H:i:s') >= '2018-01-20 00:00:00') { // only warn for deprecations for releases/branches that follow the introduction of deprecated licenses - foreach ($licenses as $license) { - $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); - if ($spdxLicense && $spdxLicense[3]) { - if (preg_match('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { - $this->warnings[] = sprintf( - 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', - $license - ); - } elseif (preg_match('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { - $this->warnings[] = sprintf( - 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', - $license - ); - } else { - $this->warnings[] = sprintf( - 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', - $license - ); - } - } - } } } } diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php index e5f64ec23..c5f762f92 100644 --- a/src/Composer/Util/ConfigValidator.php +++ b/src/Composer/Util/ConfigValidator.php @@ -18,6 +18,7 @@ use Composer\Package\Loader\InvalidPackageException; use Composer\Json\JsonValidationException; use Composer\IO\IOInterface; use Composer\Json\JsonFile; +use Composer\Spdx\SpdxLicenses; /** * Validates a composer configuration. @@ -74,6 +75,38 @@ class ConfigValidator // validate actual data if (empty($manifest['license'])) { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; + } else { + $licenses = (array) $manifest['license']; + + // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer + foreach ($licenses as $key => $license) { + if ('proprietary' === $license) { + unset($licenses[$key]); + } + } + + $licenseValidator = new SpdxLicenses(); + foreach ($licenses as $license) { + $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); + if ($spdxLicense && $spdxLicense[3]) { + if (preg_match('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', + $license + ); + } elseif (preg_match('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', + $license + ); + } else { + $warnings[] = sprintf( + 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', + $license + ); + } + } + } } if (isset($manifest['version'])) { From b90987fdeb13e1ef9180d6d65fec998aefecfa7d Mon Sep 17 00:00:00 2001 From: Patrick Rose Date: Fri, 9 Feb 2018 10:05:46 +0000 Subject: [PATCH 076/580] Fix permissions when using the PearBinaryInstaller The PearBinaryInstaller is old and not used by many people, so the world writable permissions weren't caught --- src/Composer/Installer/PearBinaryInstaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer/PearBinaryInstaller.php b/src/Composer/Installer/PearBinaryInstaller.php index d0c63c410..f0d6783bb 100644 --- a/src/Composer/Installer/PearBinaryInstaller.php +++ b/src/Composer/Installer/PearBinaryInstaller.php @@ -61,9 +61,9 @@ class PearBinaryInstaller extends BinaryInstaller { parent::initializeBinDir(); file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode()); - @chmod($this->binDir.'/composer-php', 0777); + @chmod($this->binDir.'/composer-php', 0777 & ~umask()); file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode()); - @chmod($this->binDir.'/composer-php.bat', 0777); + @chmod($this->binDir.'/composer-php.bat', 0777 & ~umask()); } protected function generateWindowsProxyCode($bin, $link) From 8a5645ffda5af3b83f7deb2fe0995704a4945941 Mon Sep 17 00:00:00 2001 From: austris argalis Date: Fri, 16 Feb 2018 00:38:41 +0200 Subject: [PATCH 077/580] Wording: Downgrading instead of Updating #7085 --- src/Composer/Downloader/FileDownloader.php | 3 +- src/Composer/Downloader/VcsDownloader.php | 20 ++++- .../Test/Downloader/FileDownloaderTest.php | 34 ++++++++ .../Test/Downloader/GitDownloaderTest.php | 84 +++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 0cc531704..cfe0f153d 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -214,7 +214,8 @@ class FileDownloader implements DownloaderInterface $from = $initial->getPrettyVersion(); $to = $target->getPrettyVersion(); - $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . "): ", false); + $actionName = version_compare($from, $to, '<') ? 'Updating' : 'Downgrading'; + $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); $this->remove($initial, $path, false); $this->download($target, $path, false); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index cdc63e5ec..a0ccacdf1 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -130,7 +130,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $to = $target->getFullPrettyVersion(); } - $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . "): ", false); + $actionName = $this->packageCompare($initial, $target, '>') ? 'Downgrading' : 'Updating'; + $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); $this->cleanChanges($initial, $path, true); $urls = $target->getSourceUrls(); @@ -242,6 +243,23 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } } + /** + * Compare two packages. Always false if both versions are references + * + * @param PackageInterface $initial + * @param PackageInterface $target + * @param string $operation + * @return bool + */ + protected function packageCompare(PackageInterface $initial, PackageInterface $target, $operation = '<') + { + if ($initial->getPrettyVersion() == $target->getPrettyVersion()) { + return false; + } + + return version_compare($initial->getFullPrettyVersion(), $target->getFullPrettyVersion(), $operation); + } + /** * Guarantee that no changes have been made to the local copy * diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 9b9f7b671..7543e368e 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -205,4 +205,38 @@ class FileDownloaderTest extends TestCase $this->assertContains('checksum verification', $e->getMessage()); } } + + public function testDowngradeShowsAppropriateMessage() + { + $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage->expects($this->once()) + ->method('getPrettyVersion') + ->will($this->returnValue('1.0.0')); + $oldPackage->expects($this->any()) + ->method('getDistUrl') + ->will($this->returnValue($distUrl = 'http://example.com/script.js')); + $oldPackage->expects($this->once()) + ->method('getDistUrls') + ->will($this->returnValue(array($distUrl))); + + $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage->expects($this->once()) + ->method('getPrettyVersion') + ->will($this->returnValue('1.2.0')); + + $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock->expects(($this->at(0))) + ->method('writeError') + ->with($this->stringContains('Downgrading')); + + $path = $this->getUniqueTmpDirectory(); + touch($path.'/script.js'); + $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem->expects($this->once()) + ->method('removeDirectory') + ->will($this->returnValue(true)); + + $downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem); + $downloader->update($newPackage, $oldPackage, $path); + } } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 20e609712..cd6c797fb 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -596,6 +596,90 @@ composer https://github.com/old/url (push) $downloader->update($packageMock, $packageMock, $this->workingDir); } + public function testDowngradeShowsAppropriateMessage() + { + $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage->expects($this->any()) + ->method('getPrettyVersion') + ->will($this->returnValue('1.0.0')); + $oldPackage->expects($this->any()) + ->method('getFullPrettyVersion') + ->will($this->returnValue('1.0.0')); + $oldPackage->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $oldPackage->expects($this->any()) + ->method('getSourceUrls') + ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); + + $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $newPackage->expects($this->any()) + ->method('getSourceUrls') + ->will($this->returnValue(array('https://github.com/composer/composer'))); + $newPackage->expects($this->any()) + ->method('getPrettyVersion') + ->will($this->returnValue('1.2.0')); + $newPackage->expects($this->any()) + ->method('getFullPrettyVersion') + ->will($this->returnValue('1.2.0')); + + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->will($this->returnValue(0)); + + $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock->expects(($this->at(0))) + ->method('writeError') + ->with($this->stringContains('Downgrading')); + + $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); + $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); + $downloader->update($newPackage, $oldPackage, $this->workingDir); + } + + public function testNotUsingDowngradingWithReferences() + { + $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage->expects($this->any()) + ->method('getPrettyVersion') + ->will($this->returnValue('ref')); + $oldPackage->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $oldPackage->expects($this->any()) + ->method('getSourceUrls') + ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); + + $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $newPackage->expects($this->any()) + ->method('getSourceUrls') + ->will($this->returnValue(array('https://github.com/composer/composer'))); + $newPackage->expects($this->any()) + ->method('getPrettyVersion') + ->will($this->returnValue('ref')); + + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->will($this->returnValue(0)); + + $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock->expects(($this->at(0))) + ->method('writeError') + ->with($this->stringContains('updating')); + + $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); + $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); + $downloader->update($newPackage, $oldPackage, $this->workingDir); + } + public function testRemove() { $expectedGitResetCommand = $this->winCompat("cd 'composerPath' && git status --porcelain --untracked-files=no"); From fb40967349f1f6d05bd3983f7efaa97604411a96 Mon Sep 17 00:00:00 2001 From: David Yell Date: Mon, 19 Feb 2018 12:42:19 +0000 Subject: [PATCH 078/580] Added space to concatenation Fix a missed space for string concatenation --- src/Composer/Autoload/ClassLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index dc02dfb11..95f7e0978 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -377,7 +377,7 @@ class ClassLoader $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); - $search = $subPath.'\\'; + $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { From 7676a5e4ecbdaec4d16d0e581577b18ddee0d7ba Mon Sep 17 00:00:00 2001 From: Rogerio Prado de Jesus Date: Wed, 21 Feb 2018 18:09:43 -0300 Subject: [PATCH 079/580] Add option "no-secure-http" for create-project Refers to #5121, commit f13e0f975f3b513b5bbe5f04f5eef05923b39843 --- doc/03-cli.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 31d0b6b50..2d17e656e 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -665,6 +665,9 @@ By default the command checks for the packages on packagist.org. package. * **--no-progress:** Removes the progress display that can mess with some terminals or scripts which don't handle backspace characters. +* **--no-secure-http:** Disable the secure-http config option temporarily while + installing the root package. Use at your own risk. Using this flag is a bad + idea. * **--keep-vcs:** Skip the deletion of the VCS metadata for the created project. This is mostly useful if you run the command in non-interactive mode. From efc744de762f8cb8d9ae6d6d4ce22594f51a252a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Lebecq Date: Thu, 22 Feb 2018 17:02:47 +0100 Subject: [PATCH 080/580] Add abandoned property in composer schema documentation --- doc/04-schema.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/04-schema.md b/doc/04-schema.md index 9e846168a..550b5e37e 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -861,6 +861,22 @@ The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`, Optional. +### abandoned + +Indicates whether this package has been abandoned. + +It can be boolean or a package name/URL pointing to a recommended alternative. + +Examples: + +Use `"abandoned": true` to indicates this package is abandoned. +Use `"abandoned": "monolog/monolog"` to indicates this package is abandoned and the +recommended alternative is `monolog/monolog`. + +Defaults to false. + +Optional. + ### non-feature-branches A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something), From 72476b62d4c0dc816ba459b40ef1ad2daa23dff5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 1 Mar 2018 15:49:25 +0100 Subject: [PATCH 081/580] Check for license validity only on newly updated branches, refs composer/packagist#866, refs composer/packagist#883 --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 4ee444f02..fb2eafd93 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -103,7 +103,8 @@ class ValidatingArrayLoader implements LoaderInterface } } - if (isset($this->config['license'])) { + // check for license validity on newly updated branches + if (isset($this->config['license']) && (!$releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days'))) { if (is_array($this->config['license']) || is_string($this->config['license'])) { $licenses = (array) $this->config['license']; From 47ce495454b6391aea5516ed01cb86ae239c3592 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 5 Mar 2018 09:38:22 +0100 Subject: [PATCH 082/580] document the COMPOSER_MEMORY_LIMIT env var as implemented in 882b82d542b45435bae1d95b33e225427950c59c --- doc/articles/troubleshooting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 80e639a63..3be93c52f 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -140,6 +140,12 @@ Debian-like systems): memory_limit = -1 ``` +Composer also respects a memory limit defined by the `COMPOSER_MEMORY_LIMIT` environment variable: + +```sh +COMPOSER_MEMORY_LIMIT=-1 composer.phar <...> +``` + Or, you can increase the limit with a command-line argument: ```sh From 4d8b9be5b6a0bda0a3d24c0a6ff89d7ccb26f715 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 5 Mar 2018 23:34:22 +0100 Subject: [PATCH 083/580] Skip parsing equal branches/tags to avoid failures in packagist down the line --- src/Composer/Repository/VcsRepository.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index a1771c554..57639cdea 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -188,6 +188,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt continue; } + if ($existingPackage = $this->findPackage($data['name'], $data['version_normalized'])) { + if ($verbose) { + $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); + } + continue; + } + if ($verbose) { $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } @@ -205,7 +212,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->io->overwriteError('', false); } - foreach ($driver->getBranches() as $branch => $identifier) { + $branches = $driver->getBranches(); + foreach ($branches as $branch => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; if ($verbose) { $this->io->writeError($msg); @@ -213,6 +221,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->io->overwriteError($msg, false); } + if ($branch === 'trunk' && isset($branches['master'])) { + if ($verbose) { + $this->io->writeError('Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally'); + } + continue; + } + if (!$parsedBranch = $this->validateBranch($branch)) { if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', invalid name'); From ce521e5697ee479308557ed78da03bc7596c68dc Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 6 Mar 2018 12:57:44 +0100 Subject: [PATCH 084/580] Take only displayed packages into account to determine column width The ShowCommand was taking all packages into account when determining the max lengths used to display the width of each column. This was causing unnecessary hiding of columns in case of using a longer name or a longer version in a different package. This was especially visible when using the outdated command, as it applies filtering by default. --- src/Composer/Command/ShowCommand.php | 36 +++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ae2307e5c..4392e9ef2 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -329,23 +329,17 @@ EOT ksort($packages[$type]); $nameLength = $versionLength = $latestLength = 0; - foreach ($packages[$type] as $package) { - if (is_object($package)) { - $nameLength = max($nameLength, strlen($package->getPrettyName())); - if ($showVersion) { - $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); - if ($showLatest) { - $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly); - if ($latestPackage === false) { - continue; - } - $latestPackages[$package->getPrettyName()] = $latestPackage; - $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion())); + if ($showLatest && $showVersion) { + foreach ($packages[$type] as $package) { + if (is_object($package)) { + $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly); + if ($latestPackage === false) { + continue; } + + $latestPackages[$package->getPrettyName()] = $latestPackage; } - } else { - $nameLength = max($nameLength, strlen($package)); } } @@ -357,11 +351,6 @@ EOT $hasOutdatedPackages = false; $viewData[$type] = array(); - $viewMetaData[$type] = array( - 'nameLength' => $nameLength, - 'versionLength' => $versionLength, - 'latestLength' => $latestLength, - ); foreach ($packages[$type] as $package) { $packageViewData = array(); if (is_object($package)) { @@ -376,12 +365,15 @@ EOT } $packageViewData['name'] = $package->getPrettyName(); + $nameLength = max($nameLength, strlen($package->getPrettyName())); if ($writeVersion) { $packageViewData['version'] = $package->getFullPrettyVersion(); + $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); } if ($writeLatest && $latestPackage) { $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); + $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion())); } if ($writeDescription) { $packageViewData['description'] = $package->getDescription(); @@ -403,9 +395,15 @@ EOT } } else { $packageViewData['name'] = $package; + $nameLength = max($nameLength, strlen($package)); } $viewData[$type][] = $packageViewData; } + $viewMetaData[$type] = array( + 'nameLength' => $nameLength, + 'versionLength' => $versionLength, + 'latestLength' => $latestLength, + ); if ($input->getOption('strict') && $hasOutdatedPackages) { $exitCode = 1; break; From b85c6a1ca5f14ce9d0ad5426fb170a011375309a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 7 Mar 2018 23:05:37 +0100 Subject: [PATCH 085/580] Fix: Add type field to schema for inline-package --- res/composer-schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/res/composer-schema.json b/res/composer-schema.json index 8c61a6240..cdc34ca2f 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -683,6 +683,7 @@ } }, "inline-package": { + "type": "object", "required": ["name", "version"], "properties": { "name": { From ed97c2116c8472ad7c64e35f5b028e96468f12da Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Thu, 23 Nov 2017 15:52:48 +0000 Subject: [PATCH 086/580] Use external XdebugHandler library --- bin/composer | 11 +- composer.json | 1 + composer.lock | 46 ++- src/Composer/Compiler.php | 1 + .../Repository/PlatformRepository.php | 4 +- src/Composer/Util/IniHelper.php | 18 +- src/Composer/XdebugHandler.php | 283 +----------------- .../Composer/Test/Mock/XdebugHandlerMock.php | 48 --- tests/Composer/Test/Util/IniHelperTest.php | 12 +- tests/Composer/Test/XdebugHandlerTest.php | 182 ----------- 10 files changed, 65 insertions(+), 541 deletions(-) delete mode 100644 tests/Composer/Test/Mock/XdebugHandlerMock.php delete mode 100644 tests/Composer/Test/XdebugHandlerTest.php diff --git a/bin/composer b/bin/composer index 0664e04ce..a884abbb2 100755 --- a/bin/composer +++ b/bin/composer @@ -7,16 +7,13 @@ if (PHP_SAPI !== 'cli') { require __DIR__.'/../src/bootstrap.php'; -use Composer\Factory; -use Composer\XdebugHandler; use Composer\Console\Application; +use Composer\XdebugHandler\XdebugHandler; error_reporting(-1); -// Create output for XdebugHandler and Application -$output = Factory::createOutput(); - -$xdebug = new XdebugHandler($output); +// Restart without xdebug +$xdebug = new XdebugHandler('Composer', '--ansi'); $xdebug->check(); unset($xdebug); @@ -56,4 +53,4 @@ putenv('COMPOSER_BINARY='.realpath($_SERVER['argv'][0])); // run the command application $application = new Application(); -$application->run(null, $output); +$application->run(); diff --git a/composer.json b/composer.json index dd672c3b7..bbeb02c42 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.0", "seld/jsonlint": "^1.4", "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", diff --git a/composer.lock b/composer.lock index 771fca79c..ea095dbc6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "a248442611fb58177b28432be1af692c", + "content-hash": "6f0e4eb8b744ac97ff62245bed9d8aa5", "packages": [ { "name": "composer/ca-bundle", @@ -185,6 +185,50 @@ ], "time": "2018-01-31T13:17:27+00:00" }, + { + "name": "composer/xdebug-handler", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2018-03-08T13:09:50+00:00" + }, { "name": "justinrainbow/json-schema", "version": "5.2.6", diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 34eca8be2..3fdbcd867 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -122,6 +122,7 @@ class Compiler ->in(__DIR__.'/../../vendor/composer/spdx-licenses/') ->in(__DIR__.'/../../vendor/composer/semver/') ->in(__DIR__.'/../../vendor/composer/ca-bundle/') + ->in(__DIR__.'/../../vendor/composer/xdebug-handler/') ->in(__DIR__.'/../../vendor/psr/') ->sort($finderSort) ; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index da5c9ab73..02d552424 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -12,12 +12,12 @@ namespace Composer\Repository; -use Composer\XdebugHandler; use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginInterface; use Composer\Util\Silencer; +use Composer\XdebugHandler\XdebugHandler; /** * @author Jordi Boggiano @@ -120,7 +120,7 @@ class PlatformRepository extends ArrayRepository } // Check for xdebug in a restarted process - if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = strval(getenv(XdebugHandler::ENV_VERSION)))) { + if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) { $this->addExtension('xdebug', $prettyVersion); } diff --git a/src/Composer/Util/IniHelper.php b/src/Composer/Util/IniHelper.php index de1065320..d655419fc 100644 --- a/src/Composer/Util/IniHelper.php +++ b/src/Composer/Util/IniHelper.php @@ -12,6 +12,8 @@ namespace Composer\Util; +use Composer\XdebugHandler\XdebugHandler; + /** * Provides ini file location functions that work with and without a restart. * When the process has restarted it uses a tmp ini and stores the original @@ -21,8 +23,6 @@ namespace Composer\Util; */ class IniHelper { - const ENV_ORIGINAL = 'COMPOSER_ORIGINAL_INIS'; - /** * Returns an array of php.ini locations with at least one entry * @@ -33,19 +33,7 @@ class IniHelper */ public static function getAll() { - $env = getenv(self::ENV_ORIGINAL); - - if (false !== $env) { - return explode(PATH_SEPARATOR, $env); - } - - $paths = array(strval(php_ini_loaded_file())); - - if ($scanned = php_ini_scanned_files()) { - $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); - } - - return $paths; + return XdebugHandler::getAllIniFiles(); } /** diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 7031e6ff8..015ac808a 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -12,290 +12,11 @@ namespace Composer; -use Composer\Util\IniHelper; -use Symfony\Component\Console\Output\OutputInterface; +trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead.', E_USER_DEPRECATED); /** - * @author John Stevenson + * @deprecated use Composer\XdebugHandler\XdebugHandler instead */ class XdebugHandler { - const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; - const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION'; - const RESTART_ID = 'internal'; - - private $output; - private $loaded; - private $envScanDir; - private $version; - private $tmpIni; - - /** - * Constructor - */ - public function __construct(OutputInterface $output) - { - $this->output = $output; - $this->loaded = extension_loaded('xdebug'); - $this->envScanDir = getenv('PHP_INI_SCAN_DIR'); - - if ($this->loaded) { - $ext = new \ReflectionExtension('xdebug'); - $this->version = strval($ext->getVersion()); - } - } - - /** - * Checks if xdebug is loaded and composer needs to be restarted - * - * If so, then a tmp ini is created with the xdebug ini entry commented out. - * If additional inis have been loaded, these are combined into the tmp ini - * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are - * are stored in COMPOSER_ORIGINAL_INIS, for use in the restarted process. - * - * This behaviour can be disabled by setting the COMPOSER_ALLOW_XDEBUG - * environment variable to 1. This variable is used internally so that the - * restarted process is created only once and PHP_INI_SCAN_DIR can be - * restored to its original value. - */ - public function check() - { - $args = explode('|', strval(getenv(self::ENV_ALLOW)), 2); - - if ($this->needsRestart($args[0])) { - if ($this->prepareRestart()) { - $command = $this->getCommand(); - $this->restart($command); - } - - return; - } - - // Restore environment variables if we are restarting - if (self::RESTART_ID === $args[0]) { - putenv(self::ENV_ALLOW); - - if (false !== $this->envScanDir) { - // $args[1] contains the original value - if (isset($args[1])) { - putenv('PHP_INI_SCAN_DIR='.$args[1]); - } else { - putenv('PHP_INI_SCAN_DIR'); - } - } - - // Clear version if the restart failed to disable xdebug - if ($this->loaded) { - putenv(self::ENV_VERSION); - } - } - } - - /** - * Executes the restarted command then deletes the tmp ini - * - * @param string $command - */ - protected function restart($command) - { - passthru($command, $exitCode); - - if (!empty($this->tmpIni)) { - @unlink($this->tmpIni); - } - - exit($exitCode); - } - - /** - * Returns true if a restart is needed - * - * @param string $allow Environment value - * - * @return bool - */ - private function needsRestart($allow) - { - if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) { - return false; - } - - return empty($allow) && $this->loaded; - } - - /** - * Returns true if everything was written for the restart - * - * If any of the following fails (however unlikely) we must return false to - * stop potential recursion: - * - tmp ini file creation - * - environment variable creation - * - * @return bool - */ - private function prepareRestart() - { - $this->tmpIni = ''; - $iniPaths = IniHelper::getAll(); - $additional = count($iniPaths) > 1; - - if ($this->writeTmpIni($iniPaths)) { - return $this->setEnvironment($additional, $iniPaths); - } - - return false; - } - - /** - * Returns true if the tmp ini file was written - * - * The filename is passed as the -c option when the process restarts. - * - * @param array $iniPaths Locations reported by the current process - * - * @return bool - */ - private function writeTmpIni(array $iniPaths) - { - if (!$this->tmpIni = tempnam(sys_get_temp_dir(), '')) { - return false; - } - - // $iniPaths has at least one item and it may be empty - if (empty($iniPaths[0])) { - array_shift($iniPaths); - } - - $content = ''; - $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; - - foreach ($iniPaths as $file) { - $data = preg_replace($regex, ';$1', file_get_contents($file)); - $content .= $data.PHP_EOL; - } - - $content .= 'allow_url_fopen='.ini_get('allow_url_fopen').PHP_EOL; - $content .= 'disable_functions="'.ini_get('disable_functions').'"'.PHP_EOL; - $content .= 'memory_limit='.ini_get('memory_limit').PHP_EOL; - - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - // Work-around for PHP windows bug, see issue #6052 - $content .= 'opcache.enable_cli=0'.PHP_EOL; - } - - return @file_put_contents($this->tmpIni, $content); - } - - /** - * Returns the restart command line - * - * @return string - */ - private function getCommand() - { - $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); - $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv'])); - - return implode(' ', array_map(array($this, 'escape'), $params)); - } - - /** - * Returns true if the restart environment variables were set - * - * @param bool $additional Whether there were additional inis - * @param array $iniPaths Locations reported by the current process - * - * @return bool - */ - private function setEnvironment($additional, array $iniPaths) - { - // Set scan dir to an empty value if additional ini files were used - if ($additional && !putenv('PHP_INI_SCAN_DIR=')) { - return false; - } - - // Make original inis available to restarted process - if (!putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $iniPaths))) { - return false; - } - - // Make xdebug version available to restarted process - if (!putenv(self::ENV_VERSION.'='.$this->version)) { - return false; - } - - // Flag restarted process and save env scan dir state - $args = array(self::RESTART_ID); - - if (false !== $this->envScanDir) { - // Save current PHP_INI_SCAN_DIR - $args[] = $this->envScanDir; - } - - return putenv(self::ENV_ALLOW.'='.implode('|', $args)); - } - - /** - * Returns the restart script arguments, adding --ansi if required - * - * If we are a terminal with color support we must ensure that the --ansi - * option is set, because the restarted output is piped. - * - * @param array $args The argv array - * - * @return array - */ - private function getScriptArgs(array $args) - { - if (in_array('--no-ansi', $args) || in_array('--ansi', $args)) { - return $args; - } - - if ($this->output->isDecorated()) { - $offset = count($args) > 1 ? 2 : 1; - array_splice($args, $offset, 0, '--ansi'); - } - - return $args; - } - - /** - * Escapes a string to be used as a shell argument. - * - * From https://github.com/johnstevenson/winbox-args - * MIT Licensed (c) John Stevenson - * - * @param string $arg The argument to be escaped - * @param bool $meta Additionally escape cmd.exe meta characters - * - * @return string The escaped argument - */ - private function escape($arg, $meta = true) - { - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { - return escapeshellarg($arg); - } - - $quote = strpbrk($arg, " \t") !== false || $arg === ''; - $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); - - if ($meta) { - $meta = $dquotes || preg_match('/%[^%]+%/', $arg); - - if (!$meta && !$quote) { - $quote = strpbrk($arg, '^&|<>()') !== false; - } - } - - if ($quote) { - $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg); - $arg = '"'.$arg.'"'; - } - - if ($meta) { - $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); - } - - return $arg; - } } diff --git a/tests/Composer/Test/Mock/XdebugHandlerMock.php b/tests/Composer/Test/Mock/XdebugHandlerMock.php deleted file mode 100644 index 499f63b09..000000000 --- a/tests/Composer/Test/Mock/XdebugHandlerMock.php +++ /dev/null @@ -1,48 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Mock; - -use Composer\Factory; -use Composer\XdebugHandler; - -class XdebugHandlerMock extends XdebugHandler -{ - public $restarted; - public $output; - public $testVersion = '2.5.0'; - - public function __construct($loaded = null) - { - $this->output = Factory::createOutput(); - parent::__construct($this->output); - - $loaded = null === $loaded ? true : $loaded; - $class = new \ReflectionClass(get_parent_class($this)); - - $prop = $class->getProperty('loaded'); - $prop->setAccessible(true); - $prop->setValue($this, $loaded); - - $prop = $class->getProperty('version'); - $prop->setAccessible(true); - $version = $loaded ? $this->testVersion : ''; - $prop->setValue($this, $version); - - $this->restarted = false; - } - - protected function restart($command) - { - $this->restarted = true; - } -} diff --git a/tests/Composer/Test/Util/IniHelperTest.php b/tests/Composer/Test/Util/IniHelperTest.php index be59d546e..aead5fea9 100644 --- a/tests/Composer/Test/Util/IniHelperTest.php +++ b/tests/Composer/Test/Util/IniHelperTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Util; use Composer\Util\IniHelper; +use Composer\XdebugHandler\XdebugHandler; use PHPUnit\Framework\TestCase; /** @@ -41,7 +42,6 @@ class IniHelperTest extends TestCase $this->setEnv($paths); $this->assertContains('loaded.ini', IniHelper::getMessage()); - $this->assertEquals($paths, IniHelper::getAll()); } public function testWithLoadedIniAndAdditional() @@ -72,22 +72,24 @@ class IniHelperTest extends TestCase public static function setUpBeforeClass() { + // Register our name with XdebugHandler + $xdebug = new XdebugHandler('composer'); // Save current state - self::$envOriginal = getenv(IniHelper::ENV_ORIGINAL); + self::$envOriginal = getenv('COMPOSER_ORIGINAL_INIS'); } public static function tearDownAfterClass() { // Restore original state if (false !== self::$envOriginal) { - putenv(IniHelper::ENV_ORIGINAL.'='.self::$envOriginal); + putenv('COMPOSER_ORIGINAL_INIS='.self::$envOriginal); } else { - putenv(IniHelper::ENV_ORIGINAL); + putenv('COMPOSER_ORIGINAL_INIS'); } } protected function setEnv(array $paths) { - putenv(IniHelper::ENV_ORIGINAL.'='.implode(PATH_SEPARATOR, $paths)); + putenv('COMPOSER_ORIGINAL_INIS='.implode(PATH_SEPARATOR, $paths)); } } diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php deleted file mode 100644 index 6ccefad5c..000000000 --- a/tests/Composer/Test/XdebugHandlerTest.php +++ /dev/null @@ -1,182 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test; - -use Composer\Test\Mock\XdebugHandlerMock; -use Composer\Util\IniHelper; -use PHPUnit\Framework\TestCase; - -/** - * @author John Stevenson - * - * We use PHP_BINARY which only became available in PHP 5.4 * - * @requires PHP 5.4 - */ -class XdebugHandlerTest extends TestCase -{ - public static $env = array(); - - public function testRestartWhenLoaded() - { - $loaded = true; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertTrue($xdebug->restarted); - $this->assertInternalType('string', getenv(IniHelper::ENV_ORIGINAL)); - } - - public function testNoRestartWhenNotLoaded() - { - $loaded = false; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertFalse($xdebug->restarted); - $this->assertFalse(getenv(IniHelper::ENV_ORIGINAL)); - } - - public function testNoRestartWhenLoadedAndAllowed() - { - $loaded = true; - putenv(XdebugHandlerMock::ENV_ALLOW.'=1'); - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertFalse($xdebug->restarted); - } - - public function testEnvAllow() - { - $loaded = true; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $expected = XdebugHandlerMock::RESTART_ID; - $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW)); - - // Mimic restart - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertFalse($xdebug->restarted); - $this->assertFalse(getenv(XdebugHandlerMock::ENV_ALLOW)); - } - - public function testEnvAllowWithScanDir() - { - $loaded = true; - $dir = '/some/where'; - putenv('PHP_INI_SCAN_DIR='.$dir); - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $expected = XdebugHandlerMock::RESTART_ID.'|'.$dir; - $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW)); - - // Mimic setting scan dir and restart - putenv('PHP_INI_SCAN_DIR='); - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertEquals($dir, getenv('PHP_INI_SCAN_DIR')); - } - - public function testEnvAllowWithEmptyScanDir() - { - $loaded = true; - putenv('PHP_INI_SCAN_DIR='); - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $expected = XdebugHandlerMock::RESTART_ID.'|'; - $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_ALLOW)); - - // Unset scan dir and mimic restart - putenv('PHP_INI_SCAN_DIR'); - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertEquals('', getenv('PHP_INI_SCAN_DIR')); - } - - public function testEnvVersionWhenLoaded() - { - $loaded = true; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertEquals($xdebug->testVersion, getenv(XdebugHandlerMock::ENV_VERSION)); - - // Mimic successful restart - $loaded = false; - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertEquals($xdebug->testVersion, getenv(XdebugHandlerMock::ENV_VERSION)); - } - - public function testEnvVersionWhenNotLoaded() - { - $loaded = false; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertFalse(getenv(XdebugHandlerMock::ENV_VERSION)); - } - - public function testEnvVersionWhenRestartFails() - { - $loaded = true; - - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - - // Mimic failed restart - $xdebug = new XdebugHandlerMock($loaded); - $xdebug->check(); - $this->assertFalse(getenv(XdebugHandlerMock::ENV_VERSION)); - } - - public static function setUpBeforeClass() - { - // Save current state - $names = array( - XdebugHandlerMock::ENV_ALLOW, - XdebugHandlerMock::ENV_VERSION, - 'PHP_INI_SCAN_DIR', - IniHelper::ENV_ORIGINAL, - ); - - foreach ($names as $name) { - self::$env[$name] = getenv($name); - } - } - - public static function tearDownAfterClass() - { - // Restore original state - foreach (self::$env as $name => $value) { - if (false !== $value) { - putenv($name.'='.$value); - } else { - putenv($name); - } - } - } - - protected function setUp() - { - // Ensure env is unset - putenv(XdebugHandlerMock::ENV_ALLOW); - putenv(XdebugHandlerMock::ENV_VERSION); - putenv('PHP_INI_SCAN_DIR'); - putenv(IniHelper::ENV_ORIGINAL); - } -} From 6f06b45398735f293db7416310971222cd4b907b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Fri, 9 Mar 2018 08:17:31 +0100 Subject: [PATCH 087/580] Fix: Keep rules sorted --- .php_cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.php_cs b/.php_cs index ff43b3584..dd140b051 100644 --- a/.php_cs +++ b/.php_cs @@ -24,12 +24,12 @@ return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setRules(array( '@PSR2' => true, + 'array_syntax' => array('syntax' => 'long'), 'binary_operator_spaces' => true, 'blank_line_before_return' => true, 'cast_spaces' => true, 'header_comment' => array('header' => $header), 'include' => true, - 'array_syntax' => array('syntax' => 'long'), 'method_separation' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, @@ -51,8 +51,8 @@ return PhpCsFixer\Config::create() 'phpdoc_trim' => true, 'phpdoc_types' => true, 'psr0' => true, - 'single_blank_line_before_namespace' => true, 'short_scalar_cast' => true, + 'single_blank_line_before_namespace' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, 'trailing_comma_in_multiline_array' => true, From f8dc77db1804dee7beace5a4ad2cc36578672744 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sun, 11 Mar 2018 18:00:03 +0000 Subject: [PATCH 088/580] Fix bc --- src/Composer/XdebugHandler.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 015ac808a..eb94e93f4 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -12,11 +12,20 @@ namespace Composer; -trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead.', E_USER_DEPRECATED); +use Symfony\Component\Console\Output\OutputInterface; + +trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead,', E_USER_DEPRECATED); /** * @deprecated use Composer\XdebugHandler\XdebugHandler instead */ -class XdebugHandler +class XdebugHandler extends XdebugHandler\XdebugHandler { + const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; + const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION'; + + public function __construct(OutputInterface $output) + { + parent::__construct('composer', '--ansi'); + } } From 2413b55c60e42adee0b40014590fbe3f58145663 Mon Sep 17 00:00:00 2001 From: Hector Prats Date: Wed, 14 Mar 2018 17:38:12 +0100 Subject: [PATCH 089/580] LocalChanges for ArchiveFiles --- src/Composer/Command/StatusCommand.php | 16 ++- src/Composer/Downloader/ArchiveDownloader.php | 6 +- .../Downloader/DownloaderInterface.php | 9 ++ src/Composer/Downloader/FileDownloader.php | 26 +++- src/Composer/Package/Comparer/Comparer.php | 128 ++++++++++++++++++ 5 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 src/Composer/Package/Comparer/Comparer.php diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index e45d7b7c7..02dbf1b6b 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\Downloader\DownloaderInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -44,7 +45,8 @@ class StatusCommand extends BaseCommand ->setDefinition(array( new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), )) - ->setHelp(<<setHelp( + <<getLocalChanges($package, $targetDir)) { $errors[$targetDir] = $changes; } - } - - if ($downloader instanceof VcsCapableDownloaderInterface) { + } elseif ($downloader instanceof VcsCapableDownloaderInterface) { if ($currentRef = $downloader->getVcsReference($package, $targetDir)) { switch ($package->getInstallationSource()) { case 'source': @@ -121,12 +121,14 @@ EOT ); } } - } - - if ($downloader instanceof DvcsDownloaderInterface) { + } elseif ($downloader instanceof DvcsDownloaderInterface) { if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { $unpushedChanges[$targetDir] = $unpushed; } + } elseif ($downloader instanceof DownloaderInterface) { + if ($changes = $downloader->getLocalChanges($package, $targetDir)) { + $errors[$targetDir] = $changes; + } } } diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 24256c81f..d041a7f88 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -27,6 +27,8 @@ abstract class ArchiveDownloader extends FileDownloader { /** * {@inheritDoc} + * @throws \RuntimeException + * @throws \UnexpectedValueException */ public function download(PackageInterface $package, $path, $output = true) { @@ -35,7 +37,9 @@ abstract class ArchiveDownloader extends FileDownloader while ($retries--) { $fileName = parent::download($package, $path, $output); - $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); + if ($output) { + $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); + } try { $this->filesystem->ensureDirectoryExists($temporaryDir); diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index 713bf36dc..d3ed12cdf 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -61,4 +61,13 @@ interface DownloaderInterface * @return DownloaderInterface */ public function setOutputProgress($outputProgress); + + /** + * Checks for changes to the local copy + * + * @param PackageInterface $package package instance + * @param string $path package directory + * @return string|null changes or null + */ + public function getLocalChanges(PackageInterface $package, $path); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 0cc531704..7655aabe3 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\Cache; use Composer\Factory; use Composer\IO\IOInterface; +use Composer\Package\Comparer\Comparer; use Composer\Package\PackageInterface; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; @@ -166,7 +167,9 @@ class FileDownloader implements DownloaderInterface $this->cache->copyFrom($cacheKey, $fileName); } } else { - $this->io->writeError('Loading from cache', false); + if (!$this->outputProgress) { + $this->io->writeError('Loading from cache', false); + } } if (!file_exists($fileName)) { @@ -278,4 +281,25 @@ class FileDownloader implements DownloaderInterface return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); } + + /** + * {@inheritDoc} + * @throws \RuntimeException + */ + public function getLocalChanges(PackageInterface $package, $targetDir) + { + if ($this->outputProgress) { + $this->io->writeError(' - Installing Original ' . $package->getName() . ' (' . $package->getFullPrettyVersion() . ') and Checking: ', true); + } + $this->download($package, $targetDir.'_compare', false); + + $comparer = new Comparer(); + $comparer->setSource($targetDir.'_compare'); + $comparer->setUpdate($targetDir); + $comparer->doCompare(); + $output = $comparer->getChanged(true, true); + $this->filesystem->removeDirectory($targetDir.'_compare'); + + return trim($output); + } } diff --git a/src/Composer/Package/Comparer/Comparer.php b/src/Composer/Package/Comparer/Comparer.php new file mode 100644 index 000000000..ed1931e82 --- /dev/null +++ b/src/Composer/Package/Comparer/Comparer.php @@ -0,0 +1,128 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Comparer; + +/** + * class Comparer + * + * @author Hector Prats + */ +class Comparer +{ + private $source; + private $update; + private $changed; + + public function setSource($source) + { + $this->source = $source; + } + + public function setUpdate($update) + { + $this->update = $update; + } + + public function getChanged($toString = false, $explicated = false) + { + $changed = $this->changed; + if (!count($changed)) { + return false; + } + if ($explicated) { + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')'; + } + } + } + + if ($toString) { + foreach ($changed as $sectionKey => $itemSection) { + foreach ($itemSection as $itemKey => $item) { + $changed['string'][] = $item."\r\n"; + } + } + $changed = implode("\r\n", $changed['string']); + } + + return $changed; + } + + public function doCompare() + { + $source = array(); + $destination = array(); + $this->changed = array(); + $currentDirectory = getcwd(); + chdir($this->source); + $source = $this->doTree('.', $source); + if (!is_array($source)) { + return; + } + chdir($this->update); + $destination = $this->doTree('.', $destination); + if (!is_array($destination)) { + exit; + } + chdir($currentDirectory); + foreach ($source as $dir => $value) { + foreach ($value as $file => $hash) { + if (isset($destination[$dir][$file])) { + if ($hash !== $destination[$dir][$file]) { + $this->changed['changed'][] = $dir.'/'.$file; + } + } else { + $this->changed['removed'][] = $dir.'/'.$file; + } + } + } + foreach ($destination as $dir => $value) { + foreach ($value as $file => $hash) { + if (!isset($source[$dir][$file])) { + $this->changed['added'][] = $dir.'/'.$file; + } + } + } + } + + private function doTree($dir, &$array) + { + if ($dh = opendir($dir)) { + while ($file = readdir($dh)) { + if ($file !== '.' && $file !== '..') { + if (is_dir($dir.'/'.$file)) { + if (!count($array)) { + $array[0] = 'Temp'; + } + if (!$this->doTree($dir.'/'.$file, $array)) { + return false; + } + } else { + if (filesize($dir.'/'.$file)) { + set_time_limit(30); + $array[$dir][$file] = md5_file($dir.'/'.$file); + } + } + } + } + if (count($array) > 1 && isset($array['0'])) { + unset($array['0']); + } + + return $array; + } + + return false; + } +} From 90ac5e0749a8b3040eb6edfa88f69b466b2ab4c9 Mon Sep 17 00:00:00 2001 From: Hector Prats Date: Fri, 16 Mar 2018 13:15:15 +0100 Subject: [PATCH 090/580] improving doc --- src/Composer/Command/StatusCommand.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 02dbf1b6b..d558f46fd 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -37,6 +37,9 @@ class StatusCommand extends BaseCommand const EXIT_CODE_UNPUSHED_CHANGES = 2; const EXIT_CODE_VERSION_CHANGES = 4; + /** + * @throws \Symfony\Component\Console\Exception\InvalidArgumentException + */ protected function configure() { $this @@ -55,6 +58,11 @@ EOT ; } + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null + */ protected function execute(InputInterface $input, OutputInterface $output) { // init repos From 8f65518ed30b0ec0a13356e056aafd1e359a05e2 Mon Sep 17 00:00:00 2001 From: Sven Luijten Date: Fri, 23 Mar 2018 11:37:33 +0100 Subject: [PATCH 091/580] Avoid using 'just' in documentation --- doc/00-intro.md | 19 +++++++++---------- doc/01-basic-usage.md | 8 ++++---- doc/03-cli.md | 16 ++++++++-------- doc/04-schema.md | 12 ++++++------ doc/05-repositories.md | 8 ++++---- doc/articles/aliases.md | 4 ++-- .../handling-private-packages-with-satis.md | 4 ++-- doc/articles/troubleshooting.md | 4 ++-- doc/articles/versions.md | 4 ++-- ...ng-comparisons-and-wildcards-a-bad-idea.md | 2 +- 10 files changed, 40 insertions(+), 41 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index e0a81b05b..3509a59d3 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -57,16 +57,15 @@ project, or globally as a system wide executable. #### Locally -Installing Composer locally is a matter of just running the installer in your -project directory. See [the Download page](https://getcomposer.org/download/) -for instructions. +To install Composer locally, run the installer in your project directory. See +[the Download page](https://getcomposer.org/download/) for instructions. -The installer will just check a few PHP settings and then download -`composer.phar` to your working directory. This file is the Composer binary. It -is a PHAR (PHP archive), which is an archive format for PHP which can be run on +The installer will check a few PHP settings and then download `composer.phar` +to your working directory. This file is the Composer binary. It is a PHAR +(PHP archive), which is an archive format for PHP which can be run on the command line, amongst other things. -Now just run `php composer.phar` in order to run Composer. +Now run `php composer.phar` in order to run Composer. You can install Composer to a specific directory by using the `--install-dir` option and additionally (re)name it as well using the `--filename` option. When @@ -78,7 +77,7 @@ following parameters: php composer-setup.php --install-dir=bin --filename=composer ``` -Now just run `php bin/composer` in order to run Composer. +Now run `php bin/composer` in order to run Composer. #### Globally @@ -105,7 +104,7 @@ mv composer.phar /usr/local/bin/composer > **Note:** For information on changing your PATH, please read the > [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google. -Now just run `composer` in order to run Composer instead of `php composer.phar`. +Now run `composer` in order to run Composer instead of `php composer.phar`. ## Installation - Windows @@ -115,7 +114,7 @@ This is the easiest way to get Composer set up on your machine. Download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). It will -install the latest Composer version and set up your PATH so that you can just +install the latest Composer version and set up your PATH so that you can call `composer` from any directory in your command line. > **Note:** Close your current terminal. Test usage with a new terminal: This is diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 3119fd015..9c57f3e92 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -44,7 +44,7 @@ about Packagist [below](#packagist), or read more about repositories ### Package Names The package name consists of a vendor name and the project's name. Often these -will be identical - the vendor name just exists to prevent naming clashes. For +will be identical - the vendor name only exists to prevent naming clashes. For example, it would allow two different people to create a library named `json`. One might be named `igorw/json` while the other might be `seldaek/json`. @@ -86,7 +86,7 @@ versions, how versions relate to each other, and on version constraints. ## Installing Dependencies -To install the defined dependencies for your project, just run the +To install the defined dependencies for your project, run the [`install`](03-cli.md#install) command. ```sh @@ -196,7 +196,7 @@ includes PHP itself, PHP extensions and some system libraries. * `ext-` allows you to require PHP extensions (includes core extensions). Versioning can be quite inconsistent here, so it's often - a good idea to just set the constraint to `*`. An example of an extension + a good idea to set the constraint to `*`. An example of an extension package name is `ext-gd`. * `lib-` allows constraints to be made on versions of libraries used by @@ -258,7 +258,7 @@ more information. See also the docs on [optimizing the autoloader](articles/autoloader-optimization.md). > **Note:** Composer provides its own autoloader. If you don't want to use that -> one, you can just include `vendor/composer/autoload_*.php` files, which return +> one, you can include `vendor/composer/autoload_*.php` files, which return > associative arrays allowing you to configure your own autoloader. ← [Intro](00-intro.md) | [Libraries](02-libraries.md) → diff --git a/doc/03-cli.md b/doc/03-cli.md index 31d0b6b50..017e2cb4e 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -129,7 +129,7 @@ php composer.phar update This will resolve all dependencies of the project and write the exact versions into `composer.lock`. -If you just want to update a few packages and not all, you can list them as such: +If you only want to update a few packages and not all, you can list them as such: ```sh php composer.phar update vendor/package vendor/package2 @@ -184,7 +184,7 @@ php composer.phar require After adding/changing the requirements, the modified requirements will be installed or updated. -If you do not want to choose requirements interactively, you can just pass them +If you do not want to choose requirements interactively, you can pass them to the command. ```sh @@ -272,7 +272,7 @@ This can be used to install CLI utilities globally. Here is an example: php composer.phar global require friendsofphp/php-cs-fixer ``` -Now the `php-cs-fixer` binary is available globally. Just make sure your global +Now the `php-cs-fixer` binary is available globally. Make sure your global [vendor binaries](articles/vendor-binaries.md) directory is in your `$PATH` environment variable, you can get its location with the following command : @@ -280,7 +280,7 @@ environment variable, you can get its location with the following command : php composer.phar global config bin-dir --absolute ``` -If you wish to update the binary later on you can just run a global update: +If you wish to update the binary later on you can run a global update: ```sh php composer.phar global update @@ -289,7 +289,7 @@ php composer.phar global update ## search The search command allows you to search through the current project's package -repositories. Usually this will be just packagist. You simply pass it the +repositories. Usually this will be packagist. You simply pass it the terms you want to search for. ```sh @@ -520,7 +520,7 @@ vendor/seld/jsonlint: ## self-update (selfupdate) -To update Composer itself to the latest version, just run the `self-update` +To update Composer itself to the latest version, run the `self-update` command. It will replace your `composer.phar` with the latest version. ```sh @@ -721,7 +721,7 @@ Lists the name, version and license of every package installed. Use * **--list (-l):** List user defined scripts. To run [scripts](articles/scripts.md) manually you can use this command, -just give it the script name and optionally any required arguments. +give it the script name and optionally any required arguments. ## exec @@ -762,7 +762,7 @@ php composer.phar archive vendor/package 2.0.21 --format=zip ## help -To get more information about a certain command, just use `help`. +To get more information about a certain command, you can use `help`. ```sh php composer.phar help install diff --git a/doc/04-schema.md b/doc/04-schema.md index 9e846168a..969082cba 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -43,7 +43,7 @@ Required for published packages (libraries). ### description -A short description of the package. Usually this is just one line long. +A short description of the package. Usually this is one line long. Required for published packages (libraries). @@ -104,7 +104,7 @@ Out of the box, Composer supports four types: [dedicated article](articles/custom-installers.md). Only use a custom type if you need custom logic during installation. It is -recommended to omit this field and have it just default to `library`. +recommended to omit this field and have it default to `library`. ### keywords @@ -272,7 +272,7 @@ All links are optional fields. `require` and `require-dev` additionally support stability flags ([root-only](04-schema.md#root-package)). These allow you to further restrict or expand the stability of a package beyond the scope of the [minimum-stability](#minimum-stability) setting. You can apply -them to a constraint, or just apply them to an empty constraint if you want to +them to a constraint, or apply them to an empty constraint if you want to allow unstable packages of a dependency for example. Example: @@ -406,7 +406,7 @@ simply list it in `provide`. #### suggest Suggested packages that can enhance or work well with this package. These are -just informational and are displayed after the package is installed, to give +informational and are displayed after the package is installed, to give your users a hint that they could add more packages, even though they are not strictly required. @@ -711,7 +711,7 @@ Use `"prefer-stable": true` to enable. Custom package repositories to use. -By default Composer just uses the packagist repository. By specifying +By default Composer only uses the packagist repository. By specifying repositories you can get packages from elsewhere. Repositories are not resolved recursively. You can only add them to your main @@ -731,7 +731,7 @@ The following repository types are supported: project. * **package:** If you depend on a project that does not have any support for composer whatsoever you can define the package inline using a `package` - repository. You basically just inline the `composer.json` object. + repository. You basically inline the `composer.json` object. For more information on any of these, see [Repositories](05-repositories.md). diff --git a/doc/05-repositories.md b/doc/05-repositories.md index c28f34141..af0d0b01e 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -11,7 +11,7 @@ understand some of the basic concepts that Composer is built on. ### Package Composer is a dependency manager. It installs packages locally. A package is -essentially just a directory containing something. In this case it is PHP +essentially a directory containing something. In this case it is PHP code, but in theory it could be anything. And it contains a package description which has a name and a version. The name and the version are used to identify the package. @@ -57,7 +57,7 @@ The main repository type is the `composer` repository. It uses a single `packages.json` file that contains all of the package metadata. This is also the repository type that packagist uses. To reference a -`composer` repository, just supply the path before the `packages.json` file. +`composer` repository, supply the path before the `packages.json` file. In the case of packagist, that file is located at `/packages.json`, so the URL of the repository would be `packagist.org`. For `example.org/packages.json` the repository URL would be `example.org`. @@ -177,7 +177,7 @@ integrity, for example: The file above declares that acme/foo and acme/bar can be found in this repository, by loading the file referenced by `providers-url`, replacing `%package%` by the vendor namespaced package name and `%hash%` by the -sha256 field. Those files themselves just contain package definitions as +sha256 field. Those files themselves contain package definitions as described [above](#packages). These fields are optional. You probably don't need them for your own custom @@ -606,7 +606,7 @@ private packages: } ``` -Each zip artifact is just a ZIP archive with `composer.json` in root folder: +Each zip artifact is a ZIP archive with `composer.json` in root folder: ```sh unzip -l acme-corp-parser-10.3.5.zip diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index bda6f79a8..617551b61 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -60,7 +60,7 @@ Branch aliases are great for aliasing main development lines. But in order to use them you need to have control over the source repository, and you need to commit changes to version control. -This is not really fun when you just want to try a bugfix of some library that +This is not really fun when you want to try a bugfix of some library that is a dependency of your local project. For this reason, you can alias packages in your `require` and `require-dev` @@ -72,7 +72,7 @@ local project. You are using `symfony/monolog-bundle` which requires `monolog/monolog` version `1.*`. So you need your `dev-bugfix` to match that constraint. -Just add this to your project's root `composer.json`: +Add this to your project's root `composer.json`: ```json { diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index fb799a720..cdf31f6e4 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -77,7 +77,7 @@ or another constraint if you want really specific versions. } ``` -Once you've done this, you just run: +Once you've done this, you run: php bin/satis build @@ -306,7 +306,7 @@ be marked abandoned as well. It is possible to make satis automatically resolve and add all dependencies for your projects. This can be used with the Downloads functionality to have a -complete local mirror of packages. Just add the following to your `satis.json`: +complete local mirror of packages. Add the following to your `satis.json`: ```json { diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 80e639a63..92335ea3c 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -163,7 +163,7 @@ please report this [issue](https://github.com/composer/composer/issues). 2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`, `HKEY_CURRENT_USER\Software\Microsoft\Command Processor` or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`. -3. Check if it contains any path to non-existent file, if it's the case, just remove them. +3. Check if it contains any path to non-existent file, if it's the case, remove them. ## API rate limit and OAuth tokens @@ -281,7 +281,7 @@ for everyone. ## Composer hangs with SSH ControlMaster When you try to install packages from a Git repository and you use the `ControlMaster` -setting for your SSH connection, Composer might just hang endlessly and you see a `sh` +setting for your SSH connection, Composer might hang endlessly and you see a `sh` process in the `defunct` state in your process list. The reason for this is a SSH Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1988 diff --git a/doc/articles/versions.md b/doc/articles/versions.md index a584d3e88..c1fb25551 100644 --- a/doc/articles/versions.md +++ b/doc/articles/versions.md @@ -74,11 +74,11 @@ correct location in your `vendor` directory. ### Branches -If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it just copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).) +If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).) In the above example, if you wanted to check out the `my-feature` branch, you would specify `dev-my-feature` as the version constraint in your `require` clause. This would result in Composer cloning the `my-library` repository into my `vendor` directory and checking out the `my-feature` branch. -When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can just name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix. +When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix. ### Minimum Stability diff --git a/doc/faqs/why-are-version-constraints-combining-comparisons-and-wildcards-a-bad-idea.md b/doc/faqs/why-are-version-constraints-combining-comparisons-and-wildcards-a-bad-idea.md index bac633a3b..f4aa5b157 100644 --- a/doc/faqs/why-are-version-constraints-combining-comparisons-and-wildcards-a-bad-idea.md +++ b/doc/faqs/why-are-version-constraints-combining-comparisons-and-wildcards-a-bad-idea.md @@ -16,6 +16,6 @@ but it is not possible to determine if when you wrote that you were thinking of a package in version 3.0.0 or not. Should it match because you asked for `>=2` or should it not match because you asked for a `2.*`? -For this reason, Composer just throws an error and says that this is invalid. +For this reason, Composer throws an error and says that this is invalid. The easy way to fix it is to think about what you really mean, and use only one of those rules. \ No newline at end of file From b7ab08151937b4e51593ee34563c8cce5aae69ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sat, 24 Mar 2018 07:32:04 +0000 Subject: [PATCH 092/580] Allow Composer to be used without running the application For [Humbug Box](https://github.com/humbug/box/blob/master/src/Composer/ComposerOrchestrator.php#L30) we are using Composer to dump the autoload. To do so I'm using the `Composer` class from the application: ```php $composer = (new ComposerApplication())->getComposer(); ``` If you do so however this is going to fail because `Application#io` is null instead of being a `IOInterface` instance. Indeed it is initialised only when the application is run. So one solution is to initialised it with a dummy IO and the right IO object will be set when the application is run as usual. --- src/Composer/Console/Application.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 655b059d7..a7cf26921 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -12,6 +12,7 @@ namespace Composer\Console; +use Composer\IO\NullIO; use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; @@ -85,6 +86,8 @@ class Application extends BaseApplication }); } + $this->io = new NullIO(); + parent::__construct('Composer', Composer::VERSION); } From 3536c2970a32ff88fb2839880521cd1fa9627d41 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Mar 2018 23:28:02 +0200 Subject: [PATCH 093/580] Try adding a high deps build, fixes #7124 --- .travis.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b8cf1391..f2b804822 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,16 +27,16 @@ php: matrix: include: - - dist: precise - php: 5.3 + - php: 5.3 + dist: precise + - php: 7.2 + env: deps=high fast_finish: true allow_failures: - php: nightly before_install: - # determine INI file - - if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; then export INI=/etc/hhvm/php.ini; else export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi - # disable xdebug if available + # disable xdebug if available - phpenv config-rm xdebug.ini || echo "xdebug not available" # disable default memory limit - echo memory_limit = -1 >> $INI @@ -44,6 +44,8 @@ before_install: install: # flags to pass to install - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" + # update deps to latest in case of high deps build + - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi # install dependencies using system provided composer binary - composer install $flags # install dependencies using composer from source From c2fbbe3b86788eb507a0f4f45f9fdc623ca579c4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Mar 2018 23:36:46 +0200 Subject: [PATCH 094/580] Restore $INI var --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f2b804822..16a3b073c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,7 @@ before_install: # disable xdebug if available - phpenv config-rm xdebug.ini || echo "xdebug not available" # disable default memory limit + - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo memory_limit = -1 >> $INI install: From 491ae0634a680fb6fb655d9860d25d5bed2d9079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=97=AB=E5=85=B4=E8=8C=82?= Date: Fri, 30 Mar 2018 14:24:04 +0800 Subject: [PATCH 095/580] Fix bug for scripts for config command --- src/Composer/Command/ConfigCommand.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 6e457dd78..940f5df65 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -612,6 +612,15 @@ EOT return; } + // handle script + if (preg_match('/^scripts\.(.+)/', $settingKey,$matches)){ + if ($input->getOption('unset')) { + return $this->configSource->removeConfigSetting($settingKey); + } + + return $this->configSource->addConfigSetting($settingKey, $values[0]); + } + throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); } From 75be28d4a30307dc94b033ab87b15fd710f90738 Mon Sep 17 00:00:00 2001 From: Moviuro Date: Tue, 3 Apr 2018 15:02:24 +0200 Subject: [PATCH 096/580] how-to-install-composer-programmatically.md: quotes are seatbelts --- doc/faqs/how-to-install-composer-programmatically.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/faqs/how-to-install-composer-programmatically.md b/doc/faqs/how-to-install-composer-programmatically.md index 70568c506..f075df65c 100644 --- a/doc/faqs/how-to-install-composer-programmatically.md +++ b/doc/faqs/how-to-install-composer-programmatically.md @@ -9,9 +9,9 @@ An alternative is to use this script which only works with unix utils: ```bash #!/bin/sh -EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig) +EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" -ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") +ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")" if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] then From 30e9ede1e371b66e805e0ec797e59dc538e76a65 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Wed, 11 Apr 2018 17:19:13 +0100 Subject: [PATCH 097/580] Update to 1.1.0 release --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index bbeb02c42..ef423e8da 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^1.0", + "composer/xdebug-handler": "^1.1", "seld/jsonlint": "^1.4", "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", diff --git a/composer.lock b/composer.lock index ea095dbc6..807f52b86 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "6f0e4eb8b744ac97ff62245bed9d8aa5", + "content-hash": "0d1f37a66bf7821e9aa424785ea8ab52", "packages": [ { "name": "composer/ca-bundle", @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a" + "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1852e549ad0d6f2910f89c27fec833dda1b25b4a", - "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", + "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-03-08T13:09:50+00:00" + "time": "2018-04-11T15:42:36+00:00" }, { "name": "justinrainbow/json-schema", From 05c9542db8139dc4a2816301ae163e3395633d46 Mon Sep 17 00:00:00 2001 From: Thomas Flori Date: Thu, 12 Apr 2018 06:37:16 +0200 Subject: [PATCH 098/580] add readme to support fields in the schema docs --- doc/04-schema.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/04-schema.md b/doc/04-schema.md index 969082cba..d36e90eaf 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -237,6 +237,7 @@ Support information includes the following: * **source:** URL to browse or download the sources. * **docs:** URL to the documentation. * **rss:** URL to the RSS feed. +* **readme:** Relative path to the readme document. An example: From 8d06832077b110cce75cee881212468c76bb2487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20/Peggy/=20Sl=C3=A1dek?= Date: Wed, 11 Apr 2018 12:33:27 +0200 Subject: [PATCH 099/580] Update ClassMapGenerator to work better with symlinks, fixes #7252, closes #7251 --- src/Composer/Autoload/ClassMapGenerator.php | 4 ++++ .../Test/Autoload/AutoloadGeneratorTest.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 649c700c8..5ff23921c 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -94,6 +94,10 @@ class ClassMapGenerator if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) { continue; } + // check non-realpath of file for directories symlink in project dir + if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) { + continue; + } $classes = self::findClasses($filePath); diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 22ceec030..5456769b8 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -23,6 +23,7 @@ use Composer\Repository\InstalledRepositoryInterface; use Composer\Installer\InstallationManager; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\Platform; use PHPUnit_Framework_MockObject_MockObject as MockObject; class AutoloadGeneratorTest extends TestCase @@ -1292,6 +1293,7 @@ EOF; ), 'classmap' => array('composersrc/'), 'exclude-from-classmap' => array( + '/composersrc/foo/bar/', '/composersrc/excludedTests/', '/composersrc/ClassToExclude.php', '/composersrc/*/excluded/excsubpath', @@ -1326,6 +1328,18 @@ EOF; file_put_contents($this->workingDir.'/composersrc/long/excluded/excsubpath/foo.php', 'workingDir.'/composersrc/long/excluded/excsubpath/bar.php', 'fs->ensureDirectoryExists($this->workingDir.'/forks/bar/src/exclude'); + $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc/foo'); + + file_put_contents($this->workingDir.'/forks/bar/src/exclude/FooExclClass.php', 'workingDir.'/forks/bar/'; + $link = $this->workingDir.'/composersrc/foo/bar/'; + $command = Platform::isWindows() + ? 'mklink /j "' . str_replace('/', '\\', $link) . '" "' . str_replace('/', '\\', $target) + : 'ln -s "' . $target . '" "' . $link; + exec($command); + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); // Assert that autoload_classmap.php was correctly generated. From 036fc44c25e051479f06435800d76c4301d9b1fa Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 09:51:01 +0200 Subject: [PATCH 100/580] Make sure aliased packages are removed correctly from the repository, fixes #7167 --- src/Composer/Repository/ArrayRepository.php | 7 +++++++ tests/Composer/Test/Repository/ArrayRepositoryTest.php | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 4f0409a60..7a4251084 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -167,6 +167,13 @@ class ArrayRepository extends BaseRepository { $packageId = $package->getUniqueName(); + if ($package instanceof AliasPackage) { + $aliasedPackage = $package->getAliasOf(); + if ($this === $aliasedPackage->getRepository()) { + $this->removePackage($aliasedPackage); + } + } + foreach ($this->getPackages() as $key => $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { array_splice($this->packages, $key, 1); diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index 799e76a5b..5927325ee 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -68,7 +68,7 @@ class ArrayRepositoryTest extends TestCase $this->assertEquals('bar', $bar[0]->getName()); } - public function testAutomaticallyAddAliasedPackage() + public function testAutomaticallyAddAndRemoveAliasedPackage() { $repo = new ArrayRepository(); @@ -80,6 +80,10 @@ class ArrayRepositoryTest extends TestCase $this->assertCount(2, $repo); $this->assertTrue($repo->hasPackage($this->getPackage('foo', '1'))); $this->assertTrue($repo->hasPackage($this->getPackage('foo', '2'))); + + $repo->removePackage($alias); + + $this->assertCount(0, $repo); } public function testSearch() From 066351c5b9a619c7e23833d1a6e7225d14fda70a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 10:24:56 +0200 Subject: [PATCH 101/580] Remove use of deprecated getMock method --- tests/Composer/Test/ApplicationTest.php | 8 +- .../Test/Autoload/AutoloadGeneratorTest.php | 4 +- tests/Composer/Test/CacheTest.php | 11 ++- .../Test/Command/RunScriptCommandTest.php | 4 +- tests/Composer/Test/ComposerTest.php | 8 +- .../Test/Downloader/ArchiveDownloaderTest.php | 32 +++++--- .../Test/Downloader/DownloadManagerTest.php | 4 +- .../Test/Downloader/FileDownloaderTest.php | 20 ++--- .../Test/Downloader/FossilDownloaderTest.php | 28 +++---- .../Test/Downloader/GitDownloaderTest.php | 54 ++++++------- .../Test/Downloader/HgDownloaderTest.php | 28 +++---- .../Downloader/PerforceDownloaderTest.php | 14 ++-- .../Test/Downloader/XzDownloaderTest.php | 6 +- .../Test/Downloader/ZipDownloaderTest.php | 32 ++++---- .../EventDispatcher/EventDispatcherTest.php | 42 +++++----- tests/Composer/Test/IO/ConsoleIOTest.php | 80 +++++++++---------- .../Installer/InstallationManagerTest.php | 2 +- .../Test/Installer/InstallerEventTest.php | 8 +- .../Test/Installer/LibraryInstallerTest.php | 4 +- .../Installer/MetapackageInstallerTest.php | 4 +- .../SuggestedPackagesReporterTest.php | 10 +-- tests/Composer/Test/InstallerTest.php | 12 ++- .../Composer/Test/Package/BasePackageTest.php | 6 +- .../Test/Package/Dumper/ArrayDumperTest.php | 6 +- .../Loader/ValidatingArrayLoaderTest.php | 8 +- .../Package/Version/VersionSelectorTest.php | 4 +- .../Test/Plugin/PluginInstallerTest.php | 6 +- .../Repository/ComposerRepositoryTest.php | 16 ++-- .../Test/Repository/RepositoryFactoryTest.php | 4 +- .../Test/Repository/RepositoryManagerTest.php | 12 +-- .../Test/Repository/Vcs/FossilDriverTest.php | 2 +- .../Repository/Vcs/GitBitbucketDriverTest.php | 2 +- .../Test/Repository/Vcs/GitHubDriverTest.php | 14 ++-- .../Test/Repository/Vcs/HgDriverTest.php | 2 +- .../Repository/Vcs/PerforceDriverTest.php | 4 +- .../Test/Repository/Vcs/SvnDriverTest.php | 6 +- tests/Composer/Test/Util/BitbucketTest.php | 6 +- tests/Composer/Test/Util/GitHubTest.php | 2 +- tests/Composer/Test/Util/GitLabTest.php | 2 +- tests/Composer/Test/Util/PerforceTest.php | 10 +-- .../Test/Util/ProcessExecutorTest.php | 2 +- .../Test/Util/RemoteFilesystemTest.php | 22 ++--- 42 files changed, 279 insertions(+), 272 deletions(-) diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 4fa326241..37d434fa8 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -22,8 +22,8 @@ class ApplicationTest extends TestCase { $application = new Application; - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $index = 0; $inputMock->expects($this->at($index++)) @@ -75,8 +75,8 @@ class ApplicationTest extends TestCase $application->add(new \Composer\Command\SelfUpdateCommand); - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $index = 0; $inputMock->expects($this->at($index++)) diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 5456769b8..c676263a8 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -93,7 +93,7 @@ class AutoloadGeneratorTest extends TestCase $this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload'; $this->ensureDirectoryExistsAndClear($this->vendorDir); - $this->config = $this->getMock('Composer\Config'); + $this->config = $this->getMockBuilder('Composer\Config')->getMock(); $this->configValueMap = array( 'vendor-dir' => function () use ($that) { @@ -128,7 +128,7 @@ class AutoloadGeneratorTest extends TestCase return $that->vendorDir.'/'.$package->getName() . ($targetDir ? '/'.$targetDir : ''); })); - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); + $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); $this->eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->disableOriginalConstructor() diff --git a/tests/Composer/Test/CacheTest.php b/tests/Composer/Test/CacheTest.php index 4c6757489..9830fd7de 100644 --- a/tests/Composer/Test/CacheTest.php +++ b/tests/Composer/Test/CacheTest.php @@ -35,12 +35,11 @@ class CacheTest extends TestCase $this->finder = $this->getMockBuilder('Symfony\Component\Finder\Finder')->disableOriginalConstructor()->getMock(); - $io = $this->getMock('Composer\IO\IOInterface'); - $this->cache = $this->getMock( - 'Composer\Cache', - array('getFinder'), - array($io, $this->root) - ); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->cache = $this->getMockBuilder('Composer\Cache') + ->setMethods(array('getFinder')) + ->setConstructorArgs(array($io, $this->root)) + ->getMock(); $this->cache ->expects($this->any()) ->method('getFinder') diff --git a/tests/Composer/Test/Command/RunScriptCommandTest.php b/tests/Composer/Test/Command/RunScriptCommandTest.php index 83ec63b76..53478b323 100644 --- a/tests/Composer/Test/Command/RunScriptCommandTest.php +++ b/tests/Composer/Test/Command/RunScriptCommandTest.php @@ -28,7 +28,7 @@ class RunScriptCommandTest extends TestCase { $scriptName = 'testScript'; - $input = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $input = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $input ->method('getOption') ->will($this->returnValueMap(array( @@ -48,7 +48,7 @@ class RunScriptCommandTest extends TestCase ->with('command') ->willReturn(false); - $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $expectedDevMode = $dev || !$noDev; diff --git a/tests/Composer/Test/ComposerTest.php b/tests/Composer/Test/ComposerTest.php index df00c36f7..aabe1deab 100644 --- a/tests/Composer/Test/ComposerTest.php +++ b/tests/Composer/Test/ComposerTest.php @@ -20,7 +20,7 @@ class ComposerTest extends TestCase public function testSetGetPackage() { $composer = new Composer(); - $package = $this->getMock('Composer\Package\RootPackageInterface'); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $composer->setPackage($package); $this->assertSame($package, $composer->getPackage()); @@ -47,8 +47,8 @@ class ComposerTest extends TestCase public function testSetGetDownloadManager() { $composer = new Composer(); - $io = $this->getMock('Composer\IO\IOInterface'); - $manager = $this->getMock('Composer\Downloader\DownloadManager', array(), array($io)); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')->setConstructorArgs(array($io))->getMock(); $composer->setDownloadManager($manager); $this->assertSame($manager, $composer->getDownloadManager()); @@ -57,7 +57,7 @@ class ComposerTest extends TestCase public function testSetGetInstallationManager() { $composer = new Composer(); - $manager = $this->getMock('Composer\Installer\InstallationManager'); + $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); $composer->setInstallationManager($manager); $this->assertSame($manager, $composer->getInstallationManager()); diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index c97ccb053..39b53cf8f 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -18,13 +18,13 @@ class ArchiveDownloaderTest extends TestCase { public function testGetFileName() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue('http://example.com/script.js')) ; - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'getFileName'); $method->setAccessible(true); @@ -39,12 +39,12 @@ class ArchiveDownloaderTest extends TestCase $this->markTestSkipped('Requires openssl'); } - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); $expected = 'https://github.com/composer/composer/zipball/master'; - $url = $method->invoke($downloader, $this->getMock('Composer\Package\PackageInterface'), $expected); + $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); $this->assertEquals($expected, $url); } @@ -55,12 +55,12 @@ class ArchiveDownloaderTest extends TestCase $this->markTestSkipped('Requires openssl'); } - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); $expected = 'https://github.com/composer/composer/archive/master.tar.gz'; - $url = $method->invoke($downloader, $this->getMock('Composer\Package\PackageInterface'), $expected); + $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); $this->assertEquals($expected, $url); } @@ -71,12 +71,12 @@ class ArchiveDownloaderTest extends TestCase $this->markTestSkipped('Requires openssl'); } - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); $expected = 'https://api.github.com/repos/composer/composer/zipball/master'; - $url = $method->invoke($downloader, $this->getMock('Composer\Package\PackageInterface'), $expected); + $url = $method->invoke($downloader, $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(), $expected); $this->assertEquals($expected, $url); } @@ -90,14 +90,14 @@ class ArchiveDownloaderTest extends TestCase $this->markTestSkipped('Requires openssl'); } - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); $type = strpos($url, 'tar') ? 'tar' : 'zip'; $expected = 'https://api.github.com/repos/composer/composer/'.$type.'ball/ref'; - $package = $this->getMock('Composer\Package\PackageInterface'); + $package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $package->expects($this->any()) ->method('getDistReference') ->will($this->returnValue('ref')); @@ -127,14 +127,14 @@ class ArchiveDownloaderTest extends TestCase $this->markTestSkipped('Requires openssl'); } - $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface'), $this->getMock('Composer\Config'))); + $downloader = $this->getArchiveDownloaderMock(); $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); $url = $url . '.' . $extension; $expected = 'https://bitbucket.org/davereid/drush-virtualhost/get/ref.' . $extension; - $package = $this->getMock('Composer\Package\PackageInterface'); + $package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $package->expects($this->any()) ->method('getDistReference') ->will($this->returnValue('ref')); @@ -151,4 +151,12 @@ class ArchiveDownloaderTest extends TestCase array('https://bitbucket.org/davereid/drush-virtualhost/get/v1.0', 'tar.bz2'), ); } + + private function getArchiveDownloaderMock() + { + return $this->getMockForAbstractClass( + 'Composer\Downloader\ArchiveDownloader', + array($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getMockBuilder('Composer\Config')->getMock()) + ); + } } diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php index 9c2aa7f40..222e541d7 100644 --- a/tests/Composer/Test/Downloader/DownloadManagerTest.php +++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php @@ -22,8 +22,8 @@ class DownloadManagerTest extends TestCase public function setUp() { - $this->filesystem = $this->getMock('Composer\Util\Filesystem'); - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); } public function testSetGetDownloader() diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 9b9f7b671..1c51c2abe 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -20,8 +20,8 @@ class FileDownloaderTest extends TestCase { protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $rfs = null, $filesystem = null) { - $io = $io ?: $this->getMock('Composer\IO\IOInterface'); - $config = $config ?: $this->getMock('Composer\Config'); + $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); $rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); return new FileDownloader($io, $config, $eventDispatcher, $cache, $rfs, $filesystem); @@ -32,7 +32,7 @@ class FileDownloaderTest extends TestCase */ public function testDownloadForPackageWithoutDistReference() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getDistUrl') ->will($this->returnValue(null)) @@ -44,7 +44,7 @@ class FileDownloaderTest extends TestCase public function testDownloadToExistingFile() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getDistUrl') ->will($this->returnValue('url')) @@ -74,7 +74,7 @@ class FileDownloaderTest extends TestCase public function testGetFileName() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getDistUrl') ->will($this->returnValue('http://example.com/script.js')) @@ -89,7 +89,7 @@ class FileDownloaderTest extends TestCase public function testDownloadButFileIsUnsaved() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue($distUrl = 'http://example.com/script.js')) @@ -104,7 +104,7 @@ class FileDownloaderTest extends TestCase ; $path = $this->getUniqueTmpDirectory(); - $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->any()) ->method('write') ->will($this->returnCallback(function ($messages, $newline = true) use ($path) { @@ -137,7 +137,7 @@ class FileDownloaderTest extends TestCase { $expectedTtl = '99999999'; - $configMock = $this->getMock('Composer\Config'); + $configMock = $this->getMockBuilder('Composer\Config')->getMock(); $configMock ->expects($this->at(0)) ->method('get') @@ -166,7 +166,7 @@ class FileDownloaderTest extends TestCase public function testDownloadFileWithInvalidChecksum() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue($distUrl = 'http://example.com/script.js')) @@ -183,7 +183,7 @@ class FileDownloaderTest extends TestCase ->method('getDistUrls') ->will($this->returnValue(array($distUrl))) ; - $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $path = $this->getUniqueTmpDirectory(); $downloader = $this->getDownloader(null, null, null, null, null, $filesystem); diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 68babfd1d..2e1de934d 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -37,10 +37,10 @@ class FossilDownloaderTest extends TestCase protected function getDownloaderMock($io = null, $config = null, $executor = null, $filesystem = null) { - $io = $io ?: $this->getMock('Composer\IO\IOInterface'); - $config = $config ?: $this->getMock('Composer\Config'); - $executor = $executor ?: $this->getMock('Composer\Util\ProcessExecutor'); - $filesystem = $filesystem ?: $this->getMock('Composer\Util\Filesystem'); + $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); + $executor = $executor ?: $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $filesystem = $filesystem ?: $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); return new FossilDownloader($io, $config, $executor, $filesystem); } @@ -50,7 +50,7 @@ class FossilDownloaderTest extends TestCase */ public function testDownloadForPackageWithoutSourceReference() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -61,14 +61,14 @@ class FossilDownloaderTest extends TestCase public function testDownload() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('trunk')); $packageMock->expects($this->once()) ->method('getSourceUrls') ->will($this->returnValue(array('http://fossil.kd2.org/kd2fw/'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedFossilCommand = $this->getCmd('fossil clone \'http://fossil.kd2.org/kd2fw/\' \'repo.fossil\''); $processExecutor->expects($this->at(0)) @@ -97,8 +97,8 @@ class FossilDownloaderTest extends TestCase */ public function testUpdateforPackageWithoutSourceReference() { - $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); - $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $initialPackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $sourcePackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $sourcePackageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -116,14 +116,14 @@ class FossilDownloaderTest extends TestCase touch($file); } - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('trunk')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('http://fossil.kd2.org/kd2fw/'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedFossilCommand = $this->getCmd("fossil changes"); $processExecutor->expects($this->at(0)) @@ -144,12 +144,12 @@ class FossilDownloaderTest extends TestCase { $expectedResetCommand = $this->getCmd('cd \'composerPath\' && fossil status'); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->any()) ->method('execute') ->with($this->equalTo($expectedResetCommand)); - $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $filesystem->expects($this->any()) ->method('removeDirectory') ->with($this->equalTo('composerPath')) diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 20e609712..588d4d2ee 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -60,9 +60,9 @@ class GitDownloaderTest extends TestCase protected function getDownloaderMock($io = null, $config = null, $executor = null, $filesystem = null) { - $io = $io ?: $this->getMock('Composer\IO\IOInterface'); - $executor = $executor ?: $this->getMock('Composer\Util\ProcessExecutor'); - $filesystem = $filesystem ?: $this->getMock('Composer\Util\Filesystem'); + $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $executor = $executor ?: $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $filesystem = $filesystem ?: $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $config = $this->setupConfig($config); return new GitDownloader($io, $config, $executor, $filesystem); @@ -73,7 +73,7 @@ class GitDownloaderTest extends TestCase */ public function testDownloadForPackageWithoutSourceReference() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -84,7 +84,7 @@ class GitDownloaderTest extends TestCase public function testDownload() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('1234567890123456789012345678901234567890')); @@ -97,7 +97,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('dev-master')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -135,7 +135,7 @@ class GitDownloaderTest extends TestCase public function testDownloadWithCache() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('1234567890123456789012345678901234567890')); @@ -148,7 +148,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('dev-master')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -201,7 +201,7 @@ class GitDownloaderTest extends TestCase public function testDownloadUsesVariousProtocolsAndSetsPushUrlForGithub() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -214,7 +214,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('1.0.0')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -285,7 +285,7 @@ class GitDownloaderTest extends TestCase */ public function testDownloadAndSetPushUrlUseCustomVariousProtocolsForGithub($protocols, $url, $pushUrl) { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -298,7 +298,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('1.0.0')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -338,14 +338,14 @@ class GitDownloaderTest extends TestCase public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() { $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('https://example.com/composer/composer'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($this->winCompat('git --version'))) @@ -368,8 +368,8 @@ class GitDownloaderTest extends TestCase */ public function testUpdateforPackageWithoutSourceReference() { - $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); - $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $initialPackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $sourcePackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $sourcePackageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -382,7 +382,7 @@ class GitDownloaderTest extends TestCase { $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -392,7 +392,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('1.0.0')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) @@ -431,7 +431,7 @@ class GitDownloaderTest extends TestCase { $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -444,7 +444,7 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue('1.0.0')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) @@ -503,14 +503,14 @@ composer https://github.com/old/url (push) { $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/composer/composer'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) @@ -542,14 +542,14 @@ composer https://github.com/old/url (push) $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) @@ -600,13 +600,13 @@ composer https://github.com/old/url (push) { $expectedGitResetCommand = $this->winCompat("cd 'composerPath' && git status --porcelain --untracked-files=no"); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->any()) ->method('execute') ->with($this->equalTo($expectedGitResetCommand)) ->will($this->returnValue(0)); - $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $filesystem->expects($this->any()) ->method('removeDirectory') ->with($this->equalTo('composerPath')) diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 6b660e383..537d98775 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -37,10 +37,10 @@ class HgDownloaderTest extends TestCase protected function getDownloaderMock($io = null, $config = null, $executor = null, $filesystem = null) { - $io = $io ?: $this->getMock('Composer\IO\IOInterface'); - $config = $config ?: $this->getMock('Composer\Config'); - $executor = $executor ?: $this->getMock('Composer\Util\ProcessExecutor'); - $filesystem = $filesystem ?: $this->getMock('Composer\Util\Filesystem'); + $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); + $executor = $executor ?: $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $filesystem = $filesystem ?: $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); return new HgDownloader($io, $config, $executor, $filesystem); } @@ -50,7 +50,7 @@ class HgDownloaderTest extends TestCase */ public function testDownloadForPackageWithoutSourceReference() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -61,14 +61,14 @@ class HgDownloaderTest extends TestCase public function testDownload() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); $packageMock->expects($this->once()) ->method('getSourceUrls') ->will($this->returnValue(array('https://mercurial.dev/l3l0/composer'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedGitCommand = $this->getCmd('hg clone \'https://mercurial.dev/l3l0/composer\' \'composerPath\''); $processExecutor->expects($this->at(0)) @@ -91,8 +91,8 @@ class HgDownloaderTest extends TestCase */ public function testUpdateforPackageWithoutSourceReference() { - $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); - $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $initialPackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $sourcePackageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $sourcePackageMock->expects($this->once()) ->method('getSourceReference') ->will($this->returnValue(null)); @@ -105,14 +105,14 @@ class HgDownloaderTest extends TestCase { $fs = new Filesystem; $fs->ensureDirectoryExists($this->workingDir.'/.hg'); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/l3l0/composer'))); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedHgCommand = $this->getCmd("hg st"); $processExecutor->expects($this->at(0)) @@ -133,12 +133,12 @@ class HgDownloaderTest extends TestCase { $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->any()) ->method('execute') ->with($this->equalTo($expectedResetCommand)); - $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $filesystem->expects($this->any()) ->method('removeDirectory') ->with($this->equalTo('composerPath')) diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 6ec44d3c3..3fae0f7af 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -62,7 +62,7 @@ class PerforceDownloaderTest extends TestCase protected function getMockProcessExecutor() { - return $this->getMock('Composer\Util\ProcessExecutor'); + return $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); } protected function getConfig() @@ -76,12 +76,12 @@ class PerforceDownloaderTest extends TestCase protected function getMockIoInterface() { - return $this->getMock('Composer\IO\IOInterface'); + return $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); } protected function getMockPackageInterface(VcsRepository $repository) { - $package = $this->getMock('Composer\Package\PackageInterface'); + $package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $package->expects($this->any())->method('getRepository')->will($this->returnValue($repository)); return $package; @@ -94,10 +94,10 @@ class PerforceDownloaderTest extends TestCase protected function getMockRepository(array $repoConfig, IOInterface $io, Config $config) { - $class = 'Composer\Repository\VcsRepository'; - $methods = array('getRepoConfig'); - $args = array($repoConfig, $io, $config); - $repository = $this->getMock($class, $methods, $args); + $repository = $this->getMockBuilder('Composer\Repository\VcsRepository') + ->setMethods(array('getRepoConfig')) + ->setConstructorArgs(array($repoConfig, $io, $config)) + ->getMock(); $repository->expects($this->any())->method('getRepoConfig')->will($this->returnValue($repoConfig)); return $repository; diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index ba81cdaaf..fc33adcf4 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -46,7 +46,7 @@ class XzDownloaderTest extends TestCase public function testErrorMessages() { - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue($distUrl = 'file://'.__FILE__)) @@ -60,8 +60,8 @@ class XzDownloaderTest extends TestCase ->will($this->returnValue(array())) ; - $io = $this->getMock('Composer\IO\IOInterface'); - $config = $this->getMock('Composer\Config'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $config = $this->getMockBuilder('Composer\Config')->getMock(); $config->expects($this->any()) ->method('get') ->with('vendor-dir') diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 9c437e786..3d4ebac7b 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -28,8 +28,8 @@ class ZipDownloaderTest extends TestCase public function setUp() { $this->testDir = $this->getUniqueTmpDirectory(); - $this->io = $this->getMock('Composer\IO\IOInterface'); - $this->config = $this->getMock('Composer\Config'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->config = $this->getMockBuilder('Composer\Config')->getMock(); } public function tearDown() @@ -78,7 +78,7 @@ class ZipDownloaderTest extends TestCase ->with('vendor-dir') ->will($this->returnValue($this->testDir)); - $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue($distUrl = 'file://'.__FILE__)) @@ -118,7 +118,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasZipArchive', true); $downloader = new MockedZipDownloader($this->io, $this->config); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -144,7 +144,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasZipArchive', true); $downloader = new MockedZipDownloader($this->io, $this->config); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -169,7 +169,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasZipArchive', true); $downloader = new MockedZipDownloader($this->io, $this->config); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -193,7 +193,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', false); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->will($this->returnValue(1)); @@ -210,7 +210,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', false); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->will($this->returnValue(0)); @@ -229,12 +229,12 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', true); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->will($this->returnValue(1)); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -261,12 +261,12 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', true); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') ->will($this->returnValue(1)); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -289,12 +289,12 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', true); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->atLeastOnce()) ->method('execute') ->will($this->returnValue(0)); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); @@ -321,12 +321,12 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', true); $this->setPrivateProperty('hasZipArchive', true); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->atLeastOnce()) ->method('execute') ->will($this->returnValue(1)); - $zipArchive = $this->getMock('ZipArchive'); + $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') ->will($this->returnValue(true)); diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index b1e0a7c2c..7f0327d9c 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -31,7 +31,7 @@ class EventDispatcherTest extends TestCase */ public function testListenerExceptionsAreCaught() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $dispatcher = $this->getDispatcherStubForListenersTest(array( 'Composer\Test\EventDispatcher\EventDispatcherTest::call', ), $io); @@ -56,7 +56,7 @@ class EventDispatcherTest extends TestCase */ public function testDispatcherCanConvertScriptEventToCommandEventForListener() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $dispatcher = $this->getDispatcherStubForListenersTest(array( 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent', ), $io); @@ -66,7 +66,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherDoesNotAttemptConversionForListenerWithoutTypehint() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $dispatcher = $this->getDispatcherStubForListenersTest(array( 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent', ), $io); @@ -80,11 +80,11 @@ class EventDispatcherTest extends TestCase */ public function testDispatcherCanExecuteSingleCommandLineScript($command) { - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->createComposerInstance(), - $this->getMock('Composer\IO\IOInterface'), + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $process, )) ->setMethods(array('getListeners')) @@ -118,17 +118,17 @@ class EventDispatcherTest extends TestCase $composer->setAutoloadGenerator($generator); - $package = $this->getMock('Composer\Package\RootPackageInterface'); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $package->method('getScripts')->will($this->returnValue(array('scriptName' => array('scriptName')))); $composer->setPackage($package); $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest()); - $composer->setInstallationManager($this->getMock('Composer\Installer\InstallationManager')); + $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock()); $dispatcher = new EventDispatcher( $composer, - $this->getMock('Composer\IO\IOInterface'), - $this->getMock('Composer\Util\ProcessExecutor') + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock() ); $event = $this->getMockBuilder('Composer\Script\Event') @@ -169,7 +169,7 @@ class EventDispatcherTest extends TestCase ->will($this->returnValue(array())); $generator ->method('createLoader') - ->will($this->returnValue($this->getMock('Composer\Autoload\ClassLoader'))); + ->will($this->returnValue($this->getMockBuilder('Composer\Autoload\ClassLoader')->getMock())); return $generator; } @@ -181,7 +181,7 @@ class EventDispatcherTest extends TestCase ->setMethods(array('getLocalRepository')) ->getMock(); - $repo = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); + $repo = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); $repo ->method('getCanonicalPackages') ->will($this->returnValue(array())); @@ -195,7 +195,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack() { - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->createComposerInstance(), @@ -231,7 +231,7 @@ class EventDispatcherTest extends TestCase public function testDispatcherCanExecuteComposerScriptGroups() { - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $composer = $this->createComposerInstance(), @@ -279,11 +279,11 @@ class EventDispatcherTest extends TestCase */ public function testDispatcherDetectInfiniteRecursion() { - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $composer = $this->createComposerInstance(), - $io = $this->getMock('Composer\IO\IOInterface'), + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $process, )) ->setMethods(array( @@ -339,7 +339,7 @@ class EventDispatcherTest extends TestCase $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->createComposerInstance(), - $io = $this->getMock('Composer\IO\IOInterface'), + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), new ProcessExecutor($io), )) ->setMethods(array('getListeners')) @@ -366,7 +366,7 @@ class EventDispatcherTest extends TestCase $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->createComposerInstance(), - $io = $this->getMock('Composer\IO\IOInterface'), + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), new ProcessExecutor, )) ->setMethods(array('getListeners')) @@ -396,11 +396,11 @@ class EventDispatcherTest extends TestCase public function testDispatcherInstallerEvents() { - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->createComposerInstance(), - $this->getMock('Composer\IO\IOInterface'), + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $process, )) ->setMethods(array('getListeners')) @@ -410,7 +410,7 @@ class EventDispatcherTest extends TestCase ->method('getListeners') ->will($this->returnValue(array())); - $policy = $this->getMock('Composer\DependencyResolver\PolicyInterface'); + $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->disableOriginalConstructor()->getMock(); $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); @@ -444,7 +444,7 @@ class EventDispatcherTest extends TestCase $composer = new Composer; $config = new Config; $composer->setConfig($config); - $package = $this->getMock('Composer\Package\RootPackageInterface'); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $composer->setPackage($package); return $composer; diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index 9c9ca8902..f44369dba 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -20,7 +20,7 @@ class ConsoleIOTest extends TestCase { public function testIsInteractive() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $inputMock->expects($this->at(0)) ->method('isInteractive') ->will($this->returnValue(true)); @@ -28,8 +28,8 @@ class ConsoleIOTest extends TestCase ->method('isInteractive') ->will($this->returnValue(false)); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); @@ -39,15 +39,15 @@ class ConsoleIOTest extends TestCase public function testWrite() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $outputMock->expects($this->once()) ->method('getVerbosity') ->willReturn(OutputInterface::VERBOSITY_NORMAL); $outputMock->expects($this->once()) ->method('write') ->with($this->equalTo('some information about something'), $this->equalTo(false)); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->write('some information about something', false); @@ -55,8 +55,8 @@ class ConsoleIOTest extends TestCase public function testWriteError() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\ConsoleOutputInterface')->getMock(); $outputMock->expects($this->once()) ->method('getVerbosity') ->willReturn(OutputInterface::VERBOSITY_NORMAL); @@ -66,7 +66,7 @@ class ConsoleIOTest extends TestCase $outputMock->expects($this->once()) ->method('write') ->with($this->equalTo('some information about something'), $this->equalTo(false)); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->writeError('some information about something', false); @@ -74,8 +74,8 @@ class ConsoleIOTest extends TestCase public function testWriteWithMultipleLineStringWhenDebugging() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $outputMock->expects($this->once()) ->method('getVerbosity') ->willReturn(OutputInterface::VERBOSITY_NORMAL); @@ -90,7 +90,7 @@ class ConsoleIOTest extends TestCase }), $this->equalTo(false) ); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $startTime = microtime(true); @@ -102,8 +102,8 @@ class ConsoleIOTest extends TestCase public function testOverwrite() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $outputMock->expects($this->any()) ->method('getVerbosity') @@ -130,7 +130,7 @@ class ConsoleIOTest extends TestCase ->method('write') ->with($this->equalTo('something longer than initial (34)')); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->write('something (strlen = 23)'); @@ -140,10 +140,10 @@ class ConsoleIOTest extends TestCase public function testAsk() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\QuestionHelper'); - $setMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')->getMock(); + $setMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $helperMock ->expects($this->once()) @@ -168,10 +168,10 @@ class ConsoleIOTest extends TestCase public function testAskConfirmation() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\QuestionHelper'); - $setMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')->getMock(); + $setMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $helperMock ->expects($this->once()) @@ -196,10 +196,10 @@ class ConsoleIOTest extends TestCase public function testAskAndValidate() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\QuestionHelper'); - $setMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')->getMock(); + $setMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $helperMock ->expects($this->once()) @@ -227,10 +227,10 @@ class ConsoleIOTest extends TestCase public function testSelect() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\QuestionHelper'); - $setMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')->getMock(); + $setMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $helperMock ->expects($this->once()) @@ -257,9 +257,9 @@ class ConsoleIOTest extends TestCase public function testSetAndgetAuthentication() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->setAuthentication('repoName', 'l3l0', 'passwd'); @@ -272,9 +272,9 @@ class ConsoleIOTest extends TestCase public function testGetAuthenticationWhenDidNotSet() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); @@ -286,9 +286,9 @@ class ConsoleIOTest extends TestCase public function testHasAuthentication() { - $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); - $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); - $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); + $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); + $helperMock = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')->getMock(); $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); $consoleIO->setAuthentication('repoName', 'l3l0', 'passwd'); diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index f8de7ba0f..b6fb83e60 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -22,7 +22,7 @@ class InstallationManagerTest extends TestCase { public function setUp() { - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); + $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); } public function testAddGetInstaller() diff --git a/tests/Composer/Test/Installer/InstallerEventTest.php b/tests/Composer/Test/Installer/InstallerEventTest.php index a8603542a..8c99ba565 100644 --- a/tests/Composer/Test/Installer/InstallerEventTest.php +++ b/tests/Composer/Test/Installer/InstallerEventTest.php @@ -19,13 +19,13 @@ class InstallerEventTest extends TestCase { public function testGetter() { - $composer = $this->getMock('Composer\Composer'); - $io = $this->getMock('Composer\IO\IOInterface'); - $policy = $this->getMock('Composer\DependencyResolver\PolicyInterface'); + $composer = $this->getMockBuilder('Composer\Composer')->getMock(); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->disableOriginalConstructor()->getMock(); $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); - $operations = array($this->getMock('Composer\DependencyResolver\Operation\OperationInterface')); + $operations = array($this->getMockBuilder('Composer\DependencyResolver\Operation\OperationInterface')->getMock()); $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, $policy, $pool, $installedRepo, $request, $operations); $this->assertSame('EVENT_NAME', $event->getName()); diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 7ab76402d..41581f712 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -57,8 +57,8 @@ class LibraryInstallerTest extends TestCase ->getMock(); $this->composer->setDownloadManager($this->dm); - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); } protected function tearDown() diff --git a/tests/Composer/Test/Installer/MetapackageInstallerTest.php b/tests/Composer/Test/Installer/MetapackageInstallerTest.php index 5474f3815..1a6b7a264 100644 --- a/tests/Composer/Test/Installer/MetapackageInstallerTest.php +++ b/tests/Composer/Test/Installer/MetapackageInstallerTest.php @@ -23,9 +23,9 @@ class MetapackageInstallerTest extends TestCase protected function setUp() { - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); + $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->installer = new MetapackageInstaller(); } diff --git a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php index 48809bd33..7c80935cd 100644 --- a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php +++ b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php @@ -25,7 +25,7 @@ class SuggestedPackagesReporterTest extends TestCase protected function setUp() { - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); } @@ -185,9 +185,9 @@ class SuggestedPackagesReporterTest extends TestCase */ public function testOutputSkipInstalledPackages() { - $repository = $this->getMock('Composer\Repository\RepositoryInterface'); - $package1 = $this->getMock('Composer\Package\PackageInterface'); - $package2 = $this->getMock('Composer\Package\PackageInterface'); + $repository = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); + $package1 = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $package2 = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $package1->expects($this->once()) ->method('getNames') @@ -219,7 +219,7 @@ class SuggestedPackagesReporterTest extends TestCase */ public function testOutputNotGettingInstalledPackagesWhenNoSuggestions() { - $repository = $this->getMock('Composer\Repository\RepositoryInterface'); + $repository = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); $repository->expects($this->exactly(0)) ->method('getPackages'); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 468d8fbbd..8614495ee 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -57,10 +57,12 @@ class InstallerTest extends TestCase */ public function testInstaller(RootPackageInterface $rootPackage, $repositories, array $options) { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $downloadManager = $this->getMock('Composer\Downloader\DownloadManager', array(), array($io)); - $config = $this->getMock('Composer\Config'); + $downloadManager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array($io)) + ->getMock(); + $config = $this->getMockBuilder('Composer\Config')->getMock(); $repositoryManager = new RepositoryManager($io, $config); $repositoryManager->setLocalRepository(new InstalledArrayRepository()); @@ -202,7 +204,9 @@ class InstallerTest extends TestCase $composer->setLocker($locker); $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); - $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher)); + $autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator') + ->setConstructorArgs(array($eventDispatcher)) + ->getMock(); $composer->setAutoloadGenerator($autoloadGenerator); $composer->setEventDispatcher($eventDispatcher); diff --git a/tests/Composer/Test/Package/BasePackageTest.php b/tests/Composer/Test/Package/BasePackageTest.php index 35bcb9d02..32a8cd844 100644 --- a/tests/Composer/Test/Package/BasePackageTest.php +++ b/tests/Composer/Test/Package/BasePackageTest.php @@ -20,7 +20,7 @@ class BasePackageTest extends TestCase public function testSetSameRepository() { $package = $this->getMockForAbstractClass('Composer\Package\BasePackage', array('foo')); - $repository = $this->getMock('Composer\Repository\RepositoryInterface'); + $repository = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); $package->setRepository($repository); try { @@ -37,8 +37,8 @@ class BasePackageTest extends TestCase { $package = $this->getMockForAbstractClass('Composer\Package\BasePackage', array('foo')); - $package->setRepository($this->getMock('Composer\Repository\RepositoryInterface')); - $package->setRepository($this->getMock('Composer\Repository\RepositoryInterface')); + $package->setRepository($this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock()); + $package->setRepository($this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock()); } /** diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index a791b7f06..bd1c29c13 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -31,7 +31,7 @@ class ArrayDumperTest extends TestCase public function setUp() { $this->dumper = new ArrayDumper(); - $this->package = $this->getMock('Composer\Package\CompletePackageInterface'); + $this->package = $this->getMockBuilder('Composer\Package\CompletePackageInterface')->getMock(); $this->packageExpects('getTransportOptions', array()); } @@ -56,7 +56,7 @@ class ArrayDumperTest extends TestCase public function testRootPackage() { - $this->package = $this->getMock('Composer\Package\RootPackageInterface'); + $this->package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $this ->packageExpects('getMinimumStability', 'dev') @@ -92,7 +92,7 @@ class ArrayDumperTest extends TestCase */ public function testKeys($key, $value, $method = null, $expectedValue = null) { - $this->package = $this->getMock('Composer\Package\RootPackageInterface'); + $this->package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); $this->packageExpects('get'.ucfirst($method ?: $key), $value); $this->packageExpects('isAbandoned', $value); diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index c1115becd..8896f81af 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -23,7 +23,7 @@ class ValidatingArrayLoaderTest extends TestCase */ public function testLoadSuccess($config) { - $internalLoader = $this->getMock('Composer\Package\Loader\LoaderInterface'); + $internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock(); $internalLoader ->expects($this->once()) ->method('load') @@ -171,7 +171,7 @@ class ValidatingArrayLoaderTest extends TestCase */ public function testLoadFailureThrowsException($config, $expectedErrors) { - $internalLoader = $this->getMock('Composer\Package\Loader\LoaderInterface'); + $internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock(); $loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL); try { $loader->load($config); @@ -189,7 +189,7 @@ class ValidatingArrayLoaderTest extends TestCase */ public function testLoadWarnings($config, $expectedWarnings) { - $internalLoader = $this->getMock('Composer\Package\Loader\LoaderInterface'); + $internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock(); $loader = new ValidatingArrayLoader($internalLoader, true, null, ValidatingArrayLoader::CHECK_ALL); $loader->load($config); @@ -209,7 +209,7 @@ class ValidatingArrayLoaderTest extends TestCase return; } - $internalLoader = $this->getMock('Composer\Package\Loader\LoaderInterface'); + $internalLoader = $this->getMockBuilder('Composer\Package\Loader\LoaderInterface')->getMock(); $internalLoader ->expects($this->once()) ->method('load') diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index c9fc9a3e6..d3e831e5c 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -198,7 +198,7 @@ class VersionSelectorTest extends TestCase $versionSelector = new VersionSelector($pool); $versionParser = new VersionParser(); - $package = $this->getMock('\Composer\Package\PackageInterface'); + $package = $this->getMockBuilder('\Composer\Package\PackageInterface')->getMock(); $package ->expects($this->any()) ->method('getPrettyVersion') @@ -275,6 +275,6 @@ class VersionSelectorTest extends TestCase private function createMockPool() { - return $this->getMock('Composer\DependencyResolver\Pool', array(), array(), '', true); + return $this->getMockBuilder('Composer\DependencyResolver\Pool')->getMock(); } } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 57ba7f13f..26fc63efa 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -80,7 +80,7 @@ class PluginInstallerTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); + $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); $rm = $this->getMockBuilder('Composer\Repository\RepositoryManager') ->disableOriginalConstructor() @@ -89,14 +89,14 @@ class PluginInstallerTest extends TestCase ->method('getLocalRepository') ->will($this->returnValue($this->repository)); - $im = $this->getMock('Composer\Installer\InstallationManager'); + $im = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); $im->expects($this->any()) ->method('getInstallPath') ->will($this->returnCallback(function ($package) { return __DIR__.'/Fixtures/'.$package->getPrettyName(); })); - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $this->autoloadGenerator = new AutoloadGenerator($dispatcher); diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 4017bfe51..38b459730 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -31,18 +31,14 @@ class ComposerRepositoryTest extends TestCase 'url' => 'http://example.org', ); - $repository = $this->getMock( - 'Composer\Repository\ComposerRepository', - array( - 'loadRootServerFile', - 'createPackage', - ), - array( + $repository = $this->getMockBuilder('Composer\Repository\ComposerRepository') + ->setMethods(array('loadRootServerFile', 'createPackage')) + ->setConstructorArgs(array( $repoConfig, new NullIO, FactoryMock::createConfig(), - ) - ); + )) + ->getMock(); $repository ->expects($this->exactly(2)) @@ -146,7 +142,7 @@ class ComposerRepositoryTest extends TestCase ), ))); - $pool = $this->getMock('Composer\DependencyResolver\Pool'); + $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->getMock(); $pool->expects($this->any()) ->method('isPackageAcceptable') ->will($this->returnValue(true)); diff --git a/tests/Composer/Test/Repository/RepositoryFactoryTest.php b/tests/Composer/Test/Repository/RepositoryFactoryTest.php index 8ce66cfd5..e6d811fe9 100644 --- a/tests/Composer/Test/Repository/RepositoryFactoryTest.php +++ b/tests/Composer/Test/Repository/RepositoryFactoryTest.php @@ -20,8 +20,8 @@ class RepositoryFactoryTest extends TestCase public function testManagerWithAllRepositoryTypes() { $manager = RepositoryFactory::manager( - $this->getMock('Composer\IO\IOInterface'), - $this->getMock('Composer\Config') + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $this->getMockBuilder('Composer\Config')->getMock() ); $ref = new \ReflectionProperty($manager, 'repositoryClasses'); diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php index 735fbd049..a5739f32d 100644 --- a/tests/Composer/Test/Repository/RepositoryManagerTest.php +++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php @@ -36,13 +36,13 @@ class RepositoryManagerTest extends TestCase public function testPrepend() { $rm = new RepositoryManager( - $this->getMock('Composer\IO\IOInterface'), - $this->getMock('Composer\Config'), + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $this->getMockBuilder('Composer\Config')->getMock(), $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); - $repository1 = $this->getMock('Composer\Repository\RepositoryInterface'); - $repository2 = $this->getMock('Composer\Repository\RepositoryInterface'); + $repository1 = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); + $repository2 = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); $rm->addRepository($repository1); $rm->prependRepository($repository2); @@ -59,8 +59,8 @@ class RepositoryManagerTest extends TestCase } $rm = new RepositoryManager( - $this->getMock('Composer\IO\IOInterface'), - $config = $this->getMock('Composer\Config', array('get')), + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $config = $this->getMockBuilder('Composer\Config')->setMethods(array('get'))->getMock(), $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index 970abf34b..f9e73b5eb 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -64,7 +64,7 @@ class FossilDriverTest extends TestCase public function testSupport($url, $assertion) { $config = new Config(); - $result = FossilDriver::supports($this->getMock('Composer\IO\IOInterface'), $config, $url); + $result = FossilDriver::supports($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config, $url); $this->assertEquals($assertion, $result); } } diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 86e362c6f..b35bb8867 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -35,7 +35,7 @@ class GitBitbucketDriverTest extends TestCase protected function setUp() { - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->home = $this->getUniqueTmpDirectory(); diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index 30179a1a9..35e2f64c8 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -48,7 +48,7 @@ class GitHubDriverTest extends TestCase $identifier = 'v0.0.0'; $sha = 'SOMESHA'; - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->any()) ->method('isInteractive') ->will($this->returnValue(true)); @@ -57,7 +57,7 @@ class GitHubDriverTest extends TestCase ->setConstructorArgs(array($io)) ->getMock(); - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $process->expects($this->any()) ->method('execute') ->will($this->returnValue(1)); @@ -86,8 +86,8 @@ class GitHubDriverTest extends TestCase ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) ->will($this->returnValue('{"master_branch": "test_master", "private": true, "owner": {"login": "composer"}, "name": "packagist"}')); - $configSource = $this->getMock('Composer\Config\ConfigSourceInterface'); - $authConfigSource = $this->getMock('Composer\Config\ConfigSourceInterface'); + $configSource = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); + $authConfigSource = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); $this->config->setConfigSource($configSource); $this->config->setAuthConfigSource($authConfigSource); @@ -119,7 +119,7 @@ class GitHubDriverTest extends TestCase $identifier = 'v0.0.0'; $sha = 'SOMESHA'; - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->any()) ->method('isInteractive') ->will($this->returnValue(true)); @@ -162,7 +162,7 @@ class GitHubDriverTest extends TestCase $identifier = 'feature/3.2-foo'; $sha = 'SOMESHA'; - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->any()) ->method('isInteractive') ->will($this->returnValue(true)); @@ -222,7 +222,7 @@ class GitHubDriverTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->any()) ->method('isInteractive') ->will($this->returnValue(false)); diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php index 8f496da9c..441ce19c2 100644 --- a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php @@ -28,7 +28,7 @@ class HgDriverTest extends TestCase public function setUp() { - $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->home = $this->getUniqueTmpDirectory(); $this->config = new Config(); $this->config->merge(array( diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index 02b97501c..02a6e89e5 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -91,12 +91,12 @@ class PerforceDriverTest extends TestCase protected function getMockIOInterface() { - return $this->getMock('Composer\IO\IOInterface'); + return $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); } protected function getMockProcessExecutor() { - return $this->getMock('Composer\Util\ProcessExecutor'); + return $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); } protected function getMockRemoteFilesystem() diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index a3a9855db..1106c9df4 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -45,13 +45,13 @@ class SvnDriverTest extends TestCase */ public function testWrongCredentialsInUrl() { - $console = $this->getMock('Composer\IO\IOInterface'); + $console = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $output = "svn: OPTIONS of 'https://corp.svn.local/repo':"; $output .= " authorization failed: Could not authenticate to server:"; $output .= " rejected Basic challenge (https://corp.svn.local/)"; - $process = $this->getMock('Composer\Util\ProcessExecutor'); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $process->expects($this->at(1)) ->method('execute') ->will($this->returnValue(1)); @@ -95,7 +95,7 @@ class SvnDriverTest extends TestCase public function testSupport($url, $assertion) { $config = new Config(); - $result = SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $config, $url); + $result = SvnDriver::supports($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config, $url); $this->assertEquals($assertion, $result); } } diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php index c7f95e859..4323a15f5 100644 --- a/tests/Composer/Test/Util/BitbucketTest.php +++ b/tests/Composer/Test/Util/BitbucketTest.php @@ -53,7 +53,7 @@ class BitbucketTest extends TestCase ->getMock() ; - $this->config = $this->getMock('Composer\Config'); + $this->config = $this->getMockBuilder('Composer\Config')->getMock(); $this->time = time(); @@ -258,7 +258,7 @@ class BitbucketTest extends TestCase private function setExpectationsForStoringAccessToken($removeBasicAuth = false) { - $configSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface'); + $configSourceMock = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); $this->config->expects($this->once()) ->method('getConfigSource') ->willReturn($configSourceMock); @@ -267,7 +267,7 @@ class BitbucketTest extends TestCase ->method('removeConfigSetting') ->with('bitbucket-oauth.' . $this->origin); - $authConfigSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface'); + $authConfigSourceMock = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); $this->config->expects($this->atLeastOnce()) ->method('getAuthConfigSource') ->willReturn($authConfigSourceMock); diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 5cba3b5ba..28d00ce69 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -117,7 +117,7 @@ class GitHubTest extends TestCase private function getConfigMock() { - return $this->getMock('Composer\Config'); + return $this->getMockBuilder('Composer\Config')->getMock(); } private function getRemoteFilesystemMock() diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 8a5abe125..27f46b4ad 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -126,7 +126,7 @@ class GitLabTest extends TestCase private function getConfigMock() { - return $this->getMock('Composer\Config'); + return $this->getMockBuilder('Composer\Config')->getMock(); } private function getRemoteFilesystemMock() diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 54b058805..60ac209d9 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -33,7 +33,7 @@ class PerforceTest extends TestCase protected function setUp() { - $this->processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $this->processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $this->repoConfig = $this->getTestRepoConfig(); $this->io = $this->getMockIOInterface(); $this->createNewPerforceWithWindowsFlag(true); @@ -59,7 +59,7 @@ class PerforceTest extends TestCase public function getMockIOInterface() { - return $this->getMock('Composer\IO\IOInterface'); + return $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); } protected function createNewPerforceWithWindowsFlag($flag) @@ -618,7 +618,7 @@ class PerforceTest extends TestCase public function testCheckServerExists() { - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedCommand = 'p4 -p perforce.does.exist:port info -s'; $processExecutor->expects($this->at(0)) @@ -639,7 +639,7 @@ class PerforceTest extends TestCase */ public function testCheckServerClientError() { - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedCommand = 'p4 -p perforce.does.exist:port info -s'; $processExecutor->expects($this->at(0)) @@ -707,7 +707,7 @@ class PerforceTest extends TestCase public function testCleanupClientSpecShouldDeleteClient() { - $fs = $this->getMock('Composer\Util\Filesystem'); + $fs = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $this->perforce->setFilesystem($fs); $testClient = $this->perforce->getClient(); diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index 985ed2240..4a18c24e7 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -37,7 +37,7 @@ class ProcessExecutorTest extends TestCase public function testUseIOIsNotNullAndIfNotCaptured() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->once()) ->method('write') ->with($this->equalTo('foo'.PHP_EOL), false); diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 066a9275f..7da88bc8a 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -19,7 +19,7 @@ class RemoteFilesystemTest extends TestCase { public function testGetOptionsForUrl() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io ->expects($this->once()) ->method('hasAuthentication') @@ -32,7 +32,7 @@ class RemoteFilesystemTest extends TestCase public function testGetOptionsForUrlWithAuthorization() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io ->expects($this->once()) ->method('hasAuthentication') @@ -57,7 +57,7 @@ class RemoteFilesystemTest extends TestCase public function testGetOptionsForUrlWithStreamOptions() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io ->expects($this->once()) ->method('hasAuthentication') @@ -74,7 +74,7 @@ class RemoteFilesystemTest extends TestCase public function testGetOptionsForUrlWithCallOptionsKeepsHeader() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io ->expects($this->once()) ->method('hasAuthentication') @@ -101,14 +101,14 @@ class RemoteFilesystemTest extends TestCase public function testCallbackGetFileSize() { - $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface')); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); $this->callCallbackGet($fs, STREAM_NOTIFY_FILE_SIZE_IS, 0, '', 0, 0, 20); $this->assertAttributeEquals(20, 'bytesMax', $fs); } public function testCallbackGetNotifyProgress() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io ->expects($this->once()) ->method('overwriteError') @@ -124,7 +124,7 @@ class RemoteFilesystemTest extends TestCase public function testCallbackGetPassesThrough404() { - $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface')); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); $this->assertNull($this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0)); } @@ -134,7 +134,7 @@ class RemoteFilesystemTest extends TestCase */ public function testCaptureAuthenticationParamsFromUrl() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io->expects($this->once()) ->method('setAuthentication') ->with($this->equalTo('github.com'), $this->equalTo('user'), $this->equalTo('pass')); @@ -150,14 +150,14 @@ class RemoteFilesystemTest extends TestCase public function testGetContents() { - $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface')); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); $this->assertContains('testGetContents', $fs->getContents('http://example.org', 'file://'.__FILE__)); } public function testCopy() { - $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface')); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); $file = tempnam(sys_get_temp_dir(), 'c'); $this->assertTrue($fs->copy('http://example.org', 'file://'.__FILE__, $file)); @@ -171,7 +171,7 @@ class RemoteFilesystemTest extends TestCase */ public function testGetOptionsForUrlCreatesSecureTlsDefaults() { - $io = $this->getMock('Composer\IO\IOInterface'); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $res = $this->callGetOptionsForUrl($io, array('example.org', array('ssl' => array('cafile' => '/some/path/file.crt'))), array(), 'http://www.example.org'); From 4bddcd712436e421ad69f6a990299187e352c8a0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 10:35:22 +0200 Subject: [PATCH 102/580] Add support for gitlab.com URL replacement, fixes #7160 --- src/Composer/Installer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index dbdcb048d..0d842bb53 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1184,9 +1184,9 @@ class Installer $package->setSourceReference($sourceReference); } - // only update dist url for github/bitbucket dists as they use a combination of dist url + dist reference to install + // only update dist url for github/bitbucket/gitlab dists as they use a combination of dist url + dist reference to install // but for other urls this is ambiguous and could result in bad outcomes - if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com)/}i', $distUrl)) { + if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) { $package->setDistUrl($distUrl); $this->updateInstallReferences($package, $sourceReference); } @@ -1204,9 +1204,9 @@ class Installer $package->setSourceReference($reference); - if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com)/}i', $package->getDistUrl())) { + if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) { $package->setDistReference($reference); - $package->setDistUrl(preg_replace('{(?<=/)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl())); + $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl())); } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it $package->setDistReference($reference); } From ec9ba46c5f6ba537bc11a2f33d6c4483252c564c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 10:39:18 +0200 Subject: [PATCH 103/580] Fix run-script --list failing to handle native script handlers, fixes #7069 --- src/Composer/Command/RunScriptCommand.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index d78662e23..3d392c491 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -114,10 +114,14 @@ EOT $io->writeError('scripts:'); $table = array(); foreach ($scripts as $name => $script) { - $cmd = $this->getApplication()->find($name); $description = ''; - if ($cmd instanceof ScriptAliasCommand) { - $description = $cmd->getDescription(); + try { + $cmd = $this->getApplication()->find($name); + if ($cmd instanceof ScriptAliasCommand) { + $description = $cmd->getDescription(); + } + } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) { + // ignore scripts that have no command associated, like native Composer script listeners } $table[] = array(' '.$name, $description); } From 556148510b7bd45bf662506beedc542cad5ce8d8 Mon Sep 17 00:00:00 2001 From: dmsmidt Date: Sun, 11 Feb 2018 21:49:43 +0100 Subject: [PATCH 104/580] ConsoleIO::select for a single option, fixes #7106, closes #7107 --- src/Composer/IO/ConsoleIO.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index bef7cea9f..55c80e29c 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -291,6 +291,10 @@ class ConsoleIO extends BaseIO $result = $helper->ask($this->input, $this->getErrorOutput(), $question); + if (!is_array($result)) { + return (string) array_search($result, $choices, true); + } + $results = array(); foreach ($choices as $index => $choice) { if (in_array($choice, $result, true)) { From 78017bcbcb3bdf2b8bb2e1405b66f45c5f91cb7d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 12:14:30 +0200 Subject: [PATCH 105/580] Fix support for uppercase package names in why/why-not commands, fixes #7198 --- src/Composer/Command/BaseDependencyCommand.php | 2 +- src/Composer/Repository/BaseRepository.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index e36663558..5ca6effa3 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -89,7 +89,7 @@ class BaseDependencyCommand extends BaseCommand ); // Find packages that are or provide the requested package first - $packages = $pool->whatProvides($needle); + $packages = $pool->whatProvides(strtolower($needle)); if (empty($packages)) { throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index f5233e197..2b30b63cd 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -39,7 +39,7 @@ abstract class BaseRepository implements RepositoryInterface */ public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null) { - $needles = (array) $needle; + $needles = array_map('strtolower', (array) $needle); $results = array(); // initialize the array with the needles before any recursion occurs From 2f56c3c334eb0055d91f14af682dfe98277af4e0 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sat, 24 Mar 2018 13:57:39 +0100 Subject: [PATCH 106/580] Change status command help to make it clearer, closes #7213 --- src/Composer/Command/StatusCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index e45d7b7c7..83d646e39 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -40,7 +40,7 @@ class StatusCommand extends BaseCommand { $this ->setName('status') - ->setDescription('Shows a list of locally modified packages.') + ->setDescription('Shows a list of locally modified packages, for packages installed from source.') ->setDefinition(array( new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), )) From 71d058b97b6aeeeb9ad604e76291a4f74503ddfd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 2 Mar 2018 15:56:10 +0100 Subject: [PATCH 107/580] refactored "svn --version" calls into a single place, closes #7152 this saves a lot of process-spawning as we re-use the result of a process started once. --- src/Composer/Downloader/SvnDownloader.php | 7 +++---- src/Composer/Repository/Vcs/SvnDriver.php | 2 +- src/Composer/Util/Svn.php | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 54d9718be..e9455365e 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -57,11 +57,10 @@ class SvnDownloader extends VcsDownloader throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } + $util = new SvnUtil($url, $this->io, $this->config); $flags = ""; - if (0 === $this->process->execute('svn --version', $output)) { - if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match) && version_compare($match[1], '1.7.0', '>=')) { - $flags .= ' --ignore-ancestry'; - } + if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { + $flags .= ' --ignore-ancestry'; } $this->io->writeError(" Checking out " . $ref); diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 2c31f8173..1faba8195 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -353,7 +353,7 @@ class SvnDriver extends VcsDriver try { return $this->util->execute($command, $url); } catch (\RuntimeException $e) { - if (0 !== $this->process->execute('svn --version', $ignoredOutput)) { + if (null === $this->util->binaryVersion()) { throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 082ece691..5df04e8c7 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -63,6 +63,11 @@ class Svn */ protected $config; + /** + * @var string|null + */ + static private $version; + /** * @param string $url * @param \Composer\IO\IOInterface $io @@ -359,4 +364,22 @@ class Svn return $this->hasAuth = true; } + + /** + * Returns the version of the svn binary contained in PATH + * + * @return string|null + */ + public function binaryVersion() + { + if (!self::$version) { + if (0 === $this->process->execute('svn --version', $output)) { + if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match)) { + self::$version = $match[1]; + } + } + } + + return self::$version; + } } From 79d62cc51c09e1c8c9f584e886728e6d57467243 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 14:20:34 +0200 Subject: [PATCH 108/580] Escape references properly when getting commit logs for verbose update --- src/Composer/Downloader/FossilDownloader.php | 2 +- src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Downloader/HgDownloader.php | 2 +- src/Composer/Downloader/SvnDownloader.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index 0b2c0181d..6dd4c0c42 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -87,7 +87,7 @@ class FossilDownloader extends VcsDownloader */ protected function getCommitLogs($fromReference, $toReference, $path) { - $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', $toReference); + $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 626676894..740c4e3ec 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -423,7 +423,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface protected function getCommitLogs($fromReference, $toReference, $path) { $path = $this->normalizePath($path); - $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', $fromReference, $toReference); + $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index a7a42e62c..32074be71 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -82,7 +82,7 @@ class HgDownloader extends VcsDownloader */ protected function getCommitLogs($fromReference, $toReference, $path) { - $command = sprintf('hg log -r %s:%s --style compact', $fromReference, $toReference); + $command = sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference)); if (0 !== $this->process->execute($command, $output, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index e9455365e..e23958164 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -192,7 +192,7 @@ class SvnDownloader extends VcsDownloader $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference); - $command = sprintf('svn log -r%s:%s --incremental', $fromRevision, $toRevision); + $command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision)); $util = new SvnUtil($baseUrl, $this->io, $this->config); $util->setCacheCredentials($this->cacheCredentials); From 9041622b862baef250beffccf52f0c13f2845376 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 15:16:39 +0200 Subject: [PATCH 109/580] Fix version guessing regression and a few other issues, fixes #7127 --- src/Composer/Package/Version/VersionGuesser.php | 12 +----------- src/Composer/Repository/PathRepository.php | 2 +- .../Test/Package/Version/VersionGuesserTest.php | 3 ++- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 02297a1e6..e6ff84965 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -184,7 +184,7 @@ class VersionGuesser $isFeatureBranch = 0 === strpos($version, 'dev-'); if ('9999999-dev' === $version) { - $version = 'dev-' . $branch; + return array('version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch); } if (!$isFeatureBranch) { @@ -240,9 +240,6 @@ class VersionGuesser $length = strlen($output); $version = $this->versionParser->normalizeBranch($candidate); $prettyVersion = 'dev-' . $match[1]; - if ('9999999-dev' === $version) { - $version = $prettyVersion; - } } } } @@ -260,10 +257,6 @@ class VersionGuesser $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $prettyVersion = 'dev-' . $branch; - - if ('9999999-dev' === $version) { - $version = $prettyVersion; - } } // try to fetch current version from fossil tags @@ -295,9 +288,6 @@ class VersionGuesser // we are in a branches path $version = $this->versionParser->normalizeBranch($matches[3]); $prettyVersion = 'dev-' . $matches[3]; - if ('9999999-dev' === $version) { - $version = $prettyVersion; - } return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion); } diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 0ce3e90d2..ee5d702fa 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -155,7 +155,7 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn if (!isset($package['version'])) { $versionData = $this->versionGuesser->guessVersion($package, $path); - $package['version'] = $versionData['version'] ?: 'dev-master'; + $package['version'] = $versionData['pretty_version'] ?: 'dev-master'; } $output = ''; diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php index c0b6346c8..fe229d679 100644 --- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -89,7 +89,8 @@ class VersionGuesserTest extends TestCase $guesser = new VersionGuesser($config, $executor, new VersionParser()); $versionArray = $guesser->guessVersion(array(), 'dummy/path'); - $this->assertEquals('dev-' . $branch, $versionArray['version']); + $this->assertEquals("9999999-dev", $versionArray['version']); + $this->assertEquals("dev-".$branch, $versionArray['pretty_version']); $this->assertEmpty($versionArray['commit']); } From 7afd1a9385263e1af1bcc76b18f06c358d991ee8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 15:54:55 +0200 Subject: [PATCH 110/580] Update dependencies --- composer.lock | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/composer.lock b/composer.lock index 1a0d1b046..e5ee93b05 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288" + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/943b2c4fcad1ef178d16a713c2468bf7e579c288", - "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0 || ^4.0" }, @@ -60,7 +60,7 @@ "ssl", "tls" ], - "time": "2017-11-29T09:37:33+00:00" + "time": "2018-03-29T19:57:20+00:00" }, { "name": "composer/semver", @@ -187,16 +187,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.6", + "version": "5.2.7", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd" + "reference": "8560d4314577199ba51bf2032f02cd1315587c23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/d283e11b6e14c6f4664cf080415c4341293e5bbd", - "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23", + "reference": "8560d4314577199ba51bf2032f02cd1315587c23", "shasum": "" }, "require": { @@ -205,7 +205,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.1", "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.22" + "phpunit/phpunit": "^4.8.35" }, "bin": [ "bin/validate-json" @@ -249,7 +249,7 @@ "json", "schema" ], - "time": "2017-10-21T13:15:38+00:00" + "time": "2018-02-14T22:26:30+00:00" }, { "name": "psr/log", @@ -348,16 +348,16 @@ }, { "name": "seld/jsonlint", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9b355654ea99460397b89c132b5c1087b6bf4473" + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9b355654ea99460397b89c132b5c1087b6bf4473", - "reference": "9b355654ea99460397b89c132b5c1087b6bf4473", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", "shasum": "" }, "require": { @@ -393,7 +393,7 @@ "parser", "validator" ], - "time": "2018-01-03T12:13:57+00:00" + "time": "2018-01-24T12:46:19+00:00" }, { "name": "seld/phar-utils", @@ -441,16 +441,16 @@ }, { "name": "symfony/console", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de" + "reference": "7f78892d900c72a40acd1fe793c856ef0c110f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/46270f1ca44f08ebc134ce120fd2c2baf5fd63de", - "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de", + "url": "https://api.github.com/repos/symfony/console/zipball/7f78892d900c72a40acd1fe793c856ef0c110f26", + "reference": "7f78892d900c72a40acd1fe793c856ef0c110f26", "shasum": "" }, "require": { @@ -498,20 +498,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-11-29T09:33:18+00:00" + "time": "2018-04-03T05:20:27+00:00" }, { "name": "symfony/debug", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95" + "reference": "4486d2be5e068b51fece4c8551c14e709f573c8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/e72a0340dc2e273b3c4398d8eef9157ba51d8b95", - "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95", + "url": "https://api.github.com/repos/symfony/debug/zipball/4486d2be5e068b51fece4c8551c14e709f573c8d", + "reference": "4486d2be5e068b51fece4c8551c14e709f573c8d", "shasum": "" }, "require": { @@ -555,20 +555,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-11-19T19:05:05+00:00" + "time": "2018-04-03T05:20:27+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b" + "reference": "125403a59e4cb4e3ebf46d0162fabcde613d2b97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", - "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/125403a59e4cb4e3ebf46d0162fabcde613d2b97", + "reference": "125403a59e4cb4e3ebf46d0162fabcde613d2b97", "shasum": "" }, "require": { @@ -604,20 +604,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-11-19T18:39:05+00:00" + "time": "2018-02-19T16:23:47+00:00" }, { "name": "symfony/finder", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8" + "reference": "423746fc18ccf31f9abec43e4f078bb6e024b2d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", - "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", + "url": "https://api.github.com/repos/symfony/finder/zipball/423746fc18ccf31f9abec43e4f078bb6e024b2d5", + "reference": "423746fc18ccf31f9abec43e4f078bb6e024b2d5", "shasum": "" }, "require": { @@ -653,20 +653,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:25:56+00:00" + "time": "2018-04-04T13:38:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.6.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", - "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", "shasum": "" }, "require": { @@ -678,7 +678,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -712,20 +712,20 @@ "portable", "shim" ], - "time": "2017-10-11T12:05:26+00:00" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/process", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d25449e031f600807949aab7cadbf267712f4eee" + "reference": "ee2c91470ff262b1a00aec27875d38594aa87629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d25449e031f600807949aab7cadbf267712f4eee", - "reference": "d25449e031f600807949aab7cadbf267712f4eee", + "url": "https://api.github.com/repos/symfony/process/zipball/ee2c91470ff262b1a00aec27875d38594aa87629", + "reference": "ee2c91470ff262b1a00aec27875d38594aa87629", "shasum": "" }, "require": { @@ -761,7 +761,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:25:56+00:00" + "time": "2018-04-03T05:20:27+00:00" } ], "packages-dev": [ @@ -870,16 +870,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.3", + "version": "1.7.5", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", "shasum": "" }, "require": { @@ -891,7 +891,7 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" }, "type": "library", "extra": { @@ -929,7 +929,7 @@ "spy", "stub" ], - "time": "2017-11-24T13:59:53+00:00" + "time": "2018-02-19T10:16:54+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1681,16 +1681,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.32", + "version": "v2.8.38", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "968ef42161e4bc04200119da473077f9e7015128" + "reference": "be720fcfae4614df204190d57795351059946a77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/968ef42161e4bc04200119da473077f9e7015128", - "reference": "968ef42161e4bc04200119da473077f9e7015128", + "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", + "reference": "be720fcfae4614df204190d57795351059946a77", "shasum": "" }, "require": { @@ -1726,7 +1726,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-11-29T09:33:18+00:00" + "time": "2018-01-03T07:36:31+00:00" } ], "aliases": [], From 174c71de04f9dc47e0eaa31935052544b27eb128 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 15:57:14 +0200 Subject: [PATCH 111/580] Handle http 401/403 differently to allow reading warning message --- src/Composer/Util/RemoteFilesystem.php | 60 +++++++++++++++----------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 6bc1505dd..66c78a69c 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -175,6 +175,24 @@ class RemoteFilesystem return $value; } + /** + * @param array $headers array of returned headers like from getLastHeaders() + * @return string|null + */ + public function findStatusMessage(array $headers) + { + $value = null; + foreach ($headers as $header) { + if (preg_match('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + /** * Get file content or copy action. * @@ -299,6 +317,20 @@ class RemoteFilesystem try { $result = file_get_contents($fileUrl, false, $ctx); + if (!empty($http_response_header[0])) { + $statusCode = $this->findStatusCode($http_response_header); + if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) { + $warning = null; + if ($this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { + $data = json_decode($result, true); + if (!empty($data['warning'])) { + $warning = $data['warning']; + } + } + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning); + } + } + $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP @@ -558,29 +590,6 @@ class RemoteFilesystem // but you do not send an appropriate certificate throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); } - // intentional fallthrough to the next case as the notificationCode - // isn't always consistent and we should inspect the messageCode for 401s - - case STREAM_NOTIFY_AUTH_REQUIRED: - if (401 === $messageCode) { - // Bail if the caller is going to handle authentication failures itself. - if (!$this->retryAuthFailure) { - break; - } - - $this->promptAuthAndRetry($messageCode); - } - break; - - case STREAM_NOTIFY_AUTH_RESULT: - if (403 === $messageCode) { - // Bail if the caller is going to handle authentication failures itself. - if (!$this->retryAuthFailure) { - break; - } - - $this->promptAuthAndRetry($messageCode, $message); - } break; case STREAM_NOTIFY_FILE_SIZE_IS: @@ -603,7 +612,7 @@ class RemoteFilesystem } } - protected function promptAuthAndRetry($httpStatus, $reason = null) + protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null) { if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { $message = "\n".'Could not fetch '.$this->fileUrl.', please create a GitHub OAuth token '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit'); @@ -674,6 +683,9 @@ class RemoteFilesystem } $this->io->overwriteError(''); + if ($warning) { + $this->io->writeError(' '.$warning.''); + } $this->io->writeError(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); From 5460e5d86fc5651960424797538bcf040e8209eb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 16:22:40 +0200 Subject: [PATCH 112/580] Respect current PHP version when figuring out requirements for init command, fixes #7257 --- src/Composer/Command/InitCommand.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index a17efe6b9..50c319b8d 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -319,11 +319,16 @@ EOT $io->writeError(array('', 'Define your dependencies.', '')); + // prepare to resolve dependencies + $repos = $this->getRepos(); + $preferredStability = $minimumStability ?: 'stable'; + $phpVersion = $repos->findPackage('php', '*')->getPrettyVersion(); + $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $require = $input->getOption('require'); $requirements = array(); if ($require || $io->askConfirmation($question, true)) { - $requirements = $this->determineRequirements($input, $output, $require); + $requirements = $this->determineRequirements($input, $output, $require, $phpVersion, $preferredStability); } $input->setOption('require', $requirements); @@ -331,7 +336,7 @@ EOT $requireDev = $input->getOption('require-dev'); $devRequirements = array(); if ($requireDev || $io->askConfirmation($question, true)) { - $devRequirements = $this->determineRequirements($input, $output, $requireDev); + $devRequirements = $this->determineRequirements($input, $output, $requireDev, $phpVersion, $preferredStability); } $input->setOption('require-dev', $devRequirements); } From c9aa9c0d2fb2280f6ebda68b175f920a155e69b9 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Fri, 30 Mar 2018 18:37:56 +0200 Subject: [PATCH 113/580] Fix usage of svn user-provided credentials, fixes #7114, closes #7228 --- src/Composer/Util/Svn.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 5df04e8c7..898fadce0 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -107,9 +107,7 @@ class Svn // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); - $svnCommand = $this->getCommand($command, $url, $path); - - return $this->executeWithAuthRetry($svnCommand, $cwd, $path, $verbose); + return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose); } /** @@ -126,18 +124,15 @@ class Svn */ public function executeLocal($command, $path, $cwd = null, $verbose = false) { - $svnCommand = sprintf('%s %s%s %s', - $command, - '--non-interactive ', - $this->getCredentialString(), - ProcessExecutor::escape($path) - ); - - return $this->executeWithAuthRetry($svnCommand, $cwd, $path, $verbose); + // A local command has no remote url + return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); } - private function executeWithAuthRetry($command, $cwd, $path, $verbose) + private function executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose) { + // Regenerate the command at each try, to use the newly user-provided credentials + $command = $this->getCommand($svnCommand, $url, $path); + $output = null; $io = $this->io; $handler = function ($type, $buffer) use (&$output, $io, $verbose) { @@ -175,7 +170,7 @@ class Svn // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { // restart the process - return $this->executeWithAuthRetry($command, $cwd, $path, $verbose); + return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose); } throw new \RuntimeException( From 1336029b72d5e33826db030be5b499467195981a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 17:36:57 +0200 Subject: [PATCH 114/580] Fix type hints to match latest symfony, fixes #7199 --- src/Composer/IO/IOInterface.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 75c86de20..5766ba479 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -103,8 +103,8 @@ interface IOInterface /** * Asks a question to the user. * - * @param string|array $question The question to ask - * @param string $default The default answer if none is given by the user + * @param string $question The question to ask + * @param string $default The default answer if none is given by the user * * @throws \RuntimeException If there is no data to read in the input stream * @return string The user answer @@ -116,8 +116,8 @@ interface IOInterface * * The question will be asked until the user answers by nothing, yes, or no. * - * @param string|array $question The question to ask - * @param bool $default The default answer if the user enters nothing + * @param string $question The question to ask + * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ @@ -130,10 +130,10 @@ interface IOInterface * validated data when the data is valid and throw an exception * otherwise. * - * @param string|array $question The question to ask - * @param callable $validator A PHP callback - * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) - * @param mixed $default The default answer if none is given by the user + * @param string $question The question to ask + * @param callable $validator A PHP callback + * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) + * @param mixed $default The default answer if none is given by the user * * @throws \Exception When any of the validators return an error * @return mixed @@ -152,12 +152,12 @@ interface IOInterface /** * Asks the user to select a value. * - * @param string|array $question The question to ask - * @param array $choices List of choices to pick from - * @param bool|string $default The default answer if the user enters nothing - * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) - * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked - * @param bool $multiselect Select more than one value separated by comma + * @param string $question The question to ask + * @param array $choices List of choices to pick from + * @param bool|string $default The default answer if the user enters nothing + * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param bool $multiselect Select more than one value separated by comma * * @throws \InvalidArgumentException * @return int|string|array The selected value or values (the key of the choices array) From 9bee2ca28eea068f108ac4398fe82f12a8af6bc1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 5 Mar 2018 09:45:48 +0100 Subject: [PATCH 115/580] make sure we only cache resources which contain a svn revision like we do in the VCS driver. Closes #7158 --- src/Composer/Repository/Vcs/SvnDriver.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 1faba8195..b963f6856 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -115,19 +115,29 @@ class SvnDriver extends VcsDriver return null; } + /** + * {@inheritdoc} + */ + protected function shouldCache($identifier) + { + return $this->cache && preg_match('{@\d+$}', $identifier); + } + /** * {@inheritdoc} */ public function getComposerInformation($identifier) { if (!isset($this->infoCache[$identifier])) { - if ($res = $this->cache->read($identifier.'.json')) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); - $this->cache->write($identifier.'.json', json_encode($composer)); + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier.'.json', json_encode($composer)); + } $this->infoCache[$identifier] = $composer; } From 277f32d388b4a2d26aed199d04dff937cfb5de8c Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Fri, 9 Feb 2018 09:03:28 +0100 Subject: [PATCH 116/580] Lock _readme: remove outdated hashtag link part, closes #7096 --- src/Composer/Package/Locker.php | 2 +- tests/Composer/Test/Package/LockerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index fe2616cb4..7fbe536f2 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -289,7 +289,7 @@ class Locker { $lock = array( '_readme' => array('This file locks the dependencies of your project to a known state', - 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file', + 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', 'This file is @gener'.'ated automatically', ), 'content-hash' => $this->contentHash, 'packages' => null, diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index a37e09907..b5692b009 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -133,7 +133,7 @@ class LockerTest extends TestCase ->method('write') ->with(array( '_readme' => array('This file locks the dependencies of your project to a known state', - 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file', + 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', 'This file is @gener'.'ated automatically', ), 'content-hash' => $contentHash, 'packages' => array( From b1a78b60fe552bc6e7df09cb1c0154797fea6f64 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 18:40:07 +0200 Subject: [PATCH 117/580] Remove output while the changes are being collected --- src/Composer/Downloader/FileDownloader.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 7655aabe3..e090c1b6e 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\Cache; use Composer\Factory; use Composer\IO\IOInterface; +use Composer\IO\NullIO; use Composer\Package\Comparer\Comparer; use Composer\Package\PackageInterface; use Composer\Plugin\PluginEvents; @@ -288,9 +289,13 @@ class FileDownloader implements DownloaderInterface */ public function getLocalChanges(PackageInterface $package, $targetDir) { - if ($this->outputProgress) { - $this->io->writeError(' - Installing Original ' . $package->getName() . ' (' . $package->getFullPrettyVersion() . ') and Checking: ', true); - } + $prevIO = $this->io; + $prevProgress = $this->outputProgress; + + $this->io = new NullIO; + $this->io->loadConfiguration($this->config); + $this->outputProgress = false; + $this->download($package, $targetDir.'_compare', false); $comparer = new Comparer(); @@ -300,6 +305,9 @@ class FileDownloader implements DownloaderInterface $output = $comparer->getChanged(true, true); $this->filesystem->removeDirectory($targetDir.'_compare'); + $this->io = $prevIO; + $this->outputProgress = $prevProgress; + return trim($output); } } From 89e138a593d1d23a862c9e6e51201026b90b7df9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 19:06:24 +0200 Subject: [PATCH 118/580] Add hint about .local/bin to docs, fixes #7132 --- doc/00-intro.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/00-intro.md b/doc/00-intro.md index e0a81b05b..2470f45fe 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -94,6 +94,10 @@ you can run this to move composer.phar to a directory that is in your path: mv composer.phar /usr/local/bin/composer ``` +If you like to install it only for your user and avoid requiring root permissions, +you can use `~/.local/bin` instead which is available by default on some +Linux distributions. + > **Note:** If the above fails due to permissions, you may need to run it again > with sudo. From af3783b5f40bae32a23e353eaf0a00c9b8ce82e2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 5 Mar 2018 09:34:40 +0100 Subject: [PATCH 119/580] properly cache when a branch in a certain revision does not contain a composer.json this prevents requesting/trying to get the composer.json over and over again even if no commits happend Closes #7156 --- src/Composer/Repository/Vcs/SvnDriver.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index b963f6856..822510601 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -133,7 +133,16 @@ class SvnDriver extends VcsDriver return $this->infoCache[$identifier] = JsonFile::parseJson($res); } - $composer = $this->getBaseComposerInformation($identifier); + try { + $composer = $this->getBaseComposerInformation($identifier); + } catch(TransportException $e) { + $message = $e->getMessage(); + if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) { + throw $e; + } + // remember a not-existent composer.json + $composer = ''; + } if ($this->shouldCache($identifier)) { $this->cache->write($identifier.'.json', json_encode($composer)); From 288631a37e29b48574611639eae69d42e0e9bcb5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 19:42:23 +0200 Subject: [PATCH 120/580] Log the source of the failure when an aliased script fails, fixes #7201 --- src/Composer/EventDispatcher/EventDispatcher.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 4a5656d9f..2a180614c 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -186,7 +186,12 @@ class EventDispatcher $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); } - $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); + try { + $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); + } catch (ScriptExecutionException $e) { + $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); + throw $e; + } } } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); From 24ef605473cc0ee79e8bed1041ecc8ff67f86c42 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 19:49:18 +0200 Subject: [PATCH 121/580] Clarify that #refs is root-only, fixes #7241 --- doc/04-schema.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index c338b4673..250b4e415 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -307,7 +307,9 @@ releases for the `doctrine/data-fixtures` package : `require` and `require-dev` additionally support explicit references (i.e. commit) for dev versions to make sure they are locked to a given state, even when you run update. These only work if you explicitly require a dev version -and append the reference with `#`. +and append the reference with `#`. This is also a +[root-only](04-schema.md#root-package) feature and will be ignored in +dependencies. Example: From fa539766b8ce217fed1d617db2f17aca8cfcf20e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Apr 2018 19:56:35 +0200 Subject: [PATCH 122/580] Output outdated warning on stderr, fixes #7218 --- src/Composer/Command/ShowCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ae2307e5c..ec0261acc 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -467,7 +467,7 @@ EOT } $io->write(''); if (isset($package['warning'])) { - $io->write('' . $package['warning'] . ''); + $io->writeError('' . $package['warning'] . ''); } } From 9dc62222889e310ba20a7aa1c4f4b9fc9434d6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20Rodr=C3=ADguez?= Date: Wed, 21 Feb 2018 00:49:11 -0300 Subject: [PATCH 123/580] Allow using fossil dependency in a fossil repository, fixes #7125, closes #7126 --- src/Composer/Downloader/FossilDownloader.php | 2 +- src/Composer/Repository/Vcs/FossilDriver.php | 2 +- tests/Composer/Test/Downloader/FossilDownloaderTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index 6dd4c0c42..135e973e0 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -36,7 +36,7 @@ class FossilDownloader extends VcsDownloader if (0 !== $this->process->execute($command, $ignoredOutput)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } - $command = sprintf('fossil open %s', ProcessExecutor::escape($repoFile)); + $command = sprintf('fossil open %s --nested', ProcessExecutor::escape($repoFile)); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index 7af97ad3e..0b689e7bf 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -96,7 +96,7 @@ class FossilDriver extends VcsDriver throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output); } - if (0 !== $this->process->execute(sprintf('fossil open %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) { + if (0 !== $this->process->execute(sprintf('fossil open %s --nested', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 2e1de934d..961686bef 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -76,7 +76,7 @@ class FossilDownloaderTest extends TestCase ->with($this->equalTo($expectedFossilCommand)) ->will($this->returnValue(0)); - $expectedFossilCommand = $this->getCmd('fossil open \'repo.fossil\''); + $expectedFossilCommand = $this->getCmd('fossil open \'repo.fossil\' --nested'); $processExecutor->expects($this->at(1)) ->method('execute') ->with($this->equalTo($expectedFossilCommand)) From 6825592d65e5111921525f86d81a7b6a477691de Mon Sep 17 00:00:00 2001 From: Thomas Flori Date: Fri, 13 Apr 2018 09:06:45 +0200 Subject: [PATCH 124/580] move readme description one level up --- doc/04-schema.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index 393020777..06e7fc80b 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -127,6 +127,12 @@ An URL to the website of the project. Optional. +### readme + +A relative path to the readme document. + +Optional. + ### time Release date of the version. @@ -237,7 +243,6 @@ Support information includes the following: * **source:** URL to browse or download the sources. * **docs:** URL to the documentation. * **rss:** URL to the RSS feed. -* **readme:** Relative path to the readme document. An example: From 58e4326067754ade5b6d02adc39713e04bf8f7ee Mon Sep 17 00:00:00 2001 From: Thomas Flori Date: Fri, 13 Apr 2018 09:09:58 +0200 Subject: [PATCH 125/580] add readme node to composer.json schema --- res/composer-schema.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/composer-schema.json b/res/composer-schema.json index 8c61a6240..3a4f2d1d0 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -33,6 +33,10 @@ "description": "Homepage URL for the project.", "format": "uri" }, + "readme": { + "type": "string", + "description": "Relative path to the readme document." + }, "version": { "type": "string", "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes." From f0a1eaaabfe23a5fdbc7664d39b17d183d53ee9f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 09:49:07 +0200 Subject: [PATCH 126/580] Clarify that inline aliases are root only, fixes #7217 --- doc/articles/aliases.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index bda6f79a8..49261b22a 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -92,13 +92,14 @@ Just add this to your project's root `composer.json`: That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub and alias it to `1.0.x-dev`. -> **Note:** If a package with inline aliases is required, the alias (right of -> the `as`) is used as the version constraint. The part left of the `as` is -> discarded. As a consequence, if A requires B and B requires `monolog/monolog` -> version `dev-bugfix as 1.0.x-dev`, installing A will make B require -> `1.0.x-dev`, which may exist as a branch alias or an actual `1.0` branch. If -> it does not, it must be re-inline-aliased in A's `composer.json`. +> **Note:** Inline aliasing is a root-only feature. If a package with inline +> aliases is required, the alias (right of the `as`) is used as the version +> constraint. The part left of the `as` is discarded. As a consequence, if +> A requires B and B requires `monolog/monolog` version `dev-bugfix as 1.0.x-dev`, +> installing A will make B require `1.0.x-dev`, which may exist as a branch +> alias or an actual `1.0` branch. If it does not, it must be +> inline-aliased again in A's `composer.json`. > **Note:** Inline aliasing should be avoided, especially for published -> packages. If you found a bug, try and get your fix merged upstream. This -> helps to avoid issues for users of your package. +> packages/libraries. If you found a bug, try and get your fix merged upstream. +> This helps to avoid issues for users of your package. From e37e7f1329af4c205439d49dc1297c46f6648ccb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 10:43:39 +0200 Subject: [PATCH 127/580] Fix handling of non-callable event listeners, fixes #7229 --- src/Composer/EventDispatcher/EventDispatcher.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 2a180614c..7f7a2cd86 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -166,7 +166,12 @@ class EventDispatcher $return = 0; foreach ($listeners as $callable) { - if (!is_string($callable) && is_callable($callable)) { + if (!is_string($callable)) { + if (!is_callable($callable)) { + $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; + + throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); + } $event = $this->checkListenerExpectedEvent($callable, $event); $return = false === call_user_func($callable, $event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { From 2bded580e2e55777a64e5e4a81c1b11fed7a1ca7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 12:04:18 +0200 Subject: [PATCH 128/580] Update changelog for 1.6.4 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9a026a3..9803c2e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### [1.6.4] 2018-04-13 + + * Security fixes in some edge case scenarios, recommended update for all users + * Fixed regression in version guessing of path repositories + * Fixed removing aliased packages from the repository, which might resolve some odd update bugs + * Fixed updating of package URLs for GitLab + * Fixed run-script --list failing when script handlers were defined + * Fixed init command not respecting the current php version when selecting package versions + * Fixed handling of uppercase package names in why/why-not commands + * Fixed exclude-from-classmap symlink handling + * Fixed filesystem permissions of PEAR binaries + * Improved performance of subversion repos + * Other minor fixes + ### [1.6.3] 2018-01-31 * Fixed GitLab downloads failing in some edge cases @@ -628,6 +642,7 @@ * Initial release +[1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 [1.6.3]: https://github.com/composer/composer/compare/1.6.2...1.6.3 [1.6.2]: https://github.com/composer/composer/compare/1.6.1...1.6.2 [1.6.1]: https://github.com/composer/composer/compare/1.6.0...1.6.1 From 0ab843a05886850d9c442871866f713fd9adf5b8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 13:10:22 +0200 Subject: [PATCH 129/580] Fix setting of scripts from config command, refs #7225 --- src/Composer/Command/ConfigCommand.php | 6 +++--- src/Composer/Config/JsonConfigSource.php | 8 ++++---- src/Composer/Json/JsonManipulator.php | 12 ++++++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 940f5df65..4e0232844 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -613,12 +613,12 @@ EOT } // handle script - if (preg_match('/^scripts\.(.+)/', $settingKey,$matches)){ + if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)){ if ($input->getOption('unset')) { - return $this->configSource->removeConfigSetting($settingKey); + return $this->configSource->removeProperty($settingKey); } - return $this->configSource->addConfigSetting($settingKey, $values[0]); + return $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); } throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index 68de49ab3..128ebf8ec 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -135,10 +135,10 @@ class JsonConfigSource implements ConfigSourceInterface public function addProperty($name, $value) { $this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) { - if (substr($key, 0, 6) === 'extra.') { + if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') { $bits = explode('.', $key); $last = array_pop($bits); - $arr = &$config['extra']; + $arr = &$config[reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { $arr[$bit] = array(); @@ -159,10 +159,10 @@ class JsonConfigSource implements ConfigSourceInterface { $authConfig = $this->authConfig; $this->manipulateJson('removeProperty', $name, function (&$config, $key) { - if (substr($key, 0, 6) === 'extra.') { + if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') { $bits = explode('.', $key); $last = array_pop($bits); - $arr = &$config['extra']; + $arr = &$config[reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { return; diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 52834527b..b9dc24bb5 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -171,6 +171,10 @@ class JsonManipulator return $this->addSubNode('extra', substr($name, 6), $value); } + if (substr($name, 0, 8) === 'scripts.') { + return $this->addSubNode('scripts', substr($name, 8), $value); + } + return $this->addMainKey($name, $value); } @@ -180,6 +184,10 @@ class JsonManipulator return $this->removeSubNode('extra', substr($name, 6)); } + if (substr($name, 0, 8) === 'scripts.') { + return $this->removeSubNode('scripts', substr($name, 8)); + } + return $this->removeMainKey($name); } @@ -188,7 +196,7 @@ class JsonManipulator $decoded = JsonFile::parseJson($this->contents); $subName = null; - if (in_array($mainNode, array('config', 'extra')) && false !== strpos($name, '.')) { + if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) { list($name, $subName) = explode('.', $name, 2); } @@ -308,7 +316,7 @@ class JsonManipulator } $subName = null; - if (in_array($mainNode, array('config', 'extra')) && false !== strpos($name, '.')) { + if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) { list($name, $subName) = explode('.', $name, 2); } From c917865fe9f62fd0a7fa6db910597ec4259fc7bf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 13:48:30 +0200 Subject: [PATCH 130/580] Fix handling of dev versions and consolidate logic, refs #7119 --- src/Composer/Downloader/FileDownloader.php | 3 +- src/Composer/Downloader/VcsDownloader.php | 19 +--------- .../Package/Version/VersionParser.php | 11 ++++++ .../Test/Downloader/FileDownloaderTest.php | 22 +++++++---- .../Test/Downloader/FossilDownloaderTest.php | 3 ++ .../Test/Downloader/GitDownloaderTest.php | 38 +++++++++++-------- .../Test/Downloader/HgDownloaderTest.php | 3 ++ .../Package/Version/VersionParserTest.php | 22 +++++++++++ 8 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index da4695f57..11d20eb99 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -19,6 +19,7 @@ use Composer\IO\IOInterface; use Composer\IO\NullIO; use Composer\Package\Comparer\Comparer; use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; @@ -218,7 +219,7 @@ class FileDownloader implements DownloaderInterface $from = $initial->getPrettyVersion(); $to = $target->getPrettyVersion(); - $actionName = version_compare($from, $to, '<') ? 'Updating' : 'Downgrading'; + $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading'; $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); $this->remove($initial, $path, false); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index a0ccacdf1..aa666058e 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -130,7 +130,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $to = $target->getFullPrettyVersion(); } - $actionName = $this->packageCompare($initial, $target, '>') ? 'Downgrading' : 'Updating'; + $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading'; $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); $this->cleanChanges($initial, $path, true); @@ -243,23 +243,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } } - /** - * Compare two packages. Always false if both versions are references - * - * @param PackageInterface $initial - * @param PackageInterface $target - * @param string $operation - * @return bool - */ - protected function packageCompare(PackageInterface $initial, PackageInterface $target, $operation = '<') - { - if ($initial->getPrettyVersion() == $target->getPrettyVersion()) { - return false; - } - - return version_compare($initial->getFullPrettyVersion(), $target->getFullPrettyVersion(), $operation); - } - /** * Guarantee that no changes have been made to the local copy * diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 22b6dad50..636ccd4da 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -14,6 +14,7 @@ namespace Composer\Package\Version; use Composer\Repository\PlatformRepository; use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Semver\Semver; class VersionParser extends SemverVersionParser { @@ -63,4 +64,14 @@ class VersionParser extends SemverVersionParser return $result; } + + /** + * @return bool + */ + public static function isUpgrade($normalizedFrom, $normalizedTo) + { + $sorted = Semver::sort(array($normalizedTo, $normalizedFrom)); + + return $sorted[0] === $normalizedFrom; + } } diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index fab0c43d1..d186e982e 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -211,18 +211,24 @@ class FileDownloaderTest extends TestCase $oldPackage = $this->getMock('Composer\Package\PackageInterface'); $oldPackage->expects($this->once()) ->method('getPrettyVersion') - ->will($this->returnValue('1.0.0')); - $oldPackage->expects($this->any()) - ->method('getDistUrl') - ->will($this->returnValue($distUrl = 'http://example.com/script.js')); + ->will($this->returnValue('1.2.0')); $oldPackage->expects($this->once()) - ->method('getDistUrls') - ->will($this->returnValue(array($distUrl))); + ->method('getVersion') + ->will($this->returnValue('1.2.0.0')); $newPackage = $this->getMock('Composer\Package\PackageInterface'); $newPackage->expects($this->once()) ->method('getPrettyVersion') - ->will($this->returnValue('1.2.0')); + ->will($this->returnValue('1.0.0')); + $newPackage->expects($this->once()) + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); + $newPackage->expects($this->any()) + ->method('getDistUrl') + ->will($this->returnValue($distUrl = 'http://example.com/script.js')); + $newPackage->expects($this->once()) + ->method('getDistUrls') + ->will($this->returnValue(array($distUrl))); $ioMock = $this->getMock('Composer\IO\IOInterface'); $ioMock->expects(($this->at(0))) @@ -237,6 +243,6 @@ class FileDownloaderTest extends TestCase ->will($this->returnValue(true)); $downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem); - $downloader->update($newPackage, $oldPackage, $path); + $downloader->update($oldPackage, $newPackage, $path); } } diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 961686bef..ca941fe20 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -123,6 +123,9 @@ class FossilDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('http://fossil.kd2.org/kd2fw/'))); + $packageMock->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $expectedFossilCommand = $this->getCmd("fossil changes"); diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 2400fdb56..f487a5e28 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -390,8 +390,8 @@ class GitDownloaderTest extends TestCase ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/composer/composer'))); $packageMock->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('1.0.0')); + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -442,8 +442,8 @@ class GitDownloaderTest extends TestCase ->method('getSourceUrl') ->will($this->returnValue('https://github.com/composer/composer')); $packageMock->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('1.0.0')); + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -510,6 +510,9 @@ composer https://github.com/old/url (push) $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/composer/composer'))); + $packageMock->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') @@ -546,6 +549,9 @@ composer https://github.com/old/url (push) $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); @@ -600,11 +606,11 @@ composer https://github.com/old/url (push) { $oldPackage = $this->getMock('Composer\Package\PackageInterface'); $oldPackage->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('1.0.0')); + ->method('getVersion') + ->will($this->returnValue('1.2.0.0')); $oldPackage->expects($this->any()) ->method('getFullPrettyVersion') - ->will($this->returnValue('1.0.0')); + ->will($this->returnValue('1.2.0')); $oldPackage->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -620,11 +626,11 @@ composer https://github.com/old/url (push) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/composer/composer'))); $newPackage->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('1.2.0')); + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $newPackage->expects($this->any()) ->method('getFullPrettyVersion') - ->will($this->returnValue('1.2.0')); + ->will($this->returnValue('1.0.0')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) @@ -638,15 +644,15 @@ composer https://github.com/old/url (push) $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); - $downloader->update($newPackage, $oldPackage, $this->workingDir); + $downloader->update($oldPackage, $newPackage, $this->workingDir); } public function testNotUsingDowngradingWithReferences() { $oldPackage = $this->getMock('Composer\Package\PackageInterface'); $oldPackage->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('ref')); + ->method('getVersion') + ->will($this->returnValue('dev-ref')); $oldPackage->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -662,8 +668,8 @@ composer https://github.com/old/url (push) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/composer/composer'))); $newPackage->expects($this->any()) - ->method('getPrettyVersion') - ->will($this->returnValue('ref')); + ->method('getVersion') + ->will($this->returnValue('dev-ref2')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) @@ -677,7 +683,7 @@ composer https://github.com/old/url (push) $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); - $downloader->update($newPackage, $oldPackage, $this->workingDir); + $downloader->update($oldPackage, $newPackage, $this->workingDir); } public function testRemove() diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 537d98775..714388f2c 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -109,6 +109,9 @@ class HgDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue('1.0.0.0')); $packageMock->expects($this->any()) ->method('getSourceUrls') ->will($this->returnValue(array('https://github.com/l3l0/composer'))); diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 93728fd61..80dceb822 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -35,4 +35,26 @@ class VersionParserTest extends TestCase array(array('php', 'ext-apcu'), array(array('name' => 'php'), array('name' => 'ext-apcu'))), ); } + + /** + * @dataProvider getIsUpgradeTests + */ + public function testIsUpgrade($from, $to, $expected) + { + $this->assertSame($expected, VersionParser::isUpgrade($from, $to)); + } + + public function getIsUpgradeTests() + { + return array( + array('0.9.0.0', '1.0.0.0', true), + array('1.0.0.0', '0.9.0.0', false), + array('1.0.0.0', '9999999-dev', true), + array('9999999-dev', '9999999-dev', true), + array('9999999-dev', '1.0.0.0', false), + array('1.0.0.0', 'dev-foo', true), + array('dev-foo', 'dev-foo', true), + array('dev-foo', '1.0.0.0', true), + ); + } } From ef7252b358fcb4e37f02ced72230cef50a34a7be Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 14:09:00 +0200 Subject: [PATCH 131/580] Avoid relying on internal usort behavior --- src/Composer/Package/Version/VersionParser.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 636ccd4da..831c61d5f 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -70,6 +70,10 @@ class VersionParser extends SemverVersionParser */ public static function isUpgrade($normalizedFrom, $normalizedTo) { + if (substr($normalizedFrom, 0, 4) === 'dev-' || substr($normalizedTo, 0, 4) === 'dev-') { + return true; + } + $sorted = Semver::sort(array($normalizedTo, $normalizedFrom)); return $sorted[0] === $normalizedFrom; From f42e6a5772c405efb6f2c46bec6fc0d1424acf61 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 14:49:26 +0200 Subject: [PATCH 132/580] Compute keep-vcs/remove-vcs last minute to allow plugins to change the value, refs #7002 --- src/Composer/Command/CreateProjectCommand.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index b1e711f3a..289b8909c 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -137,16 +137,14 @@ EOT $input->getOption('repository') ?: $input->getOption('repository-url'), $input->getOption('no-plugins'), $input->getOption('no-scripts'), - $input->getOption('keep-vcs'), $input->getOption('no-progress'), $input->getOption('no-install'), $input->getOption('ignore-platform-reqs'), - !$input->getOption('no-secure-http'), - $input->getOption('remove-vcs') + !$input->getOption('no-secure-http') ); } - public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true, $removeVcs = false) + public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true) { $oldCwd = getcwd(); @@ -156,7 +154,7 @@ EOT $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); if ($packageName !== null) { - $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $keepVcs, $noProgress, $ignorePlatformReqs, $secureHttp); + $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $noProgress, $ignorePlatformReqs, $secureHttp); } else { $installedFromVcs = false; } @@ -198,10 +196,10 @@ EOT $hasVcs = $installedFromVcs; if ( - !$keepVcs + !$input->getOption('keep-vcs') && $installedFromVcs && ( - $removeVcs + $input->getOption('remove-vcs') || !$io->isInteractive() || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? ', true) ) @@ -258,7 +256,7 @@ EOT return 0; } - protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true) + protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true) { if (!$secureHttp) { $config->merge(array('config' => array('secure-http' => false))); From 0704cd514258c2b72c44415c729aac005c210d9a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 15:25:33 +0200 Subject: [PATCH 133/580] Revert symfony/console to 2.8.37 Workaround for issues caused by https://github.com/symfony/symfony/pull/26609 Fixes #7264 --- composer.json | 3 +++ composer.lock | 14 +++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 74030a47b..338a8b29b 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,9 @@ "phpunit/phpunit": "^4.8.35 || ^5.7", "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" }, + "conflict": { + "symfony/console": "2.8.38" + }, "config": { "platform": { "php": "5.3.9" diff --git a/composer.lock b/composer.lock index e5ee93b05..edcfa43c8 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c8fe8c8c57c958b318515f636a6839e", + "content-hash": "9296254a03c57515ec82689fffcd8008", "packages": [ { "name": "composer/ca-bundle", @@ -441,16 +441,16 @@ }, { "name": "symfony/console", - "version": "v2.8.38", + "version": "v2.8.37", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7f78892d900c72a40acd1fe793c856ef0c110f26" + "reference": "390fa4899dbcc47bd41935d87c4572ea4305d3ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7f78892d900c72a40acd1fe793c856ef0c110f26", - "reference": "7f78892d900c72a40acd1fe793c856ef0c110f26", + "url": "https://api.github.com/repos/symfony/console/zipball/390fa4899dbcc47bd41935d87c4572ea4305d3ce", + "reference": "390fa4899dbcc47bd41935d87c4572ea4305d3ce", "shasum": "" }, "require": { @@ -498,7 +498,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:20:27+00:00" + "time": "2018-03-19T21:13:58+00:00" }, { "name": "symfony/debug", From 0f373e3249b0d9523c94766d69d47676f190fdb4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 13 Apr 2018 15:51:58 +0200 Subject: [PATCH 134/580] Fix issues introduced by #7191, fixes #7263 --- src/Composer/Command/StatusCommand.php | 12 ++++++------ src/Composer/Downloader/DownloaderInterface.php | 9 --------- src/Composer/Downloader/FileDownloader.php | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 274cd846d..f7b0ef37e 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -101,7 +101,9 @@ EOT if ($changes = $downloader->getLocalChanges($package, $targetDir)) { $errors[$targetDir] = $changes; } - } elseif ($downloader instanceof VcsCapableDownloaderInterface) { + } + + if ($downloader instanceof VcsCapableDownloaderInterface) { if ($currentRef = $downloader->getVcsReference($package, $targetDir)) { switch ($package->getInstallationSource()) { case 'source': @@ -129,14 +131,12 @@ EOT ); } } - } elseif ($downloader instanceof DvcsDownloaderInterface) { + } + + if ($downloader instanceof DvcsDownloaderInterface) { if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { $unpushedChanges[$targetDir] = $unpushed; } - } elseif ($downloader instanceof DownloaderInterface) { - if ($changes = $downloader->getLocalChanges($package, $targetDir)) { - $errors[$targetDir] = $changes; - } } } diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index d3ed12cdf..713bf36dc 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -61,13 +61,4 @@ interface DownloaderInterface * @return DownloaderInterface */ public function setOutputProgress($outputProgress); - - /** - * Checks for changes to the local copy - * - * @param PackageInterface $package package instance - * @param string $path package directory - * @return string|null changes or null - */ - public function getLocalChanges(PackageInterface $package, $path); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 11d20eb99..d2a3e7edc 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -35,7 +35,7 @@ use Composer\Util\Url as UrlUtil; * @author François Pluchino * @author Nils Adermann */ -class FileDownloader implements DownloaderInterface +class FileDownloader implements DownloaderInterface, ChangeReportInterface { protected $io; protected $config; From a06ae28c69526aaa7dc12e7abd1cbf9281e3e87f Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Tue, 24 Apr 2018 17:45:51 +0200 Subject: [PATCH 135/580] Improve grammar in the programmatic Composer installation page --- doc/faqs/how-to-install-composer-programmatically.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/faqs/how-to-install-composer-programmatically.md b/doc/faqs/how-to-install-composer-programmatically.md index f075df65c..02ca21d2b 100644 --- a/doc/faqs/how-to-install-composer-programmatically.md +++ b/doc/faqs/how-to-install-composer-programmatically.md @@ -2,9 +2,9 @@ As noted on the download page, the installer script contains a signature which changes when the installer code changes and as such -it should not be relied upon long term. +it should not be relied upon in the long term. -An alternative is to use this script which only works with unix utils: +An alternative is to use this script which only works with UNIX utilities: ```bash #!/bin/sh @@ -29,8 +29,8 @@ exit $RESULT The script will exit with 1 in case of failure, or 0 on success, and is quiet if no error occurs. -Alternatively if you want to rely on an exact copy of the installer you can fetch -a specific version from github's history. The commit hash should be enough to +Alternatively, if you want to rely on an exact copy of the installer, you can fetch +a specific version from GitHub's history. The commit hash should be enough to give it uniqueness and authenticity as long as you can trust the GitHub servers. For example: From 61f54e19ced1fc479dad365f65bffbc3d4d38c03 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Wed, 25 Apr 2018 20:36:43 +0200 Subject: [PATCH 136/580] Fix the JSON schema for package repositories --- res/composer-schema.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/composer-schema.json b/res/composer-schema.json index 3a4f2d1d0..77721edda 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -678,9 +678,7 @@ { "$ref": "#/definitions/inline-package" }, { "type": "array", - "items": { - "type": { "$ref": "#/definitions/inline-package" } - } + "items": { "$ref": "#/definitions/inline-package" } } ] } From 658685744b776d773d38dcc27e7872aad430b74c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 May 2018 17:17:22 -0700 Subject: [PATCH 137/580] Force "C" locale to prevent issue with turkish "I" --- bin/composer | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/composer b/bin/composer index 0664e04ce..57baebc2e 100755 --- a/bin/composer +++ b/bin/composer @@ -5,6 +5,7 @@ if (PHP_SAPI !== 'cli') { echo 'Warning: Composer should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL; } +setlocale(LC_ALL, 'C'); require __DIR__.'/../src/bootstrap.php'; use Composer\Factory; From 635d96b5e50753d5da62e5566332d9c107bddc5b Mon Sep 17 00:00:00 2001 From: mw-jko Date: Thu, 3 May 2018 15:43:36 +0200 Subject: [PATCH 138/580] add hint for possible need of gitlab-domains option --- doc/06-config.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/06-config.md b/doc/06-config.md index 6b10ed634..87ffb02a0 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -65,13 +65,17 @@ an OAuth token for GitHub. A list of domain names and oauth keys. For example using `{"gitlab.com": "oauthtoken"}` as the value of this option will use `oauthtoken` to access -private repositories on gitlab. +private repositories on gitlab. Please note: If the package is not hosted at +gitlab.com the domain names must be also specified with the +[`gitlab-domains`](06-config.md#gitlab-domains) option. ## gitlab-token A list of domain names and private tokens. For example using `{"gitlab.com": "privatetoken"}` as the value of this option will use `privatetoken` to access -private repositories on gitlab. +private repositories on gitlab. Please note: If the package is not hosted at +gitlab.com the domain names must be also specified with the +[`gitlab-domains`](06-config.md#gitlab-domains) option. ## disable-tls From 43e33be79f5c883fb50aef17689aba6685c747bb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 3 May 2018 17:30:19 +0200 Subject: [PATCH 139/580] Fix regression in 036fc44c25e051479f06435800d76c4301d9b1fa, fixes #7268 --- src/Composer/Installer.php | 5 +++++ src/Composer/Repository/ArrayRepository.php | 7 ------- tests/Composer/Test/Repository/ArrayRepositoryTest.php | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 0d842bb53..c60b707d6 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -17,6 +17,7 @@ use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Pool; @@ -716,6 +717,10 @@ class Installer foreach ($devPackages as $pkg) { $packagesToSkip[$pkg->getName()] = true; if ($installedDevPkg = $localRepo->findPackage($pkg->getName(), '*')) { + if ($installedDevPkg instanceof AliasPackage) { + $finalOps[] = new MarkAliasUninstalledOperation($installedDevPkg, 'non-dev install removing it'); + $installedDevPkg = $installedDevPkg->getAliasOf(); + } $finalOps[] = new UninstallOperation($installedDevPkg, 'non-dev install removing it'); } } diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 7a4251084..4f0409a60 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -167,13 +167,6 @@ class ArrayRepository extends BaseRepository { $packageId = $package->getUniqueName(); - if ($package instanceof AliasPackage) { - $aliasedPackage = $package->getAliasOf(); - if ($this === $aliasedPackage->getRepository()) { - $this->removePackage($aliasedPackage); - } - } - foreach ($this->getPackages() as $key => $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { array_splice($this->packages, $key, 1); diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index 5927325ee..bd19b979c 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -68,7 +68,7 @@ class ArrayRepositoryTest extends TestCase $this->assertEquals('bar', $bar[0]->getName()); } - public function testAutomaticallyAddAndRemoveAliasedPackage() + public function testAutomaticallyAddAliasedPackageButNotRemove() { $repo = new ArrayRepository(); @@ -83,7 +83,7 @@ class ArrayRepositoryTest extends TestCase $repo->removePackage($alias); - $this->assertCount(0, $repo); + $this->assertCount(1, $repo); } public function testSearch() From 3b9d6769bfc5f25e0d08574d615b6beb1024379a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 May 2018 10:55:52 +0200 Subject: [PATCH 140/580] Fix class names in comments being parsed in short_open_tags files, fixes #7289 --- src/Composer/Autoload/ClassMapGenerator.php | 4 ++++ tests/Composer/Test/Autoload/ClassMapGeneratorTest.php | 8 -------- .../Autoload/Fixtures/classmap/ShortOpenTagDocblock.php | 1 + 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 5ff23921c..5d937433b 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -179,6 +179,10 @@ class ClassMapGenerator if (false !== $pos && false === strpos(substr($contents, $pos), ' realpath(__DIR__) . '/Fixtures/classmap/ShortOpenTagDocblock.php', ); - /** - * @wontfix If short_open_tag is not enabled, we end up parsing the docblock because - * php_strip_whitespace won't recognize the file. Funky edge-case (does not apply to HHVM). - */ - if (!defined('HHVM_VERSION') && !ini_get('short_open_tag')) { - $classmap['description'] = realpath(__DIR__) . '/Fixtures/classmap/ShortOpenTagDocblock.php'; - } - $data = array( array(__DIR__ . '/Fixtures/Namespaced', array( 'Namespaced\\Bar' => realpath(__DIR__) . '/Fixtures/Namespaced/Bar.inc', diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/ShortOpenTagDocblock.php b/tests/Composer/Test/Autoload/Fixtures/classmap/ShortOpenTagDocblock.php index 72464eaf7..0142ca45e 100644 --- a/tests/Composer/Test/Autoload/Fixtures/classmap/ShortOpenTagDocblock.php +++ b/tests/Composer/Test/Autoload/Fixtures/classmap/ShortOpenTagDocblock.php @@ -2,4 +2,5 @@ /** * Some class description here. */ +// other class name in comment class ShortOpenTagDocblock {} From 78ae0a97f70c2b157d3abea348d14fa7ace96ef3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 May 2018 11:02:02 +0200 Subject: [PATCH 141/580] Terminate quoted strings --- tests/Composer/Test/Autoload/AutoloadGeneratorTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index c676263a8..75cbcd058 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -1334,10 +1334,10 @@ EOF; file_put_contents($this->workingDir.'/forks/bar/src/exclude/FooExclClass.php', 'workingDir.'/forks/bar/'; - $link = $this->workingDir.'/composersrc/foo/bar/'; + $link = $this->workingDir.'/composersrc/foo/bar'; $command = Platform::isWindows() - ? 'mklink /j "' . str_replace('/', '\\', $link) . '" "' . str_replace('/', '\\', $target) - : 'ln -s "' . $target . '" "' . $link; + ? 'mklink /j "' . str_replace('/', '\\', $link) . '" "' . str_replace('/', '\\', $target) . '"' + : 'ln -s "' . $target . '" "' . $link . '"'; exec($command); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); From e697293cd93a8c47bded63c0f2a04f2c68306fae Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 May 2018 11:17:43 +0200 Subject: [PATCH 142/580] Handle broken symlinks more cleanly, fixes #7255 --- src/Composer/Installer/LibraryInstaller.php | 13 ++++++++++++- src/Composer/Util/Filesystem.php | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 4b1a95546..34fbbbee4 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -18,6 +18,7 @@ use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; +use Composer\Util\Platform; /** * Package installation manager. @@ -71,7 +72,17 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { - return $repo->hasPackage($package) && is_readable($this->getInstallPath($package)); + if (!$repo->hasPackage($package)) { + return false; + } + + $installPath = $this->getInstallPath($package); + + if (is_readable($installPath)) { + return true; + } + + return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath); } /** diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 9e0e37279..2daa1ceb1 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -103,6 +103,10 @@ class Filesystem return $this->removeJunction($directory); } + if (is_link($directory)) { + return unlink($directory); + } + if (!file_exists($directory) || !is_dir($directory)) { return true; } From b540ce264c6396a4fb491c861f29dcb0fb5ed737 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 4 May 2018 11:44:55 +0200 Subject: [PATCH 143/580] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9803c2e79..4ba36ef1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### [1.6.5] 2018-05-04 + + * Fixed regression in 1.6.4 causing strange update behaviors with dev packages + * Fixed regression in 1.6.4 color support detection for Windows + * Fixed issues dealing with broken symlinks when switching branches and using path repositories + * Fixed JSON schema for package repositories + * Fixed issues on computers set to Turkish locale + * Fixed classmap parsing of files using short-open-tags when they are disabled in php + ### [1.6.4] 2018-04-13 * Security fixes in some edge case scenarios, recommended update for all users @@ -642,6 +651,7 @@ * Initial release +[1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5 [1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 [1.6.3]: https://github.com/composer/composer/compare/1.6.2...1.6.3 [1.6.2]: https://github.com/composer/composer/compare/1.6.1...1.6.2 From b0be87177d62611e0bf22825942be4820e7295c4 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Tue, 8 May 2018 02:24:37 +0200 Subject: [PATCH 144/580] Filter dev-dependencies from "dump-autoload --no-dev" , fixes #4343 --- src/Composer/Autoload/AutoloadGenerator.php | 43 +++++++++++++++ .../Test/Autoload/AutoloadGeneratorTest.php | 52 +++++++++++++++++-- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 9b9710d9e..b881a2163 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -388,6 +388,7 @@ EOF; public function parseAutoloads(array $packageMap, PackageInterface $mainPackage) { $mainPackageMap = array_shift($packageMap); + $packageMap = $this->filterPackageMap($packageMap, $mainPackage); $sortedPackageMap = $this->sortPackageMap($packageMap); $sortedPackageMap[] = $mainPackageMap; array_unshift($packageMap, $mainPackageMap); @@ -899,6 +900,48 @@ INITIALIZER; return md5($package->getName() . ':' . $path); } + /** + * Filters out dev-dependencies when not in dev-mode + * + * @param array $packageMap + * @param PackageInterface $mainPackage + * @return array + */ + protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) { + if ($this->devMode === true) { + return $packageMap; + } + + $packages = array(); + $include = array(); + + foreach ($packageMap as $item) { + $package = $item[0]; + $name = $package->getName(); + $packages[$name] = $package; + } + + $add = function (PackageInterface $package) use (&$add, $mainPackage, $packages, &$include) { + foreach ($package->getRequires() as $link) { + $target = $link->getTarget(); + $include[$target] = true; + if (isset($packages[$target])) { + $add($packages[$target]); + } + } + }; + $add($mainPackage); + + return array_filter( + $packageMap, + function ($item) use ($include) { + $package = $item[0]; + $name = $package->getName(); + return isset($include[$name]); + } + ); + } + /** * Sorts packages by dependency weight * diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 75cbcd058..f491a62b5 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -360,6 +360,10 @@ class AutoloadGeneratorTest extends TestCase public function testVendorsAutoloading() { $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -406,6 +410,10 @@ class AutoloadGeneratorTest extends TestCase public function testVendorsClassMapAutoloading() { $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -441,6 +449,10 @@ class AutoloadGeneratorTest extends TestCase public function testVendorsClassMapAutoloadingWithTargetDir() { $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -476,6 +488,11 @@ class AutoloadGeneratorTest extends TestCase public function testClassMapAutoloadingEmptyDirAndExactFile() { $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b'), + new Link('a', 'c/c') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -515,6 +532,11 @@ class AutoloadGeneratorTest extends TestCase public function testClassMapAutoloadingAuthoritativeAndApcu() { $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b'), + new Link('a', 'c/c') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -559,6 +581,11 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array('files' => array('root.php'))); + $package->setRequires(array( + new Link('a', 'a/a'), + new Link('a', 'b/b'), + new Link('a', 'c/c') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -604,6 +631,14 @@ class AutoloadGeneratorTest extends TestCase $notAutoloadPackage = new Package('a', '1.0', '1.0'); + $requires = array( + new Link('a', 'a/a'), + new Link('a', 'b/b'), + new Link('a', 'c/c') + ); + $autoloadPackage->setRequires($requires); + $notAutoloadPackage->setRequires($requires); + $autoloadPackages = array(); $autoloadPackages[] = $a = new Package('a/a', '1.0', '1.0'); $autoloadPackages[] = $b = new Package('b/b', '1.0', '1.0'); @@ -667,9 +702,12 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array('files' => array('root2.php'))); - $package->setRequires(array(new Link('a', 'z/foo'))); - $package->setRequires(array(new Link('a', 'd/d'))); - $package->setRequires(array(new Link('a', 'e/e'))); + $package->setRequires(array( + new Link('a', 'z/foo'), + new Link('a', 'b/bar'), + new Link('a', 'd/d'), + new Link('a', 'e/e') + )); $packages = array(); $packages[] = $z = new Package('z/foo', '1.0', '1.0'); @@ -736,7 +774,10 @@ class AutoloadGeneratorTest extends TestCase 'psr-0' => array('A\\B' => $this->workingDir.'/lib'), 'classmap' => array($this->workingDir.'/src'), )); - $mainPackage->setRequires(array(new Link('z', 'a/a'))); + $mainPackage->setRequires(array( + new Link('z', 'a/a'), + new Link('z', 'b/b') + )); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); @@ -993,6 +1034,9 @@ EOF; 'classmap' => array('classmap'), 'files' => array('test.php'), )); + $package->setRequires(array( + new Link('a', 'b/b') + )); $vendorPackage = new Package('b/b', '1.0', '1.0'); $vendorPackage->setAutoload(array( From b99798068d8705eb6f755d8aa538975bba371dc3 Mon Sep 17 00:00:00 2001 From: Helmut Hummel Date: Mon, 14 May 2018 16:54:16 +0200 Subject: [PATCH 145/580] Use symfony/console for hidden questions Fixes: #7337 --- README.md | 2 -- composer.json | 1 - composer.lock | 50 +---------------------------------- src/Composer/Compiler.php | 3 +-- src/Composer/IO/ConsoleIO.php | 7 +++-- 5 files changed, 7 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 3e1b7815d..691fda8dd 100644 --- a/README.md +++ b/README.md @@ -62,5 +62,3 @@ Acknowledgments - This project's Solver started out as a PHP port of openSUSE's [Libzypp satsolver](https://en.opensuse.org/openSUSE:Libzypp_satsolver). -- This project uses hiddeninput.exe to prompt for passwords on windows, sources - and details can be found on the [github page of the project](https://github.com/Seldaek/hidden-input). diff --git a/composer.json b/composer.json index 60c85711e..7cecf7cae 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,6 @@ "symfony/process": "^2.7 || ^3.0 || ^4.0", "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", "seld/phar-utils": "^1.0", - "seld/cli-prompt": "^1.0", "psr/log": "^1.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 4b7e927d9..5cf0feae0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c6378672910186460447c90de26a6c03", + "content-hash": "2dc8cdba11ff8ac7c8fd9a3767af326b", "packages": [ { "name": "composer/ca-bundle", @@ -342,54 +342,6 @@ ], "time": "2016-10-10T12:19:37+00:00" }, - { - "name": "seld/cli-prompt", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/cli-prompt.git", - "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/a19a7376a4689d4d94cab66ab4f3c816019ba8dd", - "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\CliPrompt\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", - "keywords": [ - "cli", - "console", - "hidden", - "input", - "prompt" - ], - "time": "2017-03-18T11:32:45+00:00" - }, { "name": "seld/jsonlint", "version": "1.7.1", diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 3fdbcd867..4064b20b5 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -105,7 +105,7 @@ class Compiler foreach ($finder as $file) { $this->addFile($phar, $file, false); } - $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/seld/cli-prompt/res/hiddeninput.exe'), false); + $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe'), false); $finder = new Finder(); $finder->files() @@ -117,7 +117,6 @@ class Compiler ->exclude('docs') ->in(__DIR__.'/../../vendor/symfony/') ->in(__DIR__.'/../../vendor/seld/jsonlint/') - ->in(__DIR__.'/../../vendor/seld/cli-prompt/') ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/') ->in(__DIR__.'/../../vendor/composer/spdx-licenses/') ->in(__DIR__.'/../../vendor/composer/semver/') diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 55c80e29c..c0f235659 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -272,9 +272,12 @@ class ConsoleIO extends BaseIO */ public function askAndHideAnswer($question) { - $this->writeError($question, false); + /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */ + $helper = $this->helperSet->get('question'); + $question = new Question($question); + $question->setHidden(true); - return \Seld\CliPrompt\CliPrompt::hiddenPrompt(true); + return $helper->ask($this->input, $this->getErrorOutput(), $question); } /** From af1dccb1fb67ae3fc337ae95376ef77658d9a711 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 15 May 2018 15:20:18 +0200 Subject: [PATCH 146/580] Avoid showing virtual packages in search results, fixes #7310 --- src/Composer/Repository/ComposerRepository.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 60ab0dfbd..bba67f999 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -201,9 +201,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $hostname = parse_url($url, PHP_URL_HOST) ?: $url; $json = $this->rfs->getContents($hostname, $url, false); - $results = JsonFile::parseJson($json, $url); + $search = JsonFile::parseJson($json, $url); - return $results['results']; + $results = array(); + foreach ($search['results'] as $result) { + // do not show virtual packages in results as they are not directly useful from a composer perspective + if (empty($result['virtual'])) { + $results[] = $result; + } + } + + return $results; } if ($this->hasProviders()) { From fd00ea7ce856b44514884c3b91ab4ad0303eccb1 Mon Sep 17 00:00:00 2001 From: zefrog Date: Tue, 15 May 2018 15:31:31 +0200 Subject: [PATCH 147/580] Fix Git detection of authentication failure when no tty is attached to the process --- src/Composer/Util/Git.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index e4d6bd803..a5fa87982 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -254,6 +254,7 @@ class Git 'remote error: Invalid username or password.', 'error: 401 Unauthorized', 'fatal: unable to access', + 'fatal: could not read Username', ); foreach ($authFailures as $authFailure) { From c6d53abf8991b022cb9c2156a00ccc5ed744d991 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 15 May 2018 16:06:56 +0200 Subject: [PATCH 148/580] Fix tests --- src/Composer/Repository/ComposerRepository.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index bba67f999..53c16129f 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -203,6 +203,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $json = $this->rfs->getContents($hostname, $url, false); $search = JsonFile::parseJson($json, $url); + if (empty($search['results'])) { + return array(); + } + $results = array(); foreach ($search['results'] as $result) { // do not show virtual packages in results as they are not directly useful from a composer perspective From 5435877bd9325322e52bae454878743b38f20f13 Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Fri, 18 May 2018 10:59:09 +0200 Subject: [PATCH 149/580] Improve SAT resolving developer debug information --- src/Composer/DependencyResolver/Solver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 1775cb5ce..c9a87221b 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -223,9 +223,10 @@ class Solver /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); - $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); + $this->io->writeError('Resolving dependencies through SAT', false, IOInterface::DEBUG); $before = microtime(true); $this->runSat(true); + $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); // decide to remove everything that's installed and undecided @@ -762,6 +763,7 @@ class Solver for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { if ($i == $rulesCount) { + $this->io->writeError('.', false, IOInterface::DEBUG); $i = 0; } From ee97f26931b7d28174adb4db79ef060a7d9db9af Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Tue, 22 May 2018 10:39:25 +1200 Subject: [PATCH 150/580] FIX Update PHPDocs to indicate more specific return types --- src/Composer/Repository/RepositoryManager.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 7a7fc0a9b..87b82d14d 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -54,6 +54,7 @@ class RepositoryManager public function findPackage($name, $constraint) { foreach ($this->repositories as $repository) { + /** @var RepositoryInterface $repository */ if ($package = $repository->findPackage($name, $constraint)) { return $package; } @@ -68,13 +69,13 @@ class RepositoryManager * @param string $name package name * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * - * @return array + * @return PackageInterface[] */ public function findPackages($name, $constraint) { $packages = array(); - foreach ($this->repositories as $repository) { + foreach ($this->getRepositories() as $repository) { $packages = array_merge($packages, $repository->findPackages($name, $constraint)); } @@ -147,7 +148,7 @@ class RepositoryManager /** * Returns all repositories, except local one. * - * @return array + * @return RepositoryInterface[] */ public function getRepositories() { From d82bdc04ac0f4de4174e69ed883c8eac63db6df3 Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Thu, 24 May 2018 11:46:50 +0200 Subject: [PATCH 151/580] Improved debugging output --- src/Composer/DependencyResolver/Solver.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index c9a87221b..862411c61 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -223,7 +223,7 @@ class Solver /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); - $this->io->writeError('Resolving dependencies through SAT', false, IOInterface::DEBUG); + $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); $before = microtime(true); $this->runSat(true); $this->io->writeError('', true, IOInterface::DEBUG); @@ -760,11 +760,19 @@ class Solver } $rulesCount = count($this->rules); + $pass = 1; + $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG); for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { if ($i == $rulesCount) { - $this->io->writeError('.', false, IOInterface::DEBUG); + if (1 === $pass) { + $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG); + } else { + $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG); + } + $i = 0; + $pass++; } $rule = $this->rules->ruleById[$i]; From eedbd218f52c526ec41c6c623137c3781eb4d928 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 31 May 2018 17:02:04 +0200 Subject: [PATCH 152/580] Make sure circular dependencies do not break the autoload dumper, refs #7316, refs #7348 --- src/Composer/Autoload/AutoloadGenerator.php | 15 +++++---- .../Test/Autoload/AutoloadGeneratorTest.php | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index b881a2163..5a0da1f40 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -902,12 +902,13 @@ INITIALIZER; /** * Filters out dev-dependencies when not in dev-mode - * + * * @param array $packageMap * @param PackageInterface $mainPackage * @return array */ - protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) { + protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) + { if ($this->devMode === true) { return $packageMap; } @@ -924,14 +925,16 @@ INITIALIZER; $add = function (PackageInterface $package) use (&$add, $mainPackage, $packages, &$include) { foreach ($package->getRequires() as $link) { $target = $link->getTarget(); - $include[$target] = true; - if (isset($packages[$target])) { - $add($packages[$target]); + if (!isset($include[$target])) { + $include[$target] = true; + if (isset($packages[$target])) { + $add($packages[$target]); + } } } }; $add($mainPackage); - + return array_filter( $packageMap, function ($item) use ($include) { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index f491a62b5..dd99bfd59 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -386,6 +386,39 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testNonDevAutoloadExclusionWithRecursion() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); + $a->setRequires(array( + new Link('a/a', 'b/b'), + )); + $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); + $b->setRequires(array( + new Link('b/b', 'a/a'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); From d7645a83a2ae94aacd0fc6483dd4b5cd65f82438 Mon Sep 17 00:00:00 2001 From: Sam Pablo Kuper Date: Thu, 31 May 2018 23:50:31 +0100 Subject: [PATCH 153/580] Document the versioning system used by Composer. Fixes #7356. --- ...which-version-numbering-system-does-composer-itself-use.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/faqs/which-version-numbering-system-does-composer-itself-use.md diff --git a/doc/faqs/which-version-numbering-system-does-composer-itself-use.md b/doc/faqs/which-version-numbering-system-does-composer-itself-use.md new file mode 100644 index 000000000..20095bb01 --- /dev/null +++ b/doc/faqs/which-version-numbering-system-does-composer-itself-use.md @@ -0,0 +1,4 @@ +# Which version numbering system does Composer itself use? + +Composer uses [Semantic Versioning (aka SemVer) +2.0.0](https://semver.org/spec/v2.0.0.html). From 6ff74d3ed1b02b8614cdd50795fa2b743181ca81 Mon Sep 17 00:00:00 2001 From: Jeroen Ketelaar Date: Tue, 5 Jun 2018 15:55:14 +0200 Subject: [PATCH 154/580] [BUGFIX] Fixed typo in comment --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index c60b707d6..f4f778ddb 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1741,7 +1741,7 @@ class Installer } /** - * Should the operations (packge install, update and removal) be executed on disk? + * Should the operations (package install, update and removal) be executed on disk? * * This is disabled implicitly when enabling dryRun * From 0a27ca7b653856a0b989f889a8a089b70581059e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 31 May 2018 17:02:04 +0200 Subject: [PATCH 155/580] Make sure circular dependencies do not break the autoload dumper, refs #7316, refs #7348 --- src/Composer/Autoload/AutoloadGenerator.php | 15 +++++---- .../Test/Autoload/AutoloadGeneratorTest.php | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index b881a2163..5a0da1f40 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -902,12 +902,13 @@ INITIALIZER; /** * Filters out dev-dependencies when not in dev-mode - * + * * @param array $packageMap * @param PackageInterface $mainPackage * @return array */ - protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) { + protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) + { if ($this->devMode === true) { return $packageMap; } @@ -924,14 +925,16 @@ INITIALIZER; $add = function (PackageInterface $package) use (&$add, $mainPackage, $packages, &$include) { foreach ($package->getRequires() as $link) { $target = $link->getTarget(); - $include[$target] = true; - if (isset($packages[$target])) { - $add($packages[$target]); + if (!isset($include[$target])) { + $include[$target] = true; + if (isset($packages[$target])) { + $add($packages[$target]); + } } } }; $add($mainPackage); - + return array_filter( $packageMap, function ($item) use ($include) { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index f491a62b5..dd99bfd59 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -386,6 +386,39 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testNonDevAutoloadExclusionWithRecursion() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); + $a->setRequires(array( + new Link('a/a', 'b/b'), + )); + $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); + $b->setRequires(array( + new Link('b/b', 'a/a'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); From 07867724d0b053a1b43241ee983143f5a5156d8b Mon Sep 17 00:00:00 2001 From: Rasmus Schultz Date: Fri, 1 Jun 2018 12:47:22 +0200 Subject: [PATCH 156/580] add back the warning about missing unzip display an error-message on non-Windows OS if unzip is unavailable, per #7383 --- src/Composer/Downloader/ZipDownloader.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 00f7fbd1b..aeade031b 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -58,6 +58,11 @@ class ZipDownloader extends ArchiveDownloader if (null === self::$isWindows) { self::$isWindows = Platform::isWindows(); + + if (!self::$isWindows && !self::$hasSystemUnzip) { + $this->io->writeError("As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them."); + } } if (!self::$hasZipArchive && !self::$hasSystemUnzip) { From 5bae1913abfea7bdea9fc13c81f26403e36b3d68 Mon Sep 17 00:00:00 2001 From: Rasmus Schultz Date: Thu, 7 Jun 2018 11:08:50 +0200 Subject: [PATCH 157/580] swap tests to prevent conflicting error-messages --- src/Composer/Downloader/ZipDownloader.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index aeade031b..6217aaa53 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -56,6 +56,14 @@ class ZipDownloader extends ArchiveDownloader self::$hasZipArchive = class_exists('ZipArchive'); } + if (!self::$hasZipArchive && !self::$hasSystemUnzip) { + // php.ini path is added to the error message to help users find the correct file + $iniMessage = IniHelper::getMessage(); + $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage; + + throw new \RuntimeException($error); + } + if (null === self::$isWindows) { self::$isWindows = Platform::isWindows(); @@ -65,14 +73,6 @@ class ZipDownloader extends ArchiveDownloader } } - if (!self::$hasZipArchive && !self::$hasSystemUnzip) { - // php.ini path is added to the error message to help users find the correct file - $iniMessage = IniHelper::getMessage(); - $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage; - - throw new \RuntimeException($error); - } - return parent::download($package, $path, $output); } From 87646ae6897ff7f72c5e3b2776ce5d7b54626722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sat, 9 Jun 2018 12:23:41 +0100 Subject: [PATCH 158/580] Hide suggest reason when there is not one --- .../Installer/SuggestedPackagesReporter.php | 4 ++-- .../Installer/SuggestedPackagesReporterTest.php | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer/SuggestedPackagesReporter.php b/src/Composer/Installer/SuggestedPackagesReporter.php index ed89ec02b..25788e547 100644 --- a/src/Composer/Installer/SuggestedPackagesReporter.php +++ b/src/Composer/Installer/SuggestedPackagesReporter.php @@ -115,10 +115,10 @@ class SuggestedPackagesReporter } $this->io->writeError(sprintf( - '%s suggests installing %s (%s)', + '%s suggests installing %s%s', $suggestion['source'], $this->escapeOutput($suggestion['target']), - $this->escapeOutput($suggestion['reason']) + $this->escapeOutput('' !== $suggestion['reason'] ? ' ('.$suggestion['reason'].')' : '') )); } diff --git a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php index 7c80935cd..6aef0fa7d 100644 --- a/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php +++ b/tests/Composer/Test/Installer/SuggestedPackagesReporterTest.php @@ -142,6 +142,20 @@ class SuggestedPackagesReporterTest extends TestCase $this->suggestedPackagesReporter->output(); } + /** + * @covers ::output + */ + public function testOutputWithNoSuggestedPackage() + { + $this->suggestedPackagesReporter->addPackage('a', 'b', ''); + + $this->io->expects($this->once()) + ->method('writeError') + ->with('a suggests installing b'); + + $this->suggestedPackagesReporter->output(); + } + /** * @covers ::output */ From 20e89a76210accf20f3574553796bd4c059a4bb9 Mon Sep 17 00:00:00 2001 From: mohsen shafiei Date: Mon, 11 Jun 2018 01:09:52 +0430 Subject: [PATCH 159/580] composer show options -t and -l do not work together, fixes #7210 --- src/Composer/Command/ShowCommand.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 168b0f285..3f419c3d3 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -118,6 +118,12 @@ EOT return 1; } + if ($input->getOption('tree') && $input->getOption('latest')) { + $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)'); + + return 1; + } + $format = $input->getOption('format'); if (!in_array($format, array('text', 'json'))) { $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); From 3db5e0e3fc3fd30e1384437ce4d6e3c0ba674b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Sun, 17 Jun 2018 12:01:38 +0200 Subject: [PATCH 160/580] DX: .php_cs - drop commented option of v1 This option had been removed in v2.0, linter is enabled and can't be disabled. Don't worry - under PHP 7 it has no performance impact --- .php_cs | 1 - 1 file changed, 1 deletion(-) diff --git a/.php_cs b/.php_cs index dd140b051..c3fe1e894 100644 --- a/.php_cs +++ b/.php_cs @@ -20,7 +20,6 @@ $finder = PhpCsFixer\Finder::create() return PhpCsFixer\Config::create() ->setUsingCache(true) - //->setUsingLinter(false) ->setRiskyAllowed(true) ->setRules(array( '@PSR2' => true, From 7221e4ea4ee63d0fd03dae62dfe21990d2faf7d0 Mon Sep 17 00:00:00 2001 From: Matrosov Date: Tue, 19 Jun 2018 17:29:00 +0200 Subject: [PATCH 161/580] Generate tree view before displaying it Add support json tree view --- src/Composer/Command/ShowCommand.php | 226 ++++++++++++++++++++------- 1 file changed, 172 insertions(+), 54 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 168b0f285..e5dcca4d0 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -178,9 +178,6 @@ EOT // show single package or single version if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) { - if ('json' === $format) { - $io->writeError('Format "json" is only supported for package listings, falling back to format "text"'); - } if (empty($package)) { list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); @@ -200,7 +197,13 @@ EOT $exitCode = 0; if ($input->getOption('tree')) { - $this->displayPackageTree($package, $installedRepo, $repos); + $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); + + if ('json' === $format) { + $io->write(JsonFile::encode(array('installed' => array($arrayTree)))); + } else { + $this->displayPackageTree(array($arrayTree)); + } } else { $latestPackage = null; if ($input->getOption('latest')) { @@ -228,18 +231,22 @@ EOT // show tree view if requested if ($input->getOption('tree')) { - if ('json' === $format) { - $io->writeError('Format "json" is only supported for package listings, falling back to format "text"'); - } $rootRequires = $this->getRootRequires(); $packages = $installedRepo->getPackages(); usort($packages, 'strcmp'); + $arrayTree = array(); foreach ($packages as $package) { if (in_array($package->getName(), $rootRequires, true)) { - $this->displayPackageTree($package, $installedRepo, $repos); + $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos); } } + if ('json' === $format) { + $io->write(JsonFile::encode(array('installed' => $arrayTree))); + } else { + $this->displayPackageTree($arrayTree); + } + return 0; } @@ -706,40 +713,144 @@ EOT /** * Display the tree * - * @param PackageInterface|string $package - * @param RepositoryInterface $installedRepo - * @param RepositoryInterface $distantRepos + * @param $arrayTree */ - protected function displayPackageTree(PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos) + protected function displayPackageTree(array $arrayTree) { $io = $this->getIO(); - $io->write(sprintf('%s', $package->getPrettyName()), false); - $io->write(' ' . $package->getPrettyVersion(), false); - $io->write(' ' . strtok($package->getDescription(), "\r\n")); + foreach ($arrayTree as $package) { + $io->write(sprintf('%s', $package['name']), false); + $io->write(' ' . $package['version'], false); + $io->write(' ' . strtok($package['description'], "\r\n")); + if (isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = '├'; + $j = 0; + $total = count($requires); + foreach ($requires as $require) { + $requireName = $require['name']; + $j++; + if ($j === $total) { + $treeBar = '└'; + } + $level = 1; + $color = $this->colors[$level]; + $info = sprintf( + '%s──<%s>%s %s', + $treeBar, + $color, + $requireName, + $color, + $require['version'] + ); + $this->writeTreeLine($info); + + $treeBar = str_replace('└', ' ', $treeBar); + $packagesInTree = array($package['name'], $requireName); + + $this->displayTree($require, $packagesInTree, $treeBar, $level + 1); + } + } + } + } + + /** + * Generate the package tree + * + * @param PackageInterface|string $package + * @param RepositoryInterface $installedRepo + * @param RepositoryInterface $distantRepos + * @return array + */ + protected function generatePackageTree( + PackageInterface $package, + RepositoryInterface $installedRepo, + RepositoryInterface $distantRepos + ) { if (is_object($package)) { $requires = $package->getRequires(); ksort($requires); - $treeBar = '├'; - $j = 0; - $total = count($requires); + $children = array(); foreach ($requires as $requireName => $require) { - $j++; - if ($j == 0) { - $this->writeTreeLine($treeBar); + $packagesInTree = array($package->getName(), $requireName); + + $treeChildDesc = array( + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ); + + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree); + + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; } - if ($j == $total) { - $treeBar = '└'; + + $children[] = $treeChildDesc; + } + $tree = array( + 'name' => $package->getPrettyName(), + 'version' => $package->getPrettyVersion(), + 'description' => $package->getDescription(), + ); + + if ($children) { + $tree['requires'] = $children; + } + + return $tree; + } + } + + /** + * Display a package tree + * + * @param PackageInterface|string $package + * @param array $packagesInTree + * @param string $previousTreeBar + * @param int $level + */ + protected function displayTree( + $package, + array $packagesInTree, + $previousTreeBar = '├', + $level = 1 + ) { + $previousTreeBar = str_replace('├', '│', $previousTreeBar); + if (isset($package['requires'])) { + $requires = $package['requires']; + $treeBar = $previousTreeBar . ' ├'; + $i = 0; + $total = count($requires); + foreach ($requires as $require) { + $currentTree = $packagesInTree; + $i++; + if ($i === $total) { + $treeBar = $previousTreeBar . ' └'; } - $level = 1; - $color = $this->colors[$level]; - $info = sprintf('%s──<%s>%s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint()); + $colorIdent = $level % count($this->colors); + $color = $this->colors[$colorIdent]; + + $circularWarn = in_array( + $require['name'], + $currentTree, + true + ) ? '(circular dependency aborted here)' : ''; + $info = rtrim(sprintf( + '%s──<%s>%s %s %s', + $treeBar, + $color, + $require['name'], + $color, + $require['version'], + $circularWarn + )); $this->writeTreeLine($info); $treeBar = str_replace('└', ' ', $treeBar); - $packagesInTree = array($package->getName(), $requireName); - $this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1); + $currentTree[] = $require['name']; + $this->displayTree($require, $currentTree, $treeBar, $level + 1); } } } @@ -747,44 +858,51 @@ EOT /** * Display a package tree * - * @param string $name - * @param PackageInterface|string $package - * @param RepositoryInterface $installedRepo - * @param RepositoryInterface $distantRepos - * @param array $packagesInTree - * @param string $previousTreeBar - * @param int $level + * @param string $name + * @param PackageInterface|string $package + * @param RepositoryInterface $installedRepo + * @param RepositoryInterface $distantRepos + * @param array $packagesInTree + * @return array */ - protected function displayTree($name, $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos, array $packagesInTree, $previousTreeBar = '├', $level = 1) - { - $previousTreeBar = str_replace('├', '│', $previousTreeBar); - list($package, $versions) = $this->getPackage($installedRepo, $distantRepos, $name, $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()); + protected function addTree( + $name, + $package, + RepositoryInterface $installedRepo, + RepositoryInterface $distantRepos, + array $packagesInTree + ) { + $children = array(); + list($package, $versions) = $this->getPackage( + $installedRepo, + $distantRepos, + $name, + $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint() + ); if (is_object($package)) { $requires = $package->getRequires(); ksort($requires); - $treeBar = $previousTreeBar . ' ├'; - $i = 0; - $total = count($requires); foreach ($requires as $requireName => $require) { $currentTree = $packagesInTree; - $i++; - if ($i == $total) { - $treeBar = $previousTreeBar . ' └'; - } - $colorIdent = $level % count($this->colors); - $color = $this->colors[$colorIdent]; - $circularWarn = in_array($requireName, $currentTree) ? '(circular dependency aborted here)' : ''; - $info = rtrim(sprintf('%s──<%s>%s %s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint(), $circularWarn)); - $this->writeTreeLine($info); + $treeChildDesc = array( + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), + ); - $treeBar = str_replace('└', ' ', $treeBar); - if (!in_array($requireName, $currentTree)) { + if (!in_array($requireName, $currentTree, true)) { $currentTree[] = $requireName; - $this->displayTree($requireName, $require, $installedRepo, $distantRepos, $currentTree, $treeBar, $level + 1); + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree); + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; + } } + + $children[] = $treeChildDesc; } } + + return $children; } private function updateStatusToVersionStyle($updateStatus) From dcdef95785e5bfa97e1344ec317e36d04d4fb085 Mon Sep 17 00:00:00 2001 From: Nathan Purcell Date: Thu, 21 Jun 2018 09:02:29 +0100 Subject: [PATCH 162/580] Update example for auth.json in BitBucket config Remove the `config` level --- doc/05-repositories.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index af0d0b01e..db54a4fb1 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -305,12 +305,10 @@ After creating an OAuth consumer in the BitBucket control panel, you need to set the credentials like this (more info [here](https://getcomposer.org/doc/06-config.md#bitbucket-oauth)): ```json { - "config": { - "bitbucket-oauth": { - "bitbucket.org": { - "consumer-key": "myKey", - "consumer-secret": "mySecret" - } + "bitbucket-oauth": { + "bitbucket.org": { + "consumer-key": "myKey", + "consumer-secret": "mySecret" } } } From e0022f69dadef49723c68b3825ebc1db5e457798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sun, 24 Jun 2018 10:56:35 +0200 Subject: [PATCH 163/580] Enhancement: Normalize composer.json --- composer.json | 64 +++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 7cecf7cae..91da0ec60 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,13 @@ { "name": "composer/composer", - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", - "keywords": ["package", "dependency", "autoload"], - "homepage": "https://getcomposer.org/", "type": "library", + "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "keywords": [ + "package", + "dependency", + "autoload" + ], + "homepage": "https://getcomposer.org/", "license": "MIT", "authors": [ { @@ -17,55 +21,61 @@ "homepage": "http://seld.be" } ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/composer/issues" - }, "require": { "php": "^5.3.2 || ^7.0", - "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", "composer/xdebug-handler": "^1.1", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "psr/log": "^1.0", "seld/jsonlint": "^1.4", - "symfony/console": "^2.7 || ^3.0 || ^4.0", - "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", "seld/phar-utils": "^1.0", - "psr/log": "^1.0" + "symfony/console": "^2.7 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0" + }, + "conflict": { + "symfony/console": "2.8.38" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7", "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" }, - "conflict": { - "symfony/console": "2.8.38" + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" }, "config": { "platform": { "php": "5.3.9" } }, - "suggest": { - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests", - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages" - }, - "autoload": { - "psr-4": { "Composer\\": "src/Composer" } - }, - "autoload-dev": { - "psr-4": { "Composer\\Test\\": "tests/Composer/Test" } - }, - "bin": ["bin/composer"], "extra": { "branch-alias": { "dev-master": "1.7-dev" } }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "autoload-dev": { + "psr-4": { + "Composer\\Test\\": "tests/Composer/Test" + } + }, + "bin": [ + "bin/composer" + ], "scripts": { "test": "phpunit" + }, + "support": { + "issues": "https://github.com/composer/composer/issues", + "irc": "irc://irc.freenode.org/composer" } } From c5cbdec0de46bb78f0ab2d5fa50ef1fbd189ea9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sun, 24 Jun 2018 18:53:36 +0200 Subject: [PATCH 164/580] Fix: Remove VersionEye badges --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 691fda8dd..c538df803 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Composer helps you declare, manage, and install dependencies of PHP projects. See [https://getcomposer.org/](https://getcomposer.org/) for more information and documentation. [![Build Status](https://travis-ci.org/composer/composer.svg?branch=master)](https://travis-ci.org/composer/composer) -[![Dependency Status](https://www.versioneye.com/php/composer:composer/dev-master/badge.svg)](https://www.versioneye.com/php/composer:composer/dev-master) -[![Reference Status](https://www.versioneye.com/php/composer:composer/reference_badge.svg?style=flat)](https://www.versioneye.com/php/composer:composer/references) Installation / Usage -------------------- From 9355ebd3f67bd91d4595fddd856e4b344f5512ec Mon Sep 17 00:00:00 2001 From: efajnzilberg Date: Mon, 25 Jun 2018 10:34:00 +0200 Subject: [PATCH 165/580] Closing the ZipArchive in ArtifactRepository::getComposerInformation() --- src/Composer/Repository/ArtifactRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index a8002123a..374f04cea 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -138,6 +138,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito } $configurationFileName = $zip->getNameIndex($foundFileIndex); + $zip->close(); $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; $json = file_get_contents($composerFile); From 7d9f8e22474ce3d6c92f5f2469b5f6423892b617 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 4 Jul 2018 21:52:04 -0300 Subject: [PATCH 166/580] Improvements Small improvements, such as remove unused imports, unecessaries casts, parentheses, etc. --- src/Composer/Autoload/AutoloadGenerator.php | 2 +- src/Composer/Command/ShowCommand.php | 2 +- src/Composer/Command/StatusCommand.php | 1 - src/Composer/DependencyResolver/GenericRule.php | 2 +- src/Composer/DependencyResolver/Problem.php | 2 +- src/Composer/DependencyResolver/Rule2Literals.php | 2 +- src/Composer/DependencyResolver/RuleSetGenerator.php | 4 ++-- src/Composer/DependencyResolver/Transaction.php | 2 +- src/Composer/Installer.php | 9 +++------ src/Composer/Installer/BinaryInstaller.php | 2 +- src/Composer/Json/JsonFile.php | 1 - src/Composer/Json/JsonFormatter.php | 2 +- src/Composer/Package/Loader/RootPackageLoader.php | 2 +- src/Composer/Package/Loader/ValidatingArrayLoader.php | 1 - src/Composer/Plugin/PluginManager.php | 4 ++-- src/Composer/Util/Filesystem.php | 2 +- src/Composer/Util/TlsHelper.php | 2 +- src/Composer/Util/Url.php | 1 - 18 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 5a0da1f40..f7dfdef0d 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -922,7 +922,7 @@ INITIALIZER; $packages[$name] = $package; } - $add = function (PackageInterface $package) use (&$add, $mainPackage, $packages, &$include) { + $add = function (PackageInterface $package) use (&$add, $packages, &$include) { foreach ($package->getRequires() as $link) { $target = $link->getTarget(); if (!isset($include[$target])) { diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 168b0f285..d42da046b 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -383,7 +383,7 @@ EOT } if ($latestPackage && $latestPackage->isAbandoned()) { - $replacement = (is_string($latestPackage->getReplacementPackage())) + $replacement = is_string($latestPackage->getReplacementPackage()) ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $packageWarning = sprintf( diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index f7b0ef37e..6ea388a7a 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -12,7 +12,6 @@ namespace Composer\Command; -use Composer\Downloader\DownloaderInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; diff --git a/src/Composer/DependencyResolver/GenericRule.php b/src/Composer/DependencyResolver/GenericRule.php index 0af3617d4..df8a2a003 100644 --- a/src/Composer/DependencyResolver/GenericRule.php +++ b/src/Composer/DependencyResolver/GenericRule.php @@ -75,7 +75,7 @@ class GenericRule extends Rule */ public function __toString() { - $result = ($this->isDisabled()) ? 'disabled(' : '('; + $result = $this->isDisabled() ? 'disabled(' : '('; foreach ($this->literals as $i => $literal) { if ($i != 0) { diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index debc8678c..184a55636 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -247,6 +247,6 @@ class Problem */ protected function constraintToText($constraint) { - return ($constraint) ? ' '.$constraint->getPrettyString() : ''; + return $constraint ? ' '.$constraint->getPrettyString() : ''; } } diff --git a/src/Composer/DependencyResolver/Rule2Literals.php b/src/Composer/DependencyResolver/Rule2Literals.php index 359fe4f20..c44a15ab2 100644 --- a/src/Composer/DependencyResolver/Rule2Literals.php +++ b/src/Composer/DependencyResolver/Rule2Literals.php @@ -93,7 +93,7 @@ class Rule2Literals extends Rule */ public function __toString() { - $result = ($this->isDisabled()) ? 'disabled(' : '('; + $result = $this->isDisabled() ? 'disabled(' : '('; $result .= $this->literal1 . '|' . $this->literal2 . ')'; diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 2cf150a33..3855f7d04 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -215,7 +215,7 @@ class RuleSetGenerator } // check obsoletes and implicit obsoletes of a package - $isInstalled = (isset($this->installedMap[$package->id])); + $isInstalled = isset($this->installedMap[$package->id]); foreach ($package->getReplaces() as $link) { $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); @@ -226,7 +226,7 @@ class RuleSetGenerator } if (!$this->obsoleteImpossibleForAlias($package, $provider)) { - $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; + $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link)); } } diff --git a/src/Composer/DependencyResolver/Transaction.php b/src/Composer/DependencyResolver/Transaction.php index 3674a1989..c8d3bbe53 100644 --- a/src/Composer/DependencyResolver/Transaction.php +++ b/src/Composer/DependencyResolver/Transaction.php @@ -49,7 +49,7 @@ class Transaction $package = $this->pool->literalToPackage($literal); // wanted & installed || !wanted & !installed - if (($literal > 0) == (isset($this->installedMap[$package->id]))) { + if (($literal > 0) == isset($this->installedMap[$package->id])) { continue; } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f4f778ddb..1ae72aed0 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -251,7 +251,7 @@ class Installer continue; } - $replacement = (is_string($package->getReplacementPackage())) + $replacement = is_string($package->getReplacementPackage()) ? 'Use ' . $package->getReplacementPackage() . ' instead' : 'No replacement was suggested'; @@ -1033,11 +1033,8 @@ class Installer $package->setReplaces($newPackage->getReplaces()); } - if ($task === 'force-updates' && $newPackage && ( - (($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference()) - || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference()) - ) - )) { + if ($task === 'force-updates' && $newPackage && ($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference()) + || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference())) { $operations[] = new UpdateOperation($package, $newPackage); continue; diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index a61ea86a1..0f709f60b 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -113,7 +113,7 @@ class BinaryInstaller } // attempt removing the bin dir in case it is left empty - if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) { + if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) { Silencer::call('rmdir', $this->binDir); } } diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 0557847d6..b84791420 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -281,7 +281,6 @@ class JsonFile * @param string $json * @param string $file * @throws \UnexpectedValueException - * @throws JsonValidationException * @throws ParsingException * @return bool true on success */ diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php index 8e2005715..680a57baf 100644 --- a/src/Composer/Json/JsonFormatter.php +++ b/src/Composer/Json/JsonFormatter.php @@ -88,7 +88,7 @@ class JsonFormatter if (':' === $char) { // Add a space after the : character $char .= ' '; - } elseif (('}' === $char || ']' === $char)) { + } elseif ('}' === $char || ']' === $char) { $pos--; $prevChar = substr($json, $i - 1, 1); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 04b44f7b6..f917eb838 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -230,7 +230,7 @@ class RootPackageLoader extends ArrayLoader { foreach ($requires as $reqName => $reqVersion) { $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); - if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === ($stabilityName = VersionParser::parseStability($reqVersion))) { + if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) { $name = strtolower($reqName); $references[$name] = $match[1]; } diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index fb2eafd93..2a71be6cb 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -12,7 +12,6 @@ namespace Composer\Package\Loader; -use Composer\Package; use Composer\Package\BasePackage; use Composer\Semver\Constraint\Constraint; use Composer\Package\Version\VersionParser; diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 9cae9ee5d..e8f4b58c3 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -169,7 +169,7 @@ class PluginManager $generator = $this->composer->getAutoloadGenerator(); $autoloads = array(); foreach ($autoloadPackages as $autoloadPackage) { - $downloadPath = $this->getInstallPath($autoloadPackage, ($globalRepo && $globalRepo->hasPackage($autoloadPackage))); + $downloadPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); $autoloads[] = array($autoloadPackage, $downloadPath); } @@ -307,7 +307,7 @@ class PluginManager { $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); - return (!empty($packages)) ? $packages[0] : null; + return !empty($packages) ? $packages[0] : null; } /** diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 2daa1ceb1..f262432ff 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -565,7 +565,7 @@ class Filesystem chdir($cwd); - return (bool) $result; + return $result; } /** diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index b261f47a5..e04c7157e 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -140,7 +140,7 @@ final class TlsHelper //Convert PEM to DER before SHA1'ing $start = '-----BEGIN PUBLIC KEY-----'; $end = '-----END PUBLIC KEY-----'; - $pemtrim = substr($pubkeypem, (strpos($pubkeypem, $start) + strlen($start)), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); + $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); $der = base64_decode($pemtrim); return sha1($der); diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index f8d4b446d..4a5d5f90c 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -13,7 +13,6 @@ namespace Composer\Util; use Composer\Config; -use Composer\IO\IOInterface; /** * @author Jordi Boggiano From 354eec76aea9d16401dae184f575585b00f7d613 Mon Sep 17 00:00:00 2001 From: Kazuhiro Inari Date: Sat, 7 Jul 2018 00:47:27 +0900 Subject: [PATCH 167/580] Add repositories path glob --- src/Composer/Repository/PathRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index ee5d702fa..c6e0bb39d 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -177,6 +177,6 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn // Ensure environment-specific path separators are normalized to URL separators return array_map(function ($val) { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); - }, glob($this->url, GLOB_MARK | GLOB_ONLYDIR)); + }, glob($this->url, GLOB_MARK | GLOB_ONLYDIR | GLOB_BRACE)); } } From 0aa7ec2d2cbec6cb61f70b3bfc4d1faab6f3e292 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Jul 2018 14:01:57 +0200 Subject: [PATCH 168/580] Use a simpler hashing for the Rule2Literal case this speeds up "composer update" by ~18% --- src/Composer/DependencyResolver/Rule2Literals.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Composer/DependencyResolver/Rule2Literals.php b/src/Composer/DependencyResolver/Rule2Literals.php index c44a15ab2..b4fa0cf24 100644 --- a/src/Composer/DependencyResolver/Rule2Literals.php +++ b/src/Composer/DependencyResolver/Rule2Literals.php @@ -50,9 +50,7 @@ class Rule2Literals extends Rule public function getHash() { - $data = unpack('ihash', md5($this->literal1.','.$this->literal2, true)); - - return $data['hash']; + return $this->literal1.','.$this->literal2; } /** From 07383552b30bf37efa827c5582dea4679ccadbd7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Jul 2018 14:09:46 +0200 Subject: [PATCH 169/580] Temporary save the package-name into a variable this reduces number of unnecessary function calls in the hot path of "composer update" --- src/Composer/DependencyResolver/RuleSetGenerator.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 3855f7d04..7bd294a7a 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -232,7 +232,8 @@ class RuleSetGenerator } } - $obsoleteProviders = $this->pool->whatProvides($package->getName(), null); + $packageName = $package->getName(); + $obsoleteProviders = $this->pool->whatProvides($packageName, null); foreach ($obsoleteProviders as $provider) { if ($provider === $package) { @@ -242,7 +243,7 @@ class RuleSetGenerator if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package)); } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) { - $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; + $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRule2Literals($package, $provider, $reason, $package)); } } From 7a4937bbccab9a9bd8f344e347fee908781f5e15 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Jul 2018 18:24:06 +0200 Subject: [PATCH 170/580] Specialize Rule2Literal->equals(Rule2Literal) for speedup --- src/Composer/DependencyResolver/Rule2Literals.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Composer/DependencyResolver/Rule2Literals.php b/src/Composer/DependencyResolver/Rule2Literals.php index c44a15ab2..c2d95f483 100644 --- a/src/Composer/DependencyResolver/Rule2Literals.php +++ b/src/Composer/DependencyResolver/Rule2Literals.php @@ -65,6 +65,19 @@ class Rule2Literals extends Rule */ public function equals(Rule $rule) { + // specialized fast-case + if ($rule instanceof self) { + if ($this->literal1 !== $rule->literal1) { + return false; + } + + if ($this->literal2 !== $rule->literal2) { + return false; + } + + return true; + } + $literals = $rule->getLiterals(); if (2 != count($literals)) { return false; From e3a23f4ae6c12a2ac52719f41e2d986d38db0acd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Jul 2018 20:49:24 +0200 Subject: [PATCH 171/580] Remove unnecessary abs() calls Literal cannot be negative at this point --- src/Composer/DependencyResolver/Solver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 862411c61..0b8e983ed 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -796,10 +796,10 @@ class Solver continue 2; // next rule } } else { - if ($this->decisions->decidedInstall(abs($literal))) { + if ($this->decisions->decidedInstall($literal)) { continue 2; // next rule } - if ($this->decisions->undecided(abs($literal))) { + if ($this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } From 0e16dbabde469752c2f9db8ce086aa9c762d6693 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Jul 2018 20:55:14 +0200 Subject: [PATCH 172/580] Removed another unnecessary abs() call --- src/Composer/DependencyResolver/Solver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 0b8e983ed..6716de7d3 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -792,7 +792,7 @@ class Solver // foreach ($literals as $literal) { if ($literal <= 0) { - if (!$this->decisions->decidedInstall(abs($literal))) { + if (!$this->decisions->decidedInstall($literal)) { continue 2; // next rule } } else { From 42516901f1b0592119daa3fbb41a40c24f0338f1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Jul 2018 20:59:39 +0200 Subject: [PATCH 173/580] Removed another unnecessary abs() call --- src/Composer/DependencyResolver/Solver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 6716de7d3..2139e2bf6 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -100,7 +100,7 @@ class Solver $literals = $rule->getLiterals(); $literal = $literals[0]; - if (!$this->decisions->decided(abs($literal))) { + if (!$this->decisions->decided($literal)) { $this->decisions->decide($literal, 1, $rule); continue; } From 86d5de2965a3cfe168a9c8955e3ee0d1ed34b634 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Jul 2018 21:01:52 +0200 Subject: [PATCH 174/580] Define variable only when actually used --- src/Composer/DependencyResolver/Solver.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 862411c61..6f1999e36 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -510,9 +510,8 @@ class Solver */ private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule) { - $why = spl_object_hash($conflictRule); - if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { + $why = spl_object_hash($conflictRule); $learnedWhy = $this->learnedWhy[$why]; $problemRules = $this->learnedPool[$learnedWhy]; From b63e2de819acd7d571f2a51023484335eb31b6a0 Mon Sep 17 00:00:00 2001 From: refael iliaguyev Date: Wed, 11 Jul 2018 20:21:09 +0300 Subject: [PATCH 175/580] add alias `i` to the install command --- doc/03-cli.md | 2 +- src/Composer/Command/InstallCommand.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index ef2f13523..40c141cec 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -65,7 +65,7 @@ php composer.phar init to a `composer` repository or a JSON string which similar to what the [repositories](04-schema.md#repositories) key accepts. -## install +## install / i The `install` command reads the `composer.json` file from the current directory, resolves the dependencies, and installs them into `vendor`. diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index b6ca802eb..7a8980cc4 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -32,6 +32,7 @@ class InstallCommand extends BaseCommand { $this ->setName('install') + ->setAliases(array('i')) ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.') ->setDefinition(array( new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), From 50565cb0c8bbdc9be17e6c1452b4f856a28b3be0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Jul 2018 20:44:24 +0200 Subject: [PATCH 176/580] Use variable to call count() less often --- src/Composer/DependencyResolver/RuleWatchNode.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleWatchNode.php b/src/Composer/DependencyResolver/RuleWatchNode.php index 3d31a38c5..eeaa54162 100644 --- a/src/Composer/DependencyResolver/RuleWatchNode.php +++ b/src/Composer/DependencyResolver/RuleWatchNode.php @@ -37,8 +37,9 @@ class RuleWatchNode $literals = $rule->getLiterals(); - $this->watch1 = count($literals) > 0 ? $literals[0] : 0; - $this->watch2 = count($literals) > 1 ? $literals[1] : 0; + $literalCount = count($literals); + $this->watch1 = $literalCount > 0 ? $literals[0] : 0; + $this->watch2 = $literalCount > 1 ? $literals[1] : 0; } /** From cd39efc72c9b00c350080e30994ed6eede53163a Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Sat, 14 Jul 2018 20:55:26 +0200 Subject: [PATCH 177/580] New finding by Static Code Analysis --- src/Composer/Installer.php | 35 +++++++++++-------- .../Package/Archiver/ArchiveManager.php | 8 ++--- src/Composer/Util/Git.php | 5 +-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 1ae72aed0..7d60e6030 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -543,16 +543,17 @@ class Installer foreach ($operations as $operation) { // collect suggestions - if ('install' === $operation->getJobType()) { + $jobType = $operation->getJobType(); + if ('install' === $jobType) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); } // updating, force dev packages' references if they're in root package refs if ($this->update) { $package = null; - if ('update' === $operation->getJobType()) { + if ('update' === $jobType) { $package = $operation->getTargetPackage(); - } elseif ('install' === $operation->getJobType()) { + } elseif ('install' === $jobType) { $package = $operation->getPackage(); } if ($package && $package->isDev()) { @@ -561,20 +562,24 @@ class Installer $this->updateInstallReferences($package, $references[$package->getName()]); } } - if ('update' === $operation->getJobType() - && $operation->getTargetPackage()->isDev() - && $operation->getTargetPackage()->getVersion() === $operation->getInitialPackage()->getVersion() - && (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference()) - && (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference()) - ) { - $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version', true, IOInterface::DEBUG); - $this->io->writeError('', true, IOInterface::DEBUG); + if ('update' === $jobType) { + $targetPackage = $operation->getTargetPackage(); + if ($targetPackage->isDev()) { + $initialPackage = $operation->getInitialPackage(); + if ($targetPackage->getVersion() === $initialPackage->getVersion() + && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) + && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) + ) { + $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); + $this->io->writeError('', true, IOInterface::DEBUG); - continue; + continue; + } + } } } - $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType()); + $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($jobType); if (defined($event) && $this->runScripts) { $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } @@ -589,7 +594,7 @@ class Installer $this->installationManager->execute($localRepo, $operation); // output reasons why the operation was ran, only for install/update operations - if ($this->verbose && $this->io->isVeryVerbose() && in_array($operation->getJobType(), array('install', 'update'))) { + if ($this->verbose && $this->io->isVeryVerbose() && in_array($jobType, array('install', 'update'))) { $reason = $operation->getReason(); if ($reason instanceof Rule) { switch ($reason->getReason()) { @@ -605,7 +610,7 @@ class Installer } } - $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($operation->getJobType()); + $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); if (defined($event) && $this->runScripts) { $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 35a3d3467..35ff6f600 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -75,9 +75,9 @@ class ArchiveManager $nameParts = array(preg_replace('#[^a-z0-9-_]#i', '-', $package->getName())); if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) { - $nameParts = array_merge($nameParts, array($package->getDistReference(), $package->getDistType())); + array_push($nameParts, $package->getDistReference(), $package->getDistType()); } else { - $nameParts = array_merge($nameParts, array($package->getPrettyVersion(), $package->getDistReference())); + array_push($nameParts, $package->getPrettyVersion(), $package->getDistReference()); } if ($package->getSourceReference()) { @@ -144,7 +144,7 @@ class ArchiveManager $sourcePath = realpath('.'); } else { // Directory used to download the sources - $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); + $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid('', true); $filesystem->ensureDirectoryExists($sourcePath); // Download sources @@ -161,7 +161,7 @@ class ArchiveManager } // Create the archive - $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format; + $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid('', true).'.'.$format; $filesystem->ensureDirectoryExists(dirname($tempTarget)); $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes(), $ignoreFilters); diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index a5fa87982..86da5bdf0 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -245,7 +245,7 @@ class Git private function isAuthenticationFailure($url, &$match) { - if (!preg_match('{(https?://)([^/]+)(.*)$}i', $url, $match)) { + if (!preg_match('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { return false; } @@ -257,8 +257,9 @@ class Git 'fatal: could not read Username', ); + $errorOutput = $this->process->getErrorOutput(); foreach ($authFailures as $authFailure) { - if (strpos($this->process->getErrorOutput(), $authFailure) !== false) { + if (strpos($errorOutput, $authFailure) !== false) { return true; } } From 6f6d59426d0e1123ff80e98188000a542be5e320 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Mon, 16 Jul 2018 22:40:48 +0200 Subject: [PATCH 178/580] New finding by Static Code Analysis: revert uniqid() to comply long path requirements --- src/Composer/Package/Archiver/ArchiveManager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 35ff6f600..22f8eeafe 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -144,7 +144,7 @@ class ArchiveManager $sourcePath = realpath('.'); } else { // Directory used to download the sources - $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid('', true); + $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); $filesystem->ensureDirectoryExists($sourcePath); // Download sources @@ -161,7 +161,7 @@ class ArchiveManager } // Create the archive - $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid('', true).'.'.$format; + $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format; $filesystem->ensureDirectoryExists(dirname($tempTarget)); $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes(), $ignoreFilters); From 1a725d5e1f7d24aeab9355b2174681b7927346dd Mon Sep 17 00:00:00 2001 From: Jonas Renaudot Date: Tue, 17 Jul 2018 12:04:27 +0200 Subject: [PATCH 179/580] Add support for authentication with mercurial repositories. --- src/Composer/Downloader/HgDownloader.php | 31 ++++---- src/Composer/Repository/Vcs/HgDriver.php | 15 ++-- src/Composer/Util/Hg.php | 91 ++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/Composer/Util/Hg.php diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 32074be71..8660ec61d 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -14,6 +14,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; +use Composer\Util\Hg as HgUtils; /** * @author Per Bernhardt @@ -25,16 +26,15 @@ class HgDownloader extends VcsDownloader */ public function doDownload(PackageInterface $package, $path, $url) { - // Ensure we are allowed to use this URL by config - $this->config->prohibitUrlByConfig($url, $this->io); + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + + $cloneCommand = function($url) use ($path) { + return sprintf('hg clone %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path)); + }; + + $hgUtils->runCommand($cloneCommand, $url, $path); - $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($package->getSourceReference()); - $this->io->writeError("Cloning ".$package->getSourceReference()); - $command = sprintf('hg clone %s %s', $url, ProcessExecutor::escape($path)); - if (0 !== $this->process->execute($command, $ignoredOutput)) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } $command = sprintf('hg up %s', $ref); if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); @@ -46,21 +46,20 @@ class HgDownloader extends VcsDownloader */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { - // Ensure we are allowed to use this URL by config - $this->config->prohibitUrlByConfig($url, $this->io); + $hgUtils = new HgUtils($this->io, $this->config, $this->process); - $url = ProcessExecutor::escape($url); - $ref = ProcessExecutor::escape($target->getSourceReference()); + $ref = $target->getSourceReference(); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $command = sprintf('hg pull %s && hg up %s', $url, $ref); - if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { - throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); - } + $command = function($url) use ($ref) { + return sprintf('hg pull %s && hg up %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref)); + }; + + $hgUtils->runCommand($command, $url, $path); } /** diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index bf7c91552..8141f453b 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Config; +use Composer\Util\Hg as HgUtils; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; @@ -49,6 +50,8 @@ class HgDriver extends VcsDriver // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($this->url, $this->io); + $hgUtils = new HgUtils($this->io, $this->config, $this->process); + // update the repo if it is a valid hg repository if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) { if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) { @@ -58,15 +61,11 @@ class HgDriver extends VcsDriver // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); - if (0 !== $this->process->execute(sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoDir)), $output, $cacheDir)) { - $output = $this->process->getErrorOutput(); + $command = function($url) { + return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir)); + }; - if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { - throw new \RuntimeException('Failed to clone '.$this->url.', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); - } - - throw new \RuntimeException('Failed to clone '.$this->url.', could not read packages from it' . "\n\n" .$output); - } + $hgUtils->runCommand($command, $this->url, $this->repoDir); } } diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php new file mode 100644 index 000000000..673ad783a --- /dev/null +++ b/src/Composer/Util/Hg.php @@ -0,0 +1,91 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; + +/** + * @author Jonas Renaudot + */ +class Hg +{ + + /** + * @var \Composer\IO\IOInterface + */ + private $io; + + /** + * @var \Composer\Config + */ + private $config; + + /** + * @var \Composer\Util\ProcessExecutor + */ + private $process; + + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process) + { + $this->io = $io; + $this->config = $config; + $this->process = $process; + } + + public function runCommand($commandCallable, $url, $cwd) { + $this->config->prohibitUrlByConfig($url, $this->io); + + // Try as is + $command = call_user_func($commandCallable, $url); + + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)){ + return; + } + + // Try with the authentication informations available + if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) { + $auth = $this->io->getAuthentication($match[5]); + $authenticatedUrl = $match[1] . '://' . rawurldecode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6])? $match[6]: null); + + $command = call_user_func($commandCallable, $authenticatedUrl); + + if (0 === $this->process->execute($command)) { + return; + } + } + + $this->throwException('Failed to clone ' . $url . ', aborting', $url); + + } + + public static function sanitizeUrl($message) + { + return preg_replace_callback('{://(?P[^@]+?):(?P.+?)@}', function ($m) { + if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) { + return '://***:***@'; + } + + return '://' . $m[1] . ':***@'; + }, $message); + } + + private function throwException($message, $url) + { + if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { + throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); + } + + throw new \RuntimeException(self::sanitizeUrl($message)); + } +} From 5c2b34a1af4b6e6ac93c8f5389369a9605051a92 Mon Sep 17 00:00:00 2001 From: Elendev Date: Tue, 17 Jul 2018 19:46:25 +0200 Subject: [PATCH 180/580] Encode the username correctly (fix typo) --- src/Composer/Util/Hg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 673ad783a..00ab3b8e8 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -56,7 +56,7 @@ class Hg // Try with the authentication informations available if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) { $auth = $this->io->getAuthentication($match[5]); - $authenticatedUrl = $match[1] . '://' . rawurldecode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6])? $match[6]: null); + $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6])? $match[6]: null); $command = call_user_func($commandCallable, $authenticatedUrl); From ea5644281a1c4d27058a884ed684934875d14bde Mon Sep 17 00:00:00 2001 From: Elendev Date: Tue, 17 Jul 2018 20:03:07 +0200 Subject: [PATCH 181/580] Display the error output in the thrown exception --- src/Composer/Util/Hg.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 00ab3b8e8..5afe7b3f4 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -63,9 +63,15 @@ class Hg if (0 === $this->process->execute($command)) { return; } + + $error = $this->process->getErrorOutput(); + } else { + $error = 'The given URL (' . $url . ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)'; } - $this->throwException('Failed to clone ' . $url . ', aborting', $url); + + + $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url); } From e89d16c47d5094191ca7723e14d979c0ec41f9a9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Jul 2018 16:00:32 +0200 Subject: [PATCH 182/580] GLOB_BRACE is not defined on all platforms --- src/Composer/Repository/PathRepository.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index c6e0bb39d..8b9850c77 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -174,9 +174,15 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn */ private function getUrlMatches() { + $flags = GLOB_MARK | GLOB_ONLYDIR; + + if (defined('GLOB_BRACE')) { + $flags |= GLOB_BRACE; + } + // Ensure environment-specific path separators are normalized to URL separators return array_map(function ($val) { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); - }, glob($this->url, GLOB_MARK | GLOB_ONLYDIR | GLOB_BRACE)); + }, glob($this->url, $flags)); } } From 70a1a6e5103bc8515e5d71fc529185ee15948cd6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Jul 2018 16:38:44 +0200 Subject: [PATCH 183/580] Throw a RuntimeException when glob braces are used but not supported by the OS --- src/Composer/Repository/PathRepository.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 8b9850c77..5d50c2473 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -178,6 +178,8 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn if (defined('GLOB_BRACE')) { $flags |= GLOB_BRACE; + } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) { + throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url); } // Ensure environment-specific path separators are normalized to URL separators From 3b647f8686b123e8cc91fb3b1c9e4221e02d63fb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Jul 2018 18:24:11 +0200 Subject: [PATCH 184/580] prevent preg_replace() calls when cache is not enabled --- src/Composer/Cache.php | 60 ++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index c3cba603a..44395c3a2 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -71,11 +71,13 @@ class Cache public function read($file) { - $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->enabled && file_exists($this->root . $file)) { - $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + if ($this->enabled) { + $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); - return file_get_contents($this->root . $file); + return file_get_contents($this->root . $file); + } } return false; @@ -142,19 +144,21 @@ class Cache */ public function copyTo($file, $target) { - $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->enabled && file_exists($this->root . $file)) { - try { - touch($this->root . $file, filemtime($this->root . $file), time()); - } catch (\ErrorException $e) { - // fallback in case the above failed due to incorrect ownership - // see https://github.com/composer/composer/issues/4070 - Silencer::call('touch', $this->root . $file); + if ($this->enabled) { + $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + try { + touch($this->root . $file, filemtime($this->root . $file), time()); + } catch (\ErrorException $e) { + // fallback in case the above failed due to incorrect ownership + // see https://github.com/composer/composer/issues/4070 + Silencer::call('touch', $this->root . $file); + } + + $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); + + return copy($this->root . $file, $target); } - - $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); - - return copy($this->root . $file, $target); } return false; @@ -167,9 +171,11 @@ class Cache public function remove($file) { - $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->enabled && file_exists($this->root . $file)) { - return $this->filesystem->unlink($this->root . $file); + if ($this->enabled) { + $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return $this->filesystem->unlink($this->root . $file); + } } return false; @@ -216,9 +222,11 @@ class Cache public function sha1($file) { - $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->enabled && file_exists($this->root . $file)) { - return sha1_file($this->root . $file); + if ($this->enabled) { + $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return sha1_file($this->root . $file); + } } return false; @@ -226,9 +234,11 @@ class Cache public function sha256($file) { - $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); - if ($this->enabled && file_exists($this->root . $file)) { - return hash_file('sha256', $this->root . $file); + if ($this->enabled) { + $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); + if (file_exists($this->root . $file)) { + return hash_file('sha256', $this->root . $file); + } } return false; From f7a1c34c9238dc09c031830c3ba633d576afd84d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Jul 2018 20:21:04 +0200 Subject: [PATCH 185/580] Removed unused variables --- src/Composer/DependencyResolver/RuleSetGenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 7bd294a7a..c534be958 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -199,7 +199,7 @@ class RuleSetGenerator $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); - $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); @@ -241,10 +241,10 @@ class RuleSetGenerator } if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { - $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package)); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package)); } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) { $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; - $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRule2Literals($package, $provider, $reason, $package)); + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package)); } } } From 05499099a0d967e348ee8eaf47c37bf245b12acc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Jul 2018 20:50:46 +0200 Subject: [PATCH 186/580] Simplify Rule->getJob() --- src/Composer/DependencyResolver/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 0829c2c7e..1fe8cbd30 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -68,7 +68,7 @@ abstract class Rule public function getJob() { - return isset($this->job) ? $this->job : null; + return $this->job; } abstract public function equals(Rule $rule); From 2f347e134721b3b6ee825fca273ca056e5cedbd3 Mon Sep 17 00:00:00 2001 From: refael iliaguyev Date: Thu, 19 Jul 2018 11:38:43 +0300 Subject: [PATCH 187/580] add alias `u` to the update command --- doc/03-cli.md | 2 +- src/Composer/Command/UpdateCommand.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 40c141cec..0b46e04f5 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -115,7 +115,7 @@ resolution. requirements and force the installation even if the local machine does not fulfill these. See also the [`platform`](06-config.md#platform) config option. -## update +## update / u In order to get the latest versions of the dependencies and to update the `composer.lock` file, you should use the `update` command. This command is also diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 1610d2872..1d67f1f9b 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -34,7 +34,7 @@ class UpdateCommand extends BaseCommand { $this ->setName('update') - ->setAliases(array('upgrade')) + ->setAliases(array('u', 'upgrade')) ->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.') ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'), From 945adcf172d3c0bfba357e3ca6160104eacac46c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 20 Jul 2018 11:38:49 +0200 Subject: [PATCH 188/580] Fix doc ambiguity, fixes #7471 --- doc/01-basic-usage.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 9c57f3e92..08e096735 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -154,8 +154,10 @@ and running `install` again.) ```sh php composer.phar update ``` + > **Note:** Composer will display a Warning when executing an `install` command -> if `composer.lock` and `composer.json` are not synchronized. +> if the `composer.lock` has not been updated since changes were made to the +> `composer.json` that might affect dependency resolution. If you only want to install or update one dependency, you can whitelist them: From 1983a450b44916f4d9f8a7561bed1611c04532a5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 20 Jul 2018 11:44:54 +0200 Subject: [PATCH 189/580] Use rawurldecode instead of urldecode, fixes #7407 --- src/Composer/Util/Git.php | 2 +- src/Composer/Util/RemoteFilesystem.php | 2 +- src/Composer/Util/StreamContextFactory.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 86da5bdf0..37410eecd 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -57,7 +57,7 @@ class Git // capture username/password from URL if there is one $this->process->execute('git remote -v', $output, $cwd); if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) { - $this->io->setAuthentication($match[3], urldecode($match[1]), urldecode($match[2])); + $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 80d6ecc32..a12efe774 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -243,7 +243,7 @@ class RemoteFilesystem // capture username/password from URL if there is one if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) { - $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2])); + $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2])); } $tempAdditionalOptions = $additionalOptions; diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 6b16c8a18..f5f12d315 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -109,9 +109,9 @@ final class StreamContextFactory // handle proxy auth if present if (isset($proxy['user'])) { - $auth = urldecode($proxy['user']); + $auth = rawurldecode($proxy['user']); if (isset($proxy['pass'])) { - $auth .= ':' . urldecode($proxy['pass']); + $auth .= ':' . rawurldecode($proxy['pass']); } $auth = base64_encode($auth); From d5a9d86ee4e934d06073521e630556d4f337629c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 20 Jul 2018 12:02:31 +0200 Subject: [PATCH 190/580] Undo reformatting from #7441 --- src/Composer/Installer.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 7d60e6030..f6f78a4d9 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1038,8 +1038,14 @@ class Installer $package->setReplaces($newPackage->getReplaces()); } - if ($task === 'force-updates' && $newPackage && ($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference()) - || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference())) { + if ( + $task === 'force-updates' + && $newPackage + && ( + ($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference()) + || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference()) + ) + ) { $operations[] = new UpdateOperation($package, $newPackage); continue; From 00fdb5555cb4986bb0135f07e24f36b46df1d0f9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 20 Jul 2018 13:48:39 +0200 Subject: [PATCH 191/580] Update lock file --- composer.lock | 163 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 53 deletions(-) diff --git a/composer.lock b/composer.lock index 5cf0feae0..8da0b40dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2dc8cdba11ff8ac7c8fd9a3767af326b", + "content-hash": "5c554bae92a73c12f7797833fd44e946", "packages": [ { "name": "composer/ca-bundle", @@ -126,16 +126,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7e111c50db92fa2ced140f5ba23b4e261bc77a30" + "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7e111c50db92fa2ced140f5ba23b4e261bc77a30", - "reference": "7e111c50db92fa2ced140f5ba23b4e261bc77a30", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/cb17687e9f936acd7e7245ad3890f953770dec1b", + "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b", "shasum": "" }, "require": { @@ -183,7 +183,7 @@ "spdx", "validator" ], - "time": "2018-01-31T13:17:27+00:00" + "time": "2018-04-30T10:33:04+00:00" }, { "name": "composer/xdebug-handler", @@ -437,16 +437,16 @@ }, { "name": "symfony/console", - "version": "v2.8.37", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "390fa4899dbcc47bd41935d87c4572ea4305d3ce" + "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/390fa4899dbcc47bd41935d87c4572ea4305d3ce", - "reference": "390fa4899dbcc47bd41935d87c4572ea4305d3ce", + "url": "https://api.github.com/repos/symfony/console/zipball/e8e59b74ad1274714dad2748349b55e3e6e630c7", + "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7", "shasum": "" }, "require": { @@ -460,7 +460,7 @@ "symfony/process": "~2.1|~3.0.0" }, "suggest": { - "psr/log": "For using the console logger", + "psr/log-implementation": "For using the console logger", "symfony/event-dispatcher": "", "symfony/process": "" }, @@ -494,20 +494,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-03-19T21:13:58+00:00" + "time": "2018-05-15T21:17:45+00:00" }, { "name": "symfony/debug", - "version": "v2.8.38", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "4486d2be5e068b51fece4c8551c14e709f573c8d" + "reference": "a26ddce7fe4e884097d72435653bc7e703411f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/4486d2be5e068b51fece4c8551c14e709f573c8d", - "reference": "4486d2be5e068b51fece4c8551c14e709f573c8d", + "url": "https://api.github.com/repos/symfony/debug/zipball/a26ddce7fe4e884097d72435653bc7e703411f26", + "reference": "a26ddce7fe4e884097d72435653bc7e703411f26", "shasum": "" }, "require": { @@ -551,24 +551,25 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:20:27+00:00" + "time": "2018-06-22T15:01:26+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.38", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "125403a59e4cb4e3ebf46d0162fabcde613d2b97" + "reference": "0f685c099aca7ba86bcc31850186dbfe84a4a8a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/125403a59e4cb4e3ebf46d0162fabcde613d2b97", - "reference": "125403a59e4cb4e3ebf46d0162fabcde613d2b97", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0f685c099aca7ba86bcc31850186dbfe84a4a8a1", + "reference": "0f685c099aca7ba86bcc31850186dbfe84a4a8a1", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { @@ -600,20 +601,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-02-19T16:23:47+00:00" + "time": "2018-06-21T09:24:14+00:00" }, { "name": "symfony/finder", - "version": "v2.8.38", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "423746fc18ccf31f9abec43e4f078bb6e024b2d5" + "reference": "995cd7c28a0778cece02e2133b4d813dc509dfc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/423746fc18ccf31f9abec43e4f078bb6e024b2d5", - "reference": "423746fc18ccf31f9abec43e4f078bb6e024b2d5", + "url": "https://api.github.com/repos/symfony/finder/zipball/995cd7c28a0778cece02e2133b4d813dc509dfc3", + "reference": "995cd7c28a0778cece02e2133b4d813dc509dfc3", "shasum": "" }, "require": { @@ -649,20 +650,75 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-04-04T13:38:31+00:00" + "time": "2018-06-19T11:07:17+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "3296adf6a6454a050679cde90f95350ad604b171" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -674,7 +730,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -708,20 +764,20 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/process", - "version": "v2.8.38", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "ee2c91470ff262b1a00aec27875d38594aa87629" + "reference": "542d88b350c42750fdc14e73860ee96dd423e95d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/ee2c91470ff262b1a00aec27875d38594aa87629", - "reference": "ee2c91470ff262b1a00aec27875d38594aa87629", + "url": "https://api.github.com/repos/symfony/process/zipball/542d88b350c42750fdc14e73860ee96dd423e95d", + "reference": "542d88b350c42750fdc14e73860ee96dd423e95d", "shasum": "" }, "require": { @@ -757,7 +813,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:20:27+00:00" + "time": "2018-05-27T07:40:52+00:00" } ], "packages-dev": [ @@ -866,23 +922,23 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.5", + "version": "1.7.6", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", - "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { @@ -925,7 +981,7 @@ "spy", "stub" ], - "time": "2018-02-19T10:16:54+00:00" + "time": "2018-04-18T13:57:24+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1677,20 +1733,21 @@ }, { "name": "symfony/yaml", - "version": "v2.8.38", + "version": "v2.8.42", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "be720fcfae4614df204190d57795351059946a77" + "reference": "51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", - "reference": "be720fcfae4614df204190d57795351059946a77", + "url": "https://api.github.com/repos/symfony/yaml/zipball/51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff", + "reference": "51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { @@ -1722,7 +1779,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:36:31+00:00" + "time": "2018-05-01T22:52:40+00:00" } ], "aliases": [], From 0db48b4f2e84ae4f177189e6a21383eabd26ea1a Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Mon, 23 Jul 2018 07:53:12 +0200 Subject: [PATCH 192/580] addendum to #7428 --- src/Composer/Repository/ArtifactRepository.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 374f04cea..2383b2dd3 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -129,11 +129,15 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito $zip->open($file->getPathname()); if (0 == $zip->numFiles) { + $zip->close(); + return false; } $foundFileIndex = $this->locateFile($zip, 'composer.json'); if (false === $foundFileIndex) { + $zip->close(); + return false; } From 10f707e3169dafebb345a03e75c9e66c2a57335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Votruba?= Date: Mon, 23 Jul 2018 18:49:39 +0200 Subject: [PATCH 193/580] basic usage - promote safe version range I'm aware this just an example, but it [leads readers to see it a best practise](https://github.com/Automattic/phpcs-neutron-standard/commit/40642df5ec9ea39871f1b99ada7fa34976169788#r29802440). Having code with `"php": ">=5.4.0"` is pretty dangerous and will probably break with next major version. ```json { "require": { "php": "^5.4" } } ``` ```json { "require": { "php": "^7.1" } } ``` is much more safer. Promoting best and proven practise should from *Basic usage* helps people to use composer the best way they can. What do you think? --- doc/01-basic-usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 08e096735..ffcb1589e 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -190,11 +190,11 @@ installed on the system but are not actually installable by Composer. This includes PHP itself, PHP extensions and some system libraries. * `php` represents the PHP version of the user, allowing you to apply - constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can + constraints, e.g. `^7.1`. To require a 64bit version of php, you can require the `php-64bit` package. * `hhvm` represents the version of the HHVM runtime and allows you to apply - a constraint, e.g., `>=2.3.3`. + a constraint, e.g., `^2.3`. * `ext-` allows you to require PHP extensions (includes core extensions). Versioning can be quite inconsistent here, so it's often From c5fa3bdde0e042b64e32102c21edb2e12e474746 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 24 Jul 2018 09:30:06 +0200 Subject: [PATCH 194/580] Migrate to repo.packagist.org for package metadata --- doc/05-repositories.md | 2 +- src/Composer/Command/DiagnoseCommand.php | 10 +++++----- src/Composer/Command/InitCommand.php | 2 +- src/Composer/Config.php | 2 +- src/Composer/Repository/ComposerRepository.php | 14 ++++++++++---- src/Composer/Util/RemoteFilesystem.php | 6 +++--- tests/Composer/Test/ConfigTest.php | 8 ++++---- .../Package/Loader/ValidatingArrayLoaderTest.php | 2 +- 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index db54a4fb1..9706a07e0 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -59,7 +59,7 @@ The main repository type is the `composer` repository. It uses a single This is also the repository type that packagist uses. To reference a `composer` repository, supply the path before the `packages.json` file. In the case of packagist, that file is located at `/packages.json`, so the URL of -the repository would be `packagist.org`. For `example.org/packages.json` the +the repository would be `repo.packagist.org`. For `example.org/packages.json` the repository URL would be `example.org`. #### packages diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 3fecf52c4..b15cd35b1 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -81,7 +81,7 @@ EOT } $config->merge(array('config' => array('secure-http' => false))); - $config->prohibitUrlByConfig('http://packagist.org', new NullIO); + $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO); $this->rfs = Factory::createRemoteFilesystem($io, $config); $this->process = new ProcessExecutor($io); @@ -208,7 +208,7 @@ EOT } try { - $this->rfs->getContents('packagist.org', $proto . '://packagist.org/packages.json', false); + $this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; @@ -230,11 +230,11 @@ EOT { $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { - $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://packagist.org/packages.json', false), true); + $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); - $provider = $this->rfs->getContents('packagist.org', $protocol . '://packagist.org/'.$path, false); + $provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false); if (hash('sha256', $provider) !== $hash) { return 'It seems that your proxy is modifying http traffic on the fly'; @@ -255,7 +255,7 @@ EOT */ private function checkHttpProxyFullUriRequestParam() { - $url = 'http://packagist.org/packages.json'; + $url = 'http://repo.packagist.org/packages.json'; try { $this->rfs->getContents('packagist.org', $url, false); } catch (TransportException $e) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 802c65218..7fabb62f1 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -165,7 +165,7 @@ EOT } $repos[] = RepositoryFactory::createRepo($io, $config, array( 'type' => 'composer', - 'url' => 'https://packagist.org', + 'url' => 'https://repo.packagist.org', )); $this->repos = new CompositeRepository($repos); diff --git a/src/Composer/Config.php b/src/Composer/Config.php index a7fca2988..8ce429d31 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -72,7 +72,7 @@ class Config public static $defaultRepositories = array( 'packagist.org' => array( 'type' => 'composer', - 'url' => 'https?://packagist.org', + 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true, ), ); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 53c16129f..403b31cd9 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -90,6 +90,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->config = $config; $this->options = $repoConfig['options']; $this->url = $repoConfig['url']; + + // force url for packagist.org to repo.packagist.org + if (preg_match('{^(?Phttps?)://packagist.org/?$}i', $this->url, $match)) { + $this->url = $match['proto'].'://repo.packagist.org'; + } + $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); @@ -539,10 +545,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } // force values for packagist - if (preg_match('{^https?://packagist.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) { - $this->url = 'https://packagist.org'; - $this->baseUrl = 'https://packagist.org'; - $this->lazyProvidersUrl = $this->canonicalizeUrl('https://packagist.org/p/%package%.json'); + if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) { + $this->url = 'https://repo.packagist.org'; + $this->baseUrl = 'https://repo.packagist.org'; + $this->lazyProvidersUrl = $this->canonicalizeUrl('https://repo.packagist.org/p/%package%.json'); $this->providersUrl = null; } elseif (!empty($this->repoConfig['force-lazy-providers'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl('/p/%package%.json'); diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index a12efe774..39ed36107 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -283,9 +283,9 @@ class RemoteFilesystem $options['http']['ignore_errors'] = true; } - if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') { + if ($this->degradedMode && substr($fileUrl, 0, 26) === 'http://repo.packagist.org/') { // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol - $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20); + $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20); $degradedPackagist = true; } @@ -297,7 +297,7 @@ class RemoteFilesystem unset($origFileUrl, $actualContextOptions); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 - if ((substr($fileUrl, 0, 23) !== 'http://packagist.org/p/' || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { + if ((!preg_match('{^http://(repo\.)?packagist.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 49700c80a..f84d5d35f 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -36,7 +36,7 @@ class ConfigTest extends TestCase $data = array(); $data['local config inherits system defaults'] = array( array( - 'packagist.org' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), + 'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), ), array(), ); @@ -59,7 +59,7 @@ class ConfigTest extends TestCase array( 1 => array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), 0 => array('type' => 'pear', 'url' => 'http://pear.composer.org'), - 'packagist.org' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), + 'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), ), array( array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), @@ -70,7 +70,7 @@ class ConfigTest extends TestCase $data['system config adds above core defaults'] = array( array( 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), - 'packagist.org' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), + 'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), ), array(), array( @@ -107,7 +107,7 @@ class ConfigTest extends TestCase $data['incorrect local config does not cause ErrorException'] = array( array( - 'packagist.org' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), + 'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), 'type' => 'vcs', 'url' => 'http://example.com', ), diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 8896f81af..cc66ab399 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -119,7 +119,7 @@ class ValidatingArrayLoaderTest extends TestCase 'repositories' => array( array( 'type' => 'composer', - 'url' => 'https://packagist.org/', + 'url' => 'https://repo.packagist.org/', ), ), 'config' => array( From 02d56da41407d7c1a19a9fc79fc3dbba06f08927 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 24 Jul 2018 13:45:41 +0200 Subject: [PATCH 195/580] Update deps --- composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index 8da0b40dd..873419108 100644 --- a/composer.lock +++ b/composer.lock @@ -437,16 +437,16 @@ }, { "name": "symfony/console", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7" + "reference": "42a0adc7dd656ca2e360285eb6d822df9ce0b160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e8e59b74ad1274714dad2748349b55e3e6e630c7", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7", + "url": "https://api.github.com/repos/symfony/console/zipball/42a0adc7dd656ca2e360285eb6d822df9ce0b160", + "reference": "42a0adc7dd656ca2e360285eb6d822df9ce0b160", "shasum": "" }, "require": { @@ -494,11 +494,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-07-09T12:58:09+00:00" }, { "name": "symfony/debug", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -555,16 +555,16 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "0f685c099aca7ba86bcc31850186dbfe84a4a8a1" + "reference": "5cfc856c5b665ef5de0df796e98c54bef0fe595b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/0f685c099aca7ba86bcc31850186dbfe84a4a8a1", - "reference": "0f685c099aca7ba86bcc31850186dbfe84a4a8a1", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/5cfc856c5b665ef5de0df796e98c54bef0fe595b", + "reference": "5cfc856c5b665ef5de0df796e98c54bef0fe595b", "shasum": "" }, "require": { @@ -601,11 +601,11 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-06-21T09:24:14+00:00" + "time": "2018-07-09T13:24:25+00:00" }, { "name": "symfony/finder", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -768,7 +768,7 @@ }, { "name": "symfony/process", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -1733,7 +1733,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.42", + "version": "v2.8.43", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", From ff59bbdab0fb5bef212f291bbf9f152c7467fd7d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 24 Jul 2018 14:32:52 +0200 Subject: [PATCH 196/580] CS fixer --- .php_cs | 8 +++--- src/Composer/Autoload/AutoloadGenerator.php | 5 ++-- src/Composer/Command/AboutCommand.php | 6 +++-- src/Composer/Command/ArchiveCommand.php | 3 ++- .../Command/BaseDependencyCommand.php | 7 +++-- .../Command/CheckPlatformReqsCommand.php | 3 ++- src/Composer/Command/ClearCacheCommand.php | 3 ++- src/Composer/Command/ConfigCommand.php | 5 ++-- src/Composer/Command/CreateProjectCommand.php | 3 ++- src/Composer/Command/DependsCommand.php | 3 ++- src/Composer/Command/DiagnoseCommand.php | 3 ++- src/Composer/Command/DumpAutoloadCommand.php | 3 ++- src/Composer/Command/ExecCommand.php | 6 +++-- src/Composer/Command/GlobalCommand.php | 3 ++- src/Composer/Command/HomeCommand.php | 3 ++- src/Composer/Command/InitCommand.php | 20 +++++++++----- src/Composer/Command/InstallCommand.php | 3 ++- src/Composer/Command/LicensesCommand.php | 3 ++- src/Composer/Command/OutdatedCommand.php | 3 ++- src/Composer/Command/ProhibitsCommand.php | 3 ++- src/Composer/Command/RemoveCommand.php | 3 ++- src/Composer/Command/RequireCommand.php | 3 ++- src/Composer/Command/RunScriptCommand.php | 3 ++- src/Composer/Command/ScriptAliasCommand.php | 3 ++- src/Composer/Command/SearchCommand.php | 3 ++- src/Composer/Command/SelfUpdateCommand.php | 16 +++++++---- src/Composer/Command/ShowCommand.php | 3 ++- src/Composer/Command/StatusCommand.php | 4 +-- src/Composer/Command/SuggestsCommand.php | 3 ++- src/Composer/Command/UpdateCommand.php | 3 ++- src/Composer/Command/ValidateCommand.php | 3 ++- src/Composer/Config.php | 2 ++ src/Composer/DependencyResolver/Problem.php | 1 + .../DependencyResolver/SolverBugException.php | 3 ++- src/Composer/Downloader/DownloadManager.php | 5 +++- src/Composer/Downloader/HgDownloader.php | 4 +-- src/Composer/Downloader/PathDownloader.php | 8 ++++-- .../EventDispatcher/EventDispatcher.php | 27 ++++++++++++++----- src/Composer/Installer.php | 8 +++--- src/Composer/Json/JsonManipulator.php | 3 +-- .../Package/Archiver/GitExcludeFilter.php | 3 ++- .../Package/Archiver/PharArchiver.php | 3 ++- src/Composer/Package/Archiver/ZipArchiver.php | 3 ++- src/Composer/Package/Locker.php | 3 ++- src/Composer/Repository/PathRepository.php | 4 +-- src/Composer/Repository/Vcs/HgDriver.php | 2 +- src/Composer/Repository/Vcs/SvnDriver.php | 2 +- src/Composer/Util/Filesystem.php | 8 +++--- src/Composer/Util/Hg.php | 11 +++----- src/Composer/Util/Svn.php | 5 ++-- .../Test/Autoload/AutoloadGeneratorTest.php | 20 +++++++------- .../Composer/Test/Command/InitCommandTest.php | 6 +++-- .../Test/DependencyResolver/RequestTest.php | 6 +++-- tests/Composer/Test/IO/ConsoleIOTest.php | 1 - .../Installer/InstallationManagerTest.php | 3 ++- .../Archiver/ArchivableFilesFinderTest.php | 6 +++-- tests/Composer/Test/Package/LockerTest.php | 9 +++++-- .../Test/Repository/PearRepositoryTest.php | 6 +++-- tests/Composer/Test/Util/UrlTest.php | 2 +- 59 files changed, 196 insertions(+), 111 deletions(-) diff --git a/.php_cs b/.php_cs index c3fe1e894..ac954ab5e 100644 --- a/.php_cs +++ b/.php_cs @@ -25,11 +25,11 @@ return PhpCsFixer\Config::create() '@PSR2' => true, 'array_syntax' => array('syntax' => 'long'), 'binary_operator_spaces' => true, - 'blank_line_before_return' => true, - 'cast_spaces' => true, + 'blank_line_before_statement' => array('statements' => array('declare', 'return')), + 'cast_spaces' => array('space' => 'single'), 'header_comment' => array('header' => $header), 'include' => true, - 'method_separation' => true, + 'class_attributes_separation' => array('elements' => array('method')), 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_empty_statement' => true, @@ -38,7 +38,6 @@ return PhpCsFixer\Config::create() 'no_leading_namespace_whitespace' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_unused_imports' => true, - 'no_useless_else' => true, 'no_whitespace_in_blank_line' => true, 'object_operator_without_whitespace' => true, 'phpdoc_align' => true, @@ -50,7 +49,6 @@ return PhpCsFixer\Config::create() 'phpdoc_trim' => true, 'phpdoc_types' => true, 'psr0' => true, - 'short_scalar_cast' => true, 'single_blank_line_before_namespace' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index f7dfdef0d..8f03d330a 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -903,8 +903,8 @@ INITIALIZER; /** * Filters out dev-dependencies when not in dev-mode * - * @param array $packageMap - * @param PackageInterface $mainPackage + * @param array $packageMap + * @param PackageInterface $mainPackage * @return array */ protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) @@ -940,6 +940,7 @@ INITIALIZER; function ($item) use ($include) { $package = $item[0]; $name = $package->getName(); + return isset($include[$name]); } ); diff --git a/src/Composer/Command/AboutCommand.php b/src/Composer/Command/AboutCommand.php index fac42c312..8fbad05a8 100644 --- a/src/Composer/Command/AboutCommand.php +++ b/src/Composer/Command/AboutCommand.php @@ -25,7 +25,8 @@ class AboutCommand extends BaseCommand $this ->setName('about') ->setDescription('Shows the short information about Composer.') - ->setHelp(<<setHelp( + <<php composer.phar about EOT ) @@ -34,7 +35,8 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $this->getIO()->write(<<getIO()->write( + <<Composer - Package Management for PHP Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information. diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 4ab1e4905..29858c6fc 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -48,7 +48,8 @@ class ArchiveCommand extends BaseCommand .' Note that the format will be appended.'), new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'), )) - ->setHelp(<<setHelp( + <<archive command creates an archive of the specified format containing the files and directories of the Composer project or the specified package in the specified version and writes it to the specified directory. diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index a0c358add..4c8766ba3 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -129,8 +129,11 @@ class BaseDependencyCommand extends BaseCommand $results = $repository->getDependents($needles, $constraint, $inverted, $recursive); if (empty($results)) { $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; - $this->getIO()->writeError(sprintf('There is no installed package depending on "%s"%s', - $needle, $extra)); + $this->getIO()->writeError(sprintf( + 'There is no installed package depending on "%s"%s', + $needle, + $extra + )); } elseif ($renderTree) { $this->initStyles($output); $root = $packages[0]; diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 6a75ab4dc..5a68661cc 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -26,7 +26,8 @@ class CheckPlatformReqsCommand extends BaseCommand { $this->setName('check-platform-reqs') ->setDescription('Check that platform requirements are satisfied.') - ->setHelp(<<setHelp( + <<php composer.phar check-platform-reqs diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index 362d28500..2514f6330 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -28,7 +28,8 @@ class ClearCacheCommand extends BaseCommand ->setName('clear-cache') ->setAliases(array('clearcache')) ->setDescription('Clears composer\'s internal package cache.') - ->setHelp(<<setHelp( + <<clear-cache deletes all cached packages from composer's cache directory. EOT diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 4e0232844..b002fd3a7 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -75,7 +75,8 @@ class ConfigCommand extends BaseCommand new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), )) - ->setHelp(<<setHelp( + <<getOption('unset')) { return $this->configSource->removeProperty($settingKey); } diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 289b8909c..a7cc6650b 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -80,7 +80,8 @@ class CreateProjectCommand extends BaseCommand new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), )) - ->setHelp(<<setHelp( + <<create-project command creates a new project from a given package into a new directory. If executed without params and in a directory with a composer.json file it installs the packages for the current project. diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index cbf75f736..acbc89a70 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -31,7 +31,8 @@ class DependsCommand extends BaseDependencyCommand ->setName('depends') ->setAliases(array('why')) ->setDescription('Shows which packages cause the given package to be installed.') - ->setHelp(<<setHelp( + <<php composer.phar depends composer/composer diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index b15cd35b1..ada5faa87 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -48,7 +48,8 @@ class DiagnoseCommand extends BaseCommand $this ->setName('diagnose') ->setDescription('Diagnoses the system to identify common errors.') - ->setHelp(<<setHelp( + <<diagnose command checks common errors to help debugging problems. The process exit code will be 1 in case of warnings and 2 for errors. diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 6c79ef13f..fe80e3760 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -36,7 +36,8 @@ class DumpAutoloadCommand extends BaseCommand new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'), )) - ->setHelp(<<setHelp( + <<php composer.phar dump-autoload EOT ) diff --git a/src/Composer/Command/ExecCommand.php b/src/Composer/Command/ExecCommand.php index 2ab36f802..f07bc9d28 100644 --- a/src/Composer/Command/ExecCommand.php +++ b/src/Composer/Command/ExecCommand.php @@ -53,7 +53,8 @@ class ExecCommand extends BaseCommand throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); } - $this->getIO()->write(<<getIO()->write( + <<Available binaries: EOT ); @@ -66,7 +67,8 @@ EOT $previousBin = $bin; $bin = basename($bin); - $this->getIO()->write(<<getIO()->write( + <<- $bin EOT ); diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index d189f44d5..9e87c7e17 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -32,7 +32,8 @@ class GlobalCommand extends BaseCommand new InputArgument('command-name', InputArgument::REQUIRED, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), )) - ->setHelp(<<setHelp( + <<setHelp(<<setHelp( + <<setHelp(<<setHelp( + <<init command creates a basic composer.json file in the current directory. @@ -676,7 +677,7 @@ EOT * @param string|null $requiredVersion * @param string $minimumStability * @throws \InvalidArgumentException - * @return array name version + * @return array name version */ private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) { @@ -694,19 +695,26 @@ EOT // Check whether the PHP version was the problem if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) { throw new \InvalidArgumentException(sprintf( - 'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)', $name, $requiredVersion, $phpVersion + 'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)', + $name, + $requiredVersion, + $phpVersion )); } // Check whether the required version was the problem if ($requiredVersion && $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability)) { throw new \InvalidArgumentException(sprintf( - 'Could not find package %s in a version matching %s', $name, $requiredVersion + 'Could not find package %s in a version matching %s', + $name, + $requiredVersion )); } // Check whether the PHP version was the problem if ($phpVersion && $versionSelector->findBestCandidate($name)) { throw new \InvalidArgumentException(sprintf( - 'Could not find package %s in any version matching your PHP version (%s)', $name, $phpVersion + 'Could not find package %s in any version matching your PHP version (%s)', + $name, + $phpVersion )); } @@ -738,7 +746,7 @@ EOT return array( $package->getPrettyName(), - $versionSelector->findRecommendedRequireVersion($package) + $versionSelector->findRecommendedRequireVersion($package), ); } diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 7a8980cc4..cc590d8c9 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -52,7 +52,8 @@ class InstallCommand extends BaseCommand new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), )) - ->setHelp(<<setHelp( + <<install command reads the composer.lock file from the current directory, processes it, and downloads and installs all the libraries and dependencies outlined in that file. If the file does not diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index f3a71eb39..9dec45e1b 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -36,7 +36,8 @@ class LicensesCommand extends BaseCommand new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), )) - ->setHelp(<<setHelp( + <<setHelp(<<setHelp( + <<setName('prohibits') ->setAliases(array('why-not')) ->setDescription('Shows which packages prevent the given package from being installed.') - ->setHelp(<<setHelp( + <<php composer.phar prohibits composer/composer diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 200340e9e..c97130c3a 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -48,7 +48,8 @@ class RemoveCommand extends BaseCommand new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) - ->setHelp(<<setHelp( + <<remove command removes a package from the current list of installed packages diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 37cec85c7..01439207f 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -57,7 +57,8 @@ class RequireCommand extends InitCommand new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), )) - ->setHelp(<<setHelp( + <<setHelp(<<setHelp( + <<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index 81f1454da..1aba0b074 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -43,7 +43,8 @@ class ScriptAliasCommand extends BaseCommand new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), )) - ->setHelp(<<setHelp( + <<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index cdcc00dc4..8fa6b02b7 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -44,7 +44,8 @@ class SearchCommand extends BaseCommand new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), )) - ->setHelp(<<setHelp( + <<php composer.phar search symfony composer diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 52f7b87f0..2641a922b 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -53,7 +53,8 @@ class SelfUpdateCommand extends BaseCommand new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), )) - ->setHelp(<<setHelp( + <<self-update command checks getcomposer.org for newer versions of composer and if found, installs the latest. @@ -176,7 +177,9 @@ EOT $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); if (!file_exists($sigFile)) { - file_put_contents($home.'/keys.dev.pub', <<setHelp(<<setHelp( + <<setHelp(<<setHelp( + <<%command.name% command shows a sorted list of suggested packages. diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 1d67f1f9b..34420b747 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -61,7 +61,8 @@ class UpdateCommand extends BaseCommand new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), )) - ->setHelp(<<setHelp( + <<update command reads the composer.json file from the current directory, processes it, and updates, removes or installs all the dependencies. diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index c4f78d2df..d884febe8 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -46,7 +46,8 @@ class ValidateCommand extends BaseCommand new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'), )) - ->setHelp(<<setHelp( + <<getInstallationSource()) { throw new \LogicException(sprintf( 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', - get_class($downloader), $downloader->getInstallationSource(), $installationSource, $package + get_class($downloader), + $downloader->getInstallationSource(), + $installationSource, + $package )); } diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 8660ec61d..2921cc4b7 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -28,7 +28,7 @@ class HgDownloader extends VcsDownloader { $hgUtils = new HgUtils($this->io, $this->config, $this->process); - $cloneCommand = function($url) use ($path) { + $cloneCommand = function ($url) use ($path) { return sprintf('hg clone %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path)); }; @@ -55,7 +55,7 @@ class HgDownloader extends VcsDownloader throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $command = function($url) use ($ref) { + $command = function ($url) use ($ref) { return sprintf('hg pull %s && hg up %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref)); }; diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 250a01c3f..456ce4115 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -43,7 +43,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $realUrl = realpath($url); if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { throw new \RuntimeException(sprintf( - 'Source path "%s" is not found for package %s', $url, $package->getName() + 'Source path "%s" is not found for package %s', + $url, + $package->getName() )); } @@ -54,7 +56,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter // for previous attempts that were shut down because they did not work well enough or introduced too many risks. throw new \RuntimeException(sprintf( 'Package %s cannot install to "%s" inside its source at "%s"', - $package->getName(), realpath($path), $realUrl + $package->getName(), + realpath($path), + $realUrl )); } diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 7f7a2cd86..145944b07 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -325,22 +325,37 @@ class EventDispatcher if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\CommandEvent( - $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments() + $event->getName(), + $event->getComposer(), + $event->getIO(), + $event->isDevMode(), + $event->getArguments() ); } if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\PackageEvent( - $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), - $event->getPolicy(), $event->getPool(), $event->getInstalledRepo(), $event->getRequest(), - $event->getOperations(), $event->getOperation() + $event->getName(), + $event->getComposer(), + $event->getIO(), + $event->isDevMode(), + $event->getPolicy(), + $event->getPool(), + $event->getInstalledRepo(), + $event->getRequest(), + $event->getOperations(), + $event->getOperation() ); } if (!$event instanceof $expected && $expected === 'Composer\Script\Event') { trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); $event = new \Composer\Script\Event( - $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), - $event->getArguments(), $event->getFlags() + $event->getName(), + $event->getComposer(), + $event->getIO(), + $event->isDevMode(), + $event->getArguments(), + $event->getFlags() ); } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f6f78a4d9..a729710c0 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -521,15 +521,15 @@ class Installer } } - $this->io->writeError( - sprintf("Package operations: %d install%s, %d update%s, %d removal%s", + $this->io->writeError(sprintf( + "Package operations: %d install%s, %d update%s, %d removal%s", count($installs), 1 === count($installs) ? '' : 's', count($updates), 1 === count($updates) ? '' : 's', count($uninstalls), - 1 === count($uninstalls) ? '' : 's') - ); + 1 === count($uninstalls) ? '' : 's' + )); if ($installs) { $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); } diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index b9dc24bb5..40c0c09a2 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -510,8 +510,7 @@ class JsonManipulator if (PHP_VERSION_ID > 70000) { throw new \RuntimeException('Failed to execute regex: PREG_JIT_STACKLIMIT_ERROR', 6); } - // fallthrough - + // no break default: throw new \RuntimeException('Failed to execute regex: Unknown error'); } diff --git a/src/Composer/Package/Archiver/GitExcludeFilter.php b/src/Composer/Package/Archiver/GitExcludeFilter.php index 894a45a84..0cdc98c81 100644 --- a/src/Composer/Package/Archiver/GitExcludeFilter.php +++ b/src/Composer/Package/Archiver/GitExcludeFilter.php @@ -42,7 +42,8 @@ class GitExcludeFilter extends BaseExcludeFilter $this->parseLines( file($sourcePath.'/.gitattributes'), array($this, 'parseGitAttributesLine') - )); + ) + ); } } diff --git a/src/Composer/Package/Archiver/PharArchiver.php b/src/Composer/Package/Archiver/PharArchiver.php index cbab4ba23..f9a392353 100644 --- a/src/Composer/Package/Archiver/PharArchiver.php +++ b/src/Composer/Package/Archiver/PharArchiver.php @@ -76,7 +76,8 @@ class PharArchiver implements ArchiverInterface return $target; } catch (\UnexpectedValueException $e) { - $message = sprintf("Could not create archive '%s' from '%s': %s", + $message = sprintf( + "Could not create archive '%s' from '%s': %s", $target, $sources, $e->getMessage() diff --git a/src/Composer/Package/Archiver/ZipArchiver.php b/src/Composer/Package/Archiver/ZipArchiver.php index aaa41aea3..d1d7573f3 100644 --- a/src/Composer/Package/Archiver/ZipArchiver.php +++ b/src/Composer/Package/Archiver/ZipArchiver.php @@ -50,7 +50,8 @@ class ZipArchiver implements ArchiverInterface return $target; } } - $message = sprintf("Could not create archive '%s' from '%s': %s", + $message = sprintf( + "Could not create archive '%s' from '%s': %s", $target, $sources, $zip->getStatusString() diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 7fbe536f2..57ec74233 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -360,7 +360,8 @@ class Locker if (!$name || !$version) { throw new \LogicException(sprintf( - 'Package "%s" has no version or name and can not be locked', $package + 'Package "%s" has no version or name and can not be locked', + $package )); } diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 5d50c2473..61ebc8d8c 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -175,13 +175,13 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn private function getUrlMatches() { $flags = GLOB_MARK | GLOB_ONLYDIR; - + if (defined('GLOB_BRACE')) { $flags |= GLOB_BRACE; } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) { throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url); } - + // Ensure environment-specific path separators are normalized to URL separators return array_map(function ($val) { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 8141f453b..2db995e2e 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -61,7 +61,7 @@ class HgDriver extends VcsDriver // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); - $command = function($url) { + $command = function ($url) { return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir)); }; diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 822510601..96434517a 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -135,7 +135,7 @@ class SvnDriver extends VcsDriver try { $composer = $this->getBaseComposerInformation($identifier); - } catch(TransportException $e) { + } catch (TransportException $e) { $message = $e->getMessage(); if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) { throw $e; diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index f262432ff..04df84ecd 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -634,9 +634,11 @@ class Filesystem if (!is_dir($target)) { throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); } - $cmd = sprintf('mklink /J %s %s', - ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)), - ProcessExecutor::escape(realpath($target))); + $cmd = sprintf( + 'mklink /J %s %s', + ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)), + ProcessExecutor::escape(realpath($target)) + ); if ($this->getProcess()->execute($cmd, $output) !== 0) { throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); } diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 5afe7b3f4..48d0b3687 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -20,7 +20,6 @@ use Composer\IO\IOInterface; */ class Hg { - /** * @var \Composer\IO\IOInterface */ @@ -43,20 +42,21 @@ class Hg $this->process = $process; } - public function runCommand($commandCallable, $url, $cwd) { + public function runCommand($commandCallable, $url, $cwd) + { $this->config->prohibitUrlByConfig($url, $this->io); // Try as is $command = call_user_func($commandCallable, $url); - if (0 === $this->process->execute($command, $ignoredOutput, $cwd)){ + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } // Try with the authentication informations available if (preg_match('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi', $url, $match) && $this->io->hasAuthentication($match[5])) { $auth = $this->io->getAuthentication($match[5]); - $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6])? $match[6]: null); + $authenticatedUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[5] . (!empty($match[6]) ? $match[6] : null); $command = call_user_func($commandCallable, $authenticatedUrl); @@ -69,10 +69,7 @@ class Hg $error = 'The given URL (' . $url . ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)'; } - - $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url); - } public static function sanitizeUrl($message) diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 898fadce0..df31ccbbf 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -66,7 +66,7 @@ class Svn /** * @var string|null */ - static private $version; + private static $version; /** * @param string $url @@ -223,7 +223,8 @@ class Svn */ protected function getCommand($cmd, $url, $path = null) { - $cmd = sprintf('%s %s%s %s', + $cmd = sprintf( + '%s %s%s %s', $cmd, '--non-interactive ', $this->getCredentialString(), diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index dd99bfd59..b8eec2f43 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -362,7 +362,7 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( new Link('a', 'a/a'), - new Link('a', 'b/b') + new Link('a', 'b/b'), )); $packages = array(); @@ -445,7 +445,7 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( new Link('a', 'a/a'), - new Link('a', 'b/b') + new Link('a', 'b/b'), )); $packages = array(); @@ -484,7 +484,7 @@ class AutoloadGeneratorTest extends TestCase $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( new Link('a', 'a/a'), - new Link('a', 'b/b') + new Link('a', 'b/b'), )); $packages = array(); @@ -524,7 +524,7 @@ class AutoloadGeneratorTest extends TestCase $package->setRequires(array( new Link('a', 'a/a'), new Link('a', 'b/b'), - new Link('a', 'c/c') + new Link('a', 'c/c'), )); $packages = array(); @@ -568,7 +568,7 @@ class AutoloadGeneratorTest extends TestCase $package->setRequires(array( new Link('a', 'a/a'), new Link('a', 'b/b'), - new Link('a', 'c/c') + new Link('a', 'c/c'), )); $packages = array(); @@ -617,7 +617,7 @@ class AutoloadGeneratorTest extends TestCase $package->setRequires(array( new Link('a', 'a/a'), new Link('a', 'b/b'), - new Link('a', 'c/c') + new Link('a', 'c/c'), )); $packages = array(); @@ -667,7 +667,7 @@ class AutoloadGeneratorTest extends TestCase $requires = array( new Link('a', 'a/a'), new Link('a', 'b/b'), - new Link('a', 'c/c') + new Link('a', 'c/c'), ); $autoloadPackage->setRequires($requires); $notAutoloadPackage->setRequires($requires); @@ -739,7 +739,7 @@ class AutoloadGeneratorTest extends TestCase new Link('a', 'z/foo'), new Link('a', 'b/bar'), new Link('a', 'd/d'), - new Link('a', 'e/e') + new Link('a', 'e/e'), )); $packages = array(); @@ -809,7 +809,7 @@ class AutoloadGeneratorTest extends TestCase )); $mainPackage->setRequires(array( new Link('z', 'a/a'), - new Link('z', 'b/b') + new Link('z', 'b/b'), )); $packages = array(); @@ -1068,7 +1068,7 @@ EOF; 'files' => array('test.php'), )); $package->setRequires(array( - new Link('a', 'b/b') + new Link('a', 'b/b'), )); $vendorPackage = new Package('b/b', '1.0', '1.0'); diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 81aaf0d17..d355b6cd1 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -59,7 +59,8 @@ class InitCommandTest extends TestCase { $command = new InitCommand; $author = $command->parseAuthorString( - 'Johnathon "Johnny" Smith '); + 'Johnathon "Johnny" Smith ' + ); $this->assertEquals('Johnathon "Johnny" Smith', $author['name']); $this->assertEquals('john@example.com', $author['email']); } @@ -72,7 +73,8 @@ class InitCommandTest extends TestCase { $command = new InitCommand; $author = $command->parseAuthorString( - 'Johnathon (Johnny) Smith '); + 'Johnathon (Johnny) Smith ' + ); $this->assertEquals('Johnathon (Johnny) Smith', $author['name']); $this->assertEquals('john@example.com', $author['email']); } diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index e16ca6e11..08e0cae96 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -40,7 +40,8 @@ class RequestTest extends TestCase array('cmd' => 'install', 'packageName' => 'bar', 'constraint' => null, 'fixed' => true), array('cmd' => 'remove', 'packageName' => 'foobar', 'constraint' => null, 'fixed' => false), ), - $request->getJobs()); + $request->getJobs() + ); } public function testRequestInstallSamePackageFromDifferentRepositories() @@ -73,6 +74,7 @@ class RequestTest extends TestCase $this->assertEquals( array(array('cmd' => 'update-all')), - $request->getJobs()); + $request->getJobs() + ); } } diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index f44369dba..57809769c 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -241,7 +241,6 @@ class ConsoleIOTest extends TestCase $this->isInstanceOf('Symfony\Component\Console\Question\Question') ) ->will($this->returnValue(array('item2'))); - ; $setMock ->expects($this->once()) diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index beb4e4bf5..86e860bc2 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -86,7 +86,8 @@ class InstallationManagerTest extends TestCase $installOperation = new InstallOperation($this->createPackageMock()); $removeOperation = new UninstallOperation($this->createPackageMock()); $updateOperation = new UpdateOperation( - $this->createPackageMock(), $this->createPackageMock() + $this->createPackageMock(), + $this->createPackageMock() ); $manager diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index 669ec7a3f..5856efbf5 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -185,7 +185,8 @@ class ArchivableFilesFinderTest extends TestCase $this->finder = new ArchivableFilesFinder($this->sources, array()); - $this->assertArchivableFiles($this->getArchivedFiles('git init && '. + $this->assertArchivableFiles($this->getArchivedFiles( + 'git init && '. 'git config user.email "you@example.com" && '. 'git config user.name "Your Name" && '. 'git add .git* && '. @@ -222,7 +223,8 @@ class ArchivableFilesFinderTest extends TestCase $this->finder = new ArchivableFilesFinder($this->sources, array()); - $expectedFiles = $this->getArchivedFiles('hg init && '. + $expectedFiles = $this->getArchivedFiles( + 'hg init && '. 'hg add && '. 'hg commit -m "init" && '. 'hg archive archive.zip' diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index b5692b009..c20c21466 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -21,8 +21,13 @@ class LockerTest extends TestCase public function testIsLocked() { $json = $this->createJsonFileMock(); - $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), - $this->getJsonContent()); + $locker = new Locker( + new NullIO, + $json, + $this->createRepositoryManagerMock(), + $this->createInstallationManagerMock(), + $this->getJsonContent() + ); $json ->expects($this->any()) diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index 62c0ce822..c484820b6 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -48,7 +48,8 @@ class PearRepositoryTest extends TestCase foreach ($expectedPackages as $expectedPackage) { $package = $this->repository->findPackage($expectedPackage['name'], $expectedPackage['version']); - $this->assertInstanceOf('Composer\Package\PackageInterface', + $this->assertInstanceOf( + 'Composer\Package\PackageInterface', $package, 'Expected package ' . $expectedPackage['name'] . ', version ' . $expectedPackage['version'] . ' not found in pear channel ' . $url @@ -74,7 +75,8 @@ class PearRepositoryTest extends TestCase $this->createRepository($repoConfig); foreach ($expectedPackages as $expectedPackage) { - $this->assertInstanceOf('Composer\Package\PackageInterface', + $this->assertInstanceOf( + 'Composer\Package\PackageInterface', $this->repository->findPackage($expectedPackage['name'], $expectedPackage['version']), 'Expected package ' . $expectedPackage['name'] . ', version ' . $expectedPackage['version'] . ' not found in pear channel ' . $url diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php index 96e71d10e..7772582a5 100644 --- a/tests/Composer/Test/Util/UrlTest.php +++ b/tests/Composer/Test/Util/UrlTest.php @@ -55,7 +55,7 @@ class UrlTest extends TestCase // gitlab enterprise array('https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=abcd', 'https://mygitlab.com/api/v4/projects/foo%2Fbar/repository/archive.tar.gz?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=newref', array('gitlab-domains' => array('mygitlab.com'))), - array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'), + array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'), ); } } From 158e1c95da02cc0b932de74f9a09a1c7b6cf654f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 24 Jul 2018 14:33:38 +0200 Subject: [PATCH 197/580] Prepare changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb422a27..aff43e18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +### [1.7.0-RC] 2018-07-24 + + * Changed default repository URL from packagist.org to repo.packagist.org, this might affect people with strict firewall rules + * Changed output from Updating to Downgrading when performing package downgrades, this might affect anything parsing output + * Several minor performance improvements + * Added basic authentication support for mercurial repos + * Added explicit `i` and `u` aliases for the `install` and `update` commands + * Added support for `show` command to output json format with --tree + * Added support for {glob,braces} support in the path repository's path argument + * Added support in `status` command for showing diffs in vendor dir even for packages installed as dist/zip archives + * Added `--remove-vcs` flag to `create-project` command to avoid prompting for keeping VCS files + * Added `--no-secure-http` flag to `create-project` command to bypass https (use at your own risk) + * Added `pre-command-run` event that lets plugins modify arguments + * Added RemoteFilesystem::getRemoteContents extension point + * Fixed setting scripts via `config` command + ### [1.6.5] 2018-05-04 * Fixed regression in 1.6.4 causing strange update behaviors with dev packages @@ -651,6 +667,7 @@ * Initial release +[1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC [1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5 [1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 [1.6.3]: https://github.com/composer/composer/compare/1.6.2...1.6.3 From 76bf6bdf972eabca66bc0d2eb5c5f49796166e40 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 24 Jul 2018 18:20:04 +0200 Subject: [PATCH 198/580] Fixed typo --- src/Composer/Repository/ComposerRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 403b31cd9..8a5da2b23 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -92,7 +92,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->url = $repoConfig['url']; // force url for packagist.org to repo.packagist.org - if (preg_match('{^(?Phttps?)://packagist.org/?$}i', $this->url, $match)) { + if (preg_match('{^(?Phttps?)://packagist\.org/?$}i', $this->url, $match)) { $this->url = $match['proto'].'://repo.packagist.org'; } From eb94f8346a8b2d963ff3563d726030acdbfe1aaa Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 24 Jul 2018 18:21:40 +0200 Subject: [PATCH 199/580] Fixed typo --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 39ed36107..10db2203d 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -297,7 +297,7 @@ class RemoteFilesystem unset($origFileUrl, $actualContextOptions); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 - if ((!preg_match('{^http://(repo\.)?packagist.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { + if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } From d73aef5c8a8cb9f1cb7f707e27cbf45f3411c838 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 25 Jul 2018 10:22:05 +0200 Subject: [PATCH 200/580] Respect --no-plugins flag when firing pre-command-run event --- src/Composer/Command/BaseCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index bd7c8a9fb..19d80aec4 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -127,7 +127,8 @@ abstract class BaseCommand extends Command protected function initialize(InputInterface $input, OutputInterface $output) { // initialize a plugin-enabled Composer instance, either local or global - $composer = $this->getComposer(false, false); + $disablePlugins = $input->hasParameterOption('--no-plugins'); + $composer = $this->getComposer(false, $disablePlugins); if (null === $composer) { $composer = Factory::createGlobal($this->getIO(), false); } From 9bc578e24a32f265f01f46e389edb028a61e9a07 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 26 Jul 2018 14:15:32 +0200 Subject: [PATCH 201/580] Fix warning tag name, fixes #7494 --- src/Composer/Downloader/PathDownloader.php | 2 +- src/Composer/Downloader/ZipDownloader.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 456ce4115..e7084bd97 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -150,7 +150,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $this->io->writeError(" - Removing junction for " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } if (!$this->filesystem->removeJunction($path)) { - $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); + $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); } } else { diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 6217aaa53..5ca7a2dab 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -68,8 +68,8 @@ class ZipDownloader extends ArchiveDownloader self::$isWindows = Platform::isWindows(); if (!self::$isWindows && !self::$hasSystemUnzip) { - $this->io->writeError("As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension."); - $this->io->writeError("This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them."); + $this->io->writeError("As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them."); } } From a3bbcf9c7731930dc492527b27b6703268e4a19b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 26 Jul 2018 17:31:40 +0200 Subject: [PATCH 202/580] Make RemoteFilesystem::getRemoteContents() report response headers also on exceptions --- src/Composer/Util/RemoteFilesystem.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 10db2203d..dc570ecf3 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -315,7 +315,7 @@ class RemoteFilesystem $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); }); try { - list($http_response_header, $result) = $this->getRemoteContents($originUrl, $fileUrl, $ctx); + $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); @@ -577,13 +577,24 @@ class RemoteFilesystem * @param string $fileUrl The file URL * @param resource $context The stream context * - * @return array The response headers and the contents + * @return string|false The response contents or false on failure */ - protected function getRemoteContents($originUrl, $fileUrl, $context) + protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null) { - $contents = file_get_contents($fileUrl, false, $context); + try { + $e = null; + $result = file_get_contents($fileUrl, false, $context); + } catch (\Throwable $e) { + } catch (\Exception $e) { + } - return array(isset($http_response_header) ? $http_response_header : null, $contents); + $responseHeaders = isset($http_response_header) ? $http_response_header : array(); + + if (null !== $e) { + throw $e; + } + + return $result; } /** From de6432f5f013e240f87584c752a8a1acdd5e9d58 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 27 Jul 2018 10:59:36 +0200 Subject: [PATCH 203/580] Show overridden php version in diagnose command, fixes #7497 --- src/Composer/Command/DiagnoseCommand.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index ada5faa87..3efb34973 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\Factory; use Composer\Config; use Composer\Downloader\TransportException; +use Composer\Repository\PlatformRepository; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\ConfigValidator; @@ -153,7 +154,15 @@ EOT $io->write(sprintf('Composer version: %s', Composer::VERSION)); - $io->write(sprintf('PHP version: %s', PHP_VERSION)); + $platformOverrides = $config->get('platform') ?: array(); + $platformRepo = new PlatformRepository(array(), $platformOverrides); + $phpPkg = $platformRepo->findPackage('php', '*'); + $phpVersion = $phpPkg->getPrettyVersion(); + if (false !== strpos($phpPkg->getDescription(), 'overridden')) { + $phpVersion .= ' - ' . $phpPkg->getDescription(); + } + + $io->write(sprintf('PHP version: %s', $phpVersion)); if (defined('PHP_BINARY')) { $io->write(sprintf('PHP binary path: %s', PHP_BINARY)); From 42739e7959d81f38e0e41ab7ae96e5db88c0411d Mon Sep 17 00:00:00 2001 From: Rafael Kassner Date: Sun, 29 Jul 2018 16:12:58 +0200 Subject: [PATCH 204/580] Do not dump source and dist for metapackages --- src/Composer/Package/Dumper/ArrayDumper.php | 4 +- .../Test/Package/Dumper/ArrayDumperTest.php | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index 6593143d5..ab8b4d45b 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -45,7 +45,7 @@ class ArrayDumper $data['target-dir'] = $package->getTargetDir(); } - if ($package->getSourceType()) { + if ($package->getSourceType() && $package->getType() !== 'metapackage') { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); $data['source']['reference'] = $package->getSourceReference(); @@ -54,7 +54,7 @@ class ArrayDumper } } - if ($package->getDistType()) { + if ($package->getDistType() && $package->getType() !== 'metapackage') { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); $data['dist']['reference'] = $package->getDistReference(); diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index bd1c29c13..9b12e49a9 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -106,6 +106,57 @@ class ArrayDumperTest extends TestCase $this->assertSame($expectedValue ?: $value, $config[$key]); } + public function testMetapackageShouldNotHaveSourceEntries() + { + $this + ->packageExpects('getPrettyName', 'foo') + ->packageExpects('getPrettyVersion', '1.0') + ->packageExpects('getVersion', '1.0.0.0') + ->packageExpects('getType', 'metapackage') + ->packageExpects('getSourceType', 'composer') + ->packageExpects('getSourceUrl', 'https://packagist.org') + ->packageExpects('getSourceReference', 'packagist') + ; + + $config = $this->dumper->dump($this->package); + + $this->assertEquals( + array( + 'name' => 'foo', + 'version' => '1.0', + 'version_normalized' => '1.0.0.0', + 'type' => 'metapackage', + ), + $config + ); + } + + public function testMetapackageShouldNotHaveDistEntries() + { + $this + ->packageExpects('getPrettyName', 'foo') + ->packageExpects('getPrettyVersion', '1.0') + ->packageExpects('getVersion', '1.0.0.0') + ->packageExpects('getType', 'metapackage') + ->packageExpects('getDistType', 'composer') + ->packageExpects('getDistUrl', 'https://packagist.org') + ->packageExpects('getDistReference', 'packagist') + ->packageExpects('getDistSha1Checksum', 'packagist') + ; + + $config = $this->dumper->dump($this->package); + + $this->assertEquals( + array( + 'name' => 'foo', + 'version' => '1.0', + 'version_normalized' => '1.0.0.0', + 'type' => 'metapackage', + ), + $config + ); + } + public function getKeys() { return array( From 73f14c0c7ce94509ba49a5b2e0813d0b30da73ec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 Aug 2018 14:06:31 +0200 Subject: [PATCH 205/580] Fix output when loading zips from cache, fixes #7498 --- src/Composer/Downloader/FileDownloader.php | 33 +++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index d2a3e7edc..6596d9c8b 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -137,8 +137,11 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $checksum = $package->getDistSha1Checksum(); $cacheKey = $this->getCacheKey($package, $processedUrl); - // download if we don't have it in cache or the cache is invalidated - if (!$this->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) { + // use from cache if it is present and has a valid checksum or we have no checksum to check against + if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { + $this->io->writeError('Loading from cache', false); + } else { + // download if cache restore failed if (!$this->outputProgress) { $this->io->writeError('Downloading', false); } @@ -168,10 +171,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->lastCacheWrites[$package->getName()] = $cacheKey; $this->cache->copyFrom($cacheKey, $fileName); } - } else { - if (!$this->outputProgress) { - $this->io->writeError('Loading from cache', false); - } } if (!file_exists($fileName)) { @@ -297,19 +296,27 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->io = new NullIO; $this->io->loadConfiguration($this->config); $this->outputProgress = false; + $e = null; - $this->download($package, $targetDir.'_compare', false); + try { + $this->download($package, $targetDir.'_compare', false); - $comparer = new Comparer(); - $comparer->setSource($targetDir.'_compare'); - $comparer->setUpdate($targetDir); - $comparer->doCompare(); - $output = $comparer->getChanged(true, true); - $this->filesystem->removeDirectory($targetDir.'_compare'); + $comparer = new Comparer(); + $comparer->setSource($targetDir.'_compare'); + $comparer->setUpdate($targetDir); + $comparer->doCompare(); + $output = $comparer->getChanged(true, true); + $this->filesystem->removeDirectory($targetDir.'_compare'); + } catch (\Exception $e) { + } $this->io = $prevIO; $this->outputProgress = $prevProgress; + if ($e) { + throw $e; + } + return trim($output); } } From 0fdf746ebeecf40672b6b97daa233b1f1e8ee284 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 Aug 2018 15:23:04 +0200 Subject: [PATCH 206/580] Fix --no-plugins not working in certain edge cases --- src/Composer/Command/BaseCommand.php | 2 +- src/Composer/Command/SearchCommand.php | 2 +- src/Composer/Command/ValidateCommand.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index 19d80aec4..d6b014d7d 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -130,7 +130,7 @@ abstract class BaseCommand extends Command $disablePlugins = $input->hasParameterOption('--no-plugins'); $composer = $this->getComposer(false, $disablePlugins); if (null === $composer) { - $composer = Factory::createGlobal($this->getIO(), false); + $composer = Factory::createGlobal($this->getIO(), $disablePlugins); } if ($composer) { $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 8fa6b02b7..ed180e84c 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -60,7 +60,7 @@ EOT $platformRepo = new PlatformRepository; $io = $this->getIO(); if (!($composer = $this->getComposer(false))) { - $composer = Factory::create($this->getIO(), array()); + $composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins')); } $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index d884febe8..52bba1838 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -89,7 +89,7 @@ EOT list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); $lockErrors = array(); - $composer = Factory::create($io, $file); + $composer = Factory::create($io, $file, $input->hasParameterOption('--no-plugins')); $locker = $composer->getLocker(); if ($locker->isLocked() && !$locker->isFresh()) { $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update`.'; From a1179aa4a7a694872d8f47a2cc9b42cd893b1ed6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 3 Aug 2018 15:38:59 +0200 Subject: [PATCH 207/580] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aff43e18d..fb63f0868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### [1.7.0] 2018-08-03 + + * Added the overridden platform config's PHP version in the `diagnose` command output + * Fixed --no-plugins not being respected in a few commands + * Fixed 1.7.0-RC regression in output showing instead of proper colors + * Fixed 1.7.0-RC regression in output missing "Loading from cache" output on package install + ### [1.7.0-RC] 2018-07-24 * Changed default repository URL from packagist.org to repo.packagist.org, this might affect people with strict firewall rules @@ -667,6 +674,7 @@ * Initial release +[1.7.0]: https://github.com/composer/composer/compare/1.7.0...1.7.0 [1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC [1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5 [1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 From a5037385d198c852da3b7d2de1c0cded53059a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Z=C3=BClke?= Date: Fri, 3 Aug 2018 17:21:28 +0200 Subject: [PATCH 208/580] Fix 1.7.0 changes link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb63f0868..cb63f30ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -674,7 +674,7 @@ * Initial release -[1.7.0]: https://github.com/composer/composer/compare/1.7.0...1.7.0 +[1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0 [1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC [1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5 [1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 From a74b63985e7cd811e5cdde30753249d8466666dd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 4 Aug 2018 17:43:34 +0200 Subject: [PATCH 209/580] Avoid filtering dev-require packages when loading plugins/scripts, fixes #7516 --- src/Composer/Autoload/AutoloadGenerator.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 8f03d330a..d54f3d6f6 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -157,7 +157,7 @@ EOF; // Collect information from all packages. $packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages()); - $autoloads = $this->parseAutoloads($packageMap, $mainPackage); + $autoloads = $this->parseAutoloads($packageMap, $mainPackage, $this->devMode === false); // Process the 'psr-0' base directories. foreach ($autoloads['psr-0'] as $namespace => $paths) { @@ -383,12 +383,15 @@ EOF; * * @param array $packageMap array of array(package, installDir-relative-to-composer.json) * @param PackageInterface $mainPackage root package instance + * @param bool $filterOutRequireDevPackages whether to filter out require-dev packages * @return array array('psr-0' => array('Ns\\Foo' => array('installDir'))) */ - public function parseAutoloads(array $packageMap, PackageInterface $mainPackage) + public function parseAutoloads(array $packageMap, PackageInterface $mainPackage, $filterOutRequireDevPackages = false) { $mainPackageMap = array_shift($packageMap); - $packageMap = $this->filterPackageMap($packageMap, $mainPackage); + if ($filterOutRequireDevPackages) { + $packageMap = $this->filterPackageMap($packageMap, $mainPackage); + } $sortedPackageMap = $this->sortPackageMap($packageMap); $sortedPackageMap[] = $mainPackageMap; array_unshift($packageMap, $mainPackageMap); @@ -901,7 +904,7 @@ INITIALIZER; } /** - * Filters out dev-dependencies when not in dev-mode + * Filters out dev-dependencies * * @param array $packageMap * @param PackageInterface $mainPackage @@ -909,10 +912,6 @@ INITIALIZER; */ protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage) { - if ($this->devMode === true) { - return $packageMap; - } - $packages = array(); $include = array(); From a28808752d5a87f9a7a9e4dcbdc572fcab9028aa Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 7 Aug 2018 08:11:10 +0200 Subject: [PATCH 210/580] Attempt workaround for repo.packagist.org domain SSL on very old PHP, fixes #7530 --- src/Composer/Util/RemoteFilesystem.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index dc570ecf3..ee18ad62d 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -761,6 +761,9 @@ class RemoteFilesystem $tlsOptions['ssl']['CN_match'] = $certMap['cn']; $tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp']; + } elseif (!CaBundle::isOpensslParseSafe() && $host === 'repo.packagist.org') { + // handle subjectAltName for packagist.org's repo domain on very old PHPs + $tlsOptions['ssl']['CN_match'] = 'packagist.org'; } } From e7a9bd336229122792cbbd3fd078b8af174611de Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 7 Aug 2018 08:11:10 +0200 Subject: [PATCH 211/580] Attempt workaround for repo.packagist.org domain SSL on very old PHP, fixes #7530 --- src/Composer/Util/RemoteFilesystem.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index dc570ecf3..ee18ad62d 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -761,6 +761,9 @@ class RemoteFilesystem $tlsOptions['ssl']['CN_match'] = $certMap['cn']; $tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp']; + } elseif (!CaBundle::isOpensslParseSafe() && $host === 'repo.packagist.org') { + // handle subjectAltName for packagist.org's repo domain on very old PHPs + $tlsOptions['ssl']['CN_match'] = 'packagist.org'; } } From 9e7d48f70edd4afa51c8495afc4ae5be77a9acdf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 7 Aug 2018 09:38:26 +0200 Subject: [PATCH 212/580] Update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb63f0868..6200d2123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### [1.7.1] 2018-08-07 + + * Fixed issue autoloading plugins in require-dev in some conditions + * Fixed handling of SSL to repo.packagist.org on very old PHP versions + ### [1.7.0] 2018-08-03 * Added the overridden platform config's PHP version in the `diagnose` command output @@ -674,7 +679,8 @@ * Initial release -[1.7.0]: https://github.com/composer/composer/compare/1.7.0...1.7.0 +[1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0 [1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC [1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5 [1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4 From d87416e6d0c86dc98784ad38e2e76d12a667d7fe Mon Sep 17 00:00:00 2001 From: Daniel Karl Date: Wed, 8 Aug 2018 16:01:58 +0200 Subject: [PATCH 213/580] Using cwd for 2nd process-execution (auth) in HgUtils --- src/Composer/Util/Hg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 48d0b3687..8cf6241a6 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -60,7 +60,7 @@ class Hg $command = call_user_func($commandCallable, $authenticatedUrl); - if (0 === $this->process->execute($command)) { + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } From e718f34ba4f8fccf2a6f15d991fb69ea862f5dce Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 10 Aug 2018 08:43:51 +0200 Subject: [PATCH 214/580] Properly detect rate limit errors on github before outputting messages, fixes #6621 --- src/Composer/Repository/Vcs/GitHubDriver.php | 42 +--------------- src/Composer/Util/GitHub.php | 51 ++++++++++++++++++++ src/Composer/Util/RemoteFilesystem.php | 30 ++++++++++-- 3 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 5c5c08cf2..e150ccd10 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -378,12 +378,7 @@ class GitHubDriver extends VcsDriver return $this->attemptCloneFallback(); } - $rateLimited = false; - foreach ($e->getHeaders() as $header) { - if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { - $rateLimited = true; - } - } + $rateLimited = $githubUtil->isRateLimited($e->getHeaders()); if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { @@ -397,7 +392,7 @@ class GitHubDriver extends VcsDriver } if ($rateLimited) { - $rateLimit = $this->getRateLimit($e->getHeaders()); + $rateLimit = $githubUtil->getRateLimit($e->getHeaders()); $this->io->writeError(sprintf( 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], @@ -413,39 +408,6 @@ class GitHubDriver extends VcsDriver } } - /** - * Extract ratelimit from response. - * - * @param array $headers Headers from Composer\Downloader\TransportException. - * - * @return array Associative array with the keys limit and reset. - */ - protected function getRateLimit(array $headers) - { - $rateLimit = array( - 'limit' => '?', - 'reset' => '?', - ); - - foreach ($headers as $header) { - $header = trim($header); - if (false === strpos($header, 'X-RateLimit-')) { - continue; - } - list($type, $value) = explode(':', $header, 2); - switch ($type) { - case 'X-RateLimit-Limit': - $rateLimit['limit'] = (int) trim($value); - break; - case 'X-RateLimit-Reset': - $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); - break; - } - } - - return $rateLimit; - } - /** * Fetch root identifier from GitHub * diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 8415c9a5c..2f5dbe5cd 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -126,4 +126,55 @@ class GitHub return true; } + + /** + * Extract ratelimit from response. + * + * @param array $headers Headers from Composer\Downloader\TransportException. + * + * @return array Associative array with the keys limit and reset. + */ + public function getRateLimit(array $headers) + { + $rateLimit = array( + 'limit' => '?', + 'reset' => '?', + ); + + foreach ($headers as $header) { + $header = trim($header); + if (false === strpos($header, 'X-RateLimit-')) { + continue; + } + list($type, $value) = explode(':', $header, 2); + switch ($type) { + case 'X-RateLimit-Limit': + $rateLimit['limit'] = (int) trim($value); + break; + case 'X-RateLimit-Reset': + $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); + break; + } + } + + return $rateLimit; + } + + /** + * Finds whether a request failed due to rate limiting + * + * @param array $headers Headers from Composer\Downloader\TransportException. + * + * @return bool + */ + public function isRateLimited(array $headers) + { + foreach ($headers as $header) { + if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { + return true; + } + } + + return false; + } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index ee18ad62d..dc2b33089 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -327,7 +327,7 @@ class RemoteFilesystem $warning = $data['warning']; } } - $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning); + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning, $http_response_header); } } @@ -639,11 +639,35 @@ class RemoteFilesystem } } - protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null) + protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array()) { if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { - $message = "\n".'Could not fetch '.$this->fileUrl.', please create a GitHub OAuth token '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit'); $gitHubUtil = new GitHub($this->io, $this->config, null); + $message = "\n"; + + $rateLimited = $gitHubUtil->isRateLimited($headers); + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($headers); + if ($this->io->hasAuthentication($this->originUrl)) { + $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; + } else { + $message = 'Create a GitHub OAuth token to go over the API rate limit.'; + } + + $message = sprintf( + 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.', + $rateLimit['limit'], + $rateLimit['reset'] + )."\n"; + } else { + $message .= 'Could not fetch '.$this->fileUrl.', please '; + if ($this->io->hasAuthentication($this->originUrl)) { + $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; + } else { + $message .= 'create a GitHub OAuth token to access private repos'; + } + } + if (!$gitHubUtil->authorizeOAuth($this->originUrl) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) ) { From 1e6ceea0f935f0865af59e7e9a4c68862d732f81 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 10 Aug 2018 08:52:30 +0200 Subject: [PATCH 215/580] Update to latest CA bundle --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 873419108..768b822b5 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "ssl", "tls" ], - "time": "2018-03-29T19:57:20+00:00" + "time": "2018-08-08T08:57:40+00:00" }, { "name": "composer/semver", From 5a22a4f1f31a44c41a518764dc9e49b113431400 Mon Sep 17 00:00:00 2001 From: Alexander Kurilo Date: Thu, 2 Aug 2018 23:47:14 +0300 Subject: [PATCH 216/580] Make surrogate sequences in JSON work on PHP 5.3 Fixes #7510 --- src/Composer/Json/JsonFormatter.php | 7 +++++++ tests/Composer/Test/Json/JsonFormatterTest.php | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php index 680a57baf..44acaff59 100644 --- a/src/Composer/Json/JsonFormatter.php +++ b/src/Composer/Json/JsonFormatter.php @@ -69,6 +69,13 @@ class JsonFormatter $l = strlen($match[1]); if ($l % 2) { + $code = hexdec($match[2]); + // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped + // see https://github.com/composer/composer/issues/7510 + if (0xD800 <= $code && 0xDFFF >= $code) { + return $match[0]; + } + return str_repeat('\\', $l - 1) . mb_convert_encoding( pack('H*', $match[2]), 'UTF-8', diff --git a/tests/Composer/Test/Json/JsonFormatterTest.php b/tests/Composer/Test/Json/JsonFormatterTest.php index 7be8fafe2..fc6cf53ed 100644 --- a/tests/Composer/Test/Json/JsonFormatterTest.php +++ b/tests/Composer/Test/Json/JsonFormatterTest.php @@ -33,6 +33,20 @@ class JsonFormatterTest extends TestCase $this->assertEquals($expected, $this->getCharacterCodes($encodedData)); } + /** + * Surrogate pairs are intentionally skipped and not unescaped + * https://github.com/composer/composer/issues/7510 + */ + public function testUtf16SurrogatePair() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('Test requires the mbstring extension'); + } + + $escaped = '"\ud83d\ude00"'; + $this->assertEquals($escaped, JsonFormatter::format($escaped, true, true)); + } + /** * Convert string to character codes split by a plus sign * @param string $string From e1a6bd5ff1bf2b73cd7a6b2fd029641d4a505147 Mon Sep 17 00:00:00 2001 From: Alexander Kurilo Date: Tue, 7 Aug 2018 23:20:08 +0300 Subject: [PATCH 217/580] Make JSON formatter test clearer --- .../Composer/Test/Json/JsonFormatterTest.php | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/tests/Composer/Test/Json/JsonFormatterTest.php b/tests/Composer/Test/Json/JsonFormatterTest.php index fc6cf53ed..417f267e3 100644 --- a/tests/Composer/Test/Json/JsonFormatterTest.php +++ b/tests/Composer/Test/Json/JsonFormatterTest.php @@ -18,19 +18,18 @@ use PHPUnit\Framework\TestCase; class JsonFormatterTest extends TestCase { /** - * Test if \u0119 (196+153) will get correctly formatted - * See ticket #2613 + * Test if \u0119 will get correctly formatted (unescaped) + * https://github.com/composer/composer/issues/2613 */ public function testUnicodeWithPrependedSlash() { if (!extension_loaded('mbstring')) { $this->markTestSkipped('Test requires the mbstring extension'); } - - $data = '"' . chr(92) . chr(92) . chr(92) . 'u0119"'; - $encodedData = JsonFormatter::format($data, true, true); - $expected = '34+92+92+196+153+34'; - $this->assertEquals($expected, $this->getCharacterCodes($encodedData)); + $backslash = chr(92); + $data = '"' . $backslash . $backslash . $backslash . 'u0119"'; + $expected = '"' . $backslash . $backslash . 'ę"'; + $this->assertEquals($expected, JsonFormatter::format($data, true, true)); } /** @@ -46,19 +45,4 @@ class JsonFormatterTest extends TestCase $escaped = '"\ud83d\ude00"'; $this->assertEquals($escaped, JsonFormatter::format($escaped, true, true)); } - - /** - * Convert string to character codes split by a plus sign - * @param string $string - * @return string - */ - protected function getCharacterCodes($string) - { - $codes = array(); - for ($i = 0; $i < strlen($string); $i++) { - $codes[] = ord($string[$i]); - } - - return implode('+', $codes); - } } From 849f4eda56c8bfa20d9640c79e9a721996bf0c85 Mon Sep 17 00:00:00 2001 From: Daniel Karl Date: Wed, 8 Aug 2018 16:01:58 +0200 Subject: [PATCH 218/580] Using cwd for 2nd process-execution (auth) in HgUtils --- src/Composer/Util/Hg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Hg.php b/src/Composer/Util/Hg.php index 48d0b3687..8cf6241a6 100644 --- a/src/Composer/Util/Hg.php +++ b/src/Composer/Util/Hg.php @@ -60,7 +60,7 @@ class Hg $command = call_user_func($commandCallable, $authenticatedUrl); - if (0 === $this->process->execute($command)) { + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } From d65e1c0112944666ea31e16fbb66e9b809efb2da Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Wed, 15 Aug 2018 12:59:05 +0200 Subject: [PATCH 219/580] Revert composer.json changes when require process stops --- src/Composer/Command/RequireCommand.php | 67 ++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 01439207f..147e0cf74 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -32,6 +32,11 @@ use Composer\Repository\PlatformRepository; */ class RequireCommand extends InitCommand { + private $newlyCreated; + private $json; + private $file; + private $composerBackup; + protected function configure() { $this @@ -75,32 +80,39 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $file = Factory::getComposerFile(); + if (function_exists('pcntl_async_signals')) { + pcntl_async_signals(true); + pcntl_signal(SIGINT, array($this, 'revertComposerFile')); + pcntl_signal(SIGTERM, array($this, 'revertComposerFile')); + pcntl_signal(SIGHUP, array($this, 'revertComposerFile')); + } + + $this->file = Factory::getComposerFile(); $io = $this->getIO(); - $newlyCreated = !file_exists($file); - if ($newlyCreated && !file_put_contents($file, "{\n}\n")) { - $io->writeError(''.$file.' could not be created.'); + $this->newlyCreated = !file_exists($this->file); + if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) { + $io->writeError(''.$this->file.' could not be created.'); return 1; } - if (!is_readable($file)) { - $io->writeError(''.$file.' is not readable.'); + if (!is_readable($this->file)) { + $io->writeError(''.$this->file.' is not readable.'); return 1; } - if (!is_writable($file)) { - $io->writeError(''.$file.' is not writable.'); + if (!is_writable($this->file)) { + $io->writeError(''.$this->file.' is not writable.'); return 1; } - if (filesize($file) === 0) { - file_put_contents($file, "{\n}\n"); + if (filesize($this->file) === 0) { + file_put_contents($this->file, "{\n}\n"); } - $json = new JsonFile($file); - $composerBackup = file_get_contents($json->getPath()); + $this->json = new JsonFile($this->file); + $this->composerBackup = file_get_contents($this->json->getPath()); $composer = $this->getComposer(true, $input->getOption('no-plugins')); $repos = $composer->getRepositoryManager()->getRepositories(); @@ -133,16 +145,16 @@ EOT $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); - if (!$this->updateFileCleanly($json, $requirements, $requireKey, $removeKey, $sortPackages)) { - $composerDefinition = $json->read(); + if (!$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) { + $composerDefinition = $this->json->read(); foreach ($requirements as $package => $version) { $composerDefinition[$requireKey][$package] = $version; unset($composerDefinition[$removeKey][$package]); } - $json->write($composerDefinition); + $this->json->write($composerDefinition); } - $io->writeError(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); + $io->writeError(''.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; @@ -183,13 +195,7 @@ EOT $status = $install->run(); if ($status !== 0) { - if ($newlyCreated) { - $io->writeError("\n".'Installation failed, deleting '.$file.'.'); - unlink($json->getPath()); - } else { - $io->writeError("\n".'Installation failed, reverting '.$file.' to its original content.'); - file_put_contents($json->getPath(), $composerBackup); - } + $this->revertComposerFile(); } return $status; @@ -219,4 +225,19 @@ EOT { return; } + + public function revertComposerFile() + { + $io = $this->getIO(); + + if ($this->newlyCreated) { + $io->writeError("\n".'Installation failed, deleting '.$this->file.'.'); + unlink($this->json->getPath()); + } else { + $io->writeError("\n".'Installation failed, reverting '.$this->file.' to its original content.'); + file_put_contents($this->json->getPath(), $this->composerBackup); + } + + exit(1); + } } From b8e8cc2516d00d1af8c1cc824df04362c6c62916 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Aug 2018 15:18:40 +0200 Subject: [PATCH 220/580] Update xdebug-handler, fixes #7517 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 768b822b5..690aca23e 100644 --- a/composer.lock +++ b/composer.lock @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" + "reference": "e1809da56ce1bd1b547a752936817341ac244d8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e1809da56ce1bd1b547a752936817341ac244d8e", + "reference": "e1809da56ce1bd1b547a752936817341ac244d8e", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-04-11T15:42:36+00:00" + "time": "2018-08-16T10:54:23+00:00" }, { "name": "justinrainbow/json-schema", From 020d1f88c74490a91140322355e1d895737ca0b6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Aug 2018 16:08:31 +0200 Subject: [PATCH 221/580] Improve error reporting on global command, fixes #7556 --- src/Composer/Command/GlobalCommand.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index 9e87c7e17..e13b8aaf2 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Factory; +use Composer\Util\Filesystem; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\StringInput; @@ -75,8 +76,22 @@ EOT // change to global dir $config = Factory::createConfig(); - chdir($config->get('home')); - $this->getIO()->writeError('Changed current directory to '.$config->get('home').''); + $home = $config->get('home'); + + if (!is_dir($home)) { + $fs = new Filesystem(); + $fs->ensureDirectoryExists($home); + if (!is_dir($home)) { + throw new \RuntimeException('Could not create home directory'); + } + } + + try { + chdir($home); + } catch (\Exception $e) { + throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e); + } + $this->getIO()->writeError('Changed current directory to '.$home.''); // create new input without "global" command prefix $input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); From 0181f074911524965b244e5a7877f7f7dc7211a5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Aug 2018 16:48:47 +0200 Subject: [PATCH 222/580] Fix create-project not updating to latest commit when cache is present, fixes #7550 --- src/Composer/Command/CreateProjectCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index a7cc6650b..cca5f1871 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -345,10 +345,6 @@ EOT $package = $package->getAliasOf(); } - if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) { - $package->setSourceReference(substr($package->getPrettyVersion(), 4)); - } - $dm = $this->createDownloadManager($io, $config); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist) From 7d273f7cd8f1bc2947282e162fd6b0d418188d41 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Aug 2018 16:57:03 +0200 Subject: [PATCH 223/580] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6200d2123..b5efd1683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +### [1.7.2] 2018-08-16 + + * Fixed reporting of authentication/rate limiting issues for GitHub API access + * Fixed `create-project` not checking the checking the latest commit out when a cache was already present + * Fixed reporting of errors when `global` command can not switch the working directory + * Fixed PHP 5.3 JSON encoding issues with complex unicode character sequences + * Updated to latest ca-bundle and xdebug-handler projects, see related changelogs + ### [1.7.1] 2018-08-07 * Fixed issue autoloading plugins in require-dev in some conditions @@ -679,6 +687,7 @@ * Initial release +[1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1 [1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0 [1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC From 81516098e5ac4fe4fb6a948ec5c6477011fda232 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Aug 2018 16:58:19 +0200 Subject: [PATCH 224/580] Update target release for master --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 91da0ec60..3a1efaa17 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { From cfb0d33c4525d30cd5c283d0c6ac4b389e8b3cd7 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Mon, 20 Aug 2018 10:41:34 +0200 Subject: [PATCH 225/580] add removePackage() to RepositoryInterface --- src/Composer/Repository/RepositoryInterface.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 9a2aaf3b5..d0ceb905a 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -71,4 +71,11 @@ interface RepositoryInterface extends \Countable * @return array[] an array of array('name' => '...', 'description' => '...') */ public function search($query, $mode = 0); + + /** + * Removes a package from the registered packages list. + * + * @param PackageInterface $package + */ + public function removePackage(PackageInterface $package); } From c46f9f061af8a162b54b896f551ec20fe1fd9a6d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 22 Aug 2018 09:43:30 +0200 Subject: [PATCH 226/580] Update deps --- composer.lock | 103 ++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/composer.lock b/composer.lock index 690aca23e..878428e60 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5c554bae92a73c12f7797833fd44e946", + "content-hash": "e46280c4cfd37bf3ec8be36095feb20e", "packages": [ { "name": "composer/ca-bundle", @@ -437,16 +437,16 @@ }, { "name": "symfony/console", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "42a0adc7dd656ca2e360285eb6d822df9ce0b160" + "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/42a0adc7dd656ca2e360285eb6d822df9ce0b160", - "reference": "42a0adc7dd656ca2e360285eb6d822df9ce0b160", + "url": "https://api.github.com/repos/symfony/console/zipball/0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", + "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", "shasum": "" }, "require": { @@ -494,20 +494,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-07-09T12:58:09+00:00" + "time": "2018-07-26T11:13:39+00:00" }, { "name": "symfony/debug", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "a26ddce7fe4e884097d72435653bc7e703411f26" + "reference": "d985c8546da49c4727e27dae82bcf783ee2c5af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/a26ddce7fe4e884097d72435653bc7e703411f26", - "reference": "a26ddce7fe4e884097d72435653bc7e703411f26", + "url": "https://api.github.com/repos/symfony/debug/zipball/d985c8546da49c4727e27dae82bcf783ee2c5af0", + "reference": "d985c8546da49c4727e27dae82bcf783ee2c5af0", "shasum": "" }, "require": { @@ -551,20 +551,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-06-22T15:01:26+00:00" + "time": "2018-07-26T11:13:39+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "5cfc856c5b665ef5de0df796e98c54bef0fe595b" + "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/5cfc856c5b665ef5de0df796e98c54bef0fe595b", - "reference": "5cfc856c5b665ef5de0df796e98c54bef0fe595b", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d6a4deccdfa2e4e9f113138b93457b2d0886c15", + "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15", "shasum": "" }, "require": { @@ -601,20 +601,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-07-09T13:24:25+00:00" + "time": "2018-07-26T11:13:39+00:00" }, { "name": "symfony/finder", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "995cd7c28a0778cece02e2133b4d813dc509dfc3" + "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/995cd7c28a0778cece02e2133b4d813dc509dfc3", - "reference": "995cd7c28a0778cece02e2133b4d813dc509dfc3", + "url": "https://api.github.com/repos/symfony/finder/zipball/f0de0b51913eb2caab7dfed6413b87e14fca780e", + "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e", "shasum": "" }, "require": { @@ -650,29 +650,32 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-06-19T11:07:17+00:00" + "time": "2018-07-26T11:13:39+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -705,20 +708,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", "shasum": "" }, "require": { @@ -730,7 +733,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -764,20 +767,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/process", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "542d88b350c42750fdc14e73860ee96dd423e95d" + "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/542d88b350c42750fdc14e73860ee96dd423e95d", - "reference": "542d88b350c42750fdc14e73860ee96dd423e95d", + "url": "https://api.github.com/repos/symfony/process/zipball/cc83afdb5ac99147806b3bb65a3ff1227664f596", + "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596", "shasum": "" }, "require": { @@ -813,7 +816,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-27T07:40:52+00:00" + "time": "2018-07-26T11:13:39+00:00" } ], "packages-dev": [ @@ -922,16 +925,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -943,12 +946,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -981,7 +984,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1733,16 +1736,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.43", + "version": "v2.8.44", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff" + "reference": "fbf876678e29dc634430dcf0096e216eb0004467" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff", - "reference": "51356b7a2ff7c9fd06b2f1681cc463bb62b5c1ff", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fbf876678e29dc634430dcf0096e216eb0004467", + "reference": "fbf876678e29dc634430dcf0096e216eb0004467", "shasum": "" }, "require": { @@ -1779,7 +1782,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-01T22:52:40+00:00" + "time": "2018-07-26T09:03:18+00:00" } ], "aliases": [], From 3d01ef28faac1363e10fd797aa81f2a8a6cdbd95 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 22 Aug 2018 09:45:54 +0200 Subject: [PATCH 227/580] Revert "Do not dump source and dist for metapackages" The source/dist reference is needed to operate composer outdated and other functionality Fixes #7546 This reverts commit 42739e7959d81f38e0e41ab7ae96e5db88c0411d. --- src/Composer/Package/Dumper/ArrayDumper.php | 4 +- .../Test/Package/Dumper/ArrayDumperTest.php | 51 ------------------- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index ab8b4d45b..6593143d5 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -45,7 +45,7 @@ class ArrayDumper $data['target-dir'] = $package->getTargetDir(); } - if ($package->getSourceType() && $package->getType() !== 'metapackage') { + if ($package->getSourceType()) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); $data['source']['reference'] = $package->getSourceReference(); @@ -54,7 +54,7 @@ class ArrayDumper } } - if ($package->getDistType() && $package->getType() !== 'metapackage') { + if ($package->getDistType()) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); $data['dist']['reference'] = $package->getDistReference(); diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index 9b12e49a9..bd1c29c13 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -106,57 +106,6 @@ class ArrayDumperTest extends TestCase $this->assertSame($expectedValue ?: $value, $config[$key]); } - public function testMetapackageShouldNotHaveSourceEntries() - { - $this - ->packageExpects('getPrettyName', 'foo') - ->packageExpects('getPrettyVersion', '1.0') - ->packageExpects('getVersion', '1.0.0.0') - ->packageExpects('getType', 'metapackage') - ->packageExpects('getSourceType', 'composer') - ->packageExpects('getSourceUrl', 'https://packagist.org') - ->packageExpects('getSourceReference', 'packagist') - ; - - $config = $this->dumper->dump($this->package); - - $this->assertEquals( - array( - 'name' => 'foo', - 'version' => '1.0', - 'version_normalized' => '1.0.0.0', - 'type' => 'metapackage', - ), - $config - ); - } - - public function testMetapackageShouldNotHaveDistEntries() - { - $this - ->packageExpects('getPrettyName', 'foo') - ->packageExpects('getPrettyVersion', '1.0') - ->packageExpects('getVersion', '1.0.0.0') - ->packageExpects('getType', 'metapackage') - ->packageExpects('getDistType', 'composer') - ->packageExpects('getDistUrl', 'https://packagist.org') - ->packageExpects('getDistReference', 'packagist') - ->packageExpects('getDistSha1Checksum', 'packagist') - ; - - $config = $this->dumper->dump($this->package); - - $this->assertEquals( - array( - 'name' => 'foo', - 'version' => '1.0', - 'version_normalized' => '1.0.0.0', - 'type' => 'metapackage', - ), - $config - ); - } - public function getKeys() { return array( From 4014c914ab488fd035f0f4b9c8f3b24bdcf55b24 Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Tue, 21 Aug 2018 15:51:20 +0400 Subject: [PATCH 228/580] remove Github 404 retries --- src/Composer/Repository/Vcs/GitHubDriver.php | 27 ++++---------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index e150ccd10..fc256f82c 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -183,30 +183,13 @@ class GitHubDriver extends VcsDriver return $this->gitDriver->getFileContent($file, $identifier); } - $notFoundRetries = 2; - while ($notFoundRetries) { - try { - $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); - $resource = JsonFile::parseJson($this->getContents($resource)); - if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { - throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); - } - - return $content; - } catch (TransportException $e) { - if (404 !== $e->getCode()) { - throw $e; - } - - // TODO should be removed when possible - // retry fetching if github returns a 404 since they happen randomly - $notFoundRetries--; - - return null; - } + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = JsonFile::parseJson($this->getContents($resource)); + if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } - return null; + return $content; } /** From bf125295df9da84c44989e33f9f84b4ed4f8ea56 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 25 Aug 2018 17:45:08 +0200 Subject: [PATCH 229/580] Fix escaping of URLs in Perforce and Subversion drivers --- src/Composer/Repository/Vcs/FossilDriver.php | 2 +- src/Composer/Repository/Vcs/HgDriver.php | 4 ++-- src/Composer/Repository/Vcs/SvnDriver.php | 4 ++-- src/Composer/Util/Bitbucket.php | 2 +- src/Composer/Util/Filesystem.php | 2 +- src/Composer/Util/GitHub.php | 2 +- src/Composer/Util/GitLab.php | 2 +- src/Composer/Util/Perforce.php | 18 +++++++------- src/Composer/Util/Svn.php | 2 +- tests/Composer/Test/Util/PerforceTest.php | 25 ++++++++++---------- 10 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index 0b689e7bf..cc872474e 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -226,7 +226,7 @@ class FossilDriver extends VcsDriver return false; } - $process = new ProcessExecutor(); + $process = new ProcessExecutor($io); // check whether there is a fossil repo in that path if ($process->execute('fossil info', $output, $url) === 0) { return true; diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 2db995e2e..45f13d5fe 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -211,7 +211,7 @@ class HgDriver extends VcsDriver return false; } - $process = new ProcessExecutor(); + $process = new ProcessExecutor($io); // check whether there is a hg repo in that path if ($process->execute('hg summary', $output, $url) === 0) { return true; @@ -222,7 +222,7 @@ class HgDriver extends VcsDriver return false; } - $processExecutor = new ProcessExecutor(); + $processExecutor = new ProcessExecutor($io); $exit = $processExecutor->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored); return $exit === 0; diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 96434517a..d3e7ee175 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -307,10 +307,10 @@ class SvnDriver extends VcsDriver return false; } - $processExecutor = new ProcessExecutor(); + $processExecutor = new ProcessExecutor($io); $exit = $processExecutor->execute( - "svn info --non-interactive {$url}", + "svn info --non-interactive ".ProcessExecutor::escape('{'.$url.'}'), $ignoredOutput ); diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index 1c578576f..1fc286ac4 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -44,7 +44,7 @@ class Bitbucket { $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor; + $this->process = $process ?: new ProcessExecutor($io); $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); $this->time = $time; } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 04df84ecd..a3af3b825 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -527,7 +527,7 @@ class Filesystem protected function getProcess() { - return new ProcessExecutor; + return $this->processExecutor; } /** diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 2f5dbe5cd..1eca1a9bb 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -39,7 +39,7 @@ class GitHub { $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor; + $this->process = $process ?: new ProcessExecutor($io); $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index fb809e2d4..475c5e7ee 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -40,7 +40,7 @@ class GitLab { $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor(); + $this->process = $process ?: new ProcessExecutor($io); $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index fbd73b2b5..cb5f2ee8c 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -58,7 +58,7 @@ class Perforce { $output = null; - return 0 === $processExecutor->execute('p4 -p ' . $url . ' info -s', $output); + return 0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $output); } public function initialize($repoConfig) @@ -105,7 +105,7 @@ class Perforce public function cleanupClientSpec() { $client = $this->getClient(); - $task = 'client -d ' . $client; + $task = 'client -d ' . ProcessExecutor::escape($client); $useP4Client = false; $command = $this->generateP4Command($task, $useP4Client); $this->executeCommand($command); @@ -383,7 +383,7 @@ class Perforce if ($this->windowsFlag) { $this->windowsLogin($password); } else { - $command = 'echo ' . $password . ' | ' . $this->generateP4Command(' login -a', false); + $command = 'echo ' . ProcessExecutor::escape($password) . ' | ' . $this->generateP4Command(' login -a', false); $exitCode = $this->executeCommand($command); $result = trim($this->commandResult); if ($exitCode) { @@ -408,7 +408,7 @@ class Perforce { $path = $this->getFilePath($file, $identifier); - $command = $this->generateP4Command(' print ' . $path); + $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path)); $this->executeCommand($command); $result = $this->commandResult; @@ -429,7 +429,7 @@ class Perforce } $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); - $command = $this->generateP4Command(' files ' . $path, false); + $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false); $this->executeCommand($command); $result = $this->commandResult; $index2 = strpos($result, 'no such file(s).'); @@ -452,7 +452,7 @@ class Perforce if (!$this->isStream()) { $possibleBranches[$this->p4Branch] = $this->getStream(); } else { - $command = $this->generateP4Command('streams //' . $this->p4Depot . '/...'); + $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...')); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); @@ -464,7 +464,7 @@ class Perforce } } } - $command = $this->generateP4Command('changes '. $this->getStream() . '/...', false); + $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); @@ -527,7 +527,7 @@ class Perforce return null; } $label = substr($reference, $index); - $command = $this->generateP4Command(' changes -m1 ' . $label); + $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label)); $this->executeCommand($command); $changes = $this->commandResult; if (strpos($changes, 'Change') !== 0) { @@ -555,7 +555,7 @@ class Perforce } $index = strpos($fromReference, '@'); $main = substr($fromReference, 0, $index) . '/...'; - $command = $this->generateP4Command('filelog ' . $main . '@' . $fromChangeList. ',' . $toChangeList); + $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList)); $this->executeCommand($command); return $this->commandResult; diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index df31ccbbf..58114ac93 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -79,7 +79,7 @@ class Svn $this->url = $url; $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor; + $this->process = $process ?: new ProcessExecutor($io); } public static function cleanEnv() diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index 7d27b013e..777dbb3aa 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Util; use Composer\Util\Perforce; use PHPUnit\Framework\TestCase; +use Composer\Util\ProcessExecutor; /** * @author Matt Whittom @@ -344,7 +345,7 @@ class PerforceTest extends TestCase { $this->setPerforceToStream(); - $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port streams //depot/...'; + $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port streams '.ProcessExecutor::escape('//depot/...'); $this->processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -357,7 +358,7 @@ class PerforceTest extends TestCase } ) ); - $expectedCommand2 = 'p4 -u user -p port changes //depot/branch/...'; + $expectedCommand2 = 'p4 -u user -p port changes '.ProcessExecutor::escape('//depot/branch/...'); $expectedCallback = function ($command, &$output) { $output = 'Change 1234 on 2014/03/19 by Clark.Stuth@Clark.Stuth_test_client \'test changelist\''; @@ -374,7 +375,7 @@ class PerforceTest extends TestCase public function testGetBranchesWithoutStream() { - $expectedCommand = 'p4 -u user -p port changes //depot/...'; + $expectedCommand = 'p4 -u user -p port changes '.ProcessExecutor::escape('//depot/...'); $expectedCallback = function ($command, &$output) { $output = 'Change 5678 on 2014/03/19 by Clark.Stuth@Clark.Stuth_test_client \'test changelist\''; @@ -458,7 +459,7 @@ class PerforceTest extends TestCase public function testGetComposerInformationWithoutLabelWithoutStream() { - $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print //depot/composer.json'; + $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print '.ProcessExecutor::escape('//depot/composer.json'); $this->processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -484,7 +485,7 @@ class PerforceTest extends TestCase public function testGetComposerInformationWithLabelWithoutStream() { - $expectedCommand = 'p4 -u user -p port files //depot/composer.json@0.0.1'; + $expectedCommand = 'p4 -u user -p port files '.ProcessExecutor::escape('//depot/composer.json@0.0.1'); $this->processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -498,7 +499,7 @@ class PerforceTest extends TestCase ) ); - $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print //depot/composer.json@10001'; + $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print '.ProcessExecutor::escape('//depot/composer.json@10001'); $this->processExecutor->expects($this->at(1)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -527,7 +528,7 @@ class PerforceTest extends TestCase { $this->setPerforceToStream(); - $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print //depot/branch/composer.json'; + $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print '.ProcessExecutor::escape('//depot/branch/composer.json'); $this->processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -555,7 +556,7 @@ class PerforceTest extends TestCase public function testGetComposerInformationWithLabelWithStream() { $this->setPerforceToStream(); - $expectedCommand = 'p4 -u user -p port files //depot/branch/composer.json@0.0.1'; + $expectedCommand = 'p4 -u user -p port files '.ProcessExecutor::escape('//depot/branch/composer.json@0.0.1'); $this->processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -569,7 +570,7 @@ class PerforceTest extends TestCase ) ); - $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print //depot/branch/composer.json@10001'; + $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print '.ProcessExecutor::escape('//depot/branch/composer.json@10001'); $this->processExecutor->expects($this->at(1)) ->method('execute') ->with($this->equalTo($expectedCommand)) @@ -621,7 +622,7 @@ class PerforceTest extends TestCase { $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $expectedCommand = 'p4 -p perforce.does.exist:port info -s'; + $expectedCommand = 'p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s'; $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand), $this->equalTo(null)) @@ -642,7 +643,7 @@ class PerforceTest extends TestCase { $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $expectedCommand = 'p4 -p perforce.does.exist:port info -s'; + $expectedCommand = 'p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s'; $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedCommand), $this->equalTo(null)) @@ -712,7 +713,7 @@ class PerforceTest extends TestCase $this->perforce->setFilesystem($fs); $testClient = $this->perforce->getClient(); - $expectedCommand = 'p4 -u ' . self::TEST_P4USER . ' -p ' . self::TEST_PORT . ' client -d ' . $testClient; + $expectedCommand = 'p4 -u ' . self::TEST_P4USER . ' -p ' . self::TEST_PORT . ' client -d ' . ProcessExecutor::escape($testClient); $this->processExecutor->expects($this->once())->method('execute')->with($this->equalTo($expectedCommand)); $fs->expects($this->once())->method('remove')->with($this->perforce->getP4ClientSpec()); From 3b587eed21fce07b7c56781542628b0e22962b0d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 25 Aug 2018 17:47:11 +0200 Subject: [PATCH 230/580] Update xdebug-handler to latest, fixes #7436 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 690aca23e..be57d6bc6 100644 --- a/composer.lock +++ b/composer.lock @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "e1809da56ce1bd1b547a752936817341ac244d8e" + "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e1809da56ce1bd1b547a752936817341ac244d8e", - "reference": "e1809da56ce1bd1b547a752936817341ac244d8e", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e37cbd80da64afe314c72de8d2d2fec0e40d9373", + "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-08-16T10:54:23+00:00" + "time": "2018-08-23T12:00:19+00:00" }, { "name": "justinrainbow/json-schema", From 33341130a93add87c8c264ba3608e982600338ca Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Mon, 27 Aug 2018 09:13:52 +0200 Subject: [PATCH 231/580] Fix typo in variable name in GitHubDriver --- src/Composer/Repository/Vcs/GitHubDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index e150ccd10..53ace8de7 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -378,7 +378,7 @@ class GitHubDriver extends VcsDriver return $this->attemptCloneFallback(); } - $rateLimited = $githubUtil->isRateLimited($e->getHeaders()); + $rateLimited = $gitHubUtil->isRateLimited($e->getHeaders()); if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { @@ -392,7 +392,7 @@ class GitHubDriver extends VcsDriver } if ($rateLimited) { - $rateLimit = $githubUtil->getRateLimit($e->getHeaders()); + $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders()); $this->io->writeError(sprintf( 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], From e5b948c683e98ed2618431456ad447d0d7c8d209 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 9 Jul 2018 22:07:12 +0200 Subject: [PATCH 232/580] Refactor the handling of conflict rules in the solver Conflict rules are not added in the solver based on the packages loaded in the solver by require rules, instead of loading remote metadata for them. This has 2 benefits: - it reduces the number of conflict rules in the solver in case of conflict rules targetting packages which are not required - it fixes the behavior of replaces, which is meant to conflict with all versions of the replaced package, without introducing a performance regression (this behavior was changed when optimizing composer in the past). --- src/Composer/DependencyResolver/Pool.php | 4 +- .../DependencyResolver/RuleSetGenerator.php | 86 +++++++++++++------ 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 3dc6d90be..085aaa7bf 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -317,12 +317,12 @@ class Pool implements \Countable * Checks if the package matches the given constraint directly or through * provided or replaced packages * - * @param array|PackageInterface $candidate + * @param PackageInterface $candidate * @param string $name Name of the package to be matched * @param ConstraintInterface $constraint The constraint to verify * @return int One of the MATCH* constants of this class or 0 if there is no match */ - private function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) + public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index c534be958..60617ba43 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -28,6 +28,9 @@ class RuleSetGenerator protected $installedMap; protected $whitelistedMap; protected $addedMap; + protected $conflictAddedMap; + protected $addedPackages; + protected $addedPackagesByNames; public function __construct(PolicyInterface $policy, Pool $pool) { @@ -185,6 +188,7 @@ class RuleSetGenerator $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { + /** @var PackageInterface $package */ $package = $workQueue->dequeue(); if (isset($this->addedMap[$package->id])) { continue; @@ -192,6 +196,11 @@ class RuleSetGenerator $this->addedMap[$package->id] = true; + $this->addedPackages[] = $package; + foreach ($package->getNames() as $name) { + $this->addedPackagesByNames[$name][] = $package; + } + foreach ($package->getRequires() as $link) { if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { continue; @@ -206,32 +215,6 @@ class RuleSetGenerator } } - foreach ($package->getConflicts() as $link) { - $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); - - foreach ($possibleConflicts as $conflict) { - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link)); - } - } - - // check obsoletes and implicit obsoletes of a package - $isInstalled = isset($this->installedMap[$package->id]); - - foreach ($package->getReplaces() as $link) { - $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); - - foreach ($obsoleteProviders as $provider) { - if ($provider === $package) { - continue; - } - - if (!$this->obsoleteImpossibleForAlias($package, $provider)) { - $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; - $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link)); - } - } - } - $packageName = $package->getName(); $obsoleteProviders = $this->pool->whatProvides($packageName, null); @@ -250,6 +233,49 @@ class RuleSetGenerator } } + protected function addConflictRules() + { + /** @var PackageInterface $package */ + foreach ($this->addedPackages as $package) { + foreach ($package->getConflicts() as $link) { + if (!isset($this->addedPackagesByNames[$link->getTarget()])) { + continue; + } + + /** @var PackageInterface $possibleConflict */ + foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) { + $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true); + + if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) { + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link)); + } + + } + } + + // check obsoletes and implicit obsoletes of a package + $isInstalled = isset($this->installedMap[$package->id]); + + foreach ($package->getReplaces() as $link) { + if (!isset($this->addedPackagesByNames[$link->getTarget()])) { + continue; + } + + /** @var PackageInterface $possibleConflict */ + foreach ($this->addedPackagesByNames[$link->getTarget()] as $provider) { + if ($provider === $package) { + continue; + } + + if (!$this->obsoleteImpossibleForAlias($package, $provider)) { + $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES; + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link)); + } + } + } + } + } + protected function obsoleteImpossibleForAlias($package, $provider) { $packageIsAlias = $package instanceof AliasPackage; @@ -327,12 +353,20 @@ class RuleSetGenerator $this->pool->setWhitelist($this->whitelistedMap); $this->addedMap = array(); + $this->conflictAddedMap = array(); + $this->addedPackages = array(); + $this->addedPackagesByNames = array(); foreach ($this->installedMap as $package) { $this->addRulesForPackage($package, $ignorePlatformReqs); } $this->addRulesForJobs($ignorePlatformReqs); + $this->addConflictRules(); + + // Remove references to packages + $this->addedPackages = $this->addedPackagesByNames = null; + return $this->rules; } } From 8c3898aa576643fb4238141ad94c2f5a6e1e74ad Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 9 Jul 2018 22:51:23 +0200 Subject: [PATCH 233/580] Update tests for replace conflicts This reverts the test changes done in b4698568d2 to the original tests added in 1425bb7fc3. --- ...-installed-when-installing-from-lock.test} | 12 +++--- ...aced-packages-should-not-be-installed.test | 24 +++++++++++ ...kages-wrong-version-install-from-lock.test | 41 ------------------- 3 files changed, 29 insertions(+), 48 deletions(-) rename tests/Composer/Test/Fixtures/installer/{replaced-packages-wrong-version-install.test => replaced-packages-should-not-be-installed-when-installing-from-lock.test} (85%) create mode 100644 tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed.test delete mode 100644 tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install-from-lock.test diff --git a/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test similarity index 85% rename from tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test rename to tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test index 8971f8069..947d96b28 100644 --- a/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test +++ b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed-when-installing-from-lock.test @@ -1,5 +1,5 @@ --TEST-- -Requiring a replaced package in a version, that is not provided by the replacing package, should install correctly (although that is not a very smart idea) +Requiring a replaced package in a version, that is not provided by the replacing package, should result in a conflict, when installing from lock --COMPOSER-- { "repositories": [ @@ -17,9 +17,7 @@ Requiring a replaced package in a version, that is not provided by the replacing "foo/replaced": "2.0.0" } } ---RUN-- -install ---EXPECT-LOCK-- +--LOCK-- { "packages": [ { "name": "foo/original", "version": "1.0.0", "replace": {"foo/replaced": "1.0.0"}, "type": "library" }, @@ -34,8 +32,8 @@ install "platform": [], "platform-dev": [] } +--RUN-- +install --EXPECT-EXIT-CODE-- -0 +2 --EXPECT-- -Installing foo/original (1.0.0) -Installing foo/replaced (2.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed.test b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed.test new file mode 100644 index 000000000..0d1ea7701 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/replaced-packages-should-not-be-installed.test @@ -0,0 +1,24 @@ +--TEST-- +Requiring a replaced package in a version, that is not provided by the replacing package, should result in a conflict +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "foo/original", "version": "1.0.0", "replace": {"foo/replaced": "1.0.0"} }, + { "name": "foo/replaced", "version": "1.0.0" }, + { "name": "foo/replaced", "version": "2.0.0" } + ] + } + ], + "require": { + "foo/original": "1.0.0", + "foo/replaced": "2.0.0" + } +} +--RUN-- +install +--EXPECT-EXIT-CODE-- +2 +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install-from-lock.test b/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install-from-lock.test deleted file mode 100644 index 2721772ae..000000000 --- a/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install-from-lock.test +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Requiring a replaced package in a version, that is not provided by the replacing package, should install correctly (although that is not a very smart idea) also when installing from lock ---COMPOSER-- -{ - "repositories": [ - { - "type": "package", - "package": [ - { "name": "foo/original", "version": "1.0.0", "replace": {"foo/replaced": "1.0.0"} }, - { "name": "foo/replaced", "version": "1.0.0" }, - { "name": "foo/replaced", "version": "2.0.0" } - ] - } - ], - "require": { - "foo/original": "1.0.0", - "foo/replaced": "2.0.0" - } -} ---LOCK-- -{ - "packages": [ - { "name": "foo/original", "version": "1.0.0", "replace": {"foo/replaced": "1.0.0"}, "type": "library" }, - { "name": "foo/replaced", "version": "2.0.0", "type": "library" } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} ---RUN-- -install ---EXPECT-EXIT-CODE-- -0 ---EXPECT-- -Installing foo/original (1.0.0) -Installing foo/replaced (2.0.0) From 766ceccd006288101eaada496a8abedf26a9b24c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 27 Aug 2018 14:46:05 +0200 Subject: [PATCH 234/580] Print number of classes contained within the generated classmap to give the developer a better feeling about number of dependent classes --- src/Composer/Autoload/AutoloadGenerator.php | 2 ++ src/Composer/Command/DumpAutoloadCommand.php | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index d54f3d6f6..0db4015c3 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -312,6 +312,8 @@ EOF; 'optimize' => (bool) $scanPsr0Packages, )); } + + return count($classMap); } private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array()) diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index fe80e3760..c255e149f 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -61,11 +61,11 @@ EOT $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader'); if ($authoritative) { - $this->getIO()->writeError('Generating optimized autoload files (authoritative)'); + $this->getIO()->writeError('Generating optimized autoload files (authoritative)', false); } elseif ($optimize) { - $this->getIO()->writeError('Generating optimized autoload files'); + $this->getIO()->writeError('Generating optimized autoload files', false); } else { - $this->getIO()->writeError('Generating autoload files'); + $this->getIO()->writeError('Generating autoload files', false); } $generator = $composer->getAutoloadGenerator(); @@ -73,6 +73,14 @@ EOT $generator->setClassMapAuthoritative($authoritative); $generator->setApcu($apcu); $generator->setRunScripts(!$input->getOption('no-scripts')); - $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + + if ($authoritative) { + $this->getIO()->overwriteError('Generating optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); + } elseif ($optimize) { + $this->getIO()->overwriteError('Generating optimized autoload files containing '. $numberOfClasses .' classes'); + } else { + $this->getIO()->overwriteError('Generating autoload files containing '. $numberOfClasses .' classes'); + } } } From 4d49fabbc434bb899d34109d074a4add05bd4e64 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 27 Aug 2018 17:36:11 +0200 Subject: [PATCH 235/580] Generating -> Generated --- src/Composer/Command/DumpAutoloadCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index c255e149f..55a2c5f16 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -76,11 +76,11 @@ EOT $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); if ($authoritative) { - $this->getIO()->overwriteError('Generating optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); + $this->getIO()->overwriteError('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); } elseif ($optimize) { - $this->getIO()->overwriteError('Generating optimized autoload files containing '. $numberOfClasses .' classes'); + $this->getIO()->overwriteError('Generated optimized autoload files containing '. $numberOfClasses .' classes'); } else { - $this->getIO()->overwriteError('Generating autoload files containing '. $numberOfClasses .' classes'); + $this->getIO()->overwriteError('Generated autoload files containing '. $numberOfClasses .' classes'); } } } From 7dd2a0d6f96f308b26ca0e022fecdc92cfa53150 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Fri, 31 Aug 2018 14:27:28 +0100 Subject: [PATCH 236/580] Improve Appveyor caching and update PHP --- appveyor.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 348b0778a..e7c20c9e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,23 +2,27 @@ build: false clone_depth: 5 environment: - PHP_CHOCO_VERSION: 7.2.0 - PHP_CACHE_DIR: C:\tools\php + # This sets the PHP version (from Chocolatey) + PHPCI_CHOCO_VERSION: 7.2.9 + PHPCI_CACHE: C:\tools\phpci + PHPCI_PHP: C:\tools\phpci\php + PHPCI_COMPOSER: C:\tools\phpci\composer cache: - - '%PHP_CACHE_DIR% -> appveyor.yml' + - '%PHPCI_CACHE% -> appveyor.yml' init: - - SET PATH=%PHP_CACHE_DIR%;%PATH% - - SET COMPOSER_CACHE_DIR=%PHP_CACHE_DIR% + - SET PATH=%PHPCI_PHP%;%PHPCI_COMPOSER%;%PATH% + - SET COMPOSER_HOME=%PHPCI_COMPOSER%\home + - SET COMPOSER_CACHE_DIR=%PHPCI_COMPOSER%\cache - SET COMPOSER_NO_INTERACTION=1 - SET PHP=0 - SET ANSICON=121x90 (121x90) install: - - IF EXIST %PHP_CACHE_DIR% (SET PHP=1) - - IF %PHP%==0 cinst php -y --version %PHP_CHOCO_VERSION% --params "/InstallDir:%PHP_CACHE_DIR%" - - IF %PHP%==0 cinst composer -y --ia "/DEV=%PHP_CACHE_DIR%" + - IF EXIST %PHPCI_CACHE% (SET PHP=1) + - IF %PHP%==0 cinst php -i -y --version %PHPCI_CHOCO_VERSION% --params "/InstallDir:%PHPCI_PHP%" + - IF %PHP%==0 cinst composer -i -y --ia "/DEV=%PHPCI_COMPOSER%" - php -v - IF %PHP%==0 (composer --version) ELSE (composer self-update) - cd %APPVEYOR_BUILD_FOLDER% From 2023690c7df88fd68d43f0c1ce9408e48235a388 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 1 Sep 2018 11:26:03 +0100 Subject: [PATCH 237/580] Update xdebug-handler, fixes #7596 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index be57d6bc6..70455dc7b 100644 --- a/composer.lock +++ b/composer.lock @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.2.1", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373" + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e37cbd80da64afe314c72de8d2d2fec0e40d9373", - "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-08-23T12:00:19+00:00" + "time": "2018-08-31T19:20:00+00:00" }, { "name": "justinrainbow/json-schema", From c279c7ca96e923e54b4a0fac765e9c4682916ac1 Mon Sep 17 00:00:00 2001 From: SeRRg Date: Sun, 2 Sep 2018 11:37:19 +0500 Subject: [PATCH 238/580] Add --no-dev option to check-platform-reqs command --- .../Command/CheckPlatformReqsCommand.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 5a68661cc..89bceecab 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -17,6 +17,7 @@ use Composer\Package\PackageInterface; use Composer\Semver\Constraint\Constraint; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\PlatformRepository; @@ -26,6 +27,9 @@ class CheckPlatformReqsCommand extends BaseCommand { $this->setName('check-platform-reqs') ->setDescription('Check that platform requirements are satisfied.') + ->setDefinition(array( + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'), + )) ->setHelp( <<getComposer(); - $repos = $composer->getRepositoryManager()->getLocalRepository(); - - $allPackages = array_merge(array($composer->getPackage()), $repos->getPackages()); - $requires = $composer->getPackage()->getDevRequires(); + $requires = $composer->getPackage()->getRequires(); + if (!$input->getOption('no-dev')) { + $requires += $composer->getPackage()->getDevRequires(); + } foreach ($requires as $require => $link) { $requires[$require] = array($link); } - /** - * @var PackageInterface $package - */ - foreach ($allPackages as $package) { + $locker = $composer->getLocker() + ->getLockedRepository(!$input->getOption('no-dev')); + foreach ($locker->getPackages() as $package) { foreach ($package->getRequires() as $require => $link) { $requires[$require][] = $link; } } + ksort($requires); $platformRepo = new PlatformRepository(array(), array()); @@ -74,7 +78,7 @@ EOT $exitCode = 0; /** - * @var Link $require + * @var Link[] $links */ foreach ($requires as $require => $links) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) { From 1191bbc5f43d820cdb7306ad4e651f1a900d6f5f Mon Sep 17 00:00:00 2001 From: Nguyen Xuan Quynh Date: Tue, 4 Sep 2018 14:43:21 +0700 Subject: [PATCH 239/580] Unify Composer concept --- src/Composer/Command/AboutCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/AboutCommand.php b/src/Composer/Command/AboutCommand.php index 8fbad05a8..d1472ba17 100644 --- a/src/Composer/Command/AboutCommand.php +++ b/src/Composer/Command/AboutCommand.php @@ -37,7 +37,7 @@ EOT { $this->getIO()->write( <<Composer - Package Management for PHP +Composer - Dependency Manager for PHP Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information. EOT From 95840a0ab988b72ebca6e095c6ac703dbcfa71c0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 5 Sep 2018 00:00:25 +0200 Subject: [PATCH 240/580] Remove useless curly braces around svn args --- src/Composer/Repository/Vcs/SvnDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index d3e7ee175..a8f0c4ad4 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -310,7 +310,7 @@ class SvnDriver extends VcsDriver $processExecutor = new ProcessExecutor($io); $exit = $processExecutor->execute( - "svn info --non-interactive ".ProcessExecutor::escape('{'.$url.'}'), + "svn info --non-interactive ".ProcessExecutor::escape($url), $ignoredOutput ); From a25d6f6c355fe6de20bc3a7162f0025ab704bc4d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Sep 2018 13:58:13 +0200 Subject: [PATCH 241/580] Use local repo for platform checks when possible to avoid surprises, refs #7605 --- src/Composer/Command/CheckPlatformReqsCommand.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 89bceecab..de7f4b4d3 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -45,16 +45,17 @@ EOT $composer = $this->getComposer(); $requires = $composer->getPackage()->getRequires(); - if (!$input->getOption('no-dev')) { + if ($input->getOption('no-dev')) { + $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); + } else { + $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); $requires += $composer->getPackage()->getDevRequires(); } foreach ($requires as $require => $link) { $requires[$require] = array($link); } - $locker = $composer->getLocker() - ->getLockedRepository(!$input->getOption('no-dev')); - foreach ($locker->getPackages() as $package) { + foreach ($dependencies as $package) { foreach ($package->getRequires() as $require => $link) { $requires[$require][] = $link; } From 9c4df4d4822a20d80077f18ed4886f73155fd900 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 10 Sep 2018 14:06:28 +0200 Subject: [PATCH 242/580] Update deps --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index b5e106b48..320e1d0c0 100644 --- a/composer.lock +++ b/composer.lock @@ -437,7 +437,7 @@ }, { "name": "symfony/console", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -498,16 +498,16 @@ }, { "name": "symfony/debug", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "d985c8546da49c4727e27dae82bcf783ee2c5af0" + "reference": "cbb8a5f212148964efbc414838c527229f9951b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/d985c8546da49c4727e27dae82bcf783ee2c5af0", - "reference": "d985c8546da49c4727e27dae82bcf783ee2c5af0", + "url": "https://api.github.com/repos/symfony/debug/zipball/cbb8a5f212148964efbc414838c527229f9951b7", + "reference": "cbb8a5f212148964efbc414838c527229f9951b7", "shasum": "" }, "require": { @@ -551,20 +551,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-08-03T09:45:57+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15" + "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d6a4deccdfa2e4e9f113138b93457b2d0886c15", - "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0b252f4e25b7da17abb5a98eb60755b71d082c9c", + "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c", "shasum": "" }, "require": { @@ -601,11 +601,11 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-08-07T09:12:42+00:00" }, { "name": "symfony/finder", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -771,16 +771,16 @@ }, { "name": "symfony/process", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596" + "reference": "4be278e19064c3492095de50c9e375caae569ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/cc83afdb5ac99147806b3bb65a3ff1227664f596", - "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596", + "url": "https://api.github.com/repos/symfony/process/zipball/4be278e19064c3492095de50c9e375caae569ae1", + "reference": "4be278e19064c3492095de50c9e375caae569ae1", "shasum": "" }, "require": { @@ -816,7 +816,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-08-03T09:45:57+00:00" } ], "packages-dev": [ @@ -1736,7 +1736,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.44", + "version": "v2.8.45", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", From a17f051e2906de312a30165f5295e1dc334e4d7c Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Fri, 7 Sep 2018 22:29:49 -0300 Subject: [PATCH 243/580] Remove useless parentheses --- tests/Composer/Test/Downloader/FileDownloaderTest.php | 2 +- tests/Composer/Test/Downloader/GitDownloaderTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index d186e982e..d4f6b7ad7 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -231,7 +231,7 @@ class FileDownloaderTest extends TestCase ->will($this->returnValue(array($distUrl))); $ioMock = $this->getMock('Composer\IO\IOInterface'); - $ioMock->expects(($this->at(0))) + $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('Downgrading')); diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index f487a5e28..ff1c5c201 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -638,7 +638,7 @@ composer https://github.com/old/url (push) ->will($this->returnValue(0)); $ioMock = $this->getMock('Composer\IO\IOInterface'); - $ioMock->expects(($this->at(0))) + $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('Downgrading')); @@ -677,7 +677,7 @@ composer https://github.com/old/url (push) ->will($this->returnValue(0)); $ioMock = $this->getMock('Composer\IO\IOInterface'); - $ioMock->expects(($this->at(0))) + $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('updating')); From 71c8735e115909f3e049060a359afbf9535e0008 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Fri, 7 Sep 2018 22:35:35 -0300 Subject: [PATCH 244/580] Use combined assignment operators --- src/Composer/Command/InitCommand.php | 2 +- src/Composer/DependencyResolver/Rule.php | 2 +- src/Composer/Util/Perforce.php | 6 +++--- tests/Composer/Test/Downloader/ArchiveDownloaderTest.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index be56b23fb..760826d22 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -204,7 +204,7 @@ EOT $name = get_current_user() . '/' . $name; } else { // package names must be in the format foo/bar - $name = $name . '/' . $name; + $name .= '/' . $name; } $name = strtolower($name); } else { diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 1fe8cbd30..4760b8964 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -111,7 +111,7 @@ abstract class Rule public function enable() { - $this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_DISABLED); + $this->bitfield &= ~(255 << self::BITFIELD_DISABLED); } public function isDisabled() diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index cb5f2ee8c..c96bcb80b 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -269,9 +269,9 @@ class Perforce public function generateP4Command($command, $useClient = true) { $p4Command = 'p4 '; - $p4Command = $p4Command . '-u ' . $this->getUser() . ' '; + $p4Command .= '-u ' . $this->getUser() . ' '; if ($useClient) { - $p4Command = $p4Command . '-c ' . $this->getClient() . ' '; + $p4Command .= '-c ' . $this->getClient() . ' '; } $p4Command = $p4Command . '-p ' . $this->getPort() . ' ' . $command; @@ -312,7 +312,7 @@ class Perforce chdir($this->path); $p4SyncCommand = $this->generateP4Command('sync -f '); if (null !== $sourceReference) { - $p4SyncCommand = $p4SyncCommand . '@' . $sourceReference; + $p4SyncCommand .= '@' . $sourceReference; } $this->executeCommand($p4SyncCommand); chdir($prevDir); diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index 39b53cf8f..68852d8e0 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -131,7 +131,7 @@ class ArchiveDownloaderTest extends TestCase $method = new \ReflectionMethod($downloader, 'processUrl'); $method->setAccessible(true); - $url = $url . '.' . $extension; + $url .= '.' . $extension; $expected = 'https://bitbucket.org/davereid/drush-virtualhost/get/ref.' . $extension; $package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); From 6ef65e5319a31c6ad1ad4913af50dae7f2832356 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Mon, 10 Sep 2018 15:23:40 +0200 Subject: [PATCH 245/580] Add a new RepositorySet class and restrict pool usage to the solver Breaking change for the plugin interface so bumping the version of composer-plugin-api to 2.0.0 First step for a refactoring of the package metadata loading mechanism --- .../Command/BaseDependencyCommand.php | 9 +- src/Composer/Command/CreateProjectCommand.php | 7 +- src/Composer/Command/InitCommand.php | 19 ++-- src/Composer/Command/ShowCommand.php | 35 +++---- src/Composer/DependencyResolver/Pool.php | 1 + src/Composer/DependencyResolver/Solver.php | 23 +++-- .../EventDispatcher/EventDispatcher.php | 16 ++-- src/Composer/Installer.php | 90 ++++++++--------- src/Composer/Installer/InstallerEvent.php | 18 ++-- src/Composer/Installer/PackageEvent.php | 7 +- .../Package/Version/VersionSelector.php | 9 +- src/Composer/Plugin/PluginInterface.php | 2 +- src/Composer/Plugin/PluginManager.php | 29 +++--- src/Composer/Repository/RepositorySet.php | 59 ++++++++++++ .../Test/DependencyResolver/SolverTest.php | 19 ++-- .../EventDispatcher/EventDispatcherTest.php | 6 +- .../Test/Installer/InstallerEventTest.php | 6 +- .../Package/Version/VersionSelectorTest.php | 96 ++++++++++--------- .../Plugin/Fixtures/plugin-v1/composer.json | 2 +- .../Plugin/Fixtures/plugin-v2/composer.json | 2 +- .../Plugin/Fixtures/plugin-v3/composer.json | 2 +- .../Plugin/Fixtures/plugin-v4/composer.json | 2 +- .../Plugin/Fixtures/plugin-v8/composer.json | 2 +- .../Plugin/Fixtures/plugin-v9/composer.json | 2 +- 24 files changed, 265 insertions(+), 198 deletions(-) create mode 100644 src/Composer/Repository/RepositorySet.php diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index 4c8766ba3..ff7bae2fe 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -21,6 +21,7 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Repository\RepositorySet; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Package\Version\VersionParser; use Symfony\Component\Console\Helper\Table; @@ -71,15 +72,15 @@ class BaseDependencyCommand extends BaseCommand $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); - // Prepare repositories and set up a pool + // Prepare repositories and set up a repo set $platformOverrides = $composer->getConfig()->get('platform') ?: array(); $repository = new CompositeRepository(array( new ArrayRepository(array($composer->getPackage())), $composer->getRepositoryManager()->getLocalRepository(), new PlatformRepository(array(), $platformOverrides), )); - $pool = new Pool(); - $pool->addRepository($repository); + $repositorySet = new RepositorySet(new Pool()); + $repositorySet->addRepository($repository); // Parse package name and constraint list($needle, $textConstraint) = array_pad( @@ -89,7 +90,7 @@ class BaseDependencyCommand extends BaseCommand ); // Find packages that are or provide the requested package first - $packages = $pool->whatProvides(strtolower($needle)); + $packages = $repositorySet->findPackages(strtolower($needle)); // TODO this does not search providers if (empty($packages)) { throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index cca5f1871..4c5a21794 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -28,6 +28,7 @@ use Composer\Repository\RepositoryFactory; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\InstalledFilesystemRepository; +use Composer\Repository\RepositorySet; use Composer\Script\ScriptEvents; use Composer\Util\Silencer; use Symfony\Component\Console\Input\InputArgument; @@ -290,8 +291,8 @@ EOT throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); } - $pool = new Pool($stability); - $pool->addRepository($sourceRepo); + $repositorySet = new RepositorySet(new Pool($stability)); + $repositorySet->addRepository($sourceRepo); $phpVersion = null; $prettyPhpVersion = null; @@ -305,7 +306,7 @@ EOT } // find the latest version if there are multiple - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability); if (!$package) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index be56b23fb..86c55b152 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -21,6 +21,7 @@ use Composer\Package\Version\VersionSelector; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; +use Composer\Repository\RepositorySet; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -40,8 +41,8 @@ class InitCommand extends BaseCommand /** @var array */ private $gitConfig; - /** @var Pool[] */ - private $pools; + /** @var RepositorySet[] */ + private $repositorySets; /** * {@inheritdoc} @@ -637,16 +638,16 @@ EOT return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } - private function getPool(InputInterface $input, $minimumStability = null) + private function getRepositorySet(InputInterface $input, $minimumStability = null) { $key = $minimumStability ?: 'default'; - if (!isset($this->pools[$key])) { - $this->pools[$key] = $pool = new Pool($minimumStability ?: $this->getMinimumStability($input)); - $pool->addRepository($this->getRepos()); + if (!isset($this->repositorySets[$key])) { + $this->repositorySets[$key] = $repositorySet = new RepositorySet(new Pool($minimumStability ?: $this->getMinimumStability($input))); + $repositorySet->addRepository($this->getRepos()); } - return $this->pools[$key]; + return $this->repositorySets[$key]; } private function getMinimumStability(InputInterface $input) @@ -681,8 +682,8 @@ EOT */ private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) { - // find the latest version allowed in this pool - $versionSelector = new VersionSelector($this->getPool($input, $minimumStability)); + // find the latest version allowed in this repo set + $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability)); $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); // retry without phpVersion if platform requirements are ignored in case nothing was found diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ccea6a960..edb7198c9 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -29,6 +29,7 @@ use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositoryInterface; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Semver; use Composer\Spdx\SpdxLicenses; @@ -52,8 +53,8 @@ class ShowCommand extends BaseCommand protected $versionParser; protected $colors; - /** @var Pool */ - private $pool; + /** @var RepositorySet */ + private $repositorySet; protected function configure() { @@ -523,19 +524,13 @@ EOT $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $policy = new DefaultPolicy(); - $pool = new Pool('dev'); - $pool->addRepository($repos); + $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet->addRepository($repos); $matchedPackage = null; $versions = array(); - $matches = $pool->whatProvides($name, $constraint); + $matches = $repositorySet->findPackages($name, $constraint); foreach ($matches as $index => $package) { - // skip providers/replacers - if ($package->getName() !== $name) { - unset($matches[$index]); - continue; - } - // select an exact match if it is in the installed repo and no specific version was required if (null === $version && $installedRepo->hasPackage($package)) { $matchedPackage = $package; @@ -546,8 +541,8 @@ EOT } // select preferred package according to policy rules - if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) { - $matchedPackage = $pool->literalToPackage($preferred[0]); + if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO get rid of the pool call + $matchedPackage = $repositorySet->getPoolTemp()->literalToPackage($preferred[0]); } return array($matchedPackage, $versions); @@ -961,9 +956,9 @@ EOT */ private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false) { - // find the latest version allowed in this pool + // find the latest version allowed in this repo set $name = $package->getName(); - $versionSelector = new VersionSelector($this->getPool($composer)); + $versionSelector = new VersionSelector($this->getRepositorySet($composer)); $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { @@ -987,13 +982,13 @@ EOT return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability); } - private function getPool(Composer $composer) + private function getRepositorySet(Composer $composer) { - if (!$this->pool) { - $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); - $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); + if (!$this->repositorySet) { + $this->repositorySet = new RepositorySet(new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags())); + $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } - return $this->pool; + return $this->repositorySet; } } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 085aaa7bf..c63556974 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -15,6 +15,7 @@ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\EmptyConstraint; diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 1ed35ad9c..f5226fca5 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -15,6 +15,7 @@ namespace Composer\DependencyResolver; use Composer\IO\IOInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositorySet; /** * @author Nils Adermann @@ -26,8 +27,8 @@ class Solver /** @var PolicyInterface */ protected $policy; - /** @var Pool */ - protected $pool; + /** @var RepositorySet */ + protected $repositorySet = null; /** @var RepositoryInterface */ protected $installed; /** @var RuleSet */ @@ -36,6 +37,8 @@ class Solver protected $ruleSetGenerator; /** @var array */ protected $jobs; + /** @var Pool */ + protected $pool = null; /** @var int[] */ protected $updateMap = array(); @@ -62,17 +65,16 @@ class Solver /** * @param PolicyInterface $policy - * @param Pool $pool + * @param RepositorySet $repositorySet * @param RepositoryInterface $installed * @param IOInterface $io */ - public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io) + public function __construct(PolicyInterface $policy, RepositorySet $repositorySet, RepositoryInterface $installed, IOInterface $io) { $this->io = $io; $this->policy = $policy; - $this->pool = $pool; + $this->repositorySet = $repositorySet; $this->installed = $installed; - $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool); } /** @@ -83,6 +85,11 @@ class Solver return count($this->rules); } + public function getPool() + { + return $this->pool; + } + // aka solver_makeruledecisions private function makeAssertionRuleDecisions() @@ -210,7 +217,11 @@ class Solver { $this->jobs = $request->getJobs(); + $this->pool = $this->repositorySet->createPool(); + $this->setupInstalledMap(); + + $this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs); $this->decisions = new Decisions($this->pool); diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 145944b07..f0fcdaef6 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -13,13 +13,13 @@ namespace Composer\EventDispatcher; use Composer\DependencyResolver\PolicyInterface; -use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\Installer\InstallerEvent; use Composer\IO\IOInterface; use Composer\Composer; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositorySet; use Composer\Script; use Composer\Installer\PackageEvent; use Composer\Installer\BinaryInstaller; @@ -102,7 +102,7 @@ class EventDispatcher * @param string $eventName The constant in PackageEvents * @param bool $devMode Whether or not we are in dev mode * @param PolicyInterface $policy The policy - * @param Pool $pool The pool + * @param RepositorySet $repositorySet The repository set * @param CompositeRepository $installedRepo The installed repository * @param Request $request The request * @param array $operations The list of operations @@ -111,9 +111,9 @@ class EventDispatcher * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ - public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) + public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) { - return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations, $operation)); + return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations, $operation)); } /** @@ -122,7 +122,7 @@ class EventDispatcher * @param string $eventName The constant in InstallerEvents * @param bool $devMode Whether or not we are in dev mode * @param PolicyInterface $policy The policy - * @param Pool $pool The pool + * @param RepositorySet $repositorySet The repository set * @param CompositeRepository $installedRepo The installed repository * @param Request $request The request * @param array $operations The list of operations @@ -130,9 +130,9 @@ class EventDispatcher * @return int return code of the executed script if any, for php scripts a false return * value is changed to 1, anything else to 0 */ - public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) + public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations = array()) { - return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations)); + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations)); } /** @@ -340,7 +340,7 @@ class EventDispatcher $event->getIO(), $event->isDevMode(), $event->getPolicy(), - $event->getPool(), + $event->getRepositorySet(), $event->getInstalledRepo(), $event->getRequest(), $event->getOperations(), diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index a729710c0..73120ea4b 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -37,6 +37,7 @@ use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\Package\Locker; use Composer\Package\PackageInterface; @@ -368,21 +369,21 @@ class Installer $this->io->writeError('Loading composer repositories with package information'); - // creating repository pool + // creating repository set $policy = $this->createPolicy(); - $pool = $this->createPool($this->update ? null : $lockedRepository); - $pool->addRepository($installedRepo, $aliases); + $repositorySet = $this->createRepositorySet($this->update ? null : $lockedRepository); + $repositorySet->addRepository($installedRepo, $aliases); if ($this->update) { $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { - $pool->addRepository($repository, $aliases); + $repositorySet->addRepository($repository, $aliases); } } // Add the locked repository after the others in case we are doing a // partial update so missing packages can be found there still. // For installs from lock it's the only one added so it is first if ($lockedRepository) { - $pool->addRepository($lockedRepository, $aliases); + $repositorySet->addRepository($lockedRepository, $aliases); } // creating requirements request @@ -393,7 +394,7 @@ class Installer $removedUnstablePackages = array(); foreach ($localRepo->getPackages() as $package) { if ( - !$pool->isPackageAcceptable($package->getNames(), $package->getStability()) + !$repositorySet->isPackageAcceptable($package->getNames(), $package->getStability()) && $this->installationManager->isPackageInstalled($localRepo, $package) ) { $removedUnstablePackages[$package->getName()] = true; @@ -465,11 +466,11 @@ class Installer } // force dev packages to have the latest links if we update or install from a (potentially new) lock - $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); + $this->processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); // solve dependencies - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request); - $solver = new Solver($policy, $pool, $installedRepo, $this->io); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); + $solver = new Solver($policy, $repositorySet, $installedRepo, $this->io); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); } catch (SolverProblemsException $e) { @@ -483,11 +484,10 @@ class Installer } // force dev packages to be updated if we update or install from a (potentially new) lock - $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations); + $operations = $this->processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations); - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations); - $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); // execute operations @@ -581,7 +581,7 @@ class Installer $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($jobType); if (defined($event) && $this->runScripts) { - $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); + $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations, $operation); } // output non-alias ops when not executing operations (i.e. dry run), output alias ops in debug verbosity @@ -599,11 +599,11 @@ class Installer if ($reason instanceof Rule) { switch ($reason->getReason()) { case Rule::RULE_JOB_INSTALL: - $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($pool)); + $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($solver->getPool())); $this->io->writeError(''); break; case Rule::RULE_PACKAGE_REQUIRES: - $this->io->writeError(' REASON: '.$reason->getPrettyString($pool)); + $this->io->writeError(' REASON: '.$reason->getPrettyString($solver->getPool())); $this->io->writeError(''); break; } @@ -612,7 +612,7 @@ class Installer $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); if (defined($event) && $this->runScripts) { - $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); + $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations, $operation); } if ($this->executeOperations || $this->writeLock) { @@ -622,7 +622,7 @@ class Installer if ($this->executeOperations) { // force source/dist urls to be updated for all packages - $this->processPackageUrls($pool, $policy, $localRepo, $repositories); + $this->processPackageUrls($repositorySet, $policy, $localRepo, $repositories); $localRepo->write(); } @@ -685,9 +685,9 @@ class Installer unset($tempLocalRepo, $loader, $dumper); $policy = $this->createPolicy(); - $pool = $this->createPool(); + $repositorySet = $this->createRepositorySet(); $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo); - $pool->addRepository($installedRepo, $aliases); + $repositorySet->addRepository($installedRepo, $aliases); // creating requirements request without dev requirements $request = $this->createRequest($this->package, $platformRepo); @@ -697,10 +697,10 @@ class Installer } // solve deps to see which get removed - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request); - $solver = new Solver($policy, $pool, $installedRepo, $this->io); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request); + $solver = new Solver($policy, $repositorySet, $installedRepo, $this->io); $ops = $solver->solve($request, $this->ignorePlatformReqs); - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request, $ops); $devPackages = array(); foreach ($ops as $op) { @@ -844,6 +844,12 @@ class Installer return $installedRepo; } + private function createRepositorySet($lockedRepository = null) + { + $pool = $this->createPool($lockedRepository); + return new RepositorySet($pool); + } + /** * @param RepositoryInterface|null $lockedRepository * @return Pool @@ -946,7 +952,7 @@ class Installer /** * @param WritableRepositoryInterface $localRepo - * @param Pool $pool + * @param RepositorySet $repositorySet * @param PolicyInterface $policy * @param array $repositories * @param RepositoryInterface $installedRepo @@ -955,7 +961,7 @@ class Installer * @param array|null $operations * @return array */ - private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) + private function processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) { if ($task === 'force-updates' && null === $operations) { throw new \InvalidArgumentException('Missing operations argument'); @@ -1010,7 +1016,7 @@ class Installer } // find similar packages (name/version) in all repositories - $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); + $matches = $repositorySet->findPackages($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -1018,18 +1024,12 @@ class Installer continue; } - // skip providers/replacers - if ($match->getName() !== $package->getName()) { - unset($matches[$index]); - continue; - } - $matches[$index] = $match->getId(); } // select preferred package according to policy rules - if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { - $newPackage = $pool->literalToPackage($matches[0]); + if ($matches && $matches = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO remove temp call + $newPackage = $repositorySet->getPoolTemp()->literalToPackage($matches[0]); if ($task === 'force-links' && $newPackage) { $package->setRequires($newPackage->getRequires()); @@ -1130,12 +1130,12 @@ class Installer } /** - * @param Pool $pool + * @param RepositorySet $repositorySet * @param PolicyInterface $policy * @param WritableRepositoryInterface $localRepo * @param array $repositories */ - private function processPackageUrls($pool, $policy, $localRepo, $repositories) + private function processPackageUrls($repositorySet, $policy, $localRepo, $repositories) { if (!$this->update) { return; @@ -1145,7 +1145,7 @@ class Installer foreach ($localRepo->getCanonicalPackages() as $package) { // find similar packages (name/version) in all repositories - $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); + $matches = $repositorySet->findPackages($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -1153,18 +1153,12 @@ class Installer continue; } - // skip providers/replacers - if ($match->getName() !== $package->getName()) { - unset($matches[$index]); - continue; - } - $matches[$index] = $match->getId(); } // select preferred package according to policy rules - if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { - $newPackage = $pool->literalToPackage($matches[0]); + if ($matches && $matches = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO get rid of pool + $newPackage = $repositorySet->getPoolTemp()->literalToPackage($matches[0]); // update the dist and source URLs $sourceUrl = $package->getSourceUrl(); @@ -1325,8 +1319,8 @@ class Installer } } - $pool = new Pool('dev'); - $pool->addRepository($localOrLockRepo); + $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet->addRepository($localOrLockRepo); $seen = array(); @@ -1335,7 +1329,7 @@ class Installer foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; - $depPackages = $pool->whatProvides($packageName); + $depPackages = $repositorySet->findPackages($packageName); // TODO does this need replacers/providers? $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); @@ -1374,7 +1368,7 @@ class Installer $requires = $package->getRequires(); foreach ($requires as $require) { - $requirePackages = $pool->whatProvides($require->getTarget()); + $requirePackages = $repositorySet->findPackages($require->getTarget()); // TODO does this need replacers/providers? foreach ($requirePackages as $requirePackage) { if (isset($this->updateWhitelist[$requirePackage->getName()])) { diff --git a/src/Composer/Installer/InstallerEvent.php b/src/Composer/Installer/InstallerEvent.php index 87153bd51..2d30940a9 100644 --- a/src/Composer/Installer/InstallerEvent.php +++ b/src/Composer/Installer/InstallerEvent.php @@ -15,11 +15,11 @@ namespace Composer\Installer; use Composer\Composer; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Operation\OperationInterface; -use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\EventDispatcher\Event; use Composer\IO\IOInterface; use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositorySet; /** * An event for all installer. @@ -49,9 +49,9 @@ class InstallerEvent extends Event private $policy; /** - * @var Pool + * @var RepositorySet */ - private $pool; + private $repositorySet; /** * @var CompositeRepository @@ -76,12 +76,12 @@ class InstallerEvent extends Event * @param IOInterface $io * @param bool $devMode * @param PolicyInterface $policy - * @param Pool $pool + * @param RepositorySet $repositorySet * @param CompositeRepository $installedRepo * @param Request $request * @param OperationInterface[] $operations */ - public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) + public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations = array()) { parent::__construct($eventName); @@ -89,7 +89,7 @@ class InstallerEvent extends Event $this->io = $io; $this->devMode = $devMode; $this->policy = $policy; - $this->pool = $pool; + $this->repositorySet = $repositorySet; $this->installedRepo = $installedRepo; $this->request = $request; $this->operations = $operations; @@ -128,11 +128,11 @@ class InstallerEvent extends Event } /** - * @return Pool + * @return RepositorySet */ - public function getPool() + public function getRepositorySet() { - return $this->pool; + return $this->repositorySet; } /** diff --git a/src/Composer/Installer/PackageEvent.php b/src/Composer/Installer/PackageEvent.php index f5cf0ed6e..a563a91ba 100644 --- a/src/Composer/Installer/PackageEvent.php +++ b/src/Composer/Installer/PackageEvent.php @@ -16,7 +16,6 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; -use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\Repository\CompositeRepository; @@ -40,15 +39,15 @@ class PackageEvent extends InstallerEvent * @param IOInterface $io * @param bool $devMode * @param PolicyInterface $policy - * @param Pool $pool + * @param RepositorySet $repositorySet * @param CompositeRepository $installedRepo * @param Request $request * @param OperationInterface[] $operations * @param OperationInterface $operation */ - public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) + public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) { - parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations); + parent::__construct($eventName, $composer, $io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations); $this->operation = $operation; } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 8e225d803..d99780ab1 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -17,6 +17,7 @@ use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; /** @@ -27,13 +28,13 @@ use Composer\Semver\Constraint\Constraint; */ class VersionSelector { - private $pool; + private $repositorySet; private $parser; - public function __construct(Pool $pool) + public function __construct(RepositorySet $repositorySet) { - $this->pool = $pool; + $this->repositorySet = $repositorySet; } /** @@ -49,7 +50,7 @@ class VersionSelector public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable') { $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; - $candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true); + $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint); if ($targetPhpVersion) { $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion)); diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 6eaca4e90..5158b66f6 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -27,7 +27,7 @@ interface PluginInterface * * @var string */ - const PLUGIN_API_VERSION = '1.1.0'; + const PLUGIN_API_VERSION = '2.0.0'; /** * Apply plugin modifications to Composer diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index e8f4b58c3..c6bdefc5a 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -21,6 +21,7 @@ use Composer\Repository\RepositoryInterface; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Link; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\DependencyResolver\Pool; use Composer\Plugin\Capability\Capability; @@ -157,14 +158,14 @@ class PluginManager $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; - $pool = new Pool('dev'); - $pool->addRepository($localRepo); + $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet->addRepository($localRepo); if ($globalRepo) { - $pool->addRepository($globalRepo); + $repositorySet->addRepository($globalRepo); } $autoloadPackages = array($package->getName() => $package); - $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package); + $autoloadPackages = $this->collectDependencies($repositorySet, $autoloadPackages, $package); $generator = $this->composer->getAutoloadGenerator(); $autoloads = array(); @@ -269,13 +270,13 @@ class PluginManager /** * Recursively generates a map of package names to packages for all deps * - * @param Pool $pool Package pool of installed packages - * @param array $collected Current state of the map for recursion - * @param PackageInterface $package The package to analyze + * @param RepositorySet $repositorySet Repository set of installed packages + * @param array $collected Current state of the map for recursion + * @param PackageInterface $package The package to analyze * * @return array Map of package names to packages */ - private function collectDependencies(Pool $pool, array $collected, PackageInterface $package) + private function collectDependencies(RepositorySet $repositorySet, array $collected, PackageInterface $package) { $requires = array_merge( $package->getRequires(), @@ -283,10 +284,10 @@ class PluginManager ); foreach ($requires as $requireLink) { - $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink); + $requiredPackage = $this->lookupInstalledPackage($repositorySet, $requireLink); if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) { $collected[$requiredPackage->getName()] = $requiredPackage; - $collected = $this->collectDependencies($pool, $collected, $requiredPackage); + $collected = $this->collectDependencies($repositorySet, $collected, $requiredPackage); } } @@ -294,18 +295,18 @@ class PluginManager } /** - * Resolves a package link to a package in the installed pool + * Resolves a package link to a package in the installed repo set * * Since dependencies are already installed this should always find one. * - * @param Pool $pool Pool of installed packages only + * @param RepositorySet $repositorySet Repository set of installed packages only * @param Link $link Package link to look up * * @return PackageInterface|null The found package */ - private function lookupInstalledPackage(Pool $pool, Link $link) + private function lookupInstalledPackage(RepositorySet $repositorySet, Link $link) { - $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint()); + $packages = $repositorySet->findPackages($link->getTarget(), $link->getConstraint()); // TODO this no longer returns providers return !empty($packages) ? $packages[0] : null; } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php new file mode 100644 index 000000000..e6b6db26b --- /dev/null +++ b/src/Composer/Repository/RepositorySet.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\DependencyResolver\Pool; +use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\ConstraintInterface; + +/** + * @author Nils Adermann + */ +class RepositorySet +{ + private $pool; + + public function __construct(Pool $pool) + { + $this->pool = $pool; + } + + public function addRepository(RepositoryInterface $repo, $rootAliases = array()) + { + return $this->pool->addRepository($repo, $rootAliases); + } + + public function isPackageAcceptable($name, $stability) + { + return $this->pool->isPackageAcceptable($name, $stability); + } + + public function findPackages($name, ConstraintInterface $constraint = null) + { + return $this->pool->whatProvides($name, $constraint, true); + } + + public function createPool() + { + return $this->pool; + } + + // TODO get rid of this function + public function getPoolTemp() + { + return $this->pool; + } +} diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 28c439b9e..0142818fc 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -20,12 +20,13 @@ use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; +use Composer\Repository\RepositorySet; use Composer\TestCase; use Composer\Semver\Constraint\MultiConstraint; class SolverTest extends TestCase { - protected $pool; + protected $repoSet; protected $repo; protected $repoInstalled; protected $request; @@ -33,13 +34,13 @@ class SolverTest extends TestCase public function setUp() { - $this->pool = new Pool; + $this->repoSet = new RepositorySet(new Pool); $this->repo = new ArrayRepository; $this->repoInstalled = new ArrayRepository; - $this->request = new Request($this->pool); + $this->request = new Request($this->repoSet); $this->policy = new DefaultPolicy; - $this->solver = new Solver($this->policy, $this->pool, $this->repoInstalled, new NullIO()); + $this->solver = new Solver($this->policy, $this->repoSet, $this->repoInstalled, new NullIO()); } public function testSolverInstallSingle() @@ -90,9 +91,9 @@ class SolverTest extends TestCase $repo1->addPackage($foo1 = $this->getPackage('foo', '1')); $repo2->addPackage($foo2 = $this->getPackage('foo', '1')); - $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($repo1); - $this->pool->addRepository($repo2); + $this->repoSet->addRepository($this->repoInstalled); + $this->repoSet->addRepository($repo1); + $this->repoSet->addRepository($repo2); $this->request->install('foo'); @@ -839,8 +840,8 @@ class SolverTest extends TestCase protected function reposComplete() { - $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($this->repo); + $this->repoSet->addRepository($this->repoInstalled); + $this->repoSet->addRepository($this->repo); } protected function checkSolverResult(array $expected) diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 7f0327d9c..54460d705 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -411,12 +411,12 @@ class EventDispatcherTest extends TestCase ->will($this->returnValue(array())); $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); - $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->disableOriginalConstructor()->getMock(); + $repositorySet = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock(); $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); - $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request); - $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request, array()); + $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, true, $policy, $repositorySet, $installedRepo, $request); + $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, true, $policy, $repositorySet, $installedRepo, $request, array()); } public static function call() diff --git a/tests/Composer/Test/Installer/InstallerEventTest.php b/tests/Composer/Test/Installer/InstallerEventTest.php index 8c99ba565..2847432e3 100644 --- a/tests/Composer/Test/Installer/InstallerEventTest.php +++ b/tests/Composer/Test/Installer/InstallerEventTest.php @@ -22,18 +22,18 @@ class InstallerEventTest extends TestCase $composer = $this->getMockBuilder('Composer\Composer')->getMock(); $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock(); - $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->disableOriginalConstructor()->getMock(); + $repositorySet = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock(); $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); $operations = array($this->getMockBuilder('Composer\DependencyResolver\Operation\OperationInterface')->getMock()); - $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, $policy, $pool, $installedRepo, $request, $operations); + $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, $policy, $repositorySet, $installedRepo, $request, $operations); $this->assertSame('EVENT_NAME', $event->getName()); $this->assertInstanceOf('Composer\Composer', $event->getComposer()); $this->assertInstanceOf('Composer\IO\IOInterface', $event->getIO()); $this->assertTrue($event->isDevMode()); $this->assertInstanceOf('Composer\DependencyResolver\PolicyInterface', $event->getPolicy()); - $this->assertInstanceOf('Composer\DependencyResolver\Pool', $event->getPool()); + $this->assertInstanceOf('Composer\Repository\RepositorySet', $event->getRepositorySet()); $this->assertInstanceOf('Composer\Repository\CompositeRepository', $event->getInstalledRepo()); $this->assertInstanceOf('Composer\DependencyResolver\Request', $event->getRequest()); $this->assertCount(1, $event->getOperations()); diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index d3e831e5c..2617eecf2 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -21,7 +21,7 @@ use PHPUnit\Framework\TestCase; class VersionSelectorTest extends TestCase { // A) multiple versions, get the latest one - // B) targetPackageVersion will pass to pool + // B) targetPackageVersion will pass to repo set // C) No results, throw exception public function testLatestVersionIsReturned() @@ -33,13 +33,13 @@ class VersionSelectorTest extends TestCase $package3 = $this->createPackage('1.2.0'); $packages = array($package1, $package2, $package3); - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName); // 1.2.2 should be returned because it's the latest of the returned versions @@ -57,13 +57,13 @@ class VersionSelectorTest extends TestCase $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) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, '5.5.0'); $this->assertSame($package1, $best, 'Latest version supporting php 5.5 should be returned (1.0.0)'); @@ -77,13 +77,13 @@ class VersionSelectorTest extends TestCase $package2 = $this->createPackage('1.1.0-beta'); $packages = array($package1, $package2); - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName); $this->assertSame($package1, $best, 'Latest most stable version should be returned (1.0.0)'); @@ -97,18 +97,18 @@ class VersionSelectorTest extends TestCase $package2 = $this->createPackage('2.0.0-beta3'); $packages = array($package1, $package2); - $pool = $this->createMockPool(); - $pool->expects($this->at(0)) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->at(0)) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $pool->expects($this->at(1)) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet->expects($this->at(1)) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue(array_reverse($packages))); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, null); $this->assertSame($package2, $best, 'Expecting 2.0.0-beta3, cause beta is more stable than dev'); @@ -124,13 +124,13 @@ class VersionSelectorTest extends TestCase $package2 = $this->createPackage('1.1.0-beta'); $packages = array($package1, $package2); - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, null, 'dev'); $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); @@ -145,13 +145,13 @@ class VersionSelectorTest extends TestCase $package3 = $this->createPackage('1.2.0-alpha'); $packages = array($package1, $package2, $package3); - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, null, 'beta'); $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); @@ -165,13 +165,13 @@ class VersionSelectorTest extends TestCase $package3 = $this->createPackage('1.2.0-alpha'); $packages = array($package2, $package3); - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') - ->with($packageName, null, true) + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') + ->with($packageName, null) ->will($this->returnValue($packages)); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate($packageName, null, null, 'stable'); $this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)'); @@ -179,12 +179,12 @@ class VersionSelectorTest extends TestCase public function testFalseReturnedOnNoPackages() { - $pool = $this->createMockPool(); - $pool->expects($this->once()) - ->method('whatProvides') + $repositorySet = $this->createMockRepositorySet(); + $repositorySet->expects($this->once()) + ->method('findPackages') ->will($this->returnValue(array())); - $versionSelector = new VersionSelector($pool); + $versionSelector = new VersionSelector($repositorySet); $best = $versionSelector->findBestCandidate('foobaz'); $this->assertFalse($best, 'No versions are available returns false'); } @@ -194,8 +194,8 @@ class VersionSelectorTest extends TestCase */ public function testFindRecommendedRequireVersion($prettyVersion, $isDev, $stability, $expectedVersion, $branchAlias = null) { - $pool = $this->createMockPool(); - $versionSelector = new VersionSelector($pool); + $repositorySet = $this->createMockRepositorySet(); + $versionSelector = new VersionSelector($repositorySet); $versionParser = new VersionParser(); $package = $this->getMockBuilder('\Composer\Package\PackageInterface')->getMock(); @@ -273,8 +273,10 @@ class VersionSelectorTest extends TestCase return new Package('foo', $parser->normalize($version), $version); } - private function createMockPool() + private function createMockRepositorySet() { - return $this->getMockBuilder('Composer\DependencyResolver\Pool')->getMock(); + return $this->getMockBuilder('Composer\Repository\RepositorySet') + ->disableOriginalConstructor() + ->getMock(); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json index 574c4402f..335f772c9 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json @@ -7,6 +7,6 @@ "class": "Installer\\Plugin" }, "require": { - "composer-plugin-api": "^1.0" + "composer-plugin-api": "^2.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json index 27432acfa..4104f4be6 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json @@ -7,6 +7,6 @@ "class": "Installer\\Plugin2" }, "require": { - "composer-plugin-api": "^1.0" + "composer-plugin-api": "^2.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json index 881eb5cae..ee087e2d7 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json @@ -7,6 +7,6 @@ "class": "Installer\\Plugin2" }, "require": { - "composer-plugin-api": "^1.0" + "composer-plugin-api": "^2.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json index f61cb3fbd..a349ccc2c 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json @@ -10,6 +10,6 @@ ] }, "require": { - "composer-plugin-api": "^1.0" + "composer-plugin-api": "^2.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/composer.json index 799df2e61..aa44b5a3d 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/composer.json @@ -9,6 +9,6 @@ ] }, "require": { - "composer-plugin-api": "1.1.0" + "composer-plugin-api": "2.0.0" } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/composer.json b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/composer.json index f3ccb9397..45d8d794b 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/composer.json +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/composer.json @@ -7,6 +7,6 @@ "class": "Installer\\Plugin" }, "require": { - "composer-plugin-api": "^1.0" + "composer-plugin-api": "^2.0" } } From 1228bcdffc2fca0806b16dfb9d148ffa9d845383 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 13:33:29 +0200 Subject: [PATCH 246/580] Internalize pool creation in repository set, store root aliases in set The pool is still exposed too early in a few places which will require further refactoring --- .../Command/BaseDependencyCommand.php | 3 +- src/Composer/Command/CreateProjectCommand.php | 3 +- src/Composer/Command/InitCommand.php | 3 +- src/Composer/Command/ShowCommand.php | 5 +- src/Composer/DependencyResolver/Pool.php | 18 +-- src/Composer/Installer.php | 36 +++--- src/Composer/Plugin/PluginManager.php | 3 +- .../Repository/ComposerRepository.php | 15 ++- src/Composer/Repository/RepositorySet.php | 112 +++++++++++++++-- .../DependencyResolver/DefaultPolicyTest.php | 117 +++++++++++------- .../Test/DependencyResolver/PoolTest.php | 21 ++-- .../RuleSetIteratorTest.php | 3 +- .../Test/DependencyResolver/RuleSetTest.php | 3 +- .../Test/DependencyResolver/RuleTest.php | 3 +- .../Test/DependencyResolver/SolverTest.php | 2 +- .../Test/Plugin/PluginInstallerTest.php | 22 ++-- .../Repository/ComposerRepositoryTest.php | 12 +- 17 files changed, 248 insertions(+), 133 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index ff7bae2fe..ca80fa246 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -12,7 +12,6 @@ namespace Composer\Command; -use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\ArrayRepository; @@ -79,7 +78,7 @@ class BaseDependencyCommand extends BaseCommand $composer->getRepositoryManager()->getLocalRepository(), new PlatformRepository(array(), $platformOverrides), )); - $repositorySet = new RepositorySet(new Pool()); + $repositorySet = new RepositorySet(); $repositorySet->addRepository($repository); // Parse package name and constraint diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 4c5a21794..1b58d59c5 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -20,7 +20,6 @@ use Composer\Installer\InstallationManager; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\BasePackage; -use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\Package\Version\VersionSelector; use Composer\Package\AliasPackage; @@ -291,7 +290,7 @@ EOT throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); } - $repositorySet = new RepositorySet(new Pool($stability)); + $repositorySet = new RepositorySet(array(), $stability); $repositorySet->addRepository($sourceRepo); $phpVersion = null; diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 86c55b152..03504e29a 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -12,7 +12,6 @@ namespace Composer\Command; -use Composer\DependencyResolver\Pool; use Composer\Factory; use Composer\Json\JsonFile; use Composer\Package\BasePackage; @@ -643,7 +642,7 @@ EOT $key = $minimumStability ?: 'default'; if (!isset($this->repositorySets[$key])) { - $this->repositorySets[$key] = $repositorySet = new RepositorySet(new Pool($minimumStability ?: $this->getMinimumStability($input))); + $this->repositorySets[$key] = $repositorySet = new RepositorySet(array(), $minimumStability ?: $this->getMinimumStability($input)); $repositorySet->addRepository($this->getRepos()); } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index edb7198c9..94ec78362 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -14,7 +14,6 @@ namespace Composer\Command; use Composer\Composer; use Composer\DependencyResolver\DefaultPolicy; -use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Package\BasePackage; use Composer\Package\CompletePackageInterface; @@ -524,7 +523,7 @@ EOT $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $policy = new DefaultPolicy(); - $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet = new RepositorySet(array(), 'dev'); $repositorySet->addRepository($repos); $matchedPackage = null; @@ -985,7 +984,7 @@ EOT private function getRepositorySet(Composer $composer) { if (!$this->repositorySet) { - $this->repositorySet = new RepositorySet(new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags())); + $this->repositorySet = new RepositorySet(array(), $composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index c63556974..ee4bace97 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -54,22 +54,12 @@ class Pool implements \Countable protected $whitelist = null; protected $id = 1; - public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) + public function __construct(array $acceptableStabilities, array $stabilityFlags = array(), array $filterRequires = array()) { - $this->versionParser = new VersionParser; - $this->acceptableStabilities = array(); - foreach (BasePackage::$stabilities as $stability => $value) { - if ($value <= BasePackage::$stabilities[$minimumStability]) { - $this->acceptableStabilities[$stability] = $value; - } - } + $this->acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->filterRequires = $filterRequires; - foreach ($filterRequires as $name => $constraint) { - if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { - unset($this->filterRequires[$name]); - } - } + $this->versionParser = new VersionParser; } public function setWhitelist($whitelist) @@ -202,7 +192,7 @@ class Pool implements \Countable $candidates = array(); foreach ($this->providerRepos as $repo) { - foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) { + foreach ($repo->whatProvides($name, $bypassFilters, array($this, 'isPackageAcceptable')) as $candidate) { $candidates[] = $candidate; if ($candidate->id < 1) { $candidate->setId($this->id++); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 73120ea4b..bb27bda3c 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -20,7 +20,6 @@ use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; -use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Solver; @@ -371,19 +370,19 @@ class Installer // creating repository set $policy = $this->createPolicy(); - $repositorySet = $this->createRepositorySet($this->update ? null : $lockedRepository); - $repositorySet->addRepository($installedRepo, $aliases); + $repositorySet = $this->createRepositorySet($aliases, $this->update ? null : $lockedRepository); + $repositorySet->addRepository($installedRepo); if ($this->update) { $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { - $repositorySet->addRepository($repository, $aliases); + $repositorySet->addRepository($repository); } } // Add the locked repository after the others in case we are doing a // partial update so missing packages can be found there still. // For installs from lock it's the only one added so it is first if ($lockedRepository) { - $repositorySet->addRepository($lockedRepository, $aliases); + $repositorySet->addRepository($lockedRepository); } // creating requirements request @@ -465,6 +464,8 @@ class Installer } } + $repositorySet->getPoolTemp(); // TODO remove this, but ensures ids are set before dev packages are processed in advance of solver + // force dev packages to have the latest links if we update or install from a (potentially new) lock $this->processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); @@ -685,9 +686,9 @@ class Installer unset($tempLocalRepo, $loader, $dumper); $policy = $this->createPolicy(); - $repositorySet = $this->createRepositorySet(); + $repositorySet = $this->createRepositorySet($aliases); $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo); - $repositorySet->addRepository($installedRepo, $aliases); + $repositorySet->addRepository($installedRepo); // creating requirements request without dev requirements $request = $this->createRequest($this->package, $platformRepo); @@ -844,17 +845,12 @@ class Installer return $installedRepo; } - private function createRepositorySet($lockedRepository = null) - { - $pool = $this->createPool($lockedRepository); - return new RepositorySet($pool); - } - /** - * @param RepositoryInterface|null $lockedRepository - * @return Pool + * @param array $rootAliases + * @param RepositoryInterface|null $lockedRepository + * @return RepositorySet */ - private function createPool(RepositoryInterface $lockedRepository = null) + private function createRepositorySet(array $rootAliases = array(), $lockedRepository = null) { if ($this->update) { $minimumStability = $this->package->getMinimumStability(); @@ -886,7 +882,7 @@ class Installer } } - return new Pool($minimumStability, $stabilityFlags, $rootConstraints); + return new RepositorySet($rootAliases, $minimumStability, $stabilityFlags, $rootConstraints); } /** @@ -1319,7 +1315,7 @@ class Installer } } - $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet = new RepositorySet(array(), 'dev'); $repositorySet->addRepository($localOrLockRepo); $seen = array(); @@ -1354,11 +1350,11 @@ class Installer while (!$packageQueue->isEmpty()) { $package = $packageQueue->dequeue(); - if (isset($seen[$package->getId()])) { + if (isset($seen[spl_object_hash($package)])) { continue; } - $seen[$package->getId()] = true; + $seen[spl_object_hash($package)] = true; $this->updateWhitelist[$package->getName()] = true; if (!$this->whitelistDependencies && !$this->whitelistAllDependencies) { diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index c6bdefc5a..d16c51db1 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -23,7 +23,6 @@ use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; -use Composer\DependencyResolver\Pool; use Composer\Plugin\Capability\Capability; /** @@ -158,7 +157,7 @@ class PluginManager $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; - $repositorySet = new RepositorySet(new Pool('dev')); + $repositorySet = new RepositorySet(array(), 'dev'); $repositorySet->addRepository($localRepo); if ($globalRepo) { $repositorySet->addRepository($globalRepo); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8a5da2b23..cec2ae948 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -16,7 +16,6 @@ use Composer\Package\Loader\ArrayLoader; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; -use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Cache; use Composer\Config; @@ -136,7 +135,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $packages = $this->whatProvides(new Pool('dev'), $providerName); + $packages = $this->whatProvides($providerName); foreach ($packages as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); @@ -170,7 +169,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $candidates = $this->whatProvides(new Pool('dev'), $providerName); + $candidates = $this->whatProvides($providerName); // TODO what is the point of this? foreach ($candidates as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); @@ -289,12 +288,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } /** - * @param Pool $pool - * @param string $name package name - * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache + * @param string $name package name + * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache + * @param callable $isPackageAcceptableCallable * @return array|mixed */ - public function whatProvides(Pool $pool, $name, $bypassFilters = false) + public function whatProvides($name, $bypassFilters = false, $isPackageAcceptableCallable = null) { if (isset($this->providers[$name]) && !$bypassFilters) { return $this->providers[$name]; @@ -395,7 +394,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } else { - if (!$bypassFilters && !$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) { + if (!$bypassFilters && (!$isPackageAcceptableCallable || !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version'])))) { continue; } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index e6b6db26b..d3c652ccb 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -24,36 +24,132 @@ use Composer\Semver\Constraint\ConstraintInterface; */ class RepositorySet { - private $pool; + /** @var array */ + private $rootAliases; - public function __construct(Pool $pool) + /** @var RepositoryInterface[] */ + private $repositories; + + /** @var ComposerRepository[] */ + private $providerRepos; + + private $acceptableStabilities; + private $stabilityFlags; + protected $filterRequires; + + /** @var Pool */ + private $pool; // TODO remove this + + public function __construct(array $rootAliases = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) { - $this->pool = $pool; + $this->rootAliases = $rootAliases; + + $this->acceptableStabilities = array(); + foreach (BasePackage::$stabilities as $stability => $value) { + if ($value <= BasePackage::$stabilities[$minimumStability]) { + $this->acceptableStabilities[$stability] = $value; + } + } + $this->stabilityFlags = $stabilityFlags; + $this->filterRequires = $filterRequires; + foreach ($filterRequires as $name => $constraint) { + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { + unset($this->filterRequires[$name]); + } + } } - public function addRepository(RepositoryInterface $repo, $rootAliases = array()) + /** + * Adds a repository to this repository set + * + * @param RepositoryInterface $repo A package repository + */ + public function addRepository(RepositoryInterface $repo) { - return $this->pool->addRepository($repo, $rootAliases); + if ($repo instanceof CompositeRepository) { + $repos = $repo->getRepositories(); + } else { + $repos = array($repo); + } + + foreach ($repos as $repo) { + $this->repositories[] = $repo; + if ($repo instanceof ComposerRepository && $repo->hasProviders()) { + $this->providerRepos[] = $repo; + } + } } public function isPackageAcceptable($name, $stability) { - return $this->pool->isPackageAcceptable($name, $stability); + foreach ((array) $name as $n) { + // allow if package matches the global stability requirement and has no exception + if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) { + return true; + } + + // allow if package matches the package-specific stability flag + if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) { + return true; + } + } + + return false; } + /** + * Find packages matching name and optionally a constraint in all repositories + * + * @param $name + * @param ConstraintInterface|null $constraint + * @return array + */ public function findPackages($name, ConstraintInterface $constraint = null) { - return $this->pool->whatProvides($name, $constraint, true); + $packages = array(); + foreach ($this->repositories as $repository) { + $packages[] = $repository->findPackages($name, $constraint) ?: array(); + } + + $candidates = $packages ? call_user_func_array('array_merge', $packages) : array(); + + $result = array(); + foreach ($candidates as $candidate) { + if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { + $result[] = $candidate; + } + } + + return $candidates; } + /** + * Create a pool for dependency resolution from the packages in this repository set. + * + * @return Pool + */ public function createPool() { + if ($this->pool) { + return $this->pool; + } + + $this->pool = new Pool($this->acceptableStabilities, $this->stabilityFlags, $this->filterRequires); + + foreach ($this->repositories as $repository) { + $this->pool->addRepository($repository, $this->rootAliases); + } + return $this->pool; } // TODO get rid of this function public function getPoolTemp() { - return $this->pool; + if (!$this->pool) { + return $this->createPool(); + } else { + return $this->pool; + } } } diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index a73139d54..34a1c092b 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -18,13 +18,14 @@ use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\AliasPackage; +use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\TestCase; class DefaultPolicyTest extends TestCase { - /** @var Pool */ - protected $pool; + /** @var RepositorySet */ + protected $repositorySet; /** @var ArrayRepository */ protected $repo; /** @var ArrayRepository */ @@ -34,7 +35,7 @@ class DefaultPolicyTest extends TestCase public function setUp() { - $this->pool = new Pool('dev'); + $this->repositorySet = new RepositorySet(array(), 'dev'); $this->repo = new ArrayRepository; $this->repoInstalled = new ArrayRepository; @@ -44,12 +45,14 @@ class DefaultPolicyTest extends TestCase public function testSelectSingle() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId()); $expected = array($packageA->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -58,12 +61,14 @@ class DefaultPolicyTest extends TestCase { $this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -72,12 +77,14 @@ class DefaultPolicyTest extends TestCase { $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); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -86,13 +93,15 @@ class DefaultPolicyTest extends TestCase { $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); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); $policy = new DefaultPolicy(true); - $selected = $policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -101,12 +110,14 @@ class DefaultPolicyTest extends TestCase { $this->repo->addPackage($packageA1 = $this->getPackage('A', 'dev-foo')); $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.0')); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -115,13 +126,15 @@ class DefaultPolicyTest extends TestCase { $this->repo->addPackage($packageA = $this->getPackage('A', '2.0')); $this->repoInstalled->addPackage($packageAInstalled = $this->getPackage('A', '1.0')); - $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repoInstalled); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageAInstalled->getId()); $expected = array($packageA->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, $this->mapFromRepo($this->repoInstalled), $literals); + $selected = $this->policy->selectPreferredPackages($pool, $this->mapFromRepo($this->repoInstalled), $literals); $this->assertSame($expected, $selected); } @@ -133,14 +146,16 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $otherRepository->addPackage($packageAImportant = $this->getPackage('A', '1.0')); - $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($otherRepository); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repoInstalled); + $this->repositorySet->addRepository($otherRepository); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageAImportant->getId()); $expected = array($packageAImportant->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -155,21 +170,25 @@ class DefaultPolicyTest extends TestCase $repo2->addPackage($package3 = $this->getPackage('A', '1.1')); $repo2->addPackage($package4 = $this->getPackage('A', '1.2')); - $this->pool->addRepository($repo1); - $this->pool->addRepository($repo2); + $this->repositorySet->addRepository($repo1); + $this->repositorySet->addRepository($repo2); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($package1->getId(), $package2->getId(), $package3->getId(), $package4->getId()); $expected = array($package2->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); - $this->pool = new Pool('dev'); - $this->pool->addRepository($repo2); - $this->pool->addRepository($repo1); + $this->repositorySet = new RepositorySet(array(), 'dev'); + $this->repositorySet->addRepository($repo2); + $this->repositorySet->addRepository($repo1); + + $pool = $this->repositorySet->getPoolTemp(); $expected = array($package4->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -186,11 +205,13 @@ class DefaultPolicyTest extends TestCase $repoImportant->addPackage($packageA2AliasImportant = new AliasPackage($packageA2Important, '2.1.9999999.9999999-dev', '2.1.x-dev')); $packageAAliasImportant->setRootPackageAlias(true); - $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($repoImportant); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repoInstalled); + $this->repositorySet->addRepository($repoImportant); + $this->repositorySet->addRepository($this->repo); - $packages = $this->pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); + $pool = $this->repositorySet->getPoolTemp(); + + $packages = $pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); $literals = array(); foreach ($packages as $package) { $literals[] = $package->getId(); @@ -198,7 +219,7 @@ class DefaultPolicyTest extends TestCase $expected = array($packageAAliasImportant->getId()); - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -211,12 +232,14 @@ class DefaultPolicyTest extends TestCase $packageA->setProvides(array(new Link('A', 'X', new Constraint('==', '1.0'), 'provides'))); $packageB->setProvides(array(new Link('B', 'X', new Constraint('==', '1.0'), 'provides'))); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -228,12 +251,14 @@ class DefaultPolicyTest extends TestCase $packageB->setReplaces(array(new Link('B', 'A', new Constraint('==', '1.0'), 'replaces'))); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } @@ -247,12 +272,14 @@ class DefaultPolicyTest extends TestCase $packageA->setReplaces(array(new Link('vendor-a/replacer', 'vendor-a/package', new Constraint('==', '1.0'), 'replaces'))); $packageB->setReplaces(array(new Link('vendor-b/replacer', 'vendor-a/package', new Constraint('==', '1.0'), 'replaces'))); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals, 'vendor-a/package'); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals, 'vendor-a/package'); $this->assertEquals($expected, $selected); // test with reversed order in repo @@ -260,13 +287,15 @@ class DefaultPolicyTest extends TestCase $repo->addPackage($packageA = clone $packageA); $repo->addPackage($packageB = clone $packageB); - $pool = new Pool('dev'); - $pool->addRepository($this->repo); + $repositorySet = new RepositorySet(array(), 'dev'); + $repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; - $selected = $this->policy->selectPreferredPackages($this->pool, array(), $literals, 'vendor-a/package'); + $selected = $this->policy->selectPreferredPackages($pool, array(), $literals, 'vendor-a/package'); $this->assertSame($expected, $selected); } @@ -286,12 +315,14 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); - $this->pool->addRepository($this->repo); + $this->repositorySet->addRepository($this->repo); + + $pool = $this->repositorySet->getPoolTemp(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); - $selected = $policy->selectPreferredPackages($this->pool, array(), $literals); + $selected = $policy->selectPreferredPackages($pool, array(), $literals); $this->assertSame($expected, $selected); } diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 14b24fc9f..5169586f6 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -21,7 +21,7 @@ class PoolTest extends TestCase { public function testPool() { - $pool = new Pool; + $pool = $this->createPool(); $repo = new ArrayRepository; $package = $this->getPackage('foo', '1'); @@ -34,7 +34,7 @@ class PoolTest extends TestCase public function testPoolIgnoresIrrelevantPackages() { - $pool = new Pool('stable', array('bar' => BasePackage::STABILITY_BETA)); + $pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE), array('bar' => BasePackage::STABILITY_BETA)); $repo = new ArrayRepository; $repo->addPackage($package = $this->getPackage('bar', '1')); $repo->addPackage($betaPackage = $this->getPackage('bar', '1-beta')); @@ -53,7 +53,7 @@ class PoolTest extends TestCase */ public function testGetPriorityForNotRegisteredRepository() { - $pool = new Pool; + $pool = $this->createPool(); $repository = new ArrayRepository; $pool->getPriority($repository); @@ -61,7 +61,7 @@ class PoolTest extends TestCase public function testGetPriorityWhenRepositoryIsRegistered() { - $pool = new Pool; + $pool = $this->createPool(); $firstRepository = new ArrayRepository; $pool->addRepository($firstRepository); $secondRepository = new ArrayRepository; @@ -76,7 +76,7 @@ class PoolTest extends TestCase public function testWhatProvidesSamePackageForDifferentRepositories() { - $pool = new Pool; + $pool = $this->createPool(); $firstRepository = new ArrayRepository; $secondRepository = new ArrayRepository; @@ -96,7 +96,7 @@ class PoolTest extends TestCase public function testWhatProvidesPackageWithConstraint() { - $pool = new Pool; + $pool = $this->createPool(); $repository = new ArrayRepository; $firstPackage = $this->getPackage('foo', '1'); @@ -113,7 +113,7 @@ class PoolTest extends TestCase public function testPackageById() { - $pool = new Pool; + $pool = $this->createPool(); $repository = new ArrayRepository; $package = $this->getPackage('foo', '1'); @@ -125,8 +125,13 @@ class PoolTest extends TestCase public function testWhatProvidesWhenPackageCannotBeFound() { - $pool = new Pool; + $pool = $this->createPool(); $this->assertEquals(array(), $pool->whatProvides('foo')); } + + protected function createPool() + { + return new Pool(array('stable' => BasePackage::STABILITY_STABLE)); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index 7789881df..24a2e7c54 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -17,6 +17,7 @@ use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\RuleSetIterator; use Composer\DependencyResolver\Pool; +use Composer\Package\BasePackage; use PHPUnit\Framework\TestCase; class RuleSetIteratorTest extends TestCase @@ -26,7 +27,7 @@ class RuleSetIteratorTest extends TestCase protected function setUp() { - $this->pool = new Pool; + $this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); $this->rules = array( RuleSet::TYPE_JOB => array( diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index cecae613d..e9753c848 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -16,6 +16,7 @@ use Composer\DependencyResolver\GenericRule; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\Pool; +use Composer\Package\BasePackage; use Composer\Repository\ArrayRepository; use Composer\TestCase; @@ -25,7 +26,7 @@ class RuleSetTest extends TestCase public function setUp() { - $this->pool = new Pool; + $this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); } public function testAdd() diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index a0339f27a..c9b3dbe1a 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -16,6 +16,7 @@ use Composer\DependencyResolver\GenericRule; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\Pool; +use Composer\Package\BasePackage; use Composer\Repository\ArrayRepository; use Composer\TestCase; @@ -25,7 +26,7 @@ class RuleTest extends TestCase public function setUp() { - $this->pool = new Pool; + $this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); } public function testGetHash() diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 0142818fc..1a43c5b60 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -34,7 +34,7 @@ class SolverTest extends TestCase public function setUp() { - $this->repoSet = new RepositorySet(new Pool); + $this->repoSet = new RepositorySet(array()); $this->repo = new ArrayRepository; $this->repoInstalled = new ArrayRepository; diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 26fc63efa..7981177eb 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -130,7 +130,7 @@ class PluginInstallerTest extends TestCase public function testInstallNewPlugin() { $this->repository - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); $installer = new PluginInstaller($this->io, $this->composer); @@ -145,7 +145,7 @@ class PluginInstallerTest extends TestCase public function testInstallMultiplePlugins() { $this->repository - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array($this->packages[3]))); $installer = new PluginInstaller($this->io, $this->composer); @@ -163,7 +163,7 @@ class PluginInstallerTest extends TestCase public function testUpgradeWithNewClassName() { $this->repository - ->expects($this->exactly(3)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array($this->packages[0]))); $this->repository @@ -182,7 +182,7 @@ class PluginInstallerTest extends TestCase public function testUpgradeWithSameClassName() { $this->repository - ->expects($this->exactly(3)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array($this->packages[1]))); $this->repository @@ -201,7 +201,7 @@ class PluginInstallerTest extends TestCase public function testRegisterPluginOnlyOneTime() { $this->repository - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); $installer = new PluginInstaller($this->io, $this->composer); @@ -240,11 +240,11 @@ class PluginInstallerTest extends TestCase // Add the plugins to the repo along with the internal Plugin package on which they all rely. $this->repository - ->expects($this->any()) - ->method('getPackages') - ->will($this->returnCallback(function () use ($plugApiInternalPackage, $plugins) { - return array_merge(array($plugApiInternalPackage), $plugins); - })); + ->expects($this->any()) + ->method('getPackages') + ->will($this->returnCallback(function () use ($plugApiInternalPackage, $plugins) { + return array_merge(array($plugApiInternalPackage), $plugins); + })); $this->pm->loadInstalledPlugins(); } @@ -300,7 +300,7 @@ class PluginInstallerTest extends TestCase public function testCommandProviderCapability() { $this->repository - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array($this->packages[7]))); $installer = new PluginInstaller($this->io, $this->composer); diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 38b459730..fa6809cbb 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -142,11 +142,6 @@ class ComposerRepositoryTest extends TestCase ), ))); - $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->getMock(); - $pool->expects($this->any()) - ->method('isPackageAcceptable') - ->will($this->returnValue(true)); - $versionParser = new VersionParser(); $repo->setRootAliases(array( 'a' => array( @@ -155,7 +150,7 @@ class ComposerRepositoryTest extends TestCase ), )); - $packages = $repo->whatProvides($pool, 'a'); + $packages = $repo->whatProvides('a', false, array($this, 'isPackageAcceptableReturnTrue')); $this->assertCount(7, $packages); $this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages)); @@ -164,6 +159,11 @@ class ComposerRepositoryTest extends TestCase $this->assertSame($packages['2'], $packages['2-alias']->getAliasOf()); } + public function isPackageAcceptableReturnTrue() + { + return true; + } + public function testSearchWithType() { $repoConfig = array( From 190d263c74773e855ecc2d854766ec3963db448e Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 14:40:37 +0200 Subject: [PATCH 247/580] Fix logic for composer repository's optional acceptable callable filter --- src/Composer/Repository/ComposerRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index cec2ae948..ea583ede6 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -169,7 +169,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $candidates = $this->whatProvides($providerName); // TODO what is the point of this? + $candidates = $this->whatProvides($providerName); foreach ($candidates as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); @@ -394,7 +394,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } else { - if (!$bypassFilters && (!$isPackageAcceptableCallable || !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version'])))) { + if (!$bypassFilters && $isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { continue; } From 7036f999990a4a2ec394cf0fb0506f860ecdd19b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 14:52:44 +0200 Subject: [PATCH 248/580] RepositorySet::findPackages now has an exactMatch option Allows search for providers/replacers, or exact name search --- src/Composer/Command/BaseDependencyCommand.php | 2 +- src/Composer/Plugin/PluginManager.php | 2 +- src/Composer/Repository/RepositorySet.php | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index ca80fa246..b2b617c42 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -89,7 +89,7 @@ class BaseDependencyCommand extends BaseCommand ); // Find packages that are or provide the requested package first - $packages = $repositorySet->findPackages(strtolower($needle)); // TODO this does not search providers + $packages = $repositorySet->findPackages(strtolower($needle), null, false); if (empty($packages)) { throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index d16c51db1..786d846c5 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -305,7 +305,7 @@ class PluginManager */ private function lookupInstalledPackage(RepositorySet $repositorySet, Link $link) { - $packages = $repositorySet->findPackages($link->getTarget(), $link->getConstraint()); // TODO this no longer returns providers + $packages = $repositorySet->findPackages($link->getTarget(), $link->getConstraint(), false); return !empty($packages) ? $packages[0] : null; } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index d3c652ccb..f83de3b14 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -98,13 +98,14 @@ class RepositorySet } /** - * Find packages matching name and optionally a constraint in all repositories + * Find packages providing or matching a name and optionally meeting a constraint in all repositories * - * @param $name + * @param string $name * @param ConstraintInterface|null $constraint + * @param bool $exactMatch * @return array */ - public function findPackages($name, ConstraintInterface $constraint = null) + public function findPackages($name, ConstraintInterface $constraint = null, $exactMatch = true) { $packages = array(); foreach ($this->repositories as $repository) { @@ -115,6 +116,10 @@ class RepositorySet $result = array(); foreach ($candidates as $candidate) { + if ($exactMatch && $candidate->getName() !== $name) { + continue; + } + if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { $result[] = $candidate; } From b6e2d60c9eee1a75ac5d1b8a1bb621ae4baf6b81 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 15:49:08 +0200 Subject: [PATCH 249/580] Create the pool in the installer before giving it to the solver --- src/Composer/DependencyResolver/Solver.php | 14 +++---- src/Composer/Installer.php | 42 ++++++++++--------- src/Composer/Repository/RepositorySet.php | 28 ++++++------- .../DependencyResolver/DefaultPolicyTest.php | 30 ++++++------- .../Test/DependencyResolver/SolverTest.php | 5 ++- 5 files changed, 60 insertions(+), 59 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index f5226fca5..959f1dc4c 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -27,8 +27,8 @@ class Solver /** @var PolicyInterface */ protected $policy; - /** @var RepositorySet */ - protected $repositorySet = null; + /** @var Pool */ + protected $pool = null; /** @var RepositoryInterface */ protected $installed; /** @var RuleSet */ @@ -37,8 +37,6 @@ class Solver protected $ruleSetGenerator; /** @var array */ protected $jobs; - /** @var Pool */ - protected $pool = null; /** @var int[] */ protected $updateMap = array(); @@ -65,15 +63,15 @@ class Solver /** * @param PolicyInterface $policy - * @param RepositorySet $repositorySet + * @param Pool $pool * @param RepositoryInterface $installed * @param IOInterface $io */ - public function __construct(PolicyInterface $policy, RepositorySet $repositorySet, RepositoryInterface $installed, IOInterface $io) + public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io) { $this->io = $io; $this->policy = $policy; - $this->repositorySet = $repositorySet; + $this->pool = $pool; $this->installed = $installed; } @@ -217,8 +215,6 @@ class Solver { $this->jobs = $request->getJobs(); - $this->pool = $this->repositorySet->createPool(); - $this->setupInstalledMap(); $this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index bb27bda3c..c6f888493 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -20,6 +20,7 @@ use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; +use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\Solver; @@ -464,14 +465,15 @@ class Installer } } - $repositorySet->getPoolTemp(); // TODO remove this, but ensures ids are set before dev packages are processed in advance of solver + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); + + $pool = $repositorySet->createPool(); // force dev packages to have the latest links if we update or install from a (potentially new) lock - $this->processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); + $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); // solve dependencies - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); - $solver = new Solver($policy, $repositorySet, $installedRepo, $this->io); + $solver = new Solver($policy, $pool, $installedRepo, $this->io); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); } catch (SolverProblemsException $e) { @@ -485,7 +487,7 @@ class Installer } // force dev packages to be updated if we update or install from a (potentially new) lock - $operations = $this->processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations); + $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations); @@ -600,11 +602,11 @@ class Installer if ($reason instanceof Rule) { switch ($reason->getReason()) { case Rule::RULE_JOB_INSTALL: - $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($solver->getPool())); + $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($pool)); $this->io->writeError(''); break; case Rule::RULE_PACKAGE_REQUIRES: - $this->io->writeError(' REASON: '.$reason->getPrettyString($solver->getPool())); + $this->io->writeError(' REASON: '.$reason->getPrettyString($pool)); $this->io->writeError(''); break; } @@ -623,7 +625,7 @@ class Installer if ($this->executeOperations) { // force source/dist urls to be updated for all packages - $this->processPackageUrls($repositorySet, $policy, $localRepo, $repositories); + $this->processPackageUrls($pool, $policy, $localRepo, $repositories); $localRepo->write(); } @@ -699,7 +701,7 @@ class Installer // solve deps to see which get removed $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request); - $solver = new Solver($policy, $repositorySet, $installedRepo, $this->io); + $solver = new Solver($policy, $repositorySet->createPool(), $installedRepo, $this->io); $ops = $solver->solve($request, $this->ignorePlatformReqs); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request, $ops); @@ -948,7 +950,7 @@ class Installer /** * @param WritableRepositoryInterface $localRepo - * @param RepositorySet $repositorySet + * @param Pool $pool * @param PolicyInterface $policy * @param array $repositories * @param RepositoryInterface $installedRepo @@ -957,7 +959,7 @@ class Installer * @param array|null $operations * @return array */ - private function processDevPackages($localRepo, $repositorySet, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) + private function processDevPackages($localRepo, Pool $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) { if ($task === 'force-updates' && null === $operations) { throw new \InvalidArgumentException('Missing operations argument'); @@ -1012,7 +1014,7 @@ class Installer } // find similar packages (name/version) in all repositories - $matches = $repositorySet->findPackages($package->getName(), new Constraint('=', $package->getVersion())); + $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()), true); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -1024,8 +1026,8 @@ class Installer } // select preferred package according to policy rules - if ($matches && $matches = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO remove temp call - $newPackage = $repositorySet->getPoolTemp()->literalToPackage($matches[0]); + if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { + $newPackage = $pool->literalToPackage($matches[0]); if ($task === 'force-links' && $newPackage) { $package->setRequires($newPackage->getRequires()); @@ -1126,12 +1128,12 @@ class Installer } /** - * @param RepositorySet $repositorySet + * @param Pool $pool * @param PolicyInterface $policy * @param WritableRepositoryInterface $localRepo * @param array $repositories */ - private function processPackageUrls($repositorySet, $policy, $localRepo, $repositories) + private function processPackageUrls(Pool $pool, $policy, $localRepo, $repositories) { if (!$this->update) { return; @@ -1140,8 +1142,8 @@ class Installer $rootRefs = $this->package->getReferences(); foreach ($localRepo->getCanonicalPackages() as $package) { - // find similar packages (name/version) in all repositories - $matches = $repositorySet->findPackages($package->getName(), new Constraint('=', $package->getVersion())); + // find similar packages (name/version) in pool + $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()), true); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -1153,8 +1155,8 @@ class Installer } // select preferred package according to policy rules - if ($matches && $matches = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO get rid of pool - $newPackage = $repositorySet->getPoolTemp()->literalToPackage($matches[0]); + if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) { + $newPackage = $pool->literalToPackage($matches[0]); // update the dist and source URLs $sourceUrl = $package->getSourceUrl(); diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index f83de3b14..4bea054f9 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -18,6 +18,7 @@ use Composer\Package\Version\VersionParser; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Test\DependencyResolver\PoolTest; /** * @author Nils Adermann @@ -28,17 +29,17 @@ class RepositorySet private $rootAliases; /** @var RepositoryInterface[] */ - private $repositories; + private $repositories = array(); /** @var ComposerRepository[] */ - private $providerRepos; + private $providerRepos = array(); private $acceptableStabilities; private $stabilityFlags; protected $filterRequires; /** @var Pool */ - private $pool; // TODO remove this + private $pool; public function __construct(array $rootAliases = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) { @@ -66,6 +67,10 @@ class RepositorySet */ public function addRepository(RepositoryInterface $repo) { + if ($this->pool) { + throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); + } + if ($repo instanceof CompositeRepository) { $repos = $repo->getRepositories(); } else { @@ -135,10 +140,6 @@ class RepositorySet */ public function createPool() { - if ($this->pool) { - return $this->pool; - } - $this->pool = new Pool($this->acceptableStabilities, $this->stabilityFlags, $this->filterRequires); foreach ($this->repositories as $repository) { @@ -148,13 +149,12 @@ class RepositorySet return $this->pool; } - // TODO get rid of this function - public function getPoolTemp() + /** + * Access the pool object after it has been created, relevant for plugins which need to read info from the pool + * @return Pool + */ + public function getPool() { - if (!$this->pool) { - return $this->createPool(); - } else { - return $this->pool; - } + return $this->pool; } } diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 34a1c092b..dedb452e2 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -47,7 +47,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId()); $expected = array($packageA->getId()); @@ -63,7 +63,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -79,7 +79,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -95,7 +95,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); @@ -112,7 +112,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -129,7 +129,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repoInstalled); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageAInstalled->getId()); $expected = array($packageA->getId()); @@ -150,7 +150,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($otherRepository); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageAImportant->getId()); $expected = array($packageAImportant->getId()); @@ -173,7 +173,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repo1); $this->repositorySet->addRepository($repo2); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($package1->getId(), $package2->getId(), $package3->getId(), $package4->getId()); $expected = array($package2->getId()); @@ -185,7 +185,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repo2); $this->repositorySet->addRepository($repo1); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $expected = array($package4->getId()); $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); @@ -209,7 +209,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repoImportant); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $packages = $pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); $literals = array(); @@ -234,7 +234,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -253,7 +253,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -274,7 +274,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -290,7 +290,7 @@ class DefaultPolicyTest extends TestCase $repositorySet = new RepositorySet(array(), 'dev'); $repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -317,7 +317,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->getPoolTemp(); + $pool = $this->repositorySet->createPool(); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 1a43c5b60..e4b02968f 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -40,7 +40,6 @@ class SolverTest extends TestCase $this->request = new Request($this->repoSet); $this->policy = new DefaultPolicy; - $this->solver = new Solver($this->policy, $this->repoSet, $this->repoInstalled, new NullIO()); } public function testSolverInstallSingle() @@ -95,6 +94,8 @@ class SolverTest extends TestCase $this->repoSet->addRepository($repo1); $this->repoSet->addRepository($repo2); + $this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO()); + $this->request->install('foo'); $this->checkSolverResult(array( @@ -842,6 +843,8 @@ class SolverTest extends TestCase { $this->repoSet->addRepository($this->repoInstalled); $this->repoSet->addRepository($this->repo); + + $this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO()); } protected function checkSolverResult(array $expected) From 1747df97e756430c33a3722f194f7a804afbbf97 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 15:59:02 +0200 Subject: [PATCH 250/580] Create pool in show command to use policy, remove todos --- src/Composer/Command/ShowCommand.php | 6 ++++-- src/Composer/Installer.php | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 94ec78362..43546fdad 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -539,9 +539,11 @@ EOT $matches[$index] = $package->getId(); } + $pool = $repositorySet->createPool(); + // select preferred package according to policy rules - if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($repositorySet->getPoolTemp(), array(), $matches)) { // TODO get rid of the pool call - $matchedPackage = $repositorySet->getPoolTemp()->literalToPackage($preferred[0]); + if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) { + $matchedPackage = $pool->literalToPackage($preferred[0]); } return array($matchedPackage, $versions); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index c6f888493..029e6e18e 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -127,7 +127,7 @@ class Installer * @var array|null */ protected $updateWhitelist = null; - protected $whitelistDependencies = false; // TODO 2.0 rename to whitelistTransitiveDependencies + protected $whitelistTransitiveDependencies = false; protected $whitelistAllDependencies = false; /** @@ -1327,7 +1327,7 @@ class Installer foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; - $depPackages = $repositorySet->findPackages($packageName); // TODO does this need replacers/providers? + $depPackages = $repositorySet->findPackages($packageName, null, false); $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); @@ -1359,14 +1359,14 @@ class Installer $seen[spl_object_hash($package)] = true; $this->updateWhitelist[$package->getName()] = true; - if (!$this->whitelistDependencies && !$this->whitelistAllDependencies) { + if (!$this->whitelistTransitiveDependencies && !$this->whitelistAllDependencies) { continue; } $requires = $package->getRequires(); foreach ($requires as $require) { - $requirePackages = $repositorySet->findPackages($require->getTarget()); // TODO does this need replacers/providers? + $requirePackages = $repositorySet->findPackages($require->getTarget(), null, false); foreach ($requirePackages as $requirePackage) { if (isset($this->updateWhitelist[$requirePackage->getName()])) { @@ -1678,7 +1678,7 @@ class Installer */ public function setWhitelistTransitiveDependencies($updateTransitiveDependencies = true) { - $this->whitelistDependencies = (bool) $updateTransitiveDependencies; + $this->whitelistTransitiveDependencies = (bool) $updateTransitiveDependencies; return $this; } From 4c7d271a36ddc484b107a06f674b7eb932755334 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Tue, 11 Sep 2018 16:03:48 +0200 Subject: [PATCH 251/580] Remove deprecated function --- src/Composer/Installer.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 029e6e18e..8f6427a9a 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1659,14 +1659,6 @@ class Installer return $this; } - /** - * @deprecated use setWhitelistTransitiveDependencies instead - */ - public function setWhitelistDependencies($updateDependencies = true) - { - return $this->setWhitelistTransitiveDependencies($updateDependencies); - } - /** * Should dependencies of whitelisted packages (but not direct dependencies) be updated? * From c0f19f6c573550916373fbaae7245558ffb75af6 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 11:49:09 +0200 Subject: [PATCH 252/580] Move construction of pool from repo set into a pool builder Pool construction depends on the install request now, so only required packages get loaded, add some structure for future asynchronously loading composer repositories --- .../DependencyResolver/DefaultPolicy.php | 11 +- src/Composer/DependencyResolver/Pool.php | 117 ++------------ .../DependencyResolver/PoolBuilder.php | 147 ++++++++++++++++++ src/Composer/DependencyResolver/Problem.php | 4 +- src/Composer/DependencyResolver/Rule.php | 15 +- src/Composer/DependencyResolver/Solver.php | 2 +- .../SolverProblemsException.php | 6 +- src/Composer/Installer.php | 4 +- src/Composer/Package/Locker.php | 4 +- .../Repository/AsyncRepositoryInterface.php | 38 +++++ src/Composer/Repository/BaseRepository.php | 15 ++ .../Repository/LockArrayRepository.php | 25 +++ .../Repository/RepositoryInterface.php | 9 ++ src/Composer/Repository/RepositorySet.php | 41 +++-- .../DependencyResolver/DefaultPolicyTest.php | 31 ++-- .../Test/DependencyResolver/PoolTest.php | 79 +--------- .../Test/DependencyResolver/RuleSetTest.php | 17 +- .../Test/DependencyResolver/RuleTest.php | 20 +-- .../Test/DependencyResolver/SolverTest.php | 19 ++- tests/Composer/Test/InstallerTest.php | 1 - 20 files changed, 357 insertions(+), 248 deletions(-) create mode 100644 src/Composer/DependencyResolver/PoolBuilder.php create mode 100644 src/Composer/Repository/AsyncRepositoryInterface.php create mode 100644 src/Composer/Repository/LockArrayRepository.php diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 542c6e625..051bc7449 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -57,11 +57,6 @@ class DefaultPolicy implements PolicyInterface return $packages; } - public function getPriority(Pool $pool, PackageInterface $package) - { - return $pool->getPriority($package->getRepository()); - } - public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null) { $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals); @@ -168,7 +163,7 @@ class DefaultPolicy implements PolicyInterface return 1; } - return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1; + return ($pool->getPriority($a->id) > $pool->getPriority($b->id)) ? -1 : 1; } /** @@ -236,10 +231,10 @@ class DefaultPolicy implements PolicyInterface } if (null === $priority) { - $priority = $this->getPriority($pool, $package); + $priority = $pool->getPriority($package->id); } - if ($this->getPriority($pool, $package) != $priority) { + if ($pool->getPriority($package->id) != $priority) { break; } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index ee4bace97..6ad4d9f31 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -27,7 +27,7 @@ use Composer\Repository\PlatformRepository; use Composer\Package\PackageInterface; /** - * A package pool contains repositories that provide packages. + * A package pool contains all packages for dependency resolution * * @author Nils Adermann * @author Jordi Boggiano @@ -41,23 +41,18 @@ class Pool implements \Countable const MATCH_REPLACE = 3; const MATCH_FILTERED = 4; - protected $repositories = array(); protected $providerRepos = array(); protected $packages = array(); protected $packageByName = array(); protected $packageByExactName = array(); - protected $acceptableStabilities; - protected $stabilityFlags; + protected $priorities = array(); protected $versionParser; protected $providerCache = array(); protected $filterRequires; protected $whitelist = null; - protected $id = 1; - public function __construct(array $acceptableStabilities, array $stabilityFlags = array(), array $filterRequires = array()) + public function __construct(array $filterRequires = array()) { - $this->acceptableStabilities = $acceptableStabilities; - $this->stabilityFlags = $stabilityFlags; $this->filterRequires = $filterRequires; $this->versionParser = new VersionParser; } @@ -68,76 +63,24 @@ class Pool implements \Countable $this->providerCache = array(); } - /** - * Adds a repository and its packages to this package pool - * - * @param RepositoryInterface $repo A package repository - * @param array $rootAliases - */ - public function addRepository(RepositoryInterface $repo, $rootAliases = array()) + public function setPackages(array $packages, array $priorities = array()) { - if ($repo instanceof CompositeRepository) { - $repos = $repo->getRepositories(); - } else { - $repos = array($repo); - } + $this->priorities = $priorities; + $this->packages = $packages; - foreach ($repos as $repo) { - $this->repositories[] = $repo; + foreach ($this->packages as $package) { + $names = $package->getNames(); + $this->packageByExactName[$package->getName()][$package->id] = $package; - $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface; - - if ($repo instanceof ComposerRepository && $repo->hasProviders()) { - $this->providerRepos[] = $repo; - $repo->setRootAliases($rootAliases); - $repo->resetPackageIds(); - } else { - foreach ($repo->getPackages() as $package) { - $names = $package->getNames(); - $stability = $package->getStability(); - if ($exempt || $this->isPackageAcceptable($names, $stability)) { - $package->setId($this->id++); - $this->packages[] = $package; - $this->packageByExactName[$package->getName()][$package->id] = $package; - - foreach ($names as $provided) { - $this->packageByName[$provided][] = $package; - } - - // handle root package aliases - $name = $package->getName(); - if (isset($rootAliases[$name][$package->getVersion()])) { - $alias = $rootAliases[$name][$package->getVersion()]; - if ($package instanceof AliasPackage) { - $package = $package->getAliasOf(); - } - $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); - $aliasPackage->setRootPackageAlias(true); - $aliasPackage->setId($this->id++); - - $package->getRepository()->addPackage($aliasPackage); - $this->packages[] = $aliasPackage; - $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage; - - foreach ($aliasPackage->getNames() as $name) { - $this->packageByName[$name][] = $aliasPackage; - } - } - } - } + foreach ($names as $provided) { + $this->packageByName[$provided][] = $package; } } } - public function getPriority(RepositoryInterface $repo) + public function getPriority($id) { - $priority = array_search($repo, $this->repositories, true); - - if (false === $priority) { - throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool."); - } - - return -$priority; + return $this->priorities[$id - 1]; } /** @@ -191,25 +134,12 @@ class Pool implements \Countable { $candidates = array(); - foreach ($this->providerRepos as $repo) { - foreach ($repo->whatProvides($name, $bypassFilters, array($this, 'isPackageAcceptable')) as $candidate) { - $candidates[] = $candidate; - if ($candidate->id < 1) { - $candidate->setId($this->id++); - $this->packages[$this->id - 2] = $candidate; - } - } - } - if ($mustMatchName) { - $candidates = array_filter($candidates, function ($candidate) use ($name) { - return $candidate->getName() == $name; - }); if (isset($this->packageByExactName[$name])) { - $candidates = array_merge($candidates, $this->packageByExactName[$name]); + $candidates = $this->packageByExactName[$name]; } } elseif (isset($this->packageByName[$name])) { - $candidates = array_merge($candidates, $this->packageByName[$name]); + $candidates = $this->packageByName[$name]; } $matches = $provideMatches = array(); @@ -287,23 +217,6 @@ class Pool implements \Countable return $prefix.' '.$package->getPrettyString(); } - public function isPackageAcceptable($name, $stability) - { - foreach ((array) $name as $n) { - // allow if package matches the global stability requirement and has no exception - if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) { - return true; - } - - // allow if package matches the package-specific stability flag - if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) { - return true; - } - } - - return false; - } - /** * Checks if the package matches the given constraint directly or through * provided or replaced packages diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php new file mode 100644 index 000000000..2ec6dd680 --- /dev/null +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -0,0 +1,147 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; +use Composer\Package\PackageInterface; +use Composer\Repository\AsyncRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\LockArrayRepository; +use Composer\Repository\PlatformRepository; + +/** + * @author Nils Adermann + */ +class PoolBuilder +{ + private $isPackageAcceptableCallable; + private $filterRequires; + private $rootAliases; + + private $loadedNames = array(); + + private $id = 1; + private $packages = array(); + private $priorities = array(); + + public function __construct($isPackageAcceptableCallable, array $filterRequires = array()) + { + $this->isPackageAcceptableCallable = $isPackageAcceptableCallable; + $this->filterRequires = $filterRequires; + } + + public function buildPool(array $repositories, array $rootAliases, Request $request) + { + $this->pool = new Pool($this->filterRequires); + $this->rootAliases = $rootAliases; + + // TODO do we really want the request here? kind of want a root requirements thingy instead + $loadNames = array(); + foreach ($request->getJobs() as $job) { + switch ($job['cmd']) { + case 'install': + $loadNames[$job['packageName']] = true; + break; + } + } + + foreach ($repositories as $repository) { + if ($repository instanceof ComposerRepository && $repository->hasProviders()) { + $this->providerRepos[] = $repository; + $repository->setRootAliases($this->rootAliases); + $repository->resetPackageIds(); + } + } + + while (!empty($loadNames)) { + $loadIds = array(); + foreach ($repositories as $key => $repository) { + if ($repository instanceof AsyncRepositoryInterface) { + $loadIds[$key] = $repository->requestPackages($loadNames); + } + } + + foreach ($loadNames as $name => $void) { + $this->loadedNames[$name] = true; + } + + $newLoadNames = array(); + foreach ($repositories as $key => $repository) { + if ($repository instanceof PlatformRepository || $repository instanceof InstalledRepositoryInterface) { + continue; + } + + if ($repository instanceof AsyncRepositoryInterface) { + $packages = $repository->returnPackages($loadIds[$key]); + } else { + $packages = $repository->loadPackages($loadNames); + } + + foreach ($packages as $package) { + if (call_user_func($this->isPackageAcceptableCallable, $package->getNames(), $package->getStability())) { + $newLoadNames += $this->loadPackage($package, $key); + } + } + } + + $loadNames = $newLoadNames; + } + + foreach ($repositories as $key => $repository) { + if ($repository instanceof PlatformRepository || + $repository instanceof InstalledRepositoryInterface) { + foreach ($repository->getPackages() as $package) { + $this->loadPackage($package, $key); + } + } + } + + $this->pool->setPackages($this->packages, $this->priorities); + + return $this->pool; + } + + private function loadPackage(PackageInterface $package, $repoIndex) + { + $package->setId($this->id++); + $this->packages[] = $package; + $this->priorities[$this->id - 2] = -$repoIndex; + + // handle root package aliases + $name = $package->getName(); + if (isset($this->rootAliases[$name][$package->getVersion()])) { + $alias = $this->rootAliases[$name][$package->getVersion()]; + if ($package instanceof AliasPackage) { + $package = $package->getAliasOf(); + } + $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); + $aliasPackage->setRootPackageAlias(true); + $aliasPackage->setId($this->id++); + + $package->getRepository()->addPackage($aliasPackage); // TODO do we need this? + $this->packages[] = $aliasPackage; + } + + $loadNames = array(); + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + if (!isset($this->loadedNames[$require])) { + $loadNames[$require] = true; + } + } + + return $loadNames; + } +} + diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index de24b0991..7cd3a9813 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -71,7 +71,7 @@ class Problem * @param array $installedMap A map of all installed packages * @return string */ - public function getPrettyString(array $installedMap = array()) + public function getPrettyString(array $installedMap = array(), array $learnedPool = array()) { $reasons = call_user_func_array('array_merge', array_reverse($this->reasons)); @@ -168,7 +168,7 @@ class Problem $messages[] = $this->jobToText($job); } elseif ($rule) { if ($rule instanceof Rule) { - $messages[] = $rule->getPrettyString($this->pool, $installedMap); + $messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool); } } } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 1fe8cbd30..a627de61c 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -126,7 +126,7 @@ abstract class Rule abstract public function isAssertion(); - public function getPrettyString(Pool $pool, array $installedMap = array()) + public function getPrettyString(Pool $pool, array $installedMap = array(), array $learnedPool = array()) { $literals = $this->getLiterals(); @@ -230,7 +230,18 @@ abstract class Rule case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: return $ruleText; case self::RULE_LEARNED: - return 'Conclusion: '.$ruleText; + // TODO not sure this is a good idea, most of these rules should be listed in the problem anyway + $learnedString = '(learned rule, '; + if (isset($learnedPool[$this->reasonData])) { + foreach ($learnedPool[$this->reasonData] as $learnedRule) { + $learnedString .= $learnedRule->getPrettyString($pool, $installedMap, $learnedPool); + } + } else { + $learnedString .= 'reasoning unavailable'; + } + $learnedString .= ')'; + + return 'Conclusion: '.$ruleText.' '.$learnedString; case self::RULE_PACKAGE_ALIAS: return $ruleText; default: diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 959f1dc4c..aa5432188 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -244,7 +244,7 @@ class Solver } if ($this->problems) { - throw new SolverProblemsException($this->problems, $this->installedMap); + throw new SolverProblemsException($this->problems, $this->installedMap, $this->learnedPool); } $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions); diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index 142895697..0184bba9c 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -21,11 +21,13 @@ class SolverProblemsException extends \RuntimeException { protected $problems; protected $installedMap; + protected $learnedPool; - public function __construct(array $problems, array $installedMap) + public function __construct(array $problems, array $installedMap, array $learnedPool) { $this->problems = $problems; $this->installedMap = $installedMap; + $this->learnedPool = $learnedPool; parent::__construct($this->createMessage(), 2); } @@ -35,7 +37,7 @@ class SolverProblemsException extends \RuntimeException $text = "\n"; $hasExtensionProblems = false; foreach ($this->problems as $i => $problem) { - $text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n"; + $text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap, $this->learnedPool)."\n"; if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) { $hasExtensionProblems = true; diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8f6427a9a..95d5b6605 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -467,7 +467,7 @@ class Installer $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request); - $pool = $repositorySet->createPool(); + $pool = $repositorySet->createPool($request); // force dev packages to have the latest links if we update or install from a (potentially new) lock $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links'); @@ -701,7 +701,7 @@ class Installer // solve deps to see which get removed $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request); - $solver = new Solver($policy, $repositorySet->createPool(), $installedRepo, $this->io); + $solver = new Solver($policy, $repositorySet->createPool($request), $installedRepo, $this->io); $ops = $solver->solve($request, $this->ignorePlatformReqs); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $repositorySet, $installedRepo, $request, $ops); diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 57ec74233..405d43261 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -14,9 +14,9 @@ namespace Composer\Package; use Composer\Json\JsonFile; use Composer\Installer\InstallationManager; +use Composer\Repository\LockArrayRepository; use Composer\Repository\RepositoryManager; use Composer\Util\ProcessExecutor; -use Composer\Repository\ArrayRepository; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Loader\ArrayLoader; use Composer\Util\Git as GitUtil; @@ -150,7 +150,7 @@ class Locker public function getLockedRepository($withDevReqs = false) { $lockData = $this->getLockData(); - $packages = new ArrayRepository(); + $packages = new LockArrayRepository(); $lockedPackages = $lockData['packages']; if ($withDevReqs) { diff --git a/src/Composer/Repository/AsyncRepositoryInterface.php b/src/Composer/Repository/AsyncRepositoryInterface.php new file mode 100644 index 000000000..91f543dbf --- /dev/null +++ b/src/Composer/Repository/AsyncRepositoryInterface.php @@ -0,0 +1,38 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\PackageInterface; + +/** + * Repository interface. + * + * @author Nils Adermann + * @author Konstantin Kudryashov + * @author Jordi Boggiano + */ +interface AsyncRepositoryInterface +{ + /** + * @param array $names Names of packages to retrieve data for + * @return scalar Id to be passed to later loadPackages call + */ + public function requestPackages(array $names); + + /** + * @param array $names + * @return scalar id for load call + */ + public function returnPackages($loadId); +} + diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index 2b30b63cd..e9e97bb91 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -24,6 +24,21 @@ use Composer\Package\Link; */ abstract class BaseRepository implements RepositoryInterface { + // TODO should this stay here? some repos need a better implementation + public function loadPackages(array $packageNameMap) + { + $packages = $this->getPackages(); + + $result = array(); + foreach ($packages as $package) { + if (isset($packageNameMap[$package->getName()])) { + $result[] = $package; + } + } + + return $result; + } + /** * Returns a list of links causing the requested needle packages to be installed, as an associative array with the * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship diff --git a/src/Composer/Repository/LockArrayRepository.php b/src/Composer/Repository/LockArrayRepository.php new file mode 100644 index 000000000..0ccc998d3 --- /dev/null +++ b/src/Composer/Repository/LockArrayRepository.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +/** + * Lock array repository. + * + * Regular array repository, only uses a different type to identify the lock file as the source of info + * + * @author Nils Adermann + */ +class LockArrayRepository extends ArrayRepository implements RepositoryInterface +{ +} + diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 9a2aaf3b5..703f92e35 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -55,6 +55,7 @@ interface RepositoryInterface extends \Countable */ public function findPackages($name, $constraint = null); + // TODO this should really not be in this generic interface anymore /** * Returns list of registered packages. * @@ -62,6 +63,14 @@ interface RepositoryInterface extends \Countable */ public function getPackages(); + /** + * Returns list of registered packages with the supplied name + * + * @param bool[] $packageNameMap + * @return PackageInterface[] + */ + public function loadPackages(array $packageNameMap); + /** * Searches the repository for packages containing the query * diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 4bea054f9..bac63dd36 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -13,6 +13,8 @@ namespace Composer\Repository; use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\PoolBuilder; +use Composer\DependencyResolver\Request; use Composer\Package\BasePackage; use Composer\Package\Version\VersionParser; use Composer\Repository\CompositeRepository; @@ -31,9 +33,6 @@ class RepositorySet /** @var RepositoryInterface[] */ private $repositories = array(); - /** @var ComposerRepository[] */ - private $providerRepos = array(); - private $acceptableStabilities; private $stabilityFlags; protected $filterRequires; @@ -79,9 +78,6 @@ class RepositorySet foreach ($repos as $repo) { $this->repositories[] = $repo; - if ($repo instanceof ComposerRepository && $repo->hasProviders()) { - $this->providerRepos[] = $repo; - } } } @@ -133,20 +129,43 @@ class RepositorySet return $candidates; } + public function getPriority(RepositoryInterface $repo) + { + $priority = array_search($repo, $this->repositories, true); + + if (false === $priority) { + throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool."); + } + + return -$priority; + } + /** * Create a pool for dependency resolution from the packages in this repository set. * * @return Pool */ - public function createPool() + public function createPool(Request $request) { - $this->pool = new Pool($this->acceptableStabilities, $this->stabilityFlags, $this->filterRequires); + $poolBuilder = new PoolBuilder(array($this, 'isPackageAcceptable'), $this->filterRequires); - foreach ($this->repositories as $repository) { - $this->pool->addRepository($repository, $this->rootAliases); + return $this->pool = $poolBuilder->buildPool($this->repositories, $this->rootAliases, $request); + } + + // TODO unify this with above in some simpler version without "request"? + public function createPoolForPackage($packageName) + { + return $this->createPoolForPackages(array($packageName)); + } + + public function createPoolForPackages($packageNames) + { + $request = new Request(); + foreach ($packageNames as $packageName) { + $request->install($packageName); } - return $this->pool; + return $this->createPool($request); } /** diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index dedb452e2..c1637b8e4 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -21,6 +21,7 @@ use Composer\Package\AliasPackage; use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\TestCase; +use http\Env\Request; class DefaultPolicyTest extends TestCase { @@ -47,7 +48,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA->getId()); $expected = array($packageA->getId()); @@ -63,7 +64,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -79,7 +80,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -95,7 +96,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.1-alpha')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); @@ -112,7 +113,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '1.0.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA2->getId()); @@ -129,7 +130,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repoInstalled); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA->getId(), $packageAInstalled->getId()); $expected = array($packageA->getId()); @@ -150,7 +151,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($otherRepository); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA->getId(), $packageAImportant->getId()); $expected = array($packageAImportant->getId()); @@ -173,7 +174,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repo1); $this->repositorySet->addRepository($repo2); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($package1->getId(), $package2->getId(), $package3->getId(), $package4->getId()); $expected = array($package2->getId()); @@ -185,7 +186,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repo2); $this->repositorySet->addRepository($repo1); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $expected = array($package4->getId()); $selected = $this->policy->selectPreferredPackages($pool, array(), $literals); @@ -209,7 +210,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($repoImportant); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $packages = $pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); $literals = array(); @@ -234,7 +235,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackages(array('A', 'B')); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -253,7 +254,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackages(array('A', 'B')); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -274,7 +275,7 @@ class DefaultPolicyTest extends TestCase $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackages(array('vendor-a/replacer', 'vendor-b/replacer')); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -290,7 +291,7 @@ class DefaultPolicyTest extends TestCase $repositorySet = new RepositorySet(array(), 'dev'); $repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackages(array('vendor-a/replacer', 'vendor-b/replacer')); $literals = array($packageA->getId(), $packageB->getId()); $expected = $literals; @@ -317,7 +318,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0')); $this->repositorySet->addRepository($this->repo); - $pool = $this->repositorySet->createPool(); + $pool = $this->repositorySet->createPoolForPackage('A'); $literals = array($packageA1->getId(), $packageA2->getId()); $expected = array($packageA1->getId()); diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 5169586f6..74767c525 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -22,90 +22,25 @@ class PoolTest extends TestCase public function testPool() { $pool = $this->createPool(); - $repo = new ArrayRepository; $package = $this->getPackage('foo', '1'); - $repo->addPackage($package); - $pool->addRepository($repo); + $pool->setPackages(array($package)); $this->assertEquals(array($package), $pool->whatProvides('foo')); $this->assertEquals(array($package), $pool->whatProvides('foo')); } - public function testPoolIgnoresIrrelevantPackages() - { - $pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE), array('bar' => BasePackage::STABILITY_BETA)); - $repo = new ArrayRepository; - $repo->addPackage($package = $this->getPackage('bar', '1')); - $repo->addPackage($betaPackage = $this->getPackage('bar', '1-beta')); - $repo->addPackage($alphaPackage = $this->getPackage('bar', '1-alpha')); - $repo->addPackage($package2 = $this->getPackage('foo', '1')); - $repo->addPackage($rcPackage2 = $this->getPackage('foo', '1rc')); - - $pool->addRepository($repo); - - $this->assertEquals(array($package, $betaPackage), $pool->whatProvides('bar')); - $this->assertEquals(array($package2), $pool->whatProvides('foo')); - } - - /** - * @expectedException \RuntimeException - */ - public function testGetPriorityForNotRegisteredRepository() - { - $pool = $this->createPool(); - $repository = new ArrayRepository; - - $pool->getPriority($repository); - } - - public function testGetPriorityWhenRepositoryIsRegistered() - { - $pool = $this->createPool(); - $firstRepository = new ArrayRepository; - $pool->addRepository($firstRepository); - $secondRepository = new ArrayRepository; - $pool->addRepository($secondRepository); - - $firstPriority = $pool->getPriority($firstRepository); - $secondPriority = $pool->getPriority($secondRepository); - - $this->assertEquals(0, $firstPriority); - $this->assertEquals(-1, $secondPriority); - } - - public function testWhatProvidesSamePackageForDifferentRepositories() - { - $pool = $this->createPool(); - $firstRepository = new ArrayRepository; - $secondRepository = new ArrayRepository; - - $firstPackage = $this->getPackage('foo', '1'); - $secondPackage = $this->getPackage('foo', '1'); - $thirdPackage = $this->getPackage('foo', '2'); - - $firstRepository->addPackage($firstPackage); - $secondRepository->addPackage($secondPackage); - $secondRepository->addPackage($thirdPackage); - - $pool->addRepository($firstRepository); - $pool->addRepository($secondRepository); - - $this->assertEquals(array($firstPackage, $secondPackage, $thirdPackage), $pool->whatProvides('foo')); - } - public function testWhatProvidesPackageWithConstraint() { $pool = $this->createPool(); - $repository = new ArrayRepository; $firstPackage = $this->getPackage('foo', '1'); $secondPackage = $this->getPackage('foo', '2'); - $repository->addPackage($firstPackage); - $repository->addPackage($secondPackage); - - $pool->addRepository($repository); + $pool->setPackages(array( + $firstPackage, + $secondPackage, + )); $this->assertEquals(array($firstPackage, $secondPackage), $pool->whatProvides('foo')); $this->assertEquals(array($secondPackage), $pool->whatProvides('foo', $this->getVersionConstraint('==', '2'))); @@ -114,11 +49,9 @@ class PoolTest extends TestCase public function testPackageById() { $pool = $this->createPool(); - $repository = new ArrayRepository; $package = $this->getPackage('foo', '1'); - $repository->addPackage($package); - $pool->addRepository($repository); + $pool->setPackages(array($package)); $this->assertSame($package, $pool->packageById(1)); } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index e9753c848..9acd45455 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -22,13 +22,6 @@ use Composer\TestCase; class RuleSetTest extends TestCase { - protected $pool; - - public function setUp() - { - $this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); - } - public function testAdd() { $rules = array( @@ -146,9 +139,11 @@ class RuleSetTest extends TestCase public function testPrettyString() { - $repo = new ArrayRepository; - $repo->addPackage($p = $this->getPackage('foo', '2.1')); - $this->pool->addRepository($repo); + $pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); + $pool->setPackages(array( + $p = $this->getPackage('foo', '2.1'), + )); + $p->setId(1); $ruleSet = new RuleSet; $literal = $p->getId(); @@ -156,7 +151,7 @@ class RuleSetTest extends TestCase $ruleSet->add($rule, RuleSet::TYPE_JOB); - $this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($this->pool)); + $this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($pool)); } private function getRuleMock() diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index c9b3dbe1a..cb83266c8 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -22,13 +22,6 @@ use Composer\TestCase; class RuleTest extends TestCase { - protected $pool; - - public function setUp() - { - $this->pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); - } - public function testGetHash() { $rule = new GenericRule(array(123), Rule::RULE_JOB_INSTALL, null); @@ -100,13 +93,16 @@ class RuleTest extends TestCase public function testPrettyString() { - $repo = new ArrayRepository; - $repo->addPackage($p1 = $this->getPackage('foo', '2.1')); - $repo->addPackage($p2 = $this->getPackage('baz', '1.1')); - $this->pool->addRepository($repo); + $pool = new Pool(array('stable' => BasePackage::STABILITY_STABLE)); + $pool->setPackages(array( + $p1 = $this->getPackage('foo', '2.1'), + $p2 = $this->getPackage('baz', '1.1'), + )); + $p1->setId(1); + $p2->setId(2); $rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_JOB_INSTALL, null); - $this->assertEquals('Install command rule (don\'t install baz 1.1|install foo 2.1)', $rule->getPrettyString($this->pool)); + $this->assertEquals('Install command rule (don\'t install baz 1.1|install foo 2.1)', $rule->getPrettyString($pool)); } } diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index e4b02968f..0d221a8ab 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -20,6 +20,7 @@ use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; +use Composer\Repository\InstalledArrayRepository; use Composer\Repository\RepositorySet; use Composer\TestCase; use Composer\Semver\Constraint\MultiConstraint; @@ -31,12 +32,13 @@ class SolverTest extends TestCase protected $repoInstalled; protected $request; protected $policy; + protected $solver; public function setUp() { $this->repoSet = new RepositorySet(array()); $this->repo = new ArrayRepository; - $this->repoInstalled = new ArrayRepository; + $this->repoInstalled = new InstalledArrayRepository; $this->request = new Request($this->repoSet); $this->policy = new DefaultPolicy; @@ -71,6 +73,7 @@ class SolverTest extends TestCase $this->request->install('B', $this->getVersionConstraint('==', '1')); + $this->createSolver(); try { $transaction = $this->solver->solve($this->request); $this->fail('Unsolvable conflict did not result in exception.'); @@ -94,8 +97,6 @@ class SolverTest extends TestCase $this->repoSet->addRepository($repo1); $this->repoSet->addRepository($repo2); - $this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO()); - $this->request->install('foo'); $this->checkSolverResult(array( @@ -447,6 +448,7 @@ class SolverTest extends TestCase // must explicitly pick the provider, so error in this case $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException'); + $this->createSolver(); $this->solver->solve($this->request); } @@ -480,6 +482,7 @@ class SolverTest extends TestCase $this->request->install('A'); $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException'); + $this->createSolver(); $this->solver->solve($this->request); } @@ -652,6 +655,7 @@ class SolverTest extends TestCase $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException'); + $this->createSolver(); $this->solver->solve($this->request); } @@ -668,6 +672,7 @@ class SolverTest extends TestCase $this->request->install('A'); $this->request->install('B'); + $this->createSolver(); try { $transaction = $this->solver->solve($this->request); $this->fail('Unsolvable conflict did not result in exception.'); @@ -697,6 +702,7 @@ class SolverTest extends TestCase $this->request->install('A'); + $this->createSolver(); try { $transaction = $this->solver->solve($this->request); $this->fail('Unsolvable conflict did not result in exception.'); @@ -744,6 +750,7 @@ class SolverTest extends TestCase $this->request->install('A'); + $this->createSolver(); try { $transaction = $this->solver->solve($this->request); $this->fail('Unsolvable conflict did not result in exception.'); @@ -843,12 +850,16 @@ class SolverTest extends TestCase { $this->repoSet->addRepository($this->repoInstalled); $this->repoSet->addRepository($this->repo); + } - $this->solver = new Solver($this->policy, $this->repoSet->createPool(), $this->repoInstalled, new NullIO()); + protected function createSolver() + { + $this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), $this->repoInstalled, new NullIO()); } protected function checkSolverResult(array $expected) { + $this->createSolver(); $transaction = $this->solver->solve($this->request); $result = array(); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 8614495ee..562a71518 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -30,7 +30,6 @@ use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatter; use Composer\TestCase; -use Composer\IO\BufferIO; class InstallerTest extends TestCase { From 96c812fb2479bdbb688ae750c637d7a66ad11b22 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 13:27:10 +0200 Subject: [PATCH 253/580] Properly buffer installer test output to display as errors if necessary --- tests/Composer/Test/InstallerTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 562a71518..a244589af 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -14,6 +14,7 @@ namespace Composer\Test; use Composer\Installer; use Composer\Console\Application; +use Composer\IO\BufferIO; use Composer\Json\JsonFile; use Composer\Util\Filesystem; use Composer\Repository\ArrayRepository; @@ -56,7 +57,7 @@ class InstallerTest extends TestCase */ public function testInstaller(RootPackageInterface $rootPackage, $repositories, array $options) { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $io = new BufferIO('', OutputInterface::VERBOSITY_NORMAL, new OutputFormatter(false)); $downloadManager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($io)) @@ -81,7 +82,9 @@ class InstallerTest extends TestCase $installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator); $result = $installer->run(); - $this->assertSame(0, $result); + + $output = str_replace("\r", '', $io->getOutput()); + $this->assertEquals(0, $result, $output); $expectedInstalled = isset($options['install']) ? $options['install'] : array(); $expectedUpdated = isset($options['update']) ? $options['update'] : array(); From 019ebee185a72745d284473dc9d27e50a186c69b Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 13:56:13 +0200 Subject: [PATCH 254/580] Add missing use statement to package event to fix install --no-dev --- src/Composer/Installer/PackageEvent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Installer/PackageEvent.php b/src/Composer/Installer/PackageEvent.php index a563a91ba..d2f427a92 100644 --- a/src/Composer/Installer/PackageEvent.php +++ b/src/Composer/Installer/PackageEvent.php @@ -18,6 +18,7 @@ use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Request; use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositorySet; /** * The Package Event. From 261efe1e8e957151b910d433721e0cecd818354e Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 14:11:26 +0200 Subject: [PATCH 255/580] Implement loadPackages on Composer repositories with providers --- .../DependencyResolver/PoolBuilder.php | 5 ++++- src/Composer/Repository/BaseRepository.php | 4 ++-- .../Repository/ComposerRepository.php | 20 +++++++++++++++++++ .../Repository/RepositoryInterface.php | 3 ++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 2ec6dd680..32e14e983 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -16,6 +16,7 @@ use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\PackageInterface; use Composer\Repository\AsyncRepositoryInterface; +use Composer\Repository\ComposerRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\LockArrayRepository; use Composer\Repository\PlatformRepository; @@ -83,9 +84,11 @@ class PoolBuilder } if ($repository instanceof AsyncRepositoryInterface) { + // TODO ispackageacceptablecallable in here? $packages = $repository->returnPackages($loadIds[$key]); } else { - $packages = $repository->loadPackages($loadNames); + // TODO should we really pass the callable into here? + $packages = $repository->loadPackages($loadNames, $this->isPackageAcceptableCallable); } foreach ($packages as $package) { diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index e9e97bb91..172fe16b8 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -25,13 +25,13 @@ use Composer\Package\Link; abstract class BaseRepository implements RepositoryInterface { // TODO should this stay here? some repos need a better implementation - public function loadPackages(array $packageNameMap) + public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) { $packages = $this->getPackages(); $result = array(); foreach ($packages as $package) { - if (isset($packageNameMap[$package->getName()])) { + if (isset($packageNameMap[$package->getName()]) && call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())) { $result[] = $package; } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index ea583ede6..739027628 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -194,6 +194,26 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return parent::getPackages(); } + public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) + { + if (!$this->hasProviders()) { + // TODO build more efficient version of this + return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable); + } + + $packages = array(); + foreach ($packageNameMap as $name => $void) { + $matches = array(); + foreach ($this->whatProvides($name, false, $isPackageAcceptableCallable) as $match) { + if ($match->getName() === $name) { + $matches[] = $match; + } + } + $packages = array_merge($packages, $matches); + } + return $packages; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 703f92e35..55b76d33d 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -67,9 +67,10 @@ interface RepositoryInterface extends \Countable * Returns list of registered packages with the supplied name * * @param bool[] $packageNameMap + * @param $isPackageAcceptableCallable * @return PackageInterface[] */ - public function loadPackages(array $packageNameMap); + public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable); /** * Searches the repository for packages containing the query From 81bb8f81ad0cc37602dde4a1edae841ca1ab0935 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 14:30:18 +0200 Subject: [PATCH 256/580] Set all package ids only once the pool is created They all get set in one place only and at a specific time when nothing else will possibly change them anymore --- src/Composer/DependencyResolver/Pool.php | 3 +++ src/Composer/DependencyResolver/PoolBuilder.php | 13 +------------ src/Composer/Repository/ComposerRepository.php | 10 ---------- .../Test/DependencyResolver/RuleSetTest.php | 1 - tests/Composer/Test/DependencyResolver/RuleTest.php | 2 -- 5 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 6ad4d9f31..b5aefc4f7 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -68,7 +68,10 @@ class Pool implements \Countable $this->priorities = $priorities; $this->packages = $packages; + $id = 1; + foreach ($this->packages as $package) { + $package->id = $id++; $names = $package->getNames(); $this->packageByExactName[$package->getName()][$package->id] = $package; diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 32e14e983..e51fce4eb 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -32,7 +32,6 @@ class PoolBuilder private $loadedNames = array(); - private $id = 1; private $packages = array(); private $priorities = array(); @@ -57,14 +56,6 @@ class PoolBuilder } } - foreach ($repositories as $repository) { - if ($repository instanceof ComposerRepository && $repository->hasProviders()) { - $this->providerRepos[] = $repository; - $repository->setRootAliases($this->rootAliases); - $repository->resetPackageIds(); - } - } - while (!empty($loadNames)) { $loadIds = array(); foreach ($repositories as $key => $repository) { @@ -117,9 +108,8 @@ class PoolBuilder private function loadPackage(PackageInterface $package, $repoIndex) { - $package->setId($this->id++); $this->packages[] = $package; - $this->priorities[$this->id - 2] = -$repoIndex; + $this->priorities[] = -$repoIndex; // handle root package aliases $name = $package->getName(); @@ -130,7 +120,6 @@ class PoolBuilder } $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); $aliasPackage->setRootPackageAlias(true); - $aliasPackage->setId($this->id++); $package->getRepository()->addPackage($aliasPackage); // TODO do we need this? $this->packages[] = $aliasPackage; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 739027628..c26ab8a62 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -297,16 +297,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $this->hasProviders; } - public function resetPackageIds() - { - foreach ($this->providersByUid as $package) { - if ($package instanceof AliasPackage) { - $package->getAliasOf()->setId(-1); - } - $package->setId(-1); - } - } - /** * @param string $name package name * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index 9acd45455..81712070b 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -143,7 +143,6 @@ class RuleSetTest extends TestCase $pool->setPackages(array( $p = $this->getPackage('foo', '2.1'), )); - $p->setId(1); $ruleSet = new RuleSet; $literal = $p->getId(); diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index cb83266c8..bc081f04a 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -98,8 +98,6 @@ class RuleTest extends TestCase $p1 = $this->getPackage('foo', '2.1'), $p2 = $this->getPackage('baz', '1.1'), )); - $p1->setId(1); - $p2->setId(2); $rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_JOB_INSTALL, null); From 902cb290e792fdc3a4ff6a7af23ce7fea54b7acc Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 16:32:14 +0200 Subject: [PATCH 257/580] Only load package versions which fit the root composer.json constraints --- .../DependencyResolver/PoolBuilder.php | 4 ++-- src/Composer/Repository/BaseRepository.php | 20 ++++++++++++++++--- .../Repository/ComposerRepository.php | 19 ++++++++++++++---- .../Fixtures/installer/solver-problems.test | 8 +++++--- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index e51fce4eb..2fd3c8230 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -51,7 +51,7 @@ class PoolBuilder foreach ($request->getJobs() as $job) { switch ($job['cmd']) { case 'install': - $loadNames[$job['packageName']] = true; + $loadNames[$job['packageName']] = $job['constraint']; break; } } @@ -129,7 +129,7 @@ class PoolBuilder foreach ($package->getRequires() as $link) { $require = $link->getTarget(); if (!isset($this->loadedNames[$require])) { - $loadNames[$require] = true; + $loadNames[$require] = null; } } diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index 172fe16b8..d835d55fd 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -12,6 +12,7 @@ namespace Composer\Repository; +use Composer\Package\AliasPackage; use Composer\Package\RootPackageInterface; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; @@ -25,14 +26,27 @@ use Composer\Package\Link; abstract class BaseRepository implements RepositoryInterface { // TODO should this stay here? some repos need a better implementation - public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) + public function loadPackages(array $packageMap, $isPackageAcceptableCallable) { $packages = $this->getPackages(); $result = array(); foreach ($packages as $package) { - if (isset($packageNameMap[$package->getName()]) && call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())) { - $result[] = $package; + if (array_key_exists($package->getName(), $packageMap) && + (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) && + call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())) { + $result[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + } + + foreach ($packages as $package) { + if ($package instanceof AliasPackage) { + if (isset($result[spl_object_hash($package->getAliasOf())])) { + $result[spl_object_hash($package)] = $package; + } } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index c26ab8a62..09e2179d8 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -202,11 +202,22 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $packages = array(); - foreach ($packageNameMap as $name => $void) { + foreach ($packageNameMap as $name => $constraint) { $matches = array(); - foreach ($this->whatProvides($name, false, $isPackageAcceptableCallable) as $match) { - if ($match->getName() === $name) { - $matches[] = $match; + $candidates = $this->whatProvides($name, false, $isPackageAcceptableCallable); + foreach ($candidates as $candidate) { + if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) { + $matches[spl_object_hash($candidate)] = $candidate; + if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); + } + } + } + foreach ($candidates as $candidate) { + if ($candidate instanceof AliasPackage) { + if (isset($result[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate)] = $candidate; + } } } $packages = array_merge($packages, $matches); diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test index e0359a151..ce3c57ebb 100644 --- a/tests/Composer/Test/Fixtures/installer/solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/solver-problems.test @@ -42,14 +42,16 @@ Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - - The requested package unstable/package 2.* exists as unstable/package[1.0.0] but these are rejected by your constraint. + - The requested package unstable/package could not be found in any version, there may be a typo in the package name. Problem 2 - The requested package bogus could not be found in any version, there may be a typo in the package name. Problem 3 - - The requested package stable-requiree-excluded (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded[1.0.0] but these conflict with your requirements or minimum-stability. + - The requested package stable-requiree-excluded 1.0.1 exists as stable-requiree-excluded[1.0.0] but these are rejected by your constraint. Problem 4 + - The requested package stable-requiree-excluded (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded[1.0.0] but these conflict with your requirements or minimum-stability. + Problem 5 - Installation request for requirer 1.* -> satisfiable by requirer[1.0.0]. - - requirer 1.0.0 requires dependency 1.0.0 -> satisfiable by dependency[1.0.0] but these conflict with your requirements or minimum-stability. + - requirer 1.0.0 requires dependency 1.0.0 -> no matching package found. Potential causes: - A typo in the package name From 896d801a3080b2ebefa1b2e09f49093dc00f1f9d Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Wed, 12 Sep 2018 12:01:43 -0400 Subject: [PATCH 258/580] Fix: Bitbucket getChangeDate throws exception for branches containing a slash --- src/Composer/Repository/Vcs/BitbucketDriver.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 6361d6a04..64a954d43 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -189,6 +189,13 @@ abstract class BitbucketDriver extends VcsDriver return $this->fallbackDriver->getFileContent($file, $identifier); } + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + $resource = sprintf( 'https://api.bitbucket.org/1.0/repositories/%s/%s/raw/%s/%s', $this->owner, From 7c2d3518e5de241cefc403af8a9b31330b73ac6a Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Wed, 12 Sep 2018 19:03:57 +0200 Subject: [PATCH 259/580] Remove whitelisting of required package names, done by pool builder now --- src/Composer/DependencyResolver/Pool.php | 21 ------- .../DependencyResolver/RuleSetGenerator.php | 58 ------------------- 2 files changed, 79 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index b5aefc4f7..35feafde8 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -49,7 +49,6 @@ class Pool implements \Countable protected $versionParser; protected $providerCache = array(); protected $filterRequires; - protected $whitelist = null; public function __construct(array $filterRequires = array()) { @@ -57,12 +56,6 @@ class Pool implements \Countable $this->versionParser = new VersionParser; } - public function setWhitelist($whitelist) - { - $this->whitelist = $whitelist; - $this->providerCache = array(); - } - public function setPackages(array $packages, array $priorities = array()) { $this->priorities = $priorities; @@ -149,20 +142,6 @@ class Pool implements \Countable $nameMatch = false; foreach ($candidates as $candidate) { - $aliasOfCandidate = null; - - // alias packages are not white listed, make sure that the package - // being aliased is white listed - if ($candidate instanceof AliasPackage) { - $aliasOfCandidate = $candidate->getAliasOf(); - } - - if ($this->whitelist !== null && !$bypassFilters && ( - (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) || - ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id])) - )) { - continue; - } switch ($this->match($candidate, $name, $constraint, $bypassFilters)) { case self::MATCH_NONE: break; diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 60617ba43..54811e38b 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -26,7 +26,6 @@ class RuleSetGenerator protected $rules; protected $jobs; protected $installedMap; - protected $whitelistedMap; protected $addedMap; protected $conflictAddedMap; protected $addedPackages; @@ -147,41 +146,6 @@ class RuleSetGenerator $this->rules->add($newRule, $type); } - protected function whitelistFromPackage(PackageInterface $package) - { - $workQueue = new \SplQueue; - $workQueue->enqueue($package); - - while (!$workQueue->isEmpty()) { - $package = $workQueue->dequeue(); - if (isset($this->whitelistedMap[$package->id])) { - continue; - } - - $this->whitelistedMap[$package->id] = true; - - foreach ($package->getRequires() as $link) { - $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true); - - foreach ($possibleRequires as $require) { - $workQueue->enqueue($require); - } - } - - $obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true); - - foreach ($obsoleteProviders as $provider) { - if ($provider === $package) { - continue; - } - - if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { - $workQueue->enqueue($provider); - } - } - } - } - protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs) { $workQueue = new \SplQueue; @@ -290,20 +254,6 @@ class RuleSetGenerator return $impossible; } - protected function whitelistFromJobs() - { - foreach ($this->jobs as $job) { - switch ($job['cmd']) { - case 'install': - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true); - foreach ($packages as $package) { - $this->whitelistFromPackage($package); - } - break; - } - } - } - protected function addRulesForJobs($ignorePlatformReqs) { foreach ($this->jobs as $job) { @@ -344,14 +294,6 @@ class RuleSetGenerator $this->rules = new RuleSet; $this->installedMap = $installedMap; - $this->whitelistedMap = array(); - foreach ($this->installedMap as $package) { - $this->whitelistFromPackage($package); - } - $this->whitelistFromJobs(); - - $this->pool->setWhitelist($this->whitelistedMap); - $this->addedMap = array(); $this->conflictAddedMap = array(); $this->addedPackages = array(); From 0124e7b553b265c625800ef3035e4c7ea1849336 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 13 Sep 2018 09:27:30 +0200 Subject: [PATCH 260/580] Revert "add removePackage() to RepositoryInterface" This reverts commit cfb0d33c4525d30cd5c283d0c6ac4b389e8b3cd7. Fixes #7634 --- src/Composer/Repository/RepositoryInterface.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index d0ceb905a..9a2aaf3b5 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -71,11 +71,4 @@ interface RepositoryInterface extends \Countable * @return array[] an array of array('name' => '...', 'description' => '...') */ public function search($query, $mode = 0); - - /** - * Removes a package from the registered packages list. - * - * @param PackageInterface $package - */ - public function removePackage(PackageInterface $package); } From 4a8c416a024785eaa4094edf3a2eafec515972c3 Mon Sep 17 00:00:00 2001 From: Ahammar Yassine Date: Thu, 13 Sep 2018 11:17:30 +0000 Subject: [PATCH 261/580] Update ValidateCommand.php Skip publish and lock check even in strict check mode when the user want so. Example : `composer validate --no-check-lock --strict composer.json`. Issue : #7624 --- src/Composer/Command/ValidateCommand.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 52bba1838..b86fd92ea 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -95,9 +95,10 @@ EOT $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update`.'; } - $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); + $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true, $isStrict); - $exitCode = $errors || ($publishErrors && $checkPublish) || ($lockErrors && $checkLock) ? 2 : ($isStrict && $warnings ? 1 : 0); + // $errors include publish and lock errors when exists + $exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); if ($input->getOption('with-dependencies')) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); @@ -108,7 +109,7 @@ EOT list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); - $depCode = $errors || ($publishErrors && $checkPublish) ? 2 : ($isStrict && $warnings ? 1 : 0); + $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0); $exitCode = max($depCode, $exitCode); } } @@ -121,7 +122,7 @@ EOT return $exitCode; } - private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false) + private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false, $isStrict = false) { if (!$errors && !$publishErrors && !$warnings) { $io->write('' . $name . ' is valid'); @@ -141,16 +142,18 @@ EOT } // If checking publish errors, display them as errors, otherwise just show them as warnings + // Skip when it is a strict check and we don't want to check publish errors if ($checkPublish) { $errors = array_merge($errors, $publishErrors); - } else { + } elseif (!$isStrict) { $warnings = array_merge($warnings, $publishErrors); } // If checking lock errors, display them as errors, otherwise just show them as warnings + // Skip when it is a strict check and we don't want to check lock errors if ($checkLock) { $errors = array_merge($errors, $lockErrors); - } else { + } elseif (!$isStrict) { $warnings = array_merge($warnings, $lockErrors); } From b757c1952c2fdca5a4a745a87e03cbd38af69303 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 13 Sep 2018 15:23:51 +0200 Subject: [PATCH 262/580] Fix phpdoc --- src/Composer/Repository/AsyncRepositoryInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/AsyncRepositoryInterface.php b/src/Composer/Repository/AsyncRepositoryInterface.php index 91f543dbf..5804694ec 100644 --- a/src/Composer/Repository/AsyncRepositoryInterface.php +++ b/src/Composer/Repository/AsyncRepositoryInterface.php @@ -25,13 +25,13 @@ interface AsyncRepositoryInterface { /** * @param array $names Names of packages to retrieve data for - * @return scalar Id to be passed to later loadPackages call + * @return mixed Id to be passed to later loadPackages call */ public function requestPackages(array $names); /** * @param array $names - * @return scalar id for load call + * @return mixed id for load call */ public function returnPackages($loadId); } From f11c3573255c25f3798e4150fbbf649139472f2f Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 14 Sep 2018 14:39:24 +0200 Subject: [PATCH 263/580] Restore output of number of packages analyzed in solver --- src/Composer/Installer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 95d5b6605..cb4cc8c5d 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -491,6 +491,7 @@ class Installer $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations); + $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); // execute operations From 83efeaec5cda2f2fd75e7521cfd66bd511c4d019 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 14 Sep 2018 14:40:34 +0200 Subject: [PATCH 264/580] Attempt to prune versions which are impossible to install during pool building --- src/Composer/DependencyResolver/Pool.php | 8 ++-- .../DependencyResolver/PoolBuilder.php | 39 ++++++++++++++++++- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 35feafde8..355ba25d4 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -58,12 +58,12 @@ class Pool implements \Countable public function setPackages(array $packages, array $priorities = array()) { - $this->priorities = $priorities; - $this->packages = $packages; - $id = 1; - foreach ($this->packages as $package) { + foreach ($packages as $i => $package) { + $this->packages[] = $package; + $this->priorities[] = isset($priorities[$i]) ? $priorities[$i] : 0; + $package->id = $id++; $names = $package->getNames(); $this->packageByExactName[$package->getName()][$package->id] = $package; diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 2fd3c8230..9c5e48efa 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -20,6 +20,8 @@ use Composer\Repository\ComposerRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\LockArrayRepository; use Composer\Repository\PlatformRepository; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MultiConstraint; /** * @author Nils Adermann @@ -30,6 +32,9 @@ class PoolBuilder private $filterRequires; private $rootAliases; + private $aliasMap = array(); + private $nameConstraints = array(); + private $loadedNames = array(); private $packages = array(); @@ -52,6 +57,7 @@ class PoolBuilder switch ($job['cmd']) { case 'install': $loadNames[$job['packageName']] = $job['constraint']; + $this->nameConstraints[$job['packageName']] = $job['constraint'] ? new MultiConstraint(array($job['constraint']), false) : null; break; } } @@ -92,6 +98,17 @@ class PoolBuilder $loadNames = $newLoadNames; } + foreach ($this->packages as $i => $package) { + if (!$package instanceof AliasPackage && !isset($this->aliasMap[spl_object_hash($package)]) && isset($this->nameConstraints[$package->getName()])) { + $constraint = $this->nameConstraints[$package->getName()]; + + if ($constraint && !$constraint->matches(new Constraint('==', $package->getVersion()))) { + unset($this->packages[$i]); + unset($this->priorities[$i]); + } + } + } + foreach ($repositories as $key => $repository) { if ($repository instanceof PlatformRepository || $repository instanceof InstalledRepositoryInterface) { @@ -111,18 +128,26 @@ class PoolBuilder $this->packages[] = $package; $this->priorities[] = -$repoIndex; + if ($package instanceof AliasPackage) { + $this->aliasMap[spl_object_hash($package->getAliasOf())][] = $package; + } + // handle root package aliases $name = $package->getName(); if (isset($this->rootAliases[$name][$package->getVersion()])) { $alias = $this->rootAliases[$name][$package->getVersion()]; if ($package instanceof AliasPackage) { - $package = $package->getAliasOf(); + $basePackage = $package->getAliasOf(); + } else { + $basePackage = $package; } - $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); + $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); $aliasPackage->setRootPackageAlias(true); $package->getRepository()->addPackage($aliasPackage); // TODO do we need this? $this->packages[] = $aliasPackage; + $this->priorities[] = -$repoIndex; + $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][] = $aliasPackage; } $loadNames = array(); @@ -131,6 +156,16 @@ class PoolBuilder if (!isset($this->loadedNames[$require])) { $loadNames[$require] = null; } + if ($link->getConstraint()) { + if (!array_key_exists($require, $this->nameConstraints)) { + $this->nameConstraints[$require] = new MultiConstraint(array($link->getConstraint()), false); + } elseif ($this->nameConstraints[$require]) { + // TODO addConstraint function? + $this->nameConstraints[$require] = new MultiConstraint(array_merge(array($link->getConstraint()), $this->nameConstraints[$require]->getConstraints()), false); + } + } else { + $this->nameConstraints[$require] = null; + } } return $loadNames; From 537f4fbc3b487029b45c277744a34bc30890d2aa Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 14 Sep 2018 15:03:38 +0200 Subject: [PATCH 265/580] Prune unreachable required versions correctly for aliased packages In trials this seems pointless, so maybe better to skip aliases and reduce memory and cpu wasted on looking these things up --- .../DependencyResolver/PoolBuilder.php | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 9c5e48efa..8ab6e5244 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -99,12 +99,27 @@ class PoolBuilder } foreach ($this->packages as $i => $package) { - if (!$package instanceof AliasPackage && !isset($this->aliasMap[spl_object_hash($package)]) && isset($this->nameConstraints[$package->getName()])) { + // we check all alias related packages at once, so no need ot check individual aliases + // isset also checks non-null value + if (!$package instanceof AliasPackage && isset($this->nameConstraints[$package->getName()])) { $constraint = $this->nameConstraints[$package->getName()]; - if ($constraint && !$constraint->matches(new Constraint('==', $package->getVersion()))) { - unset($this->packages[$i]); - unset($this->priorities[$i]); + $aliasedPackages = array($i => $package); + if (isset($this->aliasMap[spl_object_hash($package)])) { + $aliasedPackages += $this->aliasMap[spl_object_hash($package)]; + } + + $found = false; + foreach ($aliasedPackages as $packageOrAlias) { + if ($constraint->matches(new Constraint('==', $packageOrAlias->getVersion()))) { + $found = true; + } + } + if (!$found) { + foreach ($aliasedPackages as $index => $packageOrAlias) { + unset($this->packages[$index]); + unset($this->priorities[$index]); + } } } } @@ -120,16 +135,21 @@ class PoolBuilder $this->pool->setPackages($this->packages, $this->priorities); + unset($this->aliasMap); + unset($this->loadedNames); + unset($this->nameConstraints); + return $this->pool; } private function loadPackage(PackageInterface $package, $repoIndex) { + $index = count($this->packages); $this->packages[] = $package; $this->priorities[] = -$repoIndex; if ($package instanceof AliasPackage) { - $this->aliasMap[spl_object_hash($package->getAliasOf())][] = $package; + $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package; } // handle root package aliases @@ -147,7 +167,7 @@ class PoolBuilder $package->getRepository()->addPackage($aliasPackage); // TODO do we need this? $this->packages[] = $aliasPackage; $this->priorities[] = -$repoIndex; - $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][] = $aliasPackage; + $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$index+1] = $aliasPackage; } $loadNames = array(); From 14c6c2c99fcf11e822607d78f813eadbab215140 Mon Sep 17 00:00:00 2001 From: Nick Wilde Date: Sun, 16 Sep 2018 13:12:14 -0700 Subject: [PATCH 266/580] Allow plugin commands to be run from child folders as well as core commands --- src/Composer/Console/Application.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index a7cf26921..e6ff7da9d 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -16,6 +16,7 @@ use Composer\IO\NullIO; use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; +use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -125,6 +126,9 @@ class Application extends BaseApplication if ($name = $this->getCommandName($input)) { try { $commandName = $this->find($name)->getName(); + } catch (CommandNotFoundException $e) { + // we'll check command validity again later after plugins are loaded + $commandName = false; } catch (\InvalidArgumentException $e) { } } From 4d86414dd1e094756d94b5f6b45ed69d514a61e8 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 17 Sep 2018 11:24:47 +0200 Subject: [PATCH 267/580] Use a case insenstive method to check that SHA384 is a supported openssl algorithm --- src/Composer/Command/SelfUpdateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 2641a922b..243755963 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -220,7 +220,7 @@ TAGSPUBKEY $pubkeyid = openssl_pkey_get_public($sigFile); $algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384'; - if (!in_array('SHA384', openssl_get_md_methods())) { + if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) { throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity'); } $signature = json_decode($signature, true); From 0ca5c6342d95b40ef11dff720d50ad41bddece7f Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Wed, 26 Sep 2018 15:12:25 +0200 Subject: [PATCH 268/580] Fix the doc markup In *nix, the star needs to be part of the content, not of the markdown markup, and so it requires escaping. --- doc/03-cli.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 0b46e04f5..0eed878e5 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -856,9 +856,9 @@ is a hidden, global (per-user on the machine) directory that is shared between all projects. By default it points to `C:\Users\\AppData\Roaming\Composer` on Windows -and `/Users//.composer` on OSX. On *nix systems that follow the [XDG Base +and `/Users//.composer` on OSX. On \*nix systems that follow the [XDG Base Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), -it points to `$XDG_CONFIG_HOME/composer`. On other *nix systems, it points to +it points to `$XDG_CONFIG_HOME/composer`. On other \*nix systems, it points to `/home//.composer`. #### COMPOSER_HOME/config.json From ed3aa1870cbcaf6ca223eca47c06770f37426143 Mon Sep 17 00:00:00 2001 From: Alexey Pyltsyn Date: Thu, 27 Sep 2018 14:00:10 +0300 Subject: [PATCH 269/580] Replace OSX with macOS --- doc/00-intro.md | 6 +++--- doc/03-cli.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index 046fea8fe..e4af54c2c 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -40,9 +40,9 @@ To install packages from sources instead of simple zip archives, you will need git, svn, fossil or hg depending on how the package is version-controlled. Composer is multi-platform and we strive to make it run equally well on Windows, -Linux and OSX. +Linux and macOS. -## Installation - Linux / Unix / OSX +## Installation - Linux / Unix / macOS ### Downloading the Composer Executable @@ -100,7 +100,7 @@ Linux distributions. > **Note:** If the above fails due to permissions, you may need to run it again > with sudo. -> **Note:** On some versions of OSX the `/usr` directory does not exist by +> **Note:** On some versions of macOS the `/usr` directory does not exist by > default. If you receive the error "/usr/local/bin/composer: No such file or > directory" then you must create the directory manually before proceeding: > `mkdir -p /usr/local/bin`. diff --git a/doc/03-cli.md b/doc/03-cli.md index 0b46e04f5..372bea85a 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -856,7 +856,7 @@ is a hidden, global (per-user on the machine) directory that is shared between all projects. By default it points to `C:\Users\\AppData\Roaming\Composer` on Windows -and `/Users//.composer` on OSX. On *nix systems that follow the [XDG Base +and `/Users//.composer` on macOS. On *nix systems that follow the [XDG Base Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), it points to `$XDG_CONFIG_HOME/composer`. On other *nix systems, it points to `/home//.composer`. @@ -878,7 +878,7 @@ configuration in the project's `composer.json` always wins. The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. -By default it points to `$COMPOSER_HOME/cache` on \*nix and OSX, and +By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and `C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. ### COMPOSER_PROCESS_TIMEOUT From add71388ca24572e2d9f959752b349273a15cfe3 Mon Sep 17 00:00:00 2001 From: "Kristof Ringleff, Fooman" Date: Fri, 21 Sep 2018 15:32:35 +1200 Subject: [PATCH 270/580] Add failing artifact --- .../artifacts/not-a-zip-with-zip-extension.zip | Bin 0 -> 126405 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/Composer/Test/Repository/Fixtures/artifacts/not-a-zip-with-zip-extension.zip diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/not-a-zip-with-zip-extension.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/not-a-zip-with-zip-extension.zip new file mode 100644 index 0000000000000000000000000000000000000000..562f032d469230f4c3b79801660e9a5a40d1e8e3 GIT binary patch literal 126405 zcmXtg2Q*yW`}OEUgdtiGWlTgbQ6sw1hG@}y3xX)oYjmUcgy=*>kKjf061@Zw5xqt4 zy?@8=TmQ9U5zEZI&OK*8``LRxBGgpm?-NiHKp>F&iV8>#2m}KTJ}jZQ;5#8Lg5BT~ zzLSEU3k1T&boYVbSs>vKfiOT6ky4tT8CymkuA1W~v#Zn3n?4*3Ah6?SpQF~A6df@M z7CRz!CEp2d1>pQV)kMmM%FByjVxmamiAEW#L*hl!l(Xwkv#u|hTr4EjBzf2RYkwPk zO&rJ+v)(x$s_mZ)XMk(wV3L24^a^@kivd34u(f`;`=a%@m5M2vHx`EZD&;vNHa;U1 zq5utCyY0Up%^Qgh(ddIE@N}_2Al=RyUQ;I?7)4HlY)(&tc;)(GL%TPe7SZp!JLQp> zYSOW2Xh=M}|C6a>oxevONE{Na`B*HAXbse4fzpRX?Q+9s^Jn`@HJWTG@u{h)
i ztXy0>ZB5c~&O;QjwxeTmYOlADnF-!%h8>s?%2-f$ZHC%Wa{ z@E}GmpFxon1Co;*#DYSS;YhO<(8^1}kkC+RDx&aSBaa!kg@`9vo^RHE=DP4x$1rp| zIE33F6iK_CGx!~*>xiv~GR}3)&CPYmaBvvOUM&)0WByv&k56Ed_upR#rx*XE$)@+q zuO!t~CpQSq=F}}oCY!bDwcpR(LVL58ALsw*!2>2}P!8BsA(A{~m`NiLt0a=BsHS)w zH7HvFDIu;H7G&@8`q%1Vg7@*pqjv|rPfXS)D&{n@Rv%hK%OKCHpb@-sECrqm?V+98 zW%`ej5WPsKlzOrP5*@&R&e33%su<&i#B-T6ms0VVwvKYQT)hR`0~?|{sjGp*HhM~4 zCli21CL)L+3{Rds(Ykpk89{n*j`g$b<@4!!XB-E^dWc|!9`x4#^!^vEL#`ffmSs^o z?!s|f9xg5w5x)!9WA%JF&W&G1W2$x&<=aSTf^cjSLUD09`Zs-b3MOQ0rlB^Xf92`E zC^vqPLNx9pu8R$e48e19SHxrU+!8b@@sonGG8qN8@$~Mbb#|s4)GV5JC+_c_stkBy zInyMM@!Ag?p^*5rwDp|i_n)`$85ttrN83|0i=!G5a{0`?iV>E}U(76L4!oADhzrfj zK8}C=`W5XhYOGQ*VWF%X?YcML))kC+!r0*ZxE#bI;=9VhCx0 zEH4b&+9F3BP9)|XKawAw#JTytJvBk*>z^eIbu#k({r$zlY=04lo2&ESR~lD*T|3!9 z1n^?Xdg=0(eNWN9yDhiP+XjwJ$BP@|CE;Kv)|r1J#l$;#1d+rIVvt6VVq}*

RSv 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 LdQa6Z_t^gc0>PFJ literal 0 HcmV?d00001 From bcf4e94e8a1bd6d2f82d8a232e59251c4880637b Mon Sep 17 00:00:00 2001 From: "Kristof Ringleff, Fooman" Date: Fri, 21 Sep 2018 15:51:38 +1200 Subject: [PATCH 271/580] Ensure opening the zip was successful before proceeding --- src/Composer/Repository/ArtifactRepository.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 2383b2dd3..e987813f0 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -126,7 +126,11 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { $zip = new \ZipArchive(); - $zip->open($file->getPathname()); + $res = $zip->open($file->getPathname()); + + if ($res !== true) { + return false; + } if (0 == $zip->numFiles) { $zip->close(); From 2ed573b22dc60ab894df430af368332d04c8cafc Mon Sep 17 00:00:00 2001 From: "Kristof Ringleff, Fooman" Date: Mon, 24 Sep 2018 20:28:22 +1200 Subject: [PATCH 272/580] Combine open zip call with conditional --- src/Composer/Repository/ArtifactRepository.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index e987813f0..0184cb4d5 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -126,9 +126,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { $zip = new \ZipArchive(); - $res = $zip->open($file->getPathname()); - - if ($res !== true) { + if ($zip->open($file->getPathname()) !== true) { return false; } From 5f2eefb49b3658b50293481b643dc4346670fa3c Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Wed, 10 Oct 2018 14:09:42 +0200 Subject: [PATCH 273/580] Vcs Repository: add option to cache/reuse entire versions --- src/Composer/Repository/VcsRepository.php | 67 ++++++++++++++++--- .../Repository/VersionCacheInterface.php | 23 +++++++ 2 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 src/Composer/Repository/VersionCacheInterface.php diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 57639cdea..785b8534b 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -41,8 +41,10 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt private $drivers; /** @var VcsDriverInterface */ private $driver; + /** @var VersionCacheInterface */ + private $versionCache; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null) { parent::__construct(); $this->drivers = $drivers ?: array( @@ -64,6 +66,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->verbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; + $this->versionCache = $versionCache; } public function getRepoConfig() @@ -152,6 +155,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt // strip the release- prefix from tags if present $tag = str_replace('release-', '', $tag); + $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $verbose); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + if (!$parsedTag = $this->validateTag($tag)) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); @@ -235,6 +245,21 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt continue; } + // make sure branch packages have a dev flag + if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) { + $version = 'dev-' . $branch; + } else { + $prefix = substr($branch, 0, 1) === 'v' ? 'v' : ''; + $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch); + } + + $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $verbose); + if ($cachedPackage) { + $this->addPackage($cachedPackage); + + continue; + } + try { if (!$data = $driver->getComposerInformation($identifier)) { if ($verbose) { @@ -244,17 +269,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } // branches are always auto-versioned, read value from branch name - $data['version'] = $branch; + $data['version'] = $version; $data['version_normalized'] = $parsedBranch; - // make sure branch packages have a dev flag - if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) { - $data['version'] = 'dev-' . $data['version']; - } else { - $prefix = substr($branch, 0, 1) === 'v' ? 'v' : ''; - $data['version'] = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch); - } - if ($verbose) { $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } @@ -325,4 +342,34 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt return false; } + + private function getCachedPackageVersion($version, $identifier, $verbose) + { + if (!$this->versionCache) { + return; + } + + $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); + if ($cachedPackage) { + $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; + if ($verbose) { + $this->io->writeError($msg); + } else { + $this->io->overwriteError($msg, false); + } + + if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) { + if ($verbose) { + $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); + } + $cachedPackage = null; + } + } + + if ($cachedPackage) { + return $this->loader->load($cachedPackage); + } + + return null; + } } diff --git a/src/Composer/Repository/VersionCacheInterface.php b/src/Composer/Repository/VersionCacheInterface.php new file mode 100644 index 000000000..db5934b59 --- /dev/null +++ b/src/Composer/Repository/VersionCacheInterface.php @@ -0,0 +1,23 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +interface VersionCacheInterface +{ + /** + * @param string $version + * @param string $identifier + * @return array Package version data + */ + public function getVersionPackage($version, $identifier); +} From 96347fbea17e2e67a0a264b1b3a57cace5385477 Mon Sep 17 00:00:00 2001 From: David Manners Date: Sun, 14 Oct 2018 11:15:25 +0000 Subject: [PATCH 274/580] composer/composer#7384: add chat to support options - update schema documents to note that chat is allowed, - validate that chat must be a string and a url similar to forum options --- doc/04-schema.md | 1 + src/Composer/Package/Loader/ValidatingArrayLoader.php | 4 ++-- .../Test/Package/Loader/ValidatingArrayLoaderTest.php | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index 42addb10f..0f44d47f9 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -243,6 +243,7 @@ Support information includes the following: * **source:** URL to browse or download the sources. * **docs:** URL to the documentation. * **rss:** URL to the RSS feed. +* **chat:** URL to chat server. An example: diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 2a71be6cb..f4753025b 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -161,7 +161,7 @@ class ValidatingArrayLoader implements LoaderInterface } if ($this->validateArray('support') && !empty($this->config['support'])) { - foreach (array('issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss') as $key) { + foreach (array('issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat') as $key) { if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) { $this->errors[] = 'support.'.$key.' : invalid value, must be a string'; unset($this->config['support'][$key]); @@ -178,7 +178,7 @@ class ValidatingArrayLoader implements LoaderInterface unset($this->config['support']['irc']); } - foreach (array('issues', 'forum', 'wiki', 'source', 'docs') as $key) { + foreach (array('issues', 'forum', 'wiki', 'source', 'docs', 'chat') as $key) { if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL'; unset($this->config['support'][$key]); diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index cc66ab399..ebe6871fe 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -71,6 +71,7 @@ class ValidatingArrayLoaderTest extends TestCase 'source' => 'http://example.org/', 'irc' => 'irc://example.org/example', 'rss' => 'http://example.org/rss', + 'chat' => 'http://example.org/chat', ), 'require' => array( 'a/b' => '1.*', @@ -305,6 +306,7 @@ class ValidatingArrayLoaderTest extends TestCase 'forum' => 'foo:bar', 'issues' => 'foo:bar', 'wiki' => 'foo:bar', + 'chat' => 'foo:bar', ), ), array( @@ -312,6 +314,7 @@ class ValidatingArrayLoaderTest extends TestCase 'support.forum : invalid value (foo:bar), must be an http/https URL', 'support.issues : invalid value (foo:bar), must be an http/https URL', 'support.wiki : invalid value (foo:bar), must be an http/https URL', + 'support.chat : invalid value (foo:bar), must be an http/https URL', ), ), array( From a1ab75a703c666e96cb753872e8cd1427725f1cb Mon Sep 17 00:00:00 2001 From: David Manners Date: Sun, 14 Oct 2018 14:19:08 +0000 Subject: [PATCH 275/580] composer/composer#7159: make the remove command to a regex lookup on package name - if you have multiple vendor modules installed you should be able to do composer remove vendor/* to remove all - update remove and also remove from alternative type to also do a preg_grep for what the user has inputed --- src/Composer/Command/RemoveCommand.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index c97130c3a..9646d6db1 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -94,12 +94,25 @@ EOT if (isset($composer[$type][$package])) { $json->removeLink($type, $composer[$type][$package]); } elseif (isset($composer[$altType][$package])) { - $io->writeError(''.$composer[$altType][$package].' could not be found in '.$type.' but it is present in '.$altType.''); + $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { - if ($io->askConfirmation('Do you want to remove it from '.$altType.' [yes]? ', true)) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ', true)) { $json->removeLink($altType, $composer[$altType][$package]); } } + } elseif (isset($composer[$type]) && $matches = preg_grep('#^'.$package.'#', array_keys($composer[$type]))) { + foreach ($matches as $matchedPackage) { + $json->removeLink($type, $matchedPackage); + } + } elseif (isset($composer[$altType]) && $matches = preg_grep('#^'.$package.'#', array_keys($composer[$altType]))) { + foreach ($matches as $matchedPackage) { + $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ', true)) { + $json->removeLink($altType, $matchedPackage); + } + } + } } else { $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); } From 3c173702b5e45ba8180f991744ef947e863ece28 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Oct 2018 17:44:04 +0200 Subject: [PATCH 276/580] :rocket: Build/Travis: test builds against PHP 7.3 Once PHP 7.3-beta came out, the `nightly` build on Travis became PHP 7.4-dev and builds haven't been tested against PHP 7.3 for months now. As of this week, Travis has (finally) made a PHP 7.3 alias available now RC3 is out, so I've added PHP 7.3 to the matrix. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 16a3b073c..575f6a527 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,13 +23,14 @@ php: - 7.0 - 7.1 - 7.2 + - 7.3 - nightly matrix: include: - php: 5.3 dist: precise - - php: 7.2 + - php: 7.3 env: deps=high fast_finish: true allow_failures: From c6da110e71bbab46090df7ef0b8f48f239f71913 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 17 Oct 2018 08:53:19 +0200 Subject: [PATCH 277/580] Updated deploy php version to 7.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 575f6a527..064782fa2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,4 +73,4 @@ deploy: on: tags: true repo: composer/composer - php: '7.1' + php: '7.2' From 819f487b38dc79f8ce3dc910a0d8ceda01086778 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Fri, 19 Oct 2018 11:55:45 +0200 Subject: [PATCH 278/580] Bitbucket: switch to v2 API --- src/Composer/Repository/Vcs/BitbucketDriver.php | 11 ++++++++--- .../Test/Repository/Vcs/GitBitbucketDriverTest.php | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 6361d6a04..3857171e8 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -190,7 +190,7 @@ abstract class BitbucketDriver extends VcsDriver } $resource = sprintf( - 'https://api.bitbucket.org/1.0/repositories/%s/%s/raw/%s/%s', + 'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s', $this->owner, $this->repository, $identifier, @@ -421,11 +421,16 @@ abstract class BitbucketDriver extends VcsDriver protected function getMainBranchData() { $resource = sprintf( - 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', + 'https://api.bitbucket.org/2.0/repositories/%s/%s?fields=mainbranch', $this->owner, $this->repository ); - return JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + if (isset($data['mainbranch'])) { + return $data['mainbranch']; + } + + return null; } } diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index b35bb8867..7547855bf 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -113,7 +113,7 @@ class GitBitbucketDriverTest extends TestCase ), array( $this->originUrl, - 'https://api.bitbucket.org/1.0/repositories/user/repo/main-branch', + 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=mainbranch', false, ), array( @@ -128,7 +128,7 @@ class GitBitbucketDriverTest extends TestCase ), array( $this->originUrl, - 'https://api.bitbucket.org/1.0/repositories/user/repo/raw/master/composer.json', + 'https://api.bitbucket.org/2.0/repositories/user/repo/src/master/composer.json', false, ), array( @@ -139,7 +139,7 @@ class GitBitbucketDriverTest extends TestCase ) ->willReturnOnConsecutiveCalls( '{"scm":"git","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo.git","name":"https"},{"href":"ssh:\/\/git@bitbucket.org\/user\/repo.git","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}', - '{"name": "master"}', + '{"mainbranch": {"name": "master"}}', '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}', '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}', '{"name": "user/repo","description": "test repo","license": "GPL","authors": [{"name": "Name","email": "local@domain.tld"}],"require": {"creator/package": "^1.0"},"require-dev": {"phpunit/phpunit": "~4.8"}}', From e92eda56786844c6e30c97b91a0cf4706e0b3085 Mon Sep 17 00:00:00 2001 From: Alexey Kopytko Date: Mon, 22 Oct 2018 10:11:34 +0200 Subject: [PATCH 279/580] composer/composer#7384: update chat note based on code review feedback Co-Authored-By: dmanners --- doc/04-schema.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index 0f44d47f9..7c66813af 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -243,7 +243,7 @@ Support information includes the following: * **source:** URL to browse or download the sources. * **docs:** URL to the documentation. * **rss:** URL to the RSS feed. -* **chat:** URL to chat server. +* **chat:** URL to the chat channel. An example: From abb6377caa7fceb844be16867574ebe8a2192796 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Tue, 23 Oct 2018 15:26:43 +0200 Subject: [PATCH 280/580] Update 05-repositories.md Fixed typo. --- doc/05-repositories.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 9706a07e0..9cd1dbf28 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -657,7 +657,7 @@ be explicitly defined in the package's `composer.json` file. If the version cannot be resolved by these means, it is assumed to be `dev-master`. The local package will be symlinked if possible, in which case the output in -the console will read `Symlinked from ../../packages/my-package`. If symlinking +the console will read `Symlinking from ../../packages/my-package`. If symlinking is _not_ possible the package will be copied. In that case, the console will output `Mirrored from ../../packages/my-package`. From 23d37eebe6b7b87d98b54436eccd1a653ca04ab6 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Wed, 24 Oct 2018 13:42:40 +0200 Subject: [PATCH 281/580] Fix: undefined index name in VcsRepository --- src/Composer/Repository/VcsRepository.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 57639cdea..4f8e12d7c 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -188,7 +188,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt continue; } - if ($existingPackage = $this->findPackage($data['name'], $data['version_normalized'])) { + $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName; + if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); } From 8b8df013361c1d57caabc36f9e9efb516a177558 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Fri, 26 Oct 2018 15:48:18 +0200 Subject: [PATCH 282/580] Bitbucket: fix redirect behaviour --- src/Composer/Util/RemoteFilesystem.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index dc2b33089..def4091a6 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -385,15 +385,18 @@ class RemoteFilesystem $statusCode = null; $contentType = null; + $locationHeader = null; if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); $contentType = $this->findHeaderValue($http_response_header, 'content-type'); + $locationHeader = $this->findHeaderValue($http_response_header, 'location'); } // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' && !$this->isPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' + && (!$locationHeader || substr($locationHeader, -4) !== '.zip') && $contentType && preg_match('{^text/html\b}i', $contentType) ) { $result = false; From 105477218dcb31c82593460850f6b5f3dd225739 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Mon, 29 Oct 2018 12:01:46 +0100 Subject: [PATCH 283/580] VcsRepository: fix undefined index notice in preProcess --- src/Composer/Repository/VcsRepository.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 4f8e12d7c..b65d19d75 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -295,7 +295,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt protected function preProcess(VcsDriverInterface $driver, array $data, $identifier) { // keep the name of the main identifier for all packages - $data['name'] = $this->packageName ?: $data['name']; + $dataPackageName = isset($data['name']) ? $data['name'] : null; + $data['name'] = $this->packageName ?: $dataPackageName; if (!isset($data['dist'])) { $data['dist'] = $driver->getDist($identifier); From 67e6d6d8a4770972ad175f876c25faa0f7fec456 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 27 Oct 2018 09:58:10 +0200 Subject: [PATCH 284/580] diagnose: write warning on stderr --- src/Composer/Command/DiagnoseCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 3efb34973..11d1e1aa0 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -121,8 +121,8 @@ EOT $rate = $this->getGithubRateLimit('github.com'); $this->outputResult(true); if (10 > $rate['remaining']) { - $io->write('WARNING'); - $io->write(sprintf( + $io->writeError('WARNING'); + $io->writeError(sprintf( 'Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL From 20107dbf777fefd7d806745ad9579474e395e777 Mon Sep 17 00:00:00 2001 From: Alexey Kopytko Date: Fri, 26 Oct 2018 12:12:10 +0900 Subject: [PATCH 285/580] Ensure that a missing SSL/TLS protection warning does not pollute STDOUT Fixes #7737 --- src/Composer/Factory.php | 2 +- tests/Composer/Test/FactoryTest.php | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/FactoryTest.php diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index d0cf6ee8a..1aac934a1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -588,7 +588,7 @@ class Factory $disableTls = false; if ($config && $config->get('disable-tls') === true) { if (!$warned) { - $io->write('You are running Composer with SSL/TLS protection disabled.'); + $io->writeError('You are running Composer with SSL/TLS protection disabled.'); } $warned = true; $disableTls = true; diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php new file mode 100644 index 000000000..a6ba01565 --- /dev/null +++ b/tests/Composer/Test/FactoryTest.php @@ -0,0 +1,40 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use PHPUnit\Framework\TestCase; +use Composer\Factory; + +class FactoryTest extends TestCase +{ + /** + * @group TLS + */ + public function testDefaultValuesAreAsExpected() + { + $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + + $ioMock->expects($this->once()) + ->method("writeError"); + + $config = $this + ->getMockBuilder('Composer\Config') + ->getMock(); + + $config->method('get') + ->with($this->equalTo('disable-tls')) + ->will($this->returnValue(true)); + + Factory::createRemoteFilesystem($ioMock, $config); + } +} From aa6d138bdca5c9d2b448d3a583bba4d2213c4806 Mon Sep 17 00:00:00 2001 From: Alexey Kopytko Date: Fri, 26 Oct 2018 12:17:38 +0900 Subject: [PATCH 286/580] Check for the actual warning description --- tests/Composer/Test/FactoryTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php index a6ba01565..34d4518bb 100644 --- a/tests/Composer/Test/FactoryTest.php +++ b/tests/Composer/Test/FactoryTest.php @@ -25,7 +25,8 @@ class FactoryTest extends TestCase $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->once()) - ->method("writeError"); + ->method("writeError") + ->with($this->equalTo('You are running Composer with SSL/TLS protection disabled.')); $config = $this ->getMockBuilder('Composer\Config') From a64b652a6b451fc40482dcf25d9150fbe2600810 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sun, 14 Oct 2018 17:45:44 -0300 Subject: [PATCH 287/580] Use func_num_args instead of counting on func_get_args --- src/Composer/Util/ProcessExecutor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 5e40470b0..d2efc5377 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -60,7 +60,7 @@ class ProcessExecutor $cwd = realpath(getcwd()); } - $this->captureOutput = count(func_get_args()) > 1; + $this->captureOutput = func_num_args() > 1; $this->errorOutput = null; $process = new Process($command, $cwd, null, null, static::getTimeout()); From 2805a69e5836a583c4af0331554a4af12f978b04 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sun, 14 Oct 2018 17:48:29 -0300 Subject: [PATCH 288/580] Simplify conditions and inline temp variables --- src/Composer/Util/Perforce.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index c96bcb80b..d7c6816cc 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -485,8 +485,7 @@ class Perforce $resArray = explode(PHP_EOL, $result); $tags = array(); foreach ($resArray as $line) { - $index = strpos($line, 'Label'); - if (!($index === false)) { + if (strpos($line, 'Label') !== false) { $fields = explode(' ', $line); $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; } @@ -502,8 +501,7 @@ class Perforce $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { - $index = strpos($line, 'Depot'); - if (!($index === false)) { + if (strpos($line, 'Depot') !== false) { $fields = explode(' ', $line); if (strcmp($this->p4Depot, $fields[1]) === 0) { $this->p4DepotType = $fields[3]; From 114217c6e3b35653923815786221c94b023c36a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Wed, 31 Oct 2018 09:55:53 +0100 Subject: [PATCH 289/580] Fix ini_get() for boolean values --- src/Composer/Autoload/ClassLoader.php | 2 +- src/Composer/Command/DiagnoseCommand.php | 6 +++--- src/Composer/Compiler.php | 2 +- src/Composer/Util/ErrorHandler.php | 2 +- src/Composer/Util/RemoteFilesystem.php | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 95f7e0978..fce8549f0 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -279,7 +279,7 @@ class ClassLoader */ public function setApcuPrefix($apcuPrefix) { - $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 3efb34973..3cb783b3b 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -481,7 +481,7 @@ EOT $errors['iconv_mbstring'] = true; } - if (!ini_get('allow_url_fopen')) { + if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { $errors['allow_url_fopen'] = true; } @@ -505,7 +505,7 @@ EOT $warnings['openssl_version'] = true; } - if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { + if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { $warnings['apc_cli'] = true; } @@ -528,7 +528,7 @@ EOT } } - if (ini_get('xdebug.profiler_enabled')) { + if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) { $warnings['xdebug_profile'] = true; } elseif (extension_loaded('xdebug')) { $warnings['xdebug_loaded'] = true; diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 4064b20b5..27b1f4816 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -255,7 +255,7 @@ class Compiler */ // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 -if (extension_loaded('apc') && ini_get('apc.enable_cli') && ini_get('apc.cache_by_default')) { +if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) { ini_set('apc.cache_by_default', 0); } else { diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 925cab74c..83e6b5ede 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -41,7 +41,7 @@ class ErrorHandler return; } - if (ini_get('xdebug.scream')) { + if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) { $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be". "\na legitimately suppressed error that you were not supposed to see."; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index def4091a6..39f049cbe 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -364,7 +364,7 @@ class RemoteFilesystem } $result = false; } - if ($errorMessage && !ini_get('allow_url_fopen')) { + if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')'; } restore_error_handler(); From 470d351926228a9f2804ae273b35cedf58846ce6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 12:47:07 +0100 Subject: [PATCH 290/580] Update ca-bundle --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 70455dc7b..d4e7f847f 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", + "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "ssl", "tls" ], - "time": "2018-08-08T08:57:40+00:00" + "time": "2018-10-18T06:09:13+00:00" }, { "name": "composer/semver", From 77457ca4742f46d1e606dcd443a52d3323177541 Mon Sep 17 00:00:00 2001 From: Mariusz Zarzycki Date: Mon, 8 Oct 2018 20:35:06 +0100 Subject: [PATCH 291/580] Show command respects --path flag Path added to package meta output --- src/Composer/Command/ShowCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ccea6a960..6de2f4cab 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -219,6 +219,12 @@ EOT if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) { $exitCode = 1; } + if ($input->getOption('path')) { + $io->write($package->getName(), false); + $io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n")); + + return $exitCode; + } $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); @@ -577,6 +583,7 @@ EOT $this->printLicenses($package); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); + $io->write('path : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); $io->write('names : ' . implode(', ', $package->getNames())); if ($latestPackage->isAbandoned()) { From f72e2312dcda584794dc5bf136b7d6d874c87cf3 Mon Sep 17 00:00:00 2001 From: Mariusz Zarzycki Date: Mon, 8 Oct 2018 20:37:25 +0100 Subject: [PATCH 292/580] Ran php-cs-fixer --- src/Composer/Command/ShowCommand.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 6de2f4cab..0b638a74b 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -219,12 +219,12 @@ EOT if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) { $exitCode = 1; } - if ($input->getOption('path')) { - $io->write($package->getName(), false); - $io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n")); + if ($input->getOption('path')) { + $io->write($package->getName(), false); + $io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n")); - return $exitCode; - } + return $exitCode; + } $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); @@ -583,7 +583,7 @@ EOT $this->printLicenses($package); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); - $io->write('path : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); + $io->write('path : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); $io->write('names : ' . implode(', ', $package->getNames())); if ($latestPackage->isAbandoned()) { From 38a63ba05a1abce51372ca3a9c12c002f3b3fe63 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 15:07:07 +0100 Subject: [PATCH 293/580] Only show path for installed packages, refs #7698 --- src/Composer/Command/ShowCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 0b638a74b..5a3c970dd 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -583,7 +583,9 @@ EOT $this->printLicenses($package); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); - $io->write('path : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); + if ($installedRepo->hasPackage($package)) { + $io->write('path : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package)))); + } $io->write('names : ' . implode(', ', $package->getNames())); if ($latestPackage->isAbandoned()) { From 41458c75184bde3805d622503b7b5973ef3f1a8b Mon Sep 17 00:00:00 2001 From: Michele Locati Date: Fri, 12 Oct 2018 15:56:26 +0200 Subject: [PATCH 294/580] Don't call Symfony ProcessUtils::escapeArgument --- src/Composer/Util/ProcessExecutor.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 5e40470b0..d34c6c3aa 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -131,15 +131,11 @@ class ProcessExecutor */ public static function escape($argument) { - if (method_exists('Symfony\Component\Process\ProcessUtils', 'escapeArgument')) { - return ProcessUtils::escapeArgument($argument); - } - return self::escapeArgument($argument); } /** - * Copy of ProcessUtils::escapeArgument() that is removed in Symfony 4. + * Copy of ProcessUtils::escapeArgument() that is deprecated in Symfony 3.3 and removed in Symfony 4. * * @param string $argument * From 3c543b275279b27d3e723ffe4363e0aecaf07857 Mon Sep 17 00:00:00 2001 From: Raffael Comi Date: Mon, 15 Oct 2018 13:41:59 +0200 Subject: [PATCH 295/580] Report "same as actual" version if override package matches actual --- src/Composer/Repository/PlatformRepository.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 02d552424..0a2e79f35 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -236,7 +236,12 @@ class PlatformRepository extends ArrayRepository // Skip if overridden if (isset($this->overrides[$package->getName()])) { $overrider = $this->findPackage($package->getName(), '*'); - $overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')'); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + $overrider->setDescription($overrider->getDescription().' ('.$actualText.')'); return; } @@ -244,7 +249,12 @@ class PlatformRepository extends ArrayRepository // Skip if PHP is overridden and we are adding a php-* package if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) { $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); - $overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')'); + if ($package->getVersion() === $overrider->getVersion()) { + $actualText = 'same as actual'; + } else { + $actualText = 'actual: '.$package->getPrettyVersion(); + } + $overrider->setDescription($overrider->getDescription().' ('.$actualText.')'); return; } From 148e503b31deb2d9b7a444eb29ec502360a9dd2f Mon Sep 17 00:00:00 2001 From: Seven Du Date: Wed, 31 Oct 2018 23:57:44 +0800 Subject: [PATCH 296/580] Modified comment block (#7700) * Modified comment block --- src/Composer/Util/RemoteFilesystem.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index dc2b33089..6e16520fd 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -114,13 +114,18 @@ class RemoteFilesystem /** * Merges new options * - * @return array $options + * @param array $options */ public function setOptions(array $options) { $this->options = array_replace_recursive($this->options, $options); } + /** + * Check is disable TLS. + * + * @return bool + */ public function isTlsDisabled() { return $this->disableTls === true; From 0ee0138bed31aa2c911041bf63f09857eff8da7a Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Wed, 31 Oct 2018 17:18:54 +0100 Subject: [PATCH 297/580] Support for ignoring packages in `outdated` command (#7682) * Support for ignoring packages in `outdated` command. Fixes #7656 --- src/Composer/Command/OutdatedCommand.php | 2 ++ src/Composer/Command/ShowCommand.php | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index f867f75f4..79409c58f 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -36,6 +36,7 @@ class OutdatedCommand extends ShowCommand new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'), )) ->setHelp( <<getOption('format'); + $args['--ignore'] = $input->getOption('ignore'); $input = new ArrayInput($args); diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index ccea6a960..0299f1458 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -74,6 +74,7 @@ class ShowCommand extends BaseCommand new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), @@ -105,6 +106,8 @@ EOT if ($input->getOption('outdated')) { $input->setOption('latest', true); + } elseif ($input->getOption('ignore')) { + $io->writeError('You are using the option "ignore" for action other than "outdated", it will be ignored.'); } if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { @@ -333,6 +336,7 @@ EOT $showAllTypes = $input->getOption('all'); $showLatest = $input->getOption('latest'); $showMinorOnly = $input->getOption('minor-only'); + $ignoredPackages = array_map('strtolower', $input->getOption('ignore')); $indent = $showAllTypes ? ' ' : ''; $latestPackages = array(); $exitCode = 0; @@ -372,7 +376,11 @@ EOT if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { $latestPackage = $latestPackages[$package->getPrettyName()]; } - if ($input->getOption('outdated') && $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) { + + // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code + $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned(); + $packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true); + if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { continue; } elseif ($input->getOption('outdated') || $input->getOption('strict')) { $hasOutdatedPackages = true; From da94e4b6199b7511704633f31e2e7bf8410be3d7 Mon Sep 17 00:00:00 2001 From: Ahammar Yassine Date: Wed, 31 Oct 2018 16:32:02 +0000 Subject: [PATCH 298/580] Skip all network-based checks (#7641) * Skip all network-based checks Change the warnings in diagnose to a friendly messages when allow_url_fopen is disabled. Issue: #7622 --- src/Composer/Command/DiagnoseCommand.php | 65 ++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 11d1e1aa0..893bd5d52 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -119,8 +119,9 @@ EOT $io->write('Checking github.com rate limit: ', false); try { $rate = $this->getGithubRateLimit('github.com'); - $this->outputResult(true); - if (10 > $rate['remaining']) { + if (!is_array($rate)) { + $this->outputResult($rate); + } elseif (10 > $rate['remaining']) { $io->writeError('WARNING'); $io->writeError(sprintf( 'Github has a rate limit on their API. ' @@ -131,6 +132,8 @@ EOT $rate['remaining'], $rate['limit'] )); + } else { + $this->outputResult(true); } } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { @@ -207,6 +210,11 @@ EOT private function checkHttp($proto, Config $config) { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + $disableTls = false; $result = array(); if ($proto === 'https' && $config->get('disable-tls') === true) { @@ -238,6 +246,11 @@ EOT private function checkHttpProxy() { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true); @@ -265,6 +278,11 @@ EOT */ private function checkHttpProxyFullUriRequestParam() { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + $url = 'http://repo.packagist.org/packages.json'; try { $this->rfs->getContents('packagist.org', $url, false); @@ -290,6 +308,11 @@ EOT */ private function checkHttpsProxyFullUriRequestParam() { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + if (!extension_loaded('openssl')) { return 'You need the openssl extension installed for this check'; } @@ -312,6 +335,11 @@ EOT private function checkGithubOauth($domain, $token) { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); try { $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; @@ -332,10 +360,15 @@ EOT * @param string $domain * @param string $token * @throws TransportException - * @return array + * @return array|string */ private function getGithubRateLimit($domain, $token = null) { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + if ($token) { $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); } @@ -390,6 +423,11 @@ EOT private function checkVersion($config) { + $result = $this->checkConnectivity(); + if ($result !== true) { + return $result; + } + $versionsUtil = new Versions($config, $this->rfs); $latest = $versionsUtil->getLatest(); @@ -413,6 +451,7 @@ EOT } $hadError = false; + $hadWarning = false; if ($result instanceof \Exception) { $result = '['.get_class($result).'] '.$result->getMessage().''; } @@ -427,6 +466,8 @@ EOT foreach ($result as $message) { if (false !== strpos($message, '')) { $hadError = true; + } elseif (false !== strpos($message, '')) { + $hadWarning = true; } } } @@ -434,7 +475,7 @@ EOT if ($hadError) { $io->write('FAIL'); $this->exitCode = 2; - } else { + } elseif ($hadWarning) { $io->write('WARNING'); $this->exitCode = 1; } @@ -668,4 +709,20 @@ EOT return !$warnings && !$errors ? true : $output; } + + + /** + * Check if allow_url_fopen is ON + * + * @return bool|string + */ + private function checkConnectivity() + { + if (!ini_get('allow_url_fopen')) { + $result = 'Skipped because allow_url_fopen is missing.'; + return $result; + } + + return true; + } } From 284da1487c4574352ba54a0d60c23d7ebd9e31b5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 17:36:38 +0100 Subject: [PATCH 299/580] Avoid downgrading from error to warning --- src/Composer/Command/DiagnoseCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 893bd5d52..f4d642f49 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -474,10 +474,10 @@ EOT if ($hadError) { $io->write('FAIL'); - $this->exitCode = 2; + $this->exitCode = max($this->exitCode, 2); } elseif ($hadWarning) { $io->write('WARNING'); - $this->exitCode = 1; + $this->exitCode = max($this->exitCode, 1); } if ($result) { @@ -716,7 +716,7 @@ EOT * * @return bool|string */ - private function checkConnectivity() + private function checkConnectivity() { if (!ini_get('allow_url_fopen')) { $result = 'Skipped because allow_url_fopen is missing.'; From a51563300c77e339300d2fbeff796aea95f3d0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tanghe?= Date: Tue, 9 Oct 2018 10:13:01 +0200 Subject: [PATCH 300/580] Warning about the UNIX permissions lost if unzip command is not installed. Some packages provide (such as Symfony Panther or Dusk) executable files, but as PHP's unzip extension does not handle UNIX permissions, those files will lose their executable ones. --- src/Composer/Downloader/ZipDownloader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 5ca7a2dab..6534db3d8 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -69,7 +69,8 @@ class ZipDownloader extends ArchiveDownloader if (!self::$isWindows && !self::$hasSystemUnzip) { $this->io->writeError("As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension."); - $this->io->writeError("This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them."); + $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); + $this->io->writeError("Installing 'unzip' may remediate them."); } } From 900e3b65db0bf70d456137648d2d315916f7949f Mon Sep 17 00:00:00 2001 From: dtranmobil <39326050+dtranmobil@users.noreply.github.com> Date: Thu, 1 Nov 2018 04:12:21 +1100 Subject: [PATCH 301/580] Update why-can't-composer-load-repositories-recursively.md (#7690) --- doc/faqs/why-can't-composer-load-repositories-recursively.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faqs/why-can't-composer-load-repositories-recursively.md b/doc/faqs/why-can't-composer-load-repositories-recursively.md index efcd00772..a39aff6fb 100644 --- a/doc/faqs/why-can't-composer-load-repositories-recursively.md +++ b/doc/faqs/why-can't-composer-load-repositories-recursively.md @@ -15,7 +15,7 @@ associated with inline VCS repositories. There are three ways the dependency solver could work with custom repositories: - Fetch the repositories of root package, get all the packages from the defined -repositories, resolve requirements. This is the current state and it works well +repositories, then resolve requirements. This is the current state and it works well except for the limitation of not loading repositories recursively. - Fetch the repositories of root package, while initializing packages from the From 1898ad12cefd58234fc83af4e57e46754037f8ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 18:23:18 +0100 Subject: [PATCH 302/580] Make sure we chdir back in case update dir is relative, refs #7519 --- src/Composer/Package/Comparer/Comparer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Package/Comparer/Comparer.php b/src/Composer/Package/Comparer/Comparer.php index ed1931e82..c0ab98d9c 100644 --- a/src/Composer/Package/Comparer/Comparer.php +++ b/src/Composer/Package/Comparer/Comparer.php @@ -70,6 +70,7 @@ class Comparer if (!is_array($source)) { return; } + chdir($currentDirectory); chdir($this->update); $destination = $this->doTree('.', $destination); if (!is_array($destination)) { From 42dca2aff5c2c9fef4fbb6e3f296356db8d61811 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 18:29:32 +0100 Subject: [PATCH 303/580] Remove weird binary file from repo --- .../not-a-zip-with-zip-extension.zip | Bin 126405 -> 10 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/not-a-zip-with-zip-extension.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/not-a-zip-with-zip-extension.zip index 562f032d469230f4c3b79801660e9a5a40d1e8e3..ff342b0e69759e21709255e5d13d1012b72444f4 100644 GIT binary patch literal 10 LcmZ>CgaR%A4ax#k literal 126405 zcmXtg2Q*yW`}OEUgdtiGWlTgbQ6sw1hG@}y3xX)oYjmUcgy=*>kKjf061@Zw5xqt4 zy?@8=TmQ9U5zEZI&OK*8``LRxBGgpm?-NiHKp>F&iV8>#2m}KTJ}jZQ;5#8Lg5BT~ zzLSEU3k1T&boYVbSs>vKfiOT6ky4tT8CymkuA1W~v#Zn3n?4*3Ah6?SpQF~A6df@M z7CRz!CEp2d1>pQV)kMmM%FByjVxmamiAEW#L*hl!l(Xwkv#u|hTr4EjBzf2RYkwPk zO&rJ+v)(x$s_mZ)XMk(wV3L24^a^@kivd34u(f`;`=a%@m5M2vHx`EZD&;vNHa;U1 zq5utCyY0Up%^Qgh(ddIE@N}_2Al=RyUQ;I?7)4HlY)(&tc;)(GL%TPe7SZp!JLQp> zYSOW2Xh=M}|C6a>oxevONE{Na`B*HAXbse4fzpRX?Q+9s^Jn`@HJWTG@u{h)

i ztXy0>ZB5c~&O;QjwxeTmYOlADnF-!%h8>s?%2-f$ZHC%Wa{ z@E}GmpFxon1Co;*#DYSS;YhO<(8^1}kkC+RDx&aSBaa!kg@`9vo^RHE=DP4x$1rp| zIE33F6iK_CGx!~*>xiv~GR}3)&CPYmaBvvOUM&)0WByv&k56Ed_upR#rx*XE$)@+q zuO!t~CpQSq=F}}oCY!bDwcpR(LVL58ALsw*!2>2}P!8BsA(A{~m`NiLt0a=BsHS)w zH7HvFDIu;H7G&@8`q%1Vg7@*pqjv|rPfXS)D&{n@Rv%hK%OKCHpb@-sECrqm?V+98 zW%`ej5WPsKlzOrP5*@&R&e33%su<&i#B-T6ms0VVwvKYQT)hR`0~?|{sjGp*HhM~4 zCli21CL)L+3{Rds(Ykpk89{n*j`g$b<@4!!XB-E^dWc|!9`x4#^!^vEL#`ffmSs^o z?!s|f9xg5w5x)!9WA%JF&W&G1W2$x&<=aSTf^cjSLUD09`Zs-b3MOQ0rlB^Xf92`E zC^vqPLNx9pu8R$e48e19SHxrU+!8b@@sonGG8qN8@$~Mbb#|s4)GV5JC+_c_stkBy zInyMM@!Ag?p^*5rwDp|i_n)`$85ttrN83|0i=!G5a{0`?iV>E}U(76L4!oADhzrfj zK8}C=`W5XhYOGQ*VWF%X?YcML))kC+!r0*ZxE#bI;=9VhCx0 zEH4b&+9F3BP9)|XKawAw#JTytJvBk*>z^eIbu#k({r$zlY=04lo2&ESR~lD*T|3!9 z1n^?Xdg=0(eNWN9yDhiP+XjwJ$BP@|CE;Kv)|r1J#l$;#1d+rIVvt6VVq}*

RSv z!GKZ|-ZJPx-Y}KtIEvzmNohXZ>IjM*_>^QRqm1y!Ht^!AqjQD4aP9`UN3l^ zmX;Pl+pSo8Fng=%l*Igk&5?#}Pa@JLj4{-H!eJcqKRAYdOGOD#G$~>)8B~IK>?&7l^?l`}v zmkg7BA(#J2jb*+y5UXZ&F#WWmu~F2n$@}<(iV#-CsK zAK#1+o&a^C#bbCt+AWp;??q3JxEKQ?7p_W%2o`14w<77b2T8pMMtJS3{`c>d-g7+s zap;@lF!TK~1y!)cmO#UZD-C_xC0sJTA?|zgcsWq zN)Q|D?Pk}O=cp}(AYC}}7JKeAFWCh4!fu6L)xQXvn2}r2hgvXooIae9cA2Z2%iSs8 zvxV>n0E1YdLDI35q3vcila0K8ORa=mPB0Tvu|+b8s4Vsd`y6`)~9@2(%m?-IQ2x5@D?z-QT(=4WFZpc9=>>9GXydo1lLy&-m z(6r2{yst3E(-0gRagBb=<>_ukgK1mP2zAWoI)JP`!m%=niFIqA|JwQEKKt*}*!ncsrS-#>{H>u; zde#1!AJs}6I7#tw12w>k+Y^tV0fD?~Vkr?dHH-L<#D z2t^n@<-KyakeC0cKFA70z*U z@u1YCmg3(ErBkjNIw~R}0h`g>ccZ9Kdei@^kl|t$0uddS`e{UXN=&ctx7s%F#}B$ZO|}9xmQViMH50SA0h@_4 zw*+itcSq~ns9KR`?)l-W1XTpGI$2{`IJPcIENA3PuEj(wVv86ud>8CYO|V3g z_8_%|eo7yL3<{BFRYOT7BGA~uYPHUOqg5}T3%@-=(2Dsd+WfLrVc@?076tW2|BJrGa+~8ifzP;+-xrd8oRWU)#Rzo1-wyn02MS@}=Bb2hOVe8yx zYp4Hj8zGWO0ZX8L_V&}xd|PnKl?jod&K;E8e;fV7hW|SSr#`=7$+-EEHP}z4tE!jt zvviqlmV${+EWV-N@XxPJ<0_LduDuf!SV9auwwRMi=Y@%?^#$t0jCHGB$<;C2j*i}A zlSq-t=2Um~C*Ix%ui&9p_&}GbnEvkt&(ao-u=f>0t0=kCwnm{lmcHUUs7jYfNlARR z6XkwnB=z6i=CVMBd&77ZfQWg;cLBoGl=+J1F zT@yu4I}}f>ciyAq?m7^qcNqIT`2Kdh)$M=WMUqvvi>ge&W+Uxop^DaIm9?s0{s;>? zuct_pt!JqBF@Vlc2cy5iKg^kPP1gwEBsdrl5)qGGWN|^k{J%1ZMDsYjUu*&ZI7Ej# zJ|3s{6uq?mYLxENYAKkYrkR^uiIIwsLcl_PRg*paV!};oQMDL5bBXR>`Rtzlf*@4- zW9U6fqVK-vZ_T~EFFb5(L`b#d3(|G+i>{RJQF77dtFe&Pd!57RY3^|v)FRB@eN9H< zNMS446RNX&>egFfjUcfm@QCT`e}TjLgO`^#^foGRlK{?!hdfp5&AjfLNPP|of!T}~ zkN*Q3-mQ0FKzVfYIFA9USvcN0c}UqC_QHUn=@p-Xs|dVDwfKE%{yzyB85x|4q^eM@Qw&3y3jE`4! zPSqV>_kqor4ZTFIP<+wkie`75>bv*0RH1Mz^t;>Ue{Yw%Vjh7C@}MtqWFwvDN=!>EB?wP@fGyh2WdNt{%U% zm>`lY1z$AT{N{WQ2i1~Bpp;xzBOk|0q?_c9l`Lc-kZ8s)0@PnJHbP{TbV#M4<2L-B zXL}15gC7N^KyOrVFA*_w^j}g79~*j&Te3QoRh3m1#cB~f(%vqeFP#hX%DOc;;^P`i zKUZz3HKeuSr)~mu#JJj`JN9>>YPLJ5#Og5YV(k(}7-4(fs}cp2G_9ch-_(%)coIqc z2xL1+PO@}umXX3M`KJ!E--A7y!~rtrsIf>UQu$VybMAki`BQGh-p=j`Yi}n%P1t=* zfT@s&UG}#iNpLWIUiFelva9}H#<;Cn2<)5vJ2SLGk*++ndT^}`?wTP-1Q$(lrO{X)0`YT zj&ol>P*<0Cr3pj>%0UbXSrV?StJ4*-`&}9?;<3|MrB`J|dH2QPucDV)5R`%0v~&C= zwrqOG*i?;0hDIM~Z2;{w0!4WJ58zn4_Xr3)3p7|G^X~dJGErg+x@-lj!HgJ@5*)-j zifC1spoi+rNS4RO=G`}kNkeytG6ml=h&v8){ZZq0e%R|N7;3I=)KGS zZInZ|?p%UoXXK`!VIIrdM1(pLyJDim>-PGzd*X{alR^GS&JS}lMMXu-Zf9ZpKVQAn z+ZX$${@MA3*)(YN^!8Tb21`AD|K<7HhS`^Nd@XYoJL4r2)PR+S zA%4*i3j6#c49mOM^sJCq*J(%RgU~8J>ZX5WHJAI85cgo%_|Uj{nvaU@JDO^!TlMyuD=H>NHhnNQWo#5X>PHZ`!<`g% z9rq-O4zM~J0MW%_mo0y7-rw2}@8*dn%i%s9&=Sm&iY8kOdY1jPsVxB9VB(-vht=T6 zN1$y-Aw#B~{q`8uTKIS!ffg6`<#%YqmYV-JoGr9va!cJ{GmQ93(X?U$kSP$-F8qJ#%F6CO}1@zZGo->Hql-CVyjsc;zs?OiLvGO^M8~jE;-zE}JVerQ)h9e@d{NEo-v#5Sa^$+nH(q`)RJD zTK|zqV$#UEtEsd!=Ef1O@6LL6+I&+;!s37l>xYsQim(Ho(<_qSEvl-Y$3C#6fg6-6 z(_ZR~cu)w3mm1c8D>1I<{`E^!v2bE(OI5;@iuG!|>Ducd`i6G8>Wx@bRLl?X5(*lc zD9~hy=%}fgCUzGzW=Bo=X|d!AU-~w%_HW6di}yrPSfo{5!Y>5ZN$zFsZY0rN;sUJ$HBaftRXc$Gx8v zlV*>-o7|&*!1qMsz@csM*e#dvJ=^03a@Gf4_l`vc@nbj{0jrSX?6gB~;$s=`w14?@ z$3EiZHmsXRgdpt&4KRie2~k9DKzT~m7_G7%e!M!ASyJtDx)TP@b37tq=@}8WR7lOH zkW|Cb`*|nk+hRHBd|#=)vwRhksZ8c;mx9HF%yP}3A0ROoKb#^{PCY$H?(lLQ^6{}kusFx&u*EUOuuK!30L z`h7@W{KOby59EmNTO@Q(|mM-|lTe)H)UoPK_ItEvO`u>Ki zNHl1AM#siZ?|zurOO)=#r1Sgke$710!Z&UO3w~ z2YzsH-nwIGPG_rMkI{{79;YTJBR+rr{4JWPA$7U;LW`p>`$OPBdW`((n^yoVfvnrJ zxU|H}$;l}MSj>%8_vNl%-kNs&9D;({ zzI%a}olHLdrYvOUb!%|k!(p?3Mb{cDeXlvVxQJf9)PMu=0*{Ec4!iPd`r8^}6-Git zC#qWl_P>M;|KlfSpcO@Cs1%wCrjIz(hMT;*KK_6e7y!539gK&`B2)RShhX_5J*C?0 zBAF`S;1U3!mbSGmo_7@q1(n3AMn6q1zwP39Gu!B!r6T=#6FdPe{oi4NJl(lMnYINg zIXXiH@8f_~V}mT;`)sca{2zL2!-I8Pfrbmk+f#jiKhJgWtLtzIJjUUqxVwa5t*`%U z*kx@Gi=it?l*{nf1_OQziYW3&v6imFv_WmAt&B2ZS9g4)J|ZIGixAbHjNx0C!JHc9 zL@L(SaJe{3?n zeP3Su5w$~_nb2K% z0UB(6m)o_EISFxtSt8-%o7GNR%dg*F4CrlnIL3|Q`!rGg z9@nesi?zJ+pNgC3I)UZ4fsRb~zYPN(n_r%)>DmZds9bI6)rEMjDKz7C5n{;U2LN@F z`WDjRL-gY*0SF1OB&eoqpX`aM!pbv5Lty9-rg&}E(5>kvV%5wc2Ja?g4i=W5K>0JB z`tQx{WG1bn7HiMJ)kd_sm*FcuF`etJF#AY0dahzpl*vm&!%~C)?DckYWx~1KglWag zw4*93D*@K9JS3FMbq83twEi-cdzZufQx5V(UdP;V=pk|>wB*Bp*}n6BYJ4io-)(n5 z?+0gF-n}!L>}s)%&SOK%Aba}yB54F{e;GCz$GJCTKFd^T1Hb@u`9uH(;(iytK0tlZ zoa{1)G3iCU1yZG5v>cNNEfFs(EB*sgQl1~{ox#rqpAxPv&A!>3d=M9HMih=!BO=zf zPhU93+9!CtJNNDfU~8l#B-m1FaJcC0)#0wZgZ=ngGQG>hIod;a{noWFLzUDnLdrZzcAU?PzZHY9$jZdAG3 zq{)%(>A!>E%mX77+C z)oo`7k^qNsGEMor+b+`E)jmoRN(;n)Pf=sFqU7Wy;a}^nqre8S<j7aO{v|}|0%n_Jc#hO8b2L2&YX&=zW6_hS--$nz>y|WCS-&Pi{G3y8i zp`Cy?^vQku?{?kgPJ?QVLv zl$4adtC=3mKy+8p#yV&TV%XD}qJm;4gG2QCc&lny#qQS^^{`$`=}eVyeEj{?k00-_ z6<HPJSNSFr6ckWC{P_M|EKEOhyMZaC*3HtUxtoYM@ z(me=P3CDUw6i!7?&;7baA7}%AMoUXeh0pz%l~dJ-IL&0A0%wW5#`{p`iu{xBOUP@-%mG^p)4c9?=g@9etstX2BN&ww(CdQ}*tl zdQs8PgoBful$iJvyildE8(&py$e(=&fmgQbdy_p%{kUN!_CAfE=+5D}Ci+%c%XN%c z5Z>PCb%?8h;ss=;TE9#gkOn{rxgF=4o4h7l8U~Z1<&nDIU0*u_^wKUN2d!j5ax$5e ziV)CUPYRd1<8N8}|2g@S-T z0$#xL?3vzMzym>#Vesdk+Cs)*z*hVa~YxA>UF@zWlxX<63+0 zbD>Io0lS=WtBz!{C)9*;8P~nhHiDd3e)SL0xuUe8LCWZde&Gia+*(U^Q^$UFF{5Ck3BB z69BO@QM*t+yd#h)jL7n?JT*5GaxmsZtu=LJA?|j4WEIca_ketPA`9CXqOs9YxOWrX zRQ2mBCLy7T3ms9(=t}m*S3p%-H*E309s?}_*rUks4#plm*5I}P&~RLH0Gkv3uC{jR zdD5;Ikmoi)+Cb-Xn<)@LScPeO2W)H1%r)R#R~IkC%*|da%QGXQ$xLTM^X&()PI)t% z5hW|=;{3WgVa1a1MKlT1KWl62ZpQcV2>|fHJ0RpO)CJQYwHS$K=c;*jSdwuFhiCN{ zl60JVF3!j4R>`?lPi_@he)HV1CJe4$>lX_)o4|2>mDwtwwEB(PddPID#h}gC>!C8o zkh$0DJ1f3fui)N?fHVr%-OKhL4AAI_176;GK>Qxjuwx5a^dt z@KX!qCtEByg?Rgk6F}6e^vSozs@RXV$8n0aTcRM0!19rT${9ie17>Y@2;|}4y27uSU$yB?pu^_;pl@??_$O-eFDgJ_QAye{2H)K zV)c@7s_QHWBk#D6UdPRXqdEHTB29vp+7aLRy#0#wL& zCt+p38;GdT4T1W!zS+czkc}maCo8dKpON#wj2-83Edf1Bd4gM)(zjiL|*1iaj& zrCAD))VLmt3+s0}E1@(*+VYAx;BlM>6N`HSW%v#MA2}$aB;<=+KFM&#vn42Qa3A0R z4vmj!PJn*;EH=s5|9oWvEH|Hsw=|2%)_$Ny5d;~MC|O7VOJE}-0CI^NC6}R-6Kx?d z{1c4}Es`}B5AkfAE5kRcsYi?a?0mfzllJ;<#bp1w*(wdlVc}n3F@#7LU<}$N#*bq@ ziOp`uI2FUbB)TDVuH*&U$$#cQbEFQNZ!R`5K+6}8wejwsa8GMV&`6clptlVuvi&DJ zGgF=Bl?tN6OpMr9{t}cR00g#~^+1-Gk0}6mRpfKRZ&R<*3B8y~dMyu#9zI>RYck$F z2NK5L@D4NM!m&tHL1Ot_U1)~HJCX)h2l-@z$Iu|yHY4z8$b#es)8n%;|1Q)YQZDR` zEFp8}@e7iHsn)xDx_PI{olV!fXaP^}Dpf8&;dhzAgmIZRU#PT{bS{tH>5SFdFRn+V z50;cJO>XH}3Sb1-jJ@j&r`0xu5<7qHQ$DRy7?V3TU27FTa3PKc`kxF~3H;0e{b&sJrG$=KdWS3DD&He*KP(TTw<%ECIhkXHbhUSUFd=2 zy-nv8E+N^AaIQBr1o@QM#>1iK#4|3#V$b`F3#ZH`j$v9RfOd*c)&?mxcX$F`dKpiIJ?@W4k#YAPc6arvz^eR2b z?u_6TQ$aYla^_}v~6ICG_BIlk)I&?Qy@cQGh0nEk{aDA%?QPj zfn{d2yvDO2gcjuRJ~TVAcG;VxoMRxs85WLZ0+(+D=MmUwG^bM45Vh}*h*5eTeM|c` zh2Sxc0&vKtkX1M1r+%!M7_cO~=P63t?cDxARdoNPYFPa8vs`|}v`T{mnYtGHg|GNA z$oq+(9rSZ|>jKm*&`7unlGZQr*2=g%Z^`X5(n1ekN3s8)!eYR#rr^sE_r^5wbR#G= zYDrr=(pO_ofrKdJnxYRc8ac$ouFd`Z{ZZQ56boK-o>}2=4_!I@m{AoAzH_n$Uo8h0}O-Fm={vuuBpw1@vVNRMA~4vnBz!(z8641KRHE^(3+4&gDLv|A2JM z#UvN4TLaWFV-P<3W$RW%0AiWT9|hlBjAcA?@dnbLr`gfjE9#ofVgAj}j2;#sR9XtS z8ap6FLlvNw7y$L<53naTM_+w^JLo-HZDv%eS!VmXg>iBv!3RaWHW4M9MCx?f9E=cZ zf&5}4*JAPfJZ`&xb$<9;l0kAgG?W+DyNp65Q(Q?Ub11;y&l_f0>Y8vN))VD~Aj7O{ zn=rh-P!J}RM!fA<4u_8HKv25heL2-jG!ut+3qde#4xAbR-M99q64E3ynnGOB|KYod zWLoFF5lL9};z@r2F{k?6ybq_kFG3jJfvlM501B(|+j_&#Hdl*r{9}0#2-w4y!$5Z1 z0OC*rZ$uOlzBkPm7dWf=)}5@|S&|heOGh#%2i6rwqvY-SI!^Wn$m-#gX=CBN6PN3X z%Z9ch;;FE*mdZDSQbIuy=UZ!AI&Axy5I28$nT)}TYVgpM8_Fz7Xzb8c)4#v>F}G(l_z zMuf|_eA=;h8J;+3N3nozyZ*U(7(7g$69Q8K>~s8qF--RBROwgqLEM9*PQ@>RYx#QV zG*xNwi7sOfDLv0nfejJ3p2_(zBJ7-3Rm^H2&vtZ7vr*v`Vcq zWlcLiH~ckOI=y7$nzxzO1 zA-VC!)%=Q25-OFoDyvR*4nU87y3W}m2~^p#;h~{__i;0Os5(e2x)Sv0-vt8q7=}`5 zg#>8eY_hvb*_USe9%XyL!C&q(SJcsyRcRSv4(IADGVW z-Ft`R3=jq=e2s(ojw(U1gEjL-=r<@o))vUORCYaQ95( zw)fAj{51---E7AH(JgXv1W^FOGIL3^7?EWCWzwD*XvmI#z(M%|^gJ9PAE%5h6fLMM zeISQP2@Gff#t>+bB!|@l|NUNmp7mp7V^|B9PzK70`9X~aKKq0{gSVd|mP-u^C%t)2 zW#{#8=onjJg45c>%E`g9HVb)9W$HrukACOQROfh;H8@B?Bh@jbp#(va7Ut%LSh$3# zI{#90BybLJjR7SGHFd&gJ`nHy&_{tyvry?=pch5+$_d9>IMk+3Y##qedZOxY4{X>a z@apN>Z;CaI3=Oup!HmMDtpW8wulkSP|139j#3q@HAdADKDmDa5YU@w#eW|X12vXRE zQiIkx)y76magx-vH)+0cxDPQcGgmE5HT8?$$-`Hc#1>GH`D&hOj6wYPTe&_nsL*pd z)wXmH60|CrtJa#hVu#kqjRzdEX42m4(R24JqI`qozNs#W$aHEiuS6gxtw)d85Q0$0BK@Ruxi_i1!H_G9H_Saf=0)ZW@5AegeHPv@wwQ~^2ckB=UL8>S1mu%t zW%0z?>(@Xo=ccg4#OcqlFy_zMjo6Ub`BKEH61UWoUr(O@GT>|}iS5S%5;eAr8rZ1{ zN^a&1RG+A8sNHv_Cj)tLX}cqY4DNY8S$vOXVP##{kd|&g2f$BIfXQt*fXPdR9V(3> zUO~JeP^?PlWrLug_%5727}o!Ro4y&S9lw(`*5%?95`+x7^sB$j0fT8*AbpS&;^g4L zd$jeNgq}(~72Dyf@QY|D&zlW9wd-=Wwd3;u7^X-ju1fYZ`7Ga)$Ma+ak}MHe`vi1H zg7>gx%+BopefU#8C3(4MfM3Q%Jq^z(85v8z4Q{eF--cVhJkbq%U-c(``!Z%xVL zR{EsAZ!{Ed#MsM>o4y->(AMx6$lCzVF`~^BU@<&dg9Fr7LR^^}p!4$D%r<)YWvHyK ztfeZxn;?H$JdXfjbx@1aZ+=jk9K|8w(kx_o`1{VI?FHQ!#@RG1( zS1Xt?NEy}W+0x_d$*U0W5)mk|kP0kWzFbkFlR#plSV8WLSnPy@R@{l|n3+joF>~cJ ziL@k4mPdc!lT_1R7-1M%t|S!P;l#7Mz-@qu`ImRr)BGg7UBU$#y~WyP-uz@l!Kk86 zr4KG`TAIy?e{c>-(fs5`k_E6a_2+FDUQU|p3D=k zR0k{NR|<)pvPSCrpo-u#$;TQBTlT~YfVRGv3`}U4S6941kTL??n2u+Z`DYK}Grx1B zf+-XeJNqrPXCGFAWc{=POjey2z)L|CCHKx3HI~RmV{S@%`X}3enm)Mhy3o@;^S=7X z76v1|ZxzmZ2xr=YL#G8aUG)%NTIE`cf2t^#SSC|G6D_`*M!oo z{#JH!AWuRuQ|&39?yt);QW0u^p!@aNQrtSwGFG6Fvzg$f<<=9xWtcNW+=@Q}r4u9I z0-(#yWOwe{n&OuAM5Ft$ORcN>(oMK-z1H+i;n9j9GsP1IjNADcVyi@MYyUuiqpbh! zj1p&M^QT!F_c1$Hy6<^5{`{tAG5z%|o$K~U+L@R!ba_>BvmsrkjC%a%>gmfe?R%gf zvVTAjdiF)L*-Vh-LG*iAg5RA9aFk-((HE z1UMI0ImhX>$dc+rSC$G<_|4cixZ z2)P-7AZVQE4fnzIUs+-IBPTK>Nn}1gktN}gH>YJri-KItBjPqSs=S!NzJ-@2G$F76 z1#Ify75BZHh!Bd>DAv2<>2`GL5nEY{PG^Rn%K+7C?+JjDWY`8V_WW`YAtW@fnuTYc zE3CPxA%Wp4*fX?V%3O#wV)27~m zmo)!ki!!n8Q1r}D_GhIX-oeOGEiY__7|DuLAdR7_;v0H3SMm~F((?kO&m`LpEFvXh5m2Wgg!w*x=RmR8)ij;odX6;VsLjXb2! zIin7QA}@>@+^_f73$t%nNF%7qv`Y{r2x+JsoaA7DZ@AGJ3_B%*k*kViL_`#Y*Q{Q1 zXEyX%{_n459UWkTM#V#z*4l`R(poSL*&+cAmWzHIwaUqwG<$n2CLD`}%`#ZtbUI73 zJXPTG_2uR7HLep$1~N=ZNcuRT!GF8(+ta?OFVPfjv#em`Nti6GIE7s*lJ8+~8w{ir zoxr6(FF2)MKKZoo_z}IFQVSXkiK*=qX(=*R`23BGe0eFwk3!Y-|IN`(d~34-LAg6itPqqRuVJua27pMTX}d|($&|YBNPt{MHserxJ+5Qo zcUlIFF~%oPx`z%M&r~Je>oVs9n)`~wq`5I>Y+Y0xNG_YM`Uy+Io_qWF%SpEbY>K-~ z4a|Ws$h;R(CmQ0fL&9&P8^@7x$(r`u4)=Fm2?0f2Bm)(plwtIv_vN}uvGihrU}T_L zGpCX92{R!D-)prBB5WDPJP^|48un9RT{eCD%CFg+h)oDp{|MC ze(p&HH#T`KM!dpu)+BD@?7=s>JG%pd|KRMAivusTP$fe+q02eL^Ef1CC(|7Cv5&r) zb%bRxCn>_Q>p+K0`0x*2I=`dzl`*~A&Tped(x-3R(0E}94`04xWI|$NLs}#t>zwHe zzulDfZ}7qrB`fiGiZf7nF&a=@8MOv0fzhx2kM_>5kohC6Kss%Zom=|I_E?{kAv>5E zB&dsh?usFw)qtmV$vA0n8$<85?aG7`ZAwYxIq=_Yw*ZWFH32oj5q$A)?>(o1z7wrS z2Yx^cd!A({AY802=Laj>pjh(W+4fp&BRLaoeB6yHpH;HL^sDe;^WSadxD*I4u3-n4 zxf4xdJr^m5y}u@IhzFUO?pFGV5HT0v5P52l#Yg|PNijLo1u&5a-(GkZSe@Zxj2pZN zXwIiTN=Oc15>wI(JRmeH>i3T}L+l;zM*?my7)bdqK%;$@rFhbnu>%z7R4e!O*L|P; z`IwYs;d3}BH1~VmH2{sEjg{0e7ZtzmY?1sPSO3$|S)d!ovHS z!*iu7mPACLN?{;MYYaVXWca;oUdN)9O09^&vkPjlP2u^OJP)4JbY55he+DtXU(QWHwmgTEu|y3Ar$@TH-W!k95Mzi_I0 z(Rm0)NIRJ2r0IXBQI2~fH9e}(@0x>x!?#NAC-8Kk^e?JxgJ5g}r!#PiNjaPrv28FX1BUe)S)c@&Do?^X%nq+l=UP~R86?Mg{n9pd$FgUG z8{~a_z=qAFCF*`1*WICxdfYIG`SPfD^R07tF*6X5^R>piCjHcYR0_A9UQ+FB~KCx=$jOmFM@mz`j)Tu$%l#r({~ z2pG|0K*K2Q!iQUoB|j@)Lu$0|p(nm@<|a$>PzBj)u_wvh-v}CBEd1N~4u8 z4ZhjTa@>VkkIsBssg^)oGCm=}dEy>-BhODE>u5!*I6i{Dm_5{UCa zM!K{9ZH+5*uC2+(-cvg00Rn6TG?)g^Gx=a*NZ8a+|F_#}#=_;A3;!qKTT`_a=QTkiZ?~0L zXan2fLav+BL_+2I0UdILj9?A0MaB0h>M;Mz7R#;xA-Obca|?V zO+r7%&_FO48l^G3yu1|k^z?`VT0An=-qY)!2Re@A{Wt}ab)YdAHW`4+$7W*|=(~k- z{1Ya~8zv^6mB6NrMSzSH60ja0Fg1z-vPKrH-Pm{kM-dp&E-D|ll||vZHAF4!SLT0} z8Vl=OJ-#V#DW87^t&4I#?{fpwZN&1b8Sv{cQD~v6bp9Ry`g z!st@<4Yk*AS^xXPB^eV}=4bifjA@mgI@x0p78<4h%L0T&w{1;U(c?nS0FCvowH@DB zB?hCmuL0@R!OLG6*G)u#8S0SbPVB2IiQUDk3JJ!dB9%pUSJ@|cI9aPwq}V*vF*#%F zmbFQ0sK?pEg?GKxn}C+n4o|*#zA(V^pWg*$wLvshWc}Ft8LG&#GIhRSqA>B?g>t6S zK*#~-c6e5+TTiQOdv(V7yLA|s`d>+WDFlvYJSq9tP498`NayVnxB{m2H{tDNtG!D< zQ>z38#4^Y<3St(Yc)a@=)$PYR0B z&>#AvU)y~9;y!}?sjo5jf7{ctShyzPT61OR9rue^o-Y1)Oyzt2_-JTvRk*~?o(i+L zc<~~Ax7~wX``Q*;5-!b_DVP6%MkAe1MFv5gxdv_pQQ`KjC2z zKBJl;Y1r8Ma`;YjOyLXaU?~|~4Yn*v=9JkI9TYUoz7{xr{z%~od99H@1~?%tq@_G% zug5;8-|-^{knEXFR%U*sm>AN%-SxGB{_``w)o)McZ)aeB++XXn2|EIAqO$YvE5a?S z^nR5X&>V06meU>!fnnbL4FrNK+4~78U&YUtlXQNU=r~%4WQNUHJ*Zmc#4a3bSu{#` zg|E*MYsW<&@__o+lBX!+>dMMOJqQwQbkR6ob?%;i@X;HHmZz(G;IP~LWb++<8{rp% z&&rt*uCZ~tP%Ps(yyqU0GaC*=@D6M*^ZQ$GUeL%|mk(5HDRN~cWU?R)o9;Mh8u_S5fqc$$XPVa3H%-b(_a7Nb#cI&d*rsxTc;c(o{h-$xT$!Vk19o4scA`3;rrM@@Y8!d3{5Ai~Y5|8VpYDcwQe* zaAU;_5Xxwfg@&45`K`_T`TmOLc}9!GcgBzaht!wXT($RhOd(lnP8Hb}jFw|=kGQfw zd=z{gqRf0x@BMxUu>?PklXB^1cgsIuc;f2iSsI{xV~TEQXjXBZ1oajAbI>PUg~#!={}!XAB&p{8{Wid{nPl0 z#>lVZje_;l(tqgT`yBy!#Q`1o;V(j8X!Xl5D=l;`ZwZPqycoONUi~ZWZ-k8c$DYLg z1AkB;8O$6i?k8#-9-r)c4Zg<|67*{cl%TmK2UiYJ#J2vV!D{<6r)^_Q<@uXukSjf^ zOCs!gai$u;$?iGSue^WvAtfac9n!zj`uojuo3%{hkKa0xE#F3^p-I$T+tNdJ|6cX$ z?5`zc?;Q>|4cy*LxO6SWi7^>_n+eTA!K0w1Wk1_^jyRmqs(tJJ zWk(c45}0tG2^dYQSXsEZ3jQBWR{>OI*LCUcF3Bq$m+p}65(Jc#5~aIKy1PR_KrTqC zbW2GIyfhc2yQQT5!#DHKsN)Pcc%SE-9c!<(HY#?e?62o~9I_eD-%W^AxBY!9AXdRp z9(XiOzoC4MeI|D|ZXx{Q^W!JI!jDj#Ik@A9SsTv&j1wetI6AJZ=*>1>6)V=;I*}zu zVTei(ZX;X`U;2dSl^&^P`MI#1pn?to4_jaM;ll5qT8hpSmL~@+j8^6ujmU* zw43gPDO+3+}UHNm7M1q#7O*xg{~15^9WvXZi``ZBCU^x*}9Ifj5}1p|o@ zvqEwm=%Rcp^i(T;r8mg&$;(3!K7qd64JkxMb=EyQ%Dexd$1MHSEm4WNlA%UXuw`{& zI9a)jUPT^3Ipeu}F|V;-pLyQgx6?rU??i<3IB_)RlNC^Gei2whPR|Fv2nkaT0QBqt0Imv8FeZ!F zI@P}aRNOt`VN&Ni#)(R7l!2?DQHwByEeBO*&&b%?te)0+vsm;e7?temf(oGtfZA>V`SZby9--72JQA>A*bsQr`v70Yny(1EL7y$3U<{VYYenI z@13JJH+vkBNf0&V{JS5^L)&Bv@$lVO>L`xhJ80!^#;}O>pk4 zb~&Ojb9qab!IUshhp|5&swu*jxAT#`pfax|Lezy?wTk-9diZ1^YE2LQ;yz2)Drtq} zNC-^H%7apcPFI7;Og!&qtKp2XEVl@qjh18?t92W$m)7REy`P*#4psotetk(g4Xk@U11@{T!xP!PHtm-0s}WZuJmCfMt?S||u2ttbUZ?K2ZI2U%G4hre*8w)B zsTdI!p9G8}GRlAaDW`2l&u{fAvX^jC7ZBy+*VBKYVwcr=NgFolOi9_2FqF#DXg>{` zj)Aead7CpG+y+m_d0)=kYUP3fnvqnYmi>H-iBZd%_|ZfOM}0Ddx&i|+*+ic{4y=A*l#uggJeU; zU5gjE)hMhhc6=m%yhW3~o!n1TW=buI~6o@;Zlu`$c(zZStNwJ#j3P zDOCig%Iyf^JyG61K_+Qs9@FB+W<{L`XvieBL^snsg3y?(HYX8eGOSP4 ziXVX)3{bq5E`v;|Y1Hg$Jq#*~f`3z5(t_;l?67CCeMr0RuO~g){cW^UAlDl0{0Pz< z(HrUB;xRNN{_u0FDbv&j3-h=d?_ktnSCMZkZ0;j~7#N%1^a?Y$+LlMth)hzMRh zj9A%MP*Is1v}rgS%NO5AFx1hPYmgI?AKV-VJhQ)}PwZuaF4tF6(10z8zall_m-;`G zttoevNj`G#MP5A!=gbCnhFBRXiscXKj2%6GPlI3%c_WekJ=Wk!Q@CCke;~i-5y2mH z=&;wUxG22BD;ayXcnZgVpvKPREZ2qx@1fpgiP}nCymo&cVf)1|hDV?i~dcy#1cq&o>U;Xay&30koRFliPhCUpFGJ=7S@IFPq9*`$% zfp(k>D3cso-FGK10bkRu8Bm4L>I~IKRifqe^}oQd6mIjD%Of?5N!aSSeh%_x>r1*Xl{*QItiy%$5}FTSvM+Xu$Np!0jE} zzvyyoJpC4Jsom%~y7C|DBUe^IGIq9m4ygh|i7aCdB1H>c<9MVVd-T&;?)a&cpdDPHL(Wj}|e znllfpeP=fhr7W|dq&G}^Ir#-<*99p)lM+@*0iF-5?xHsdLB>20%l_?FULvf7@wBNX zGdK-Fqfnf`JK`wRL3b)zKi7W&Z?21DCAxsW-LMuOym_Yj9n1T`pkm-d&;VxZUOrF? zl6~Pj`kL4635!3Zp53O*rdv~1D3gFQnc3ob)YY)f{YhCs(NZeE%WFj(N0I%$wZXbG ztm0)s80YZkN5)R8vk6w^{xI~rlAnX`pw{Fu3)+o1=4Z*gTz%wE&g-QW5g(WI*3Me_5R7m_G72EOg6-E z%)~DGw0rSc*A59IWCsQ&n1JzZKcS#jUwz{_5KM7aJly|NP@AF@Nlyg3X7K|zsx6%m~UVn z=8K7md7l*o%Q>WuEO~F!Jii9I#xIM&R`0tkh8JJ<+K;bV13z8pWQJ{a8{us~%~AS!PGSChpHc;_Vp75Lntr^&2`C{cWJ@!%JucB>FFyVg%a zS;-O_CPGg-Q-W&a#+Gh@qyPzqi`;9^z8>{zM?bXq?b6V!+#K%1m&AmpYvIroul zS#vVL?G}ZLzZA+35s?}H$~fX$6=NvT9n^W@D9^}y?Qc5ckxv|rvK(*)X8`FLC2cA# z3J7DaBGj4JPkh^R%IsieyA-ZE(zLEF(gGhQF>Z?J*)-AVhqv;}sR>%}OlCm6xzMnhg>Q9hBS`A{78)fB@Yihv>V~C@vy3$o!tuw z+D1Q#g1GcX$%PM+1T;TnM1)tEfFXs14-j;j=;?13!~NcE9mqPmEDRA0ZAi7U1kpx) zmGfHq=hbmoh;MllGyTne^Sxt0e}yvhrPE+3DzXk3af z9quRz4fUV#FaiA08$dE>^8~(cg}W`)>@mg9GGd+6%uo4Br|f(HGeI&>lO@Y2VSu>l zvHHz*LFCc%WK|ifhTj6n*~;IRQY*aUxmprFhTJT*cUD?T;VhzOQKrCje;?cS5+pt2 z)2&Do&ev>}q}AGbS95IcJePo2c?Z0uX4y2$i6bmnVa4Wqaq}|Fx4H1%V9}cb_SiqL zVQ$4)P21D4VtCfeCWPAg%bYc7LubwQ=_B{9XD60p2eL;&LxTSIT#ug%f7qA2N4?P| zLDhBaJ!a!pxcB{j(>`0Uot-M({SC9^Wa74Wck9gqh-iiK72ZhE$_Gh)YVIbH;v_%O znG{c>5(jhxO)(;%@Vvib`ch|=4HPpSqhz?F_a^c2VGO*-%t z_x&q@?)w>M!kH4Z41@YK>Bb%XEo1tEgnmukHoI|$p49xnmj!dzeIZ*Jvb42QX^Qz+ zOUXD)3d+IF7~pmGRh2o_xz*(DZjAoi^}3Cx2uW#P?u=$yPY-(LTSCqcT?bZGM?TG z?oU+T+m4df+K&Sk*rTpf*D<3uQ-IBTz{X)vEiu1;5d-(TWCh9jPil@(gmP)%6lsPv zs4Hn4-sCINwVe`?Z$u(!+)fYnJ@i0b|6LT($9NsAtv+61EkBq;(j$fmaIR&VZG;pu zlBWgIGSXKMJRNS#@R!4X*fy4^b0&BAkOfWUEmq;wKg36~m_{*E}vj09$L{C<5m@&jy zxo>Wx&21FGS>dS13}_R~`Ku`@P!c1gTo~_XyPU(s!T|fV+O%UXy`6ebkApcE4;>vH zWTTAvP1m>oKJ$JtY3L91P^XTt5JIEv>aQ33kPpxzqnkUuQRd+Fq z9WjdLt$r)4Y<0Bz+q{9qB`GxE($Vbve@mF~T&FT9JF4UX2Kazh@PL zN>D6>uKCInXD0}8Hz&fK&quz+E}K%Q_1QhTNiV>VKg)FSysgXn4@mkHUY4ng0*ro7 zzwXYL9*QHE0qo=M;(!quJ1ZyW0Tk4ErXc;opByWQ>*qOvLf=ZkLSrgUotcBT0kpi@ zKsY8P_(*lOa`)Ty z5O*r=dLwWf_4+XHR9nx-B(Iiur2g%c>{BE~VZ1CG{b`+P8rEBZZ2LSrznj4Gt|{Ei z_uyF^g=hj%Gz2M-j>4vXL3b#s|E;zarJPe+#G}u^Xqv%SgrRy21R6pnt8go)wo5#% zLc#H>6iPH+ep>JW#Pc=FO-Z#Hki$!-9VsI6IQC;fD|kQ&Y;d;$q4ZiJU3ET#(`eH7 zY-8viC`T5&fT&4BP~)WpEj|riB!O%c7>W+%2F|G$#yt{ww&DWe`R&!w5I)>b{QY!| z+K@A4u(Vl|%_L)`;WsN$-3kE-M`4y!mG?7M^DO}2azYH;!mLykIiOAU$N@@U^f3_T zzJqFBFe)lQilLV5EO`0S^bx}Y!0TK~H`AVWUB=kxtZY^`gpKkHT@#o+bbfb*n zNS&`%?hq=uNKc3BX!K**)`UYgYAsMeCK}R36CeTotF*)77@UV9OrB8%FH(+&oBLHY zFou|oz^4m5H|xf@CkrEhgeAEJ$ZlHLF{kQ0ed_l;VVLB=dSjLx_!ys}*hI|khxb~{ zW@o#@lfnt2#35=v0p^A8=E z-#!ExOxHft(vi;)*aPi^5pW792TwwT1`i0R2uE`}DeCvHR7QgEWwU&3xf7{{GO-JT z{-ItSh=&2`qe_f?F+itK%zc5&&*sp(R`}SX&st!?^$-RC$Z?;m_CVDMfA-!?1d)&6 z43(`#mz#DS!TUC97l97|2T*U$d4SqX5>L-zY*j8CqEf{g!#!N@=%dzB&EeS!7um0B zAd|TG@1@shHh=j%&~*9XQ3>qGl}}N!u`hg;XVjq#)+SLHAIEkTF;)~_^5ik`oz?)C zh3s-;GhJ)bzypKfkGp8y`@^d??_Dg{R^#6uCg5fRu2OH+QZLL(*^Di)a?=`&=05oM z;htRl=X9mQ&?;S1zAt8vN4?aIK*nv@LvG?LY39=lecfq6yt%y%ESg;vUa3_}&^xA@das64Ke^>Vla;lBR5oSE zY6#r^8y^NtsF<4s%B|owdDQJml@~yx8<9xgI%6jswAn@0~gS`n!A1(yFW)tM{=8Re-kejR3H3`k(xCXNaY+t;7*7mg{-E z%mwreCG@sd_+4w#SFcB zS>H_J@H)Src%W@SOY)x8N5WC{F2Zm%Ig|j2HW7V=%cR?>CaOkS2BQQ`u=BZjcPd&Su z(x1iOTuagQM8EIk_~FATi-Fj8sCea=4;1V4DkuzL8?Mbz;8(C*XEfS;`bu?-DEArM`j47meeiZ+vp~hk$z5I zen&t+4nns1zVnW!4ptJH8D<=sTj;x2;a3p!aML;PmRB@)v>sj^)22-T(n3m$8I&Tm z^DaWtr`t7S6o#Pk)}Zb4|8oIS%por+K!^BWwf;x`Y)%Aa5S;G-1D9nC1R~;K*S-x{ znCcs6Zi&>rnXj#t0C?p8dSX93i`{nuDrI!R5joS!vT-o;|&+-KqE#Pz_}AeN$i=4S`IU4r%$XvTqb%>JU`op-)e z0JB1X7EiEV9L%4e(@Oe|T=RcOeuit9F3d=jQ&u6oN@Y994P`>j5s>!hvJg<{*MZ! zug7Qz$;PA~?@n!0OhPR%{3RWjWmxfRBz#`T7wZK6En;a_TIm+pYQWs^Z2fc5G>Un! zgqCcbidWp!gyOOP-`ZpQ4P(&B*sxxWsYwiZX0Y>#v0x;1Jtq^X)`=TW2bxiGHx9Ic zbC@pz@7ceR3DcEAO8K8K*!!01N@R4rJ9{9j0llZK3xp_HCB+>i6EOZBZ@*){XuW1P zZGX?@$C-FbA*?Kq@|nNs?Vt?Gy;N+GjAyfPmf(k(o9EA;2XaJuew+2B|Dq>n<>BqQ z_uprfK2h_lC4xk0>0qWcEY*a!@0Bmy_x8;pJbEcHMKYm=7 z=j$uDllTIA&8-qpHIby85+=_VO;#>YUI1@(79fj+s8}^F#y=@T3wSO2MP;V3$2S0Q z`fbuQFDfe=SSgca3x0St05nd*XTFke4JI5bbb5)kUs3gY2(LtYDaI97l_js(PFCnu zePOG|(U{eFMfT#U7CAI1=p!{SQ%y1d1xXo@r+7t}L1ZgHv|8jP6{W|n@yZb+*n2bG zpt{1O^)g|7^B)s3Xf`Y;TtS7f3%q|X95!0@dkwadc|AtXipd zrzY3N^cO{;a$*={Q(hpx{qs=bR)Hm&y%1?&A^Eygt}+Z38YPlCq>OM4#PN41TkrJK z=grb5mGlT-4c`EBhrp?PuVqmQX=$@r0SyMm&-|1ay?ToRfi7o&z=cG>9sB9^cmIyt zAGyw>)0rtSc4bA&qy%1MEAWoI&t$!CI&03`@$Ylb&gIM_bY8t;#s>=&{-V?QL+5ns zMHW8Xb9ATFW?0V~cKG|m`W-cIUjUA6;oQ-!?zVp56bwdc&-;wgb$ zUa*AoR4&t#K&W zAQ-a2R3lAFsfvwB!AL@X5zH|#L5E^uOA(=w0)M!4h`$D{ag zaU5JKfL%CKhW$}3-4!7*9F0A-N*Ujk#8~t?fV9HVZ0J*mPJo%<7|=5u7Q;lI-P_7k zGw5bm8wukeKg9x%C$8NTpz4p{qkWXkutqZ+v#JMF5d?mKk;2F~+)7fZiZv>yes_#g z0@W|CtPew|25MHf5$>)Y%=qY^au?Cs-3rJ!Q?<9OBsjrQacgUDZvY*-{oh_?&BG^l zLE%?F7#JDXCMR`QfZe0}EO=`VNebGXJchln)p`uTO&CdB_CohHbEe&$$C(`Zk2f8C zf!w_}n1$pgbxr56*U~M;<~HhZd(p%FZ6YP_+n=A-mw!)0T2K(FOF}IHq3!1BGgtzZ z!s-ebKj#p8TG?c{C5D&T4O%tW9F1Ej!CwnDyOa=5fa2u$w^NlTdegg^Ha3;|Fn0;e zT@0GEkuo9`f#eOxcAoyRe1#2nv3THt0FcweVJ+$VZ^Ha)`qo69lX4B^1-^UTPaRz$ zh#>$du*Kv*Xpf@7d<~)c*lRHmKWoz6h~JL*pq)fyom{f^kCqdqOA?@>3m4Qw*6jLE zF9W!jE#~`dWn(A$$qk^nmb|EksHn?7LA6{30N`fn1kbHw1gCJErRt6dq2B#4j9}18 z(5jwtrv)(@gEo=c_M1dFUHhZvH@g6_=dN6kbXSNt@Z2~dp0}T&#zDiqiy)lzK<+EGaF?j$3c472yq*|1 zW{HGrM%ODh$Mn_#h;#Mi_GEK*s@8S-HK>W{8JWmpi@*wyLRw+j0Ww#2CdFzsGpWOG zQ!{ZiqV+34ur%?c7c&EtabQft8ex$%{}o407>8_RGkWb#o1ohqp`O$7vJLgWpp);0 zZC0YKk_AkBhde4MR(ws~U8rEL3}_o93KPAnwQiuJz7H;Z^De4!6iLj7}b)z;scmm7OLwy zb<@pOZXvR>dU{A=Zy10rGOT;o_!MxDcE!Hw3HL*w}Puk*aq&%tj^85kMHa3#23CG13HK^=HD8 zwx{*SY(%4;=g7CA-2|b}Gl#>0vu;5%SW6%TjeMKvQ}c!IHYzHr34oGs`V*er3NxlW zSK)K569vA6WWqW^seN6hCKfOCVeDuaGR;3)rnY1aftCdQ0hSpDi0{dOjQg(sy zS9`xX_|-rbG@Ssp&DrzzWZCM9Z@1E>b~*W7OQ^XsvDGqyl!#fhKHg*GGe#nh;Gp%A z;{2HyDA*ESzE3(km;F6z@rH>gTD!)yz25`qk!soP!`A1hJ3mt+(&u}xMdt!;#NO2t zs>cDK`YEf8!Q_Az&qFcM)FGhV;|vQPIP6)o=6JU-B6{%mHfhMlLXS}nLda0Ld$PT) zv$DL+n5~LzK6H3$4gd-AjP!ILSdV;QX^VY^+LOC;9q?BCV%q+1i|u9EH(EgB@3P{H zHqIvT%*8!Upa{3;^N}HvRl=6V2*%(pq)U{#gFI0itaeK`AGj-F>`#2L$BhElC1P0d z7iD3_uq4i3goWdo2UF$aKPO6*t{g!?IMZWS*joMYqe$X$l3KZM?V#orLgP60o9fWl zl(i`{0Bnl|kc7t6^YUH`z!z)oo;3>!pw(_>qLal?nf2Jzjx#A;zDR-WA~@e{W+r+A zbL5&l0XyA+R4pkVlSEocn>?3(%5xw@LnNS0#E#A2E_W&$eH(c-cNv2N4}RbH;`^e@ z?X#0ek;0r=OTb^I+Sv6*a5W{*{~& zB=iNjsm#B?$w68b!+&-K!E~ji79E$zz0mhdiSZBa!>f1%!5?IUn4ak2+kLu@!fq(G3(^Rutx4t7%xK;0ik$#0YQ z65M(&fO-^5N`0D@(sd!&UbFuKgCTYWBO*&xm~}qPxQlUN+g$p>pU4-Z1&v82N?-6T zgDjW}5w!wow?<96wlCXBQp?9a-`%yHIpaWa9aU5?xjXO93_!yBo1T&MN5}Y|iyotu z5t%GT1>jD&IW?_DWd3kk;U?A*-R&ISlxO{;l)oTYX9M?r-Et@H*<_NwMQRrfY4MVz zX8EsXxWYXGJmr#!F98R0b(x6h%P*}eDkvg~Hl%8b0Cp4B1#W0g2cW=v0aG478<56l zjlou<^_d#8l>_r-bP?GIR;BeK;$;0~?X&Z9rl+#sOoBF&a~Lfdg{yM)@JbpDBF60N z7)G$Ap16X}-1G9$H{lkyBjblXT(js?5RX2FzW#R*0iV*U+7noa9@6C>2&z5kc3_%bw z0JZUn39QDH9Q7e6Wkz(p#86_d`Ez^0kkx-ZJv~2wYuVg7n82F(kBM$q`2zKN0U$V; zD%p5$Y;R+?!VA8cZUxT<2{?LJ_IiU{77xBHk5qnRq&e>Mz8G`BKWWG5GZIzi6s6M2 z*U*pQ!BguIkl`#T2WIKZzp;jG?x?89QUofSs>#E^1l?ze^HXE`NxSlE|h)PO5=!bOYjNDUB2sE zI!>t^a%_~#ur2|I=7VC1ODz{p3*#PLO%HRkQR9L3Vo zQe9wVXxk3%#Yd2}8}^$B|LrqmQl~^I6A*$VFc2@ieBJpJn;C0nKR_F3v>?1ky}~zn zj`@IGqK_4e!Y`+z|CiPqmB+LtrOh-EsBt#|wlsPTiqsY(S{;SgB*T~pU0`w87a>3p zas&|iu6WSHog+XwE4jnW%clV73asJzID1jE(CfKx*Z#Dqqbtg2x`S?=6*P!oB7>;^?wLA_-$+E zuRz;KT?Q+&3$rp3f~fGRs;XKUWvoAq$fa2{^X9$+eTl+A{BJYjW|Zpgk+e}82dP3H zFtYZNhQVM^e3Q2t!*6>GRuJmLe(mi*!-Drbi&jv#8B#})%;(onPXK49<;g#RGghsm zIC%4kaOMkk>No<6ygwVjk=DKi)ILgpxa8>!{Gu;p=;>vPk^qe)oFLTQ#6X=%?`XV^ z0LigYe9yI+2)*^FWQ$PWJLqV>Vfz5=cgh3AT3p7I-c>%FjwZU!i+|XgA8XQaZ5rr$ z36R>+O^4jD$aUiPP({-tT{j6nMiIm-S~IRak-z_&^U0Z^(lmjQ@X=GMkUafKEDRP~WJiIT7&J@Q zotdW6Z$DVr$mrApnDm{|SN{a{3~Q!zRTWT}xB}qsRp8$Vr2rQ7tly_(F+&sh2$_8sF^i}>K2g# zX_QE37(f76LXRR@V>zwULGTIHgt4=w*d&}ILEl^_9z(<=oyg_xIx)W2-XnJ zz;nucxHLcL>z|IC*L?ag;OQ)yKcnZjS%r*{1x7N-^Rzr_j70IO0HO`p0^{H&gA@qx zgdD*A?Q%!xs$$U$Y;8)x(dW#Q@Nt#S1=}TH?8giG@>3o8qqD|dsPba>k?2*Vu83v6 zk$dh)^>OZy80+EA^?5h)KZ~N_hNp0H+`K3Ay_um~Ks(eDDuApl`TGKrrBN+s!!0b2p6e#H7r`q0ofGgW zoYg=9*$?&?UY^O&n}c!z(4ebcpG$HXx2gY?XlzZ3l4lrBw~C5F(CAGtX)S`q0DhEi z<*Tn`n{i2>_gg-*#ykwvxYG<+Fe-U{1j`jP3q)MrN;AGCL#i(}Uo&{=0u)Mt=jNoc zGEh0j&|b&L4R?eQR<4CnHJkB)IHPlDxibT3zCr--ucrXiFf!0TJV6i3iXpDiHfU~S zJ-@q>K7D!7WX#fg=z!PQNJx|LKFOLI0k=n4ZpUJyj8$V@0pUeN(EuVwaBU`om?4da z9LgPyfzZ3(<_(({%0vv=>Z?DA_s+q9J+(kH`(=On`GdATsX}Y4&uGhqvsgYOS-dLO z>u=YZfC<&Y%@I?q|0|bnppfj9_tQq0T|kwd@-TVM=-Hb0&3VYR>Nb!`*zUh1-CZ0E z6q~7Q)_R=sVa)nP20kNuczrrpB-8yZ+pW&|ln)TBKLTn}Bpk1ND&bE1_um}XK2l=R zHd-N9(?mo@YWqtnXJ4vp)5L9*IeSE9`LYlqN(x^r1i6fS`m+sFtw@W{7R%ISmi0hOD=Bti^9uUvglz><%Ae8G( zapy}?PG^eWxKRqnIvHBcnta9RhiXuPebYm zL?>u4-K@i(ZBu_-$4oFKX{jzzNoD2;UN@c^a5)+DneqjN3kx|JSpg98%~v_k^X^tw zG+oSe_L?O{8mh4I7`D;y+l=;(h@OK|8Z8D)ze!Mmu%TN9L5ACk7-=*7v7Kmi-niH( z0*1EgE$w@jEZxn|o8x4u1iO`r2hMiGc}`rY7Opz>D590^h$oyrBOx3;cO7&YLGbN-zSm1h(X4*LLrCOHF*)jeQPd%CCd35Klq_NUyoL$_7^;tMtcO> zGsE^vKX%r^b$Ld%R`}70_;DLQL|R(f-`{Fo1doI)K(SBr7D&*mU^*rJW5vbAIYPOk zRahKY%f82!M6zAG?p>SCUF)v*o`ZcjHn3x2m?hX&JOaRYH2vIrfgc`<}~_2^kK#U?AIFHb&qH> zbvgwAam)WGv5A>`iJ-T`4gFQm8KrDUI%CMW^}6nDpB?Z8Xe(=KYAPGG;oupA2FrBe zhl(;WXEES3KpxzRjW&oz#UQ#x3+Jn@!eNWEB-~zda|b^3y4_WBQJjCV7-pr~3sz7* zDu-vlaV;P+koC+|s;zcq0$C6XWUMr0n~Xcc2G*mummBwd_))coNm7LG(Tc_^|FHAtLcR(zO=0}fB<;1iBoSq|9$do-z5KXjMxL|_tG z+3cFN1~jMh9RYsvpvmw8ez@ktlP;Ib_1fqMJPnPi!$pYdD*Fj5IqO2h>1thK4$G; zu(D0>ZvY<2!yUcO@J%1QTnurNtyj6c{vt$3VEY=kAt+lBB?O84(URzgRPV>k?oXrRP(aZTtc;|V`Q2&!irxrAQ7df35RJT!no zmgm#97GI#0t_OB~b-7V*2(1qKs~1>cfgj1|Kl;68wJ>FLteM&ub?R5Z5gpin$WKML$1^ z946pJ(6hg;ye_@Zu&Doa`NX!Hay3H5&VzU0(myT;N*J5lh|qZ6olLzly@69TnQ#Mx z&S5Z;cC1YAz)p3C<(fv^CvR|M({6YQ2ba) z>Y3-bj%X{2C$2~38$gtG%ec9@d@tsJ9$ndjviKaA#FH&?ps(DtsQa2TO zvF~UB0VAe`RkkQLV|X|eyv<)#A3XGMlGEjUz*?0f8cE;3xK9*j&?ha5+5#l02lOnC zkI#A`oM)%t3z1KIjz&PMI^_Xg-k-RPa)fN;+-_0ey+JQZlhx_Wc1e<{{o;ibkWD`I z6K49?#zB~gUnI9_s&SQU7+! zTXM>}SDnGVcLjB|W4L_;SjVFgnEwt91GX1F>`^?w*pMjS0M)h8cBhc7Mn-H1W>ony zd-DG0dVZ00jD^=s#DEF9>`z1y3ko*qK7t;`>3LTrK`7pj(XAw$3|-s1C+~pVj4hN zyc&VKw<;5fh)*$+#ruI)2F%s%8y6=)&%Zlk#&H~FGXfeEi8k$@qbI z;B6gJ=L$aP#wj9Rv{Z`&vE57=*x5Y_)MdWl^r#BG|8J)iyv&iOK4=8|g%hkGVMPoE zK;L%|JWg)?6#~RH05b(mh0+)|2kKBp6f3!Qt8=3 z#Lkfqcawe-0vdS0&^;w3X-uBY(ac-b~v&*i}`Q9YJV6 zX+-Ty6nwKXV(b`&SK1Q*B=usvK;l4x7kM6y(NbL@TMJS7>Dc(vyIl$`OF+Yo%cSM+ z2tbzj&3rb~TLBKXu8|{PjuF}G3f9(U{`2qn+*cZ4xO~7Wi}7NF^G(_(gd}z5Lom{D=m4H3 zuYmWH;}eHQ0N(6o+dert`To>I`b%QR68Du4H`E0V=EA#jBl%fN4P%# zh$&o70JzQLAD3{m0bSw@5F@7d*sKlHs0;LtkO9AE( znJvB2e({2a$Q8QnJeVpDBj()d{o!`A)^jf~U=J93IQrhP-#X*_#weF1-+eOvX;n$& z>o;#4fa&MRCKdJKD}iGv&565L#G_85gJUk0w!F-v{M2gR@FEF*Z{oo{;MNV(V|P$F zWkC_iBkmEaSo6jRk^9v}8msVzyHU&;tOd$=!V*7{^O*WO*BJuwvqe>U!M0A#%oqDT zk7i@mySIdKeF9VhCGP~NMfn37ZAECjW6)`R(^zAYb;Z64qSH8cl|VCi?>(Po@29tz z_*z+gJ{dN9IO916Zq=!U0LkkGXs9^XVQuWT&vi%Q@vZW6w#GF3wc_WFISml%mFs-X z;3P)OG7RULUxowCo5PvMr(isOaN1CKfACkf$Q&#gn6-|4<~e6F@XTw83!9A7TOuA- z`~A0*?K7j5TVhR-)yrNy@q;li={|u29~v8ptJJh^=df@#617%c&STAv6FiAd4-N&l zyBXrqpt9tRY<(5(K%`gVIVV_7p$Y6qY3LuSF>K6T-d7OoqWxd??!Uw11f#eHE{#8ea~!O_+jU`1Q~6U^zD&k zCa9XR2+=+Xx|FDpu;`PlutI%QCuk^L5+(kjG(&$A>d&2k53^?I@RD>iGe za^$z$#k4`8*OZ2Ezy>Hr3$U8}rFeRv?%}cwb$YcuqAYm|BDgK9keTPF&&C;$2t>Qw zFp~cy$D&qaRwM49B!%&qbR@Zj>vm(YK>NS^?@2DXjUS%29|s&afWf?x=cEeD{Z1VJ z*!4JoI~^#3s>RR!ZLh8YNa?=|E{cDgnnFL?@Y=k z=H$Pg@qPd0nscGQ^UiILj^pzQ;Up73^129f3}dlK|G5P7rF(0$-1F{)1Ik|7!(%RKBIFf^p!c{o2@@-Mm!p=5l&8f~Y*X@1kvv zk$ZJtxFg?t3>M36$aq`15*g$C;I5TJYS$sCN#5}w;-^nJ69|#>rVsn33z2thH_Zg= z&a11dlb@CvE?{#v{V}s0$*NlrWtmbQ*4)RkO zF_)h)NKmsn)@BmfD;i5c#D*L;0{m1@f1M5ICkSDc%D(yfYGXX;;k*|QjI<*?A;cDu zoQ%Ow-ffCH@)TQD-xaYaKk4CpprT0q&qzmBmtC{iI48j)l6ZiW>f+)*c8&^t1A=J- z%fB*ICMPHNNl?y2GFK`pTEb5MKNd&LkBF`ogK0y=@a6MMXKehXLgdSpNVx5I@z(~q zKNULWG2b7M4aUUKExLL;14CDnu_WR7KVE-@fx*IdA2`exb;zMr(pSormuzgMFI9KD zySpa=O6|P>xDoe_00Lce{QC84b!+RJH@2T$6pM%nx-sPcoQTmp2t-Q5*EH(BXhyPs z>%xgP24#K4IZHGF; zZ!R>M)h&B4?5By1+tYx-Dd68!;lpbsrGde*gKFz5ut&(`?=g^V?&vVDqX-<{vZs5*;j|A9c3Eb56L%C&F$!8OEG~M@F)k1$ zup$>9edCHjj!k0t#Ze~B%s@mm?@Psi;IBpa?LhX!z9WTgREfpjcR}#9zZe)02+j~H z^g{GAB_w!0eJ_2ENc>^0w!)yQ#SK@w^Ux9NMq;W=)75Vr-up}>$P91J$;orkgZa?p zS#;lLK{2<@sy;y~iId+Y-UUcuvBgnOOW?7b=~(xAuNmB;I=&d2*$U}hp% zXUN%dRN!b?l`J+{5Q~6PpM{_WeYrkf>zzsi!_`yyghixJFxV!)CI5DUmDkbID|@mq zj=D?K9_Pmvlr*-$Zt#$x)P|Y^qW2Uj=G~UU-uCj9Y-B-!d7#w^lHS23s z)vtaQ?uQP)5aZG5=onlnK75%Gg7W=EN-wgF-m5{tUH9?XK56UjeqmHOz45e7av0F* z-PrDU5OuaDFD@@HKNFHfHYlenRnQoDJK4v*Oc#s%uJI{^fZ$rx_0Vr}xr2P6Uw#yE zZ`wfD{I$x`Qq)YZ9SRry-+;4biKLCaD$`ceD#PX&>L3^NaQ}jem&`5Aej_gE2c7um zSMb=UgWk~ka`0`kM&)eF^YKn)sm$>uHP98+irgA4iqi!p@JcnZC0P0Zc6lLSP8j%G z0|?!IaG*r987 z&gjJv^Ru74Mm23!GAAip`%R3~Eg%=zQPym%6J$JL%$%CInfF`KPgi*s4*%-s=U1NR zF=Hfrf442&d1Ll?VK$!2XZ@?pDkRa94$h`XrqB*1ijRk7xsLywgMuF2SFWZ?vD^2L ziw&3W9nfXR<{{cV0clvgV>*>rx|D%w$=%NdKJZvLpstMDmHHc1vI&X^D0>;q4q~$8 zIj^`3Ji9U*p!GXAEZ9Cwf>AApeKB%!avHy;V{T_u4HwB|RW|z&cU79t|IUQzwDtLo zF?RT!zgP>Nd@l2-BG30dSeO~CU7hA)&HeeQiGmC_4%-H{43heXSqB0Rv%v7e6;I)2 zY>e8twXH+g+1l#m(;m^{jT-(qd%<`cKg1r^E{}<9)pQa>O!Ml&=W8NxaQ9RPm*E| zfWt3=<|FE+6(C=MQsFtD?XyRE@O~{Ims8a;-5Fg+;%4 zom%UAaH9*r?wG}x2hJ;6F)a5*78>@I_h8DlAlQa_b{%0npQ`1BlkmCRa>S~t-#0ReCm%w2bL(?A1T46Cz+(_gj? zwJw9lIKG=v>NIAc|3Nx`7EC zTpf|F8=n{=sXl5k*f7YWDy*E+0w7Q5yaJG9JX3=PPE{FN7?ZQT$$A413hR=hltW;m zX*~ckaCpnBy9Nd)N!2g7Voulv`E`(@;}rIpo6Cm<(+ z0S=9;82}+BS^@5QLxxnD`|1D(csUK3lTGXI&Rj_u&C~@0DoP`T%c?jG4fkh6r!Z4WWNbK)6M2 zz2+0?YUMEA2s6PJ39oi|@E6i#an#P|14~!ATgn62o!=HBBBCb-m7Er6zaZ>!-03fM zr(zkuczx_V;`aciuz^cF3~rv#E8xvK1ZA{zrh?%x+XjwJtEQqVqgG_sXj_*bgom#SmN9eXM}I2o&@D_>nn;@9JhOKMfEJZY}_ z6%UE+dnOQ%vfA~qdIldk zgJ%+)<{Z7KL2r>==LT!eGkzHd=iUURPqgzDB|}#9u8!U7zcEJ&?1W&;km5Efs!cHR z&vVrP4Kjonh|(jkcAaYPXJ7wYK+Wv6Y~XX=aL2X!&!lC0iMeiv_!|$8Z1i_6F+IeeVkaEy%OLw$!{>uc{@6i;9GVl!^?%9S44X zei$Jnu1!_ckH+*_6tH3@~z($CHbFIV@ASL}ZjYgFBgd3!vu!~a>Qrk87` z&6oAC$@otEIlv$i`GNU!8JmR_Fiw2~UQMr$+KR|&231>W*Uy#7VE7%E)7GKzHE*B5!oRHbMpk;@@=$=9Q-~t(=xf_C2wTZ&LBx8 zX)5dT^%`)SzX2+oOQ3|Q=gpx3Oxpct%qX#;pjD@s>AH*kn~9{NV8w42Re(q6Lp(xiyOSlCCK_!%rY#&tVj{*2ChN=l?-D^KXht=<#H-NEa+ZN-%V{VQE#r9TyQ+!YhH{`4G>?*8ns7f%)zdXIo)Wx{xMt1_Sd_EMx zET|_fRmn5>#l^4&2_9++sU0urz%hiwN?R(Rod*8g@vLcJP%H=ni2hEvTIhXeTc&T=0aTRl&a6}b-~R&z1HaRX#;eu$T=u`S}!nh7;@$FT@z_&WW!c%h|V{rg9H?tTMY zu6I}bM>q9yS;0S*_G!eli zT%1O*=p4o6nP_}r%03Bn#3~=FW36U>V80eQum<=a=SN`UnQ-m?Aon+mx!z>(@ak|C zTS5NH!ZL#^jwXz%b7V|w1~)xXT7#CL{7)+`Q33(On5Fk{aloiv{w3-IV^0vWj*7YD8qwAv!wc z$e!P3AH%6y7MPqSUSfC(xB20PC&TvHks?=KhO?L7Q;ffa_e8cYlYCu*eoxU?S?cQO zSo3{9Gwd;4ZM3QcApyi@kC*r#iMp}pSYP1-ySuw*?~soWWw6lInxvMsl5Oi=Px;{SioEL-?N8^;UTwHiLolyqw0##m~Jkk|s*4(Q+2@sx%#ajA<7@ z!aBD>#R8Q&mCZ-DUWL^fOc2_GO*z=nj*6o8Ns3(hJO9>ZuwG3>1EWb=qWg#9<~oe; zB!q80&=VEc^8nCNvr6mt5{$rA(3;>=AIirzD9!5=$SNQJfcc6Qa&q$gMUyijnM8XQ zlW)l>Dwy!lYbz@&ynt-$00>#sX9LuFlF0mbGcW)z&`yLd1o2^tW@e5hpT+eX1gyTM zGyCo!XquBrmW!M=Y2qCF{H%eK{FL5#i&;vU6D^0p7GEGJVtXSX50Nd?OgFmQrlCFe zF)MPhWWX{e13v~^p<$<_b1MGhl$oR!Y7*|MR{@n92PG=R8(HIe*CFjU;WUlk3pc@f zPxLWh-MP+^e_OXW-rURqT9N!5fR?KXWbXZhkA>G16UDl3I3Fb5j#QTlQKq?o?U zt!`RF_s12NQs8e|PEx>3@mR$qg2n@&*Nf8#;Cv&%zPNgLXo6T0AHnNLMc=&{Q4~KM zPtgNuw#s7wPkrCKUo!k$0{F;vUJPy-+?OSN{nFPJ^>THKK@gYd@8% z@;8pzm!hJ^qf|*jRq_%tg6c(+%hoV$Cpff&HY)5lno=&QJ{}9D6OTngOjH^n_7W@k zQew^va5z-b?~HK|i9~TX+f)uenMy;YE-tSMiQkVEfgyPl6pFCYSQr#aNM=&c$F9P1 z-H9D~B~fS`38g$g$3Z+|<8~l5PMfm!0R1JtqQ)bRRE4w%0&3B!vBvn0MTN@|4we%S z31B+zcY$=2XI;-XCO1;UINpl$Tz^ps6sU%v!!Gk}V7k~eR29{F4kGb}b+oe#!Ow1pe()Q#7@P0NXa zG4AD`h2U%aq&a)n12U~&L0SBV{AV&OyyTb*r;d_*IqE@EUhMbp-*GE7^`|F#Uh&Qh zKA2L2A{@=3rs;Pl(23w9B%%gi)^xu->6oC$FL>;v&tm#Z;a{e~N08=j`eTHB5B`4l zyPun3hBi-MC?e1vTz=czIdvTMkV_ZO);vs=*2^j+pJwjNpKY-6$fNZsb za!0$ql5(-t=@Pq<=sCLed731eW2xbX6-kGRGU zGNjW3v$+(hUG_rN2A#rAM-e>f8V6$0ynun6@qsVCnw(b`xb}#Aulk@tN&@}G)kPM> zYA19YIvM05;%%QC7blAdLTbL`;TeR8dcId|mfygC~nQD+__fCS#y$G1kf+ z5I&G=bXCfw)*-r|TwrfmM=sq}bXZSuvWSbyO)XVLw3C%AL%X!NXb$FdR^5_wQ)&2q zx~%BCi(&Y=Pc7)>B`Yg?t~ll;8zk$$4N2U30(6wTAAr`usYdq>6*|R$OVLI*CNfwb z$L2^i?oikt^2y^@bQ0m{)r#wSFbd>mfk#-53VC&07C2~eqrx3sS=y*S=D{)^VVi*Yva=A#>jrHX0yH{d$J z{6T=6erHV95IXD^R>|u1Ql7HmQQ!ih&4kdX21%BUl^`#fZ$us!r%>ug0EEbjlzyXQeg7FI zCi1-Q7e_G2ilNz$CDwCdySWzu(Pt?d#-tSFI~u}m$^aC!lz68`KdyrJtRIdj(Khgz z>Mp-V;wf^^&=|t|!M$GE~flqDbrB(}YTsF|XAcBUL;?zb; z?Q?PAL_dI9<-n0lvba8{$;N)~z%0GNdk72BwjGkNTCyDVBo(b+k|!FIRkUB}HxWVs zHdCeI&ciCA{VWE}&jigZN@_+KE-Et5+$6niX}&zm`lyaq z2SvY`U#u#9CNt;x79&vXjI%NsTH<8N9{Y#7U#1$!{vls=Q1&Zwlm0$%();)0mC@iA z)laNhExJ9pN(;dgjiZ}sX5D(lYVBK{iRyi4?&OLvaSsmwvvfa?LGe2lZ2|Zjq?Ukl z8$({ufLmLVe`-hNSrS+h2h4vFae^ODxtq=tj25QA?dyz)!esRKhJKc(Kv_4k;a+Sf zD+-C-SRYfTv38E)3eLwE9IqbZx_q%NS7|tc~ zx~3NVY>KOilLxNk3YBsi+Tw=AC>BJ00F>fGlRg&pqPLrw|$1>}C}2>dIX( zruO540J`*vriCbHFcD9+|8k49-w9Qu_?XX`b4x4&Md2moVdN@2ZVpaeqXv?n^6U?^ zPL#32{f)|!Q719P>o=AdY~5$hz88jshtP_!?B<<*SAAK{lige*{~kb#^Lr*&VCg9D zzdvX!!m$6%O`D>C{|niN*(5=Uq@u(Wq2>Sjl>(py7jpd%*>eGJW!7rSWg2uftYmHVIp6bI!(MGkcUIPyq&s1#73* zrQMhJn>`^%6cAUdn0>a#+-dx#bwy&EneOed#EZR{wodoTLoBkV@ z-S2HSbm{LCC!db}Zasc4Ef_WL(>99wbysZ|D?FM-kXxL5dtuGra5auiI)D zKd2qipi5*&3KinL;pvd}Oj9=^2%yM*TT2i_tg0k&?}fkJ{o;Z8gBGm!;uX&tJqp7k zR*aE_i87C^9?{~Ai&_m8ioki46s4EgAfZZn3y>>O0g4-1 z44J;sLQ2tbd?A80jH#5L{Auj-=AB5Q z_97riPtALwwW5W5er_wB>C#jokc#&E*8hFjGf`;yE9p^7i)LCH}aIAQl9K zY!3K%c;jXp`Yj(7PGln!K;e3)Kol8s7j;%?QCvX@P>N|=-srwf0hqh6p|~@OO#jhA zrQQ)z`I(|6;NZw)EOYq@_{b++33#vH%IAUI!Lq)rKbd$_?7N!x#|BI$hQ50=2p|!l z2eC>tH#S~H8@}x}0+_FDcgmctjlkyU@&yVCGIBE~UaZLCv>e!(rv9YtME6*xx}ed< zD(}hJJJP@avuZ6$e(X>r>Q1Uk0$qNmZ{}-iP*Rw(ES)YiFNzWz_%NO|xva9vig^R+ zToUPYf7+YUAv^GG(*Rt*Q5RJ2$3K2hzbHZf1;G?oM=|=k13HYmobt{*aHg37`#0q_ zNpe`ZB!N?IahrsyA(pBxnyN05vOc!*`^Iu%Gebc$Ur94t!Ed^fHVKS-ox8e$Z}YXI zU@~@YL~Fc=qXkk0mJN2!>$d(ey~uD!#+)VDeqD)UU}V&KD;03jJpTs0^beyz)aN@V z!NYMyJ{J3y*B~!(>wU8!fzyJrRPS-0DOvsO((R=HU+v=`zAkV}nLFwb-yIk4t*thz4&lfkXdr*@ zMBy_hRMe8Ru~u&?%}wp?T6C0KANQXs4Hpkok~uMN7z45L|HYu)t?_O;&e;y%Tr>Vo?^V(rL`3^#U9N9|tp;cqFhN6&nT1GnN}IJu81Y5FAI zdfsS=&-S|su)&yM4tP!qcvk;D5Js<(%4grv5W_$F*QNQsZin1Z)V1_5kP~Thg zUfvPn6Y|*+nr%x_ax1~}i0{U2*f5}INrG#UKzB$9`*@FCw#1cI75?5{J612=6U+<0rlicjnozgZzak>QDK$4uS*Z&R#!(n7jQURh230NZx;bf5tq3tZGj_wknFDAPJ za*c`M4Yc{$3&`kfVijaLOHoHHr6hmDh+Vf~&Y%2oi*IOB6@}r4LolZ(MqG5gH^zKB zy+{tSM9UIRM`dD&h7okL7c)bUc`R9NGZukmLPf>}5WE`r0m~`T()>Kei1NR}BymIk z7OE_Z(Jm7hSCJLt8EPS6qN7u$OQ=#slq@6*5qp^4Tvy|(qro7t}!r3=R zkT6POm3v5*)etFu7hY_U(Sgk^TzO4JIA<4bP}XedIAYae(0WYVeK!XAxkJqSS~}4U zN)AkC>OS7BL>LNARsrgtwl+2hw}8;x%MXB%|E$&2a~j&Rt;UxP^CN?Wcm*xr??Be% zXx}S9@+JWXLN(-1E~nj9k$k1-y;hQ9*C|Typ77* zKO^?7Y|6mci?&f|ucX$?z_)47e$qpaREDBdfYVnM>Ib@BMY&XY1ZzW-APD~o^r!Y* z=ZRAbW=}05fdW@@s#~Yx-6x;QqZfxaNF=E;u14*?Z18`7(dV#VU*-pJI)mY-G)}%v z7F4t2t<6mVJsQk1H9CXBS=2?H6qQ1|82)KdTC?JmDV);KD`6bt#oj>2!bg;rnL4rKjw@y=|7ts1*_%s{u?`p587$3I>6{?5ie9JI2@XaV*kwv)M z2^DW+GPyO7$n}^swtSVfw>%j4KXww9Uz8=9d9Pv+JgG&h^l7sqX1F%AF1^j3BZ&L# zmu<-HWHV=~GwASW!Pah`7|8}!AFk1yw8J)#wc+DD@`i%$$9Ee2l~M@w9gd#WMUf)%FAo zdw(9o!^7dV%_K<)Ry~i+6CJ{W$@#|sMEUQGFS}BwCY-gst%DXSY)&0A@sn`bjJ+-0 zG=#qcA@MCmsU21UXYYFZp`PLmXE()}7fFy`%C6Db)1wfpC~W4*;{g;w9_H)cZv$M& zs!_O3^d1p^#VBYgfoeFuuU{8HiE^8S(zl)iLMgP=@jy0Z2Y?xzlmnty{}B+v3)nR$ zI%)u0T#KfGm)RmP?UXz|J}Tl65j}v*6f+c0*%`ILLJ^@;Q!q`M5?{LUS~!)+*oH^q z)yB9yt0W|OrDzV{uA$q|{Ud|r!LS*3=)0Ar!J;BWP$2@|4u5D+QZq)cR+$V7N&pI7 zKSO+C5J-bry8JytBulyzQICHEfHy0&pKsHjzS_WgHLK$z5*X`LKiPJ3{ripwa|iLh z^enU6=P6cxDy!wKnk|6sJQyM!yRk-atp3?u==3FwrGKy2qRITsyyn)WfqkZc-z75Mtqf^=VBQ zk^8b=`q|Lb^ta@MxW1_=73B04bO&@Kc2xf6-&0ZvuKff*MgrbOdU`-1IKegU-h3!3{daJsrYUzx39mz5R zk3k_vLn1Zz=Q+BrQg6Is{$)ovM(l@8CAKJ6ecOh-CVj2aM{4sphiqnW3n z9~Aj=6;fcQ5wQR?B;;NK&%l%pwNFlIE+7u{0yMf70a|pa_4Tz=JoKT-sSm1E4^(Xr zZ*t7#fA74-&bZEC=vlC2JGC)Ta9w-Ib@DA4;C^Vx{Hv3y&e~T%H`o=~>1Z6Vfe;{r z(pFd)s~;kjMb7X*Ws&$QnKJ+d7uqn*bF})mQnP2z=JSm?9YB6+1oP0YWLgZy zjgC^(+0uGS8-QSaJ#J;o*p?Hpvgv7%#;4pV$PCD+GNt2(iR`2ThN2#|+NA;d+Rht< zHa&aHA5g*a0~>#^uTEy_)WoV1S^fI+!t$9IezBB4k|-gIKn39k98st;hKksqTwcF) zsxciD>s$rLrNp0-!Lr@(?GzV{lZJ_Khun>;g{SY#z>?|{jDSiV6d^3DQvtg4>dJA) zg!pyY4LE{|)#z|dVw~rM^));`uTqX)AGLmy2;M77ZAe{xRo3JrUA3Ksq-yfdd+u<` zRzvy$CPoUU&jZ6GvrZiAlXYg&m*u27N;kbCT4axd;$>fVwH z{Wo@*@J!(Hz{i}TWL!suiHrA zk00Df0d(A`|C!C7m$NuRwsHem`HYMr8QNQrw3}EQE#pB1HahZF?4Hex^2zr!375gm zsBLW6cS$^pcv#I;u7~9gev&RX)jq9J(O#8O|7d2Q>L=JYid_)Mw6h;WmtUX@y2au#u$_Ishj<1nLPZJ z&B7X6_4iLdMDH2kcCZvEwPaBLDJ*4U*gs`Mz0P=%}MT1>IBjs zGs5a%3avD4;05Gl?1^Koqzjcj<;-`;fQF?n>$5ohvubF&8Il^6txj;sI2(gU>D z*qJTT+iTC~>j)^ejeK6?FWqlzg66rQCjU_it1${?Zg6HT$w|tc7evdsuE>bkJ&yAy zgcDd)*T{d}iYap_x)b>lbu(WI$7;~Ej<#uSt`i!66n&rux(46&%k$wQ&RO$k!lr0K zZcEcYHzC9AK}?H#O~q_Kw(&qiA@4LU78Dkmh)POIesWwKa(;DRZdqGIl{R!or~_Ou z{0(#EEBe?cV2Zsa3b#|E&UKifz3kZbf6+yAa>_<$>6|hawI=>U91xkmBZI(F)yRxm z7*t744-anq>ZqnNn+m0NdQrZxF(EWwOhOsTY5%Hd;54;ko7&1Px8V8hpg1Yt6q%Oa zB1QwSUR(nq#5s_#!FgUl>Pn6;pseyMXlR%pbo%$)?ZDx%S*756DJ{rIs5w#X<92^H z_Ufu@Q}s45ls5G%cVFe(63*@<-o}w7llnp~a-^ZOlsQ^34Hemc7D>2Sx#pZz@Y z@6z!v^YC5LY_6h0oieICRKpyMVH>`t!nVnr80G7lPmwlMB9;uP1;5466&K*rQ@({q z&T~g5n-r%{M;1k7q6T4*K_$u-!|ZT93oBE(XvoRISH_$!_n5-uRvt`uofP@_JFZU0 zKaZh&Uiy4acOd(1>YPc_0_P{N|F?7>X4*{U0)cf60B$+bliOwwJifrH?4Mp6@P3xw z)Yuq9dl^=!a^5Qe&2&%3mmMf^DtueigviU)l_A!(D%EQV<~c(_`I9*kNl|x%fYuW1 zed;>T>5jXVi{ouU%N$-GdbT+ve;l`&ZXam_*ZWlw%Z(Hfou9BLm6CPcn;3Q7k*0roDRu*H*lfgw5;`E1~)a z2>BU`!DZI~2jZv9ceg&@w*nfJXkseLVIpAu2K!<2yB?Q5c)dlHete9>y}D~d(66jg zx|AcBlj>Ml+5edRMHp+5Ocm-~4V9rIxWa)QVin9lv7tZUhu9|r6BRw#{_ZI8V>=WK zopF>knhb=KV1*O`(Vpk`r_#abWc3-xmY&q?FFn`9gPw`xa2>x1T9|NTHCNb7F zHhZ#$FLS9NX;%_p68@lyDj|O&;E2rKupE`pcS{yy@ zNR9f*+16`;r~86hz|w)Ic7$f=h#F~zO(`LaIz$7-@GnFMD7LERtj zhqPdAwpna9vY*u?jRKspt~ZKCUr@&J9-jx~{;0!+Qo5sO4PwL*F%P%y|19@R-f&+# zZ@DVfr1Y!4P6Jauqy|BEAngx3Z194g^hH$oLk1ZjF`yxl@CdFCMDYrylXiFdOSuq6 z4Rz!>)9V-*I^Z{v5W)?nkc1IM{0-|hd5>LWR;u#uo8w^rRSJ72&97~71`M>?@tIs@rvBXk!;s9RZ2zbf}RfSohNqv%ei+NLB^7=O$K(Z+j)D z3?BFTuFupxH`5b>_XBf-5Fb#qwx%Q4%UqmtC_I^!->w4z?NPxCef$#^T(>CZFt{XQ zGp*8j+%B1@zhQ{rWPuK)|F4K8s^kq{->>vje;AwJUNvH>!8j&vZ*-sxDkF$g%Ib%6 zagu*o;I^$5UKb1+V9a=%$GD&kaf4Tg$5_SHo2)q1=$@9gQvI|f5E)~2CLE>v8l_g3 zTNJ4VH5~K17ml_zX}y_Al4SyYihU|TlA@DCdgf&3kwo`lTGy1?LjQ5|^SRIP{VkjL zbLM&LZ8N_1jjE_I7TzWuk)RW>`ivFL`?OE`Z}=8`MF7Y((E(be5w@iBWB zwv06o>%xlxt7gvT(h?_Y(~r3&zQ*h8u)-V#`$NKgX)!f+ag`_%1lD0DIQec_N|DNC z%8X&wF%$H-sJhTVj5u&~^eG5_nlvlnZC|EFA|#8*FcYwW9}9YyWCs0Lax8;+TS9i_ zs}`P~T2|GP>V<83arybA07U+X*$dTDhH+1L$69w*LDCK@8#u9WyC8)-13~;m| ze#iwb&dC#s)WL76{e0Q!Hyus47Fs*5ru8Lq16yLKX5N5ZH6A#O!?<9gYX?}~+ zQe%pC3ZvF4FA%AdEl;-h3*+(gq8rpw9ABGy)y@>G4nA|{`X7A}d~hJvt2dqPKhof9 zs8gfSHHl%sj(%wYG`j24eitrVtw6BqH1t077rm=$T_7nj2ITfjPDyM+Cm4EzO*Ic1 z8m@0qLu8XsI3j3-r-=-0C2}><$z%FnAGoM?$^LIxMn03%7vv|fy4_c(3&z16i6^yKpX?X_XvEZZZjdM zI^eG0Zqku&Y-z!-j=k}VWQS&nq9tlfc5p^0w1aMDE`->s!H&Z^t)s}*K{?jEJvhO>aX zM15@eZ;PB!PJ~)vC{DSBZ9G()w&nb%%I~QWm5GgpQ%vQuDlBUFDe)}(C^McIeGZyX z8}NUVu#4Ht4;sGhbT$BI0PRnj>s1lFXB;o*P{G-~Px8yj0mkx**URbEj*2PAqa2uF zdrQ1bZ8eb)#9%Tq2za2;HI9^3`770b0f%l<1+1Bx7*4xi7D)1F%4l35S!39y<>%$( zr{}XTxl1b^3@(W~PS@w<3;#G6Kip=OI^^<{D*UZk-O{n~TY9P4ku?dQQp9<3jE4I) z+eAaVT$jKJVD#sF_&9y1Hk}WheBkVL>ZRI5^mWdgE5E*SX;nDRSD5Z3e0|}NLO}6$oRjEnz0Kh@n!V@xu2h4;_W9bM zKJn0t2&g8U)CZEJhym1-yp|7OewnNPTAu*YyIW3BcyQ{yH~*gyyj@d*NL^pST1Rip zT`n`BRn*fz-d6Zic02~K7rM}>*>ApLPjwa(<1bSuQqckARF(FK;92AEN1<+wzk+Nk z!WB#PeFMs54BcXMmPi&$%Kn26wr(2V0Fc;iVB_qnt##N{m$&p4D1<5igvOS&&8-4? zrj*!Y(biyNDsN)!rEStM4+zkR7NjMrlP~^}X&rNf!e3!7 zrg;A6^v`xjOqxqko;P1b%r~>d9MB+joc$n=YyHh`RQ>h}V!tc^y>I&n0IayEr{?eE zA0xpKde29E9=>mJKPPdnbGwcB|3i$)O{wcGo$+O(-uRi!#2I6tOzI~Bk!lc}5IRH! z=SsL+#yk~yM0YJD#x`N>Bd096xa0or0hL!R2`8luw)b+udPo~WkZJUi(nm~z_%7L> zf>JEUFcHiUrRgg=#);dAyK8I!y+*tozp5bJfwGF-({S$$>+#+3x*k=|V~?mMntF?Q z#4h+wT69lA_AJpI!Pbu7y6FDr>^MZ_y{NMjLS_kD)9y(4aH;-Zm%fNcKK~`%%huzd zmSqDhy&tIyXKn(Y&(oi`po;hbP#hb;<}6nNHCe3)0oT!nou!y^s@HP0C=ITj|9#;G z1M9N-e=WdBu6UXo&sf4=IF#+F@u%zEh43f~_-(8oyE5JFF#|kd?~#AZ@BP+8XcBCpol~UsqJzA65}KL(V(h{ z8UBX>8f)I1oDabEKW1YpV03EE{XvIgxVi1)-AAMqgS)M6_g&pRXYTJ#3p8Chp63dNg;DiUDCp)-)C7Mu1sX1{vgJLx1 zg-G~`P&-6+&l}*qYvwzc z_J3%|J-fUlp1NHRud)$UA*7VX^QA?yF>04nn6@)$o1{O(BGNQ#quYSmZd;<~UUFEDQXY%{0Izv*FWRWl_w;YC$uPzQ!I&VP#vMI zCK*lpA*qIg_iKFn%2o~9WUxtx5%8!rbAmCKLwuUG{7_SmVXaxQT?Mhv$~(=C>CwND zm4(H*%MX_4Vdp934kg0qvn(LE+EW`Mpg8F%YEssRVQ1N;oriz%#jkQ}YUR(1yb+HtUiw-e5rUyyI*YK*Sh|9;^ zhdg-KO)g4|EPP@cyV5==%JX;|JgkA3TMn0MFUz(DyqQYRMiJ)=tM688W_S8#t)$cc zw$eXLdChmO&FJXNz6i%%h$=6wWv=?kNIY!7+ruWxNI4SX-~g%&rl(twGhOl(=r{5M zUf8ugkYiU9G%bIKYQGfrF66{k;bsl^=+00E8RwE-alnqntXu;h+w5lM$KHc!=La>-HNrq2jSBc+8d z+#u0nd|#X$r8D)4Y03beX%Ik)0U}NUaxjB^cJ{Eq$kBXeA|)1yTb~1z!c{L@*olNs(US} znl93G?Y%aN(+#{O3=N3`k&%%lBzvAGd${*PN^?JtdbkfnmQpiT*O$4E82+P5J1jbL z9(IVq>0p9~QslJTQjS(zdO73YkTe%-$Y*1x$}mBjpvZn7fe^|rgeqM5ZxZ8*_G2(+ zN7h)bXU;_~4j;YVBACN1aVA#GGt}b9)Q*3gME2QZ0>-`Wl9nC6Vs01Udr^e=GXi)s0^088WVdbyF%DgLhMs#;`J@&{EM#o<5j4CAy-H5?n{towOc0R=2F}3OUP%`owy5&nH1_-pNE38aD0$C;?BD`OSV6dMp1u=1p0Z8pTCy})TGZh+wssU8$WAvuvD zjbo~#Q3c{>-=s(8?;r%|Y@a6{I3ZQLehR=fQsPnlw*MZ<`H!(*myA^13T9*r9S%6} z-<4-9GLA6xw*Pb7lqDFc(ra@zXjqA<_-oCS--m1Xy1FzqcDt@+bNf}ku=}HX$HD(y zmspSEW;>WN{_|!3#799qIxBy;9!#XLM-HcF1qv+K-MUpFkT5XEgq`k{YnL|d6q&V| z7i{Je9{e35hHg#Qmt6v7a#RCP!XM3~A`EZOQA(ufD?B}&af|6}bK9BL3|Ru%!A4E; z2~ze57FlXROXHe097?kqay1Wi&2)n|oF@39K~@6Gg~gW4YVRBa zKa96z-Bhp!h_>?p7vENMlh*nzA+t|RnQadn3k%B&Ae7&YHZd_t(OyKQ>Q=!o;Iz8= ziP4?Ft@iyV$$ti{ z`t;X@T|_MA(mq5YTBbvST&Na?^%e9H*0ZJB+y)@zSR2*(2haxR`~-;b1M~pF1_UAs zip#@_#plKY@3g)TV7lewt!=b*bI*F!Gc&f5XlJt6p9{U#MJy>2j@lW?Z*Q^KR@7ZE zI4Zx)nrZThDOWt)ewz(Z;-+ZLY4S?*j4FOzj`2*hc(Op-<6M#cg54V$tbbDV_8%yw<5aE2?{%)KxiSxYSa3q7YB0aIbW-8fGv@zv0x8gO+ZFJ6 z!2JHaaB*U@=%8m&tz|(!EF1InlC%EgEHdVkqoBUGg?&+ckoSlSaCLm32hZ&R!jgbz zBOvZ(!nHEzAtlf*h zR14;GEf|03?y7RXO+s&`d~%-MoeudwO|=td^c@oNf96H{B4l*yW;(X$ON#^8Xn3*1 zjp@$MhKb+ZR$MQt8m3+@IyUcXh;0GFi@poHScP~n%2D`2`$9CPtY}>}vpzYPA)|h7 z?xN^2tQX$(OQeZ=rClQIUlD@NP7mHkLHIy6**y|j5l7bQCLsMp#}HT(6+$1MUY2@3 zmc)_81$cPyx;}cBwq7FR19tHzB8XP*E6mcfIoytDE&rVl4&bhMkBm;EQ`vL=BwAIE z6%Iu7-pZNxu$FPN5M8>!N@^?l$A8rW6UZY84)xay?b`NOfG1ZCjV7X0P5?7mibi`S zO}wbF(nF6n@>OkWu#ns2(KKc=gYDtFgp0U>0Dy##O{gDH`CyABvR+Ax~1~MFoNwuFJ{UI(7&8c@~wZr=*zQpZ<<>1*)o+- zMbzx~qkY1)3Ai%wA8eB&6q^Ne=2Lo^B|zW3Eom<(=JHOSbK~w1Wa`X{R!artst^Ra zIK1Cj3>W0r)U>H{J_ymBc#m9B!eMw|Ui=JEAz;ibK=frPKKqdc9E$qUJXLw0v0!S* zaBULJvSokb$}aZG8SX4IY^nOxn^aRR!;R0v?cm!|UvEe)%8z?F~v{J26X+_IL3=bZR ztKP2~)l6J<(51I|W_DA!bxH|^Fj_|`pNnyEO?FmA2uVl#X8#?dFPVn5=hkQ|&lZO{ zCi+NVjiiJo8h#%WScdjYdZW{#{DuO{2E_4STLNBN*b8Z_(_Z*Ueq5_1!Qs(WsTLdd zQ>>q++GJaVp}e=h!bhP&6nn+0YMfBmt%&|0-%xIxBxG)OgH(l0e#I|cuk0hvp%W(X zrR!VhY+oj(VP8;jbIM2K<_2XXpl*WfV|a4U48d;Ugm?q>fU zVrG3*snUvz&h)Liq7a`fFG$%5Rc~{8o>pG{+l$T$*aI5&HDP=YZa?odtE)~VB_yi8 zO(V|%>qVEr5fHuP4b$tkr2nXKB*LTPr84ZPqmAV|h^B#aGmfR;RWG%9UW__Ty*|Cf zYX_;24aTM#q)t?GlOrr5V9*olHiKddWq&7p*{<9F*bckqa5e5{(6(ly>n|3yrDt&b zJ-&hs$wi=jur|3w#Fbj9mF*>$w+a24sZQHhOqdT^3+o`Z)+f)C{n)_OH@qKmb zyx4m`CghE=<_OTLL+eVs%|R><}C zaY`(h^8@hzQ+X(V#loVuQ%@LBwyI%>Kx|i8ZEkCuAVN2S-RgO7<_*Ne>%8nC_`l@G zai3KH<6XRc=Pha-AKBCi>Ynwy1$;(KM?cOF4u$i4=8_R0`0NGHEwn0uIrSE9^D7k`sQnvN^EPO9!?}NobjvS# zF1#teKQhj;0{2+H9(>bxye8?wAD+WQhG+88k${>(3?PED@V~KJ&n?jTcB%bm*~en6 z66h%BWX)a@onn^~i!i0GQwRYM_3%RoT zhO4@>F6S*3B>bLUVbl(KWDRo)R}O;;a4TV(N>Q-sfj*KehqHnT@t+g3iNMYQbTgyI zM8BB*F$LB-;YS?XE{piU9?`pgeYsZS(U(h#`uZoqOP{B}#~#A$4$!um!zl-fL7ta^ zn6e4zt0Csy4`#dDp;UMXLzL-RrAcq!#rV!&YtRQ#lDaYdW_RT+oj5j{1WjzAaqm_9 zqIZUa@*!lt&=`usb!bBSqIShK3S}5dJH_O`s^W!LQH(ldy#m(KtN)ufC5JnzVZA*! zm*6>DdHr>}<9+#g!xsO2ccPuw{#D)G?c38%-YSei+LY!RkN`_BbREg{80?gUg|z%? zxbdwaSfM=kT?!XTR`F|SSjRE~!SqjvO{xX^rX=sBC-!5gfPT`CGUB!MG?TR9*1lM; zyFJ9Q84kf7Jqx-qk{0K$Dsb}!2#=Pcc3x`G(Vb-1*Xtj+5;*-k2{Vz>{&bvrk}ZrsB49?ygY7^ zc(9yUIC&1fMSb@aO3v=+L)FH!94%=;5m)Rrl=3@?=Zm-nmm$qRUYUG|r2q-Dc-kx( z_#ofow5rp(HO}z$zzK^y_<^--Jn%+^s|Ir6 zKVt%@J6}TnbW?$is!*rG{D)Jc-+6xp&ZzHsuZ-o)aK67k-}kD-f5+EJx7u}n?{n&6H;Fx%uaT-8pX44PDwXt z84Kn?(Z#3h`)PA#e!un1V*!^rK1iQ;9svL64F_)hY8Lz-w@TG>KW2y$V9^UCV*rp8 zQC)`=Q6Ix1o5X5wKfI0Bk@{h9wVOUXodxIJTU_t^%#8*M)i#dsnQq*yGbr-pN5Xtz zZO@#vuzpxZs?DgR|G7-$*{KeA+wc~*_ft0YCBX5ssNN(k)Qjos$iRNc zPg7c#t-Q)em|{xClm5>5U7dhv+&qnIThu4 z{Ig|prt@6?+eY?2^YZ%*Hver?-tT1*gUeM-h_rhQL#ajP3LHs3ZDsr}q*4qC4T3Di zI;Gmr2{hUiifO6Ofa*RP|2g+D!xVYnDs;ln7a z3-cB8mxGwK>&zM()2ml7j(?&jlu$*Iz2{azQ?`g3-qi^oy-DC9PRIjO@p^%`r_R=| zU%aGA)QYup&5VT&z9N8ete@s(NwsKLZ;AtRz3MI(V#Nmtr3<)LBRo_2*}9DncL#o% zKFsT60{g+8S_sv$WO4*has78%mdzO_}jZV^XguiAI#qwB7j^sfW$e zylr8x(>F;5eECJ@ScAr06{N0Qu4=c;*9#Pv9c)=Agj+=MKuG+IjDr5lyIJ{faSi=j z4+gOtyGHY+kg`Er*;|TfoY~TsgRKsXxM?#hUGDf&Z+!!VuD=vgWAyNN^S5}$;ceV= z$pOnb8?P?1klE9d5vBvQaSY15oE_XK4mO3;;huem)lpfw`S-<^er}dNAGmRMYJ;^4 ztWEENznEA`as(zshU}gKA;!+K(Ha`jeFw8$e=;7NM;gTv?6l0+lohXfB zNylbFV{H3De+QiOD{rQ1JZ4*oe>c;G6$ZK~8ec#{QxQ8An;^3kZ3hoW)p!>0`krHAOkpM0gaBMy47p zDOCa0_yreruw}v`NrbP#nsW%ug!<%aLZjk7y3m{R7^W+7Qu(9WN*5*vRb~;2c&=GH zp$xvfGfe7A)u*Lem7huw^yOze=NX zE;M|X00Hnw>+?~!v~FsOR~A`hWotB_#oubP6b5L=d7M|D)7w9ZN|vkELLW}y(?J`Xvlj5PbSM(F{c&T_@iire+jN3zR zV2EU9rPHcMl>pH5erQ%(4b9oIPFZL&se^x)jKC~N(t)E<>d>xC?>g~sfQ52ZSOjsk zx2#~}JmmTT=?cJ%Fw2;^&y44xkQ%PqUNYA#7v;Mgh*qU@z`M#VpIhhWMOFgKTQH$S z;j{efHUbEq+ZAPh;sZHv-(InxNDahlSZMbvA=KJi9Lykf2?tmN`0!|(V+F=NlB{qu203vrMGkMQAZ z09oq>c|-Es9X}L{26UPNueurQlzH$*-gt{#upiVm^}2kLFs^iNgPhDxne&p0=3RDFM1pE^TmO0 zwr1E5rGq}K$^+hm^~n@`&=2P4n5et_aYWtFaD&)KVG@!jAoRM|Xs63_wo7Ht$HO)7 zMAJ+=(@vqNn?hyS-aMrh^>6>fz()?`#c2p+^h(=1W?`p8CA-K3RkZ_BFRpf3bQ6nw ze0&ItcAR30Jq41p>4J6}00y`bxsHVM$H0OJ zXaNaNe6ziY9{5eb6BWATHk>yx#?FP^Di))M6#6gL6%=ZD&?h4Z+E;K8m43aBMAyub zM2J;2^4Lpe8Cjk~CCuodFUBXA@k^U#P#{tjucFKXzxQSL@ft^|%?jeIQ+&)Ra8PTp z{Br4gcaF?=+j^xYPyM>GeI$2d z+4i|wY>tVW#x=(bK0oefb3O_(F@^!xDBL1W>0z>yFsK8tZ^}WqaXKXKvHDF)tW9W! zRm%iQ5Gk{V5FQ87>v#i!vvD~?Ceiu?QHh!E`e z*zXFkf}%*nj)_IBgvr;4&#!@o0PMZTsn)1EB*u5)y$}Rc&J2j6$qrtaAqQED>?W&KBzs(hS9wE57TC z&~LV9pg_j;-QM3_ab9f=02Li$Q>f|t z7b&hatX0c!cAv+Td%_RNhXmt`mbq&@%+_?v91zs=?P0=MApuR}s_TFN3knTMi3(jAINlM+VY(HW7`hCb22{>ri3miHqVX%!O6$))6a_)yRxvWPRb=ua5rgN;!y@r8%Zk) z;?54AN1|t^sDri+u;UDIf5W$KI;xGJsDPTrQzc>k7r^w5Gg|LHBg#-ZXw9X^MtF95 z>e+%oB}JStW-?WwD)Q6{g^E#|Gad(V^d#5OXz}&>_k9P+uh>~1QNcf6$UxSF*^Es+ zgsMjA(89(TEtPNy_Ora4l*h^<_4|2SN_yA{9ZUj?{F zj_75xD^F8;Jy*twO|YdBpP2UqUud~|f=#b{{Lf1wW||H5G*w@Z6pu<2{$C5wa@Sq$ zPeB~8)3UPld@wD8n3Wb4(V|7y2`s#-xqw-1^bj7E^@UDdR=-uAer(>2rjl=<|EPtr zAxC{615>WcS&9M+@Ty11WBHcyZF>j$$JS=9H^LHT7qGwqt<+? zxjQ)i+}JUICgZ^%e87zb`QD6=3ysaxxmXvQ9V*5`k=AC#zl+gBXL-pD`Ns!Qi5A@{ zJn&l^5e?LSeu=R2t?O8L@C#UmxhT&JO{dv3gQ3SHU`ml*+SMfc@wr?gB~LXy;aA@OwITlr?=G}&C9SXk<{JwpS3!+K|A-Jr&-uejA{`w^OViQ(Q`}i03X=@{v zJzcimvPrDr=#kBWXP75L{woc_L^jAugsK3L3HjWn9tO23)mMQE&@2uNt97%Hj#6*0 z*{6#m^>KHuzpmN$ay4vQWN+(_Tl@nNZdC1BTwI*vFaRx(#pV33EPWC?NF6C6;~@nY zDwz?%Toed?!XPsO>vYtr*S-$vo1%nkrLmVz9}y<-Z_p$TMdz<7KD~RMceGl!?0_hI z)`~;4I{i2w4OM_e5a7ko^%;U$xyk+z_9MCIJA zr}mq=Df)u8xe-I=m?i!5^tC%WY2+yV>t6z8q*x>ECOc;2f;ygQ=s>!&JVLS+@&UN8 zPpI+;YlR3LTDiAAk<0fWZ%IApK(Jei!oypqP_P$Ezpw@R_t%!^)!oD_?7#Mul}gZ6 zbhIbH4DY1RSoxpIx}i8GqP!xae4xVV5on0}5Ab{3FI|4YGx`VXub*1X7)H%H1@mZa zj(JYPy)k&4-ME1q7 zy*7z)A@paF(r#eU!uNaoG4=8KDkhj|HHKJp#v=Oj-yc{HIXQ}%wM+lL5q=cTpd{>m z82}eEdt#|RtfR4SH_u`c1RSgc5)$b+K0jsH0_?Si%GNMI{osl+rM^2^l5&MX%P+kQ zorytFCRy$tC&o-AE9Plr4NV(DA65d7f7ShA0RE~EWwFZCu|8EvU$G5uxv(O5_Htjk*9v@h-OKwtS6=rtNc>x#nX-yc zAVnD(xn;<7=vFqvkc@ryJVH_hJ9;W>XbiLzMc#+!KOD4GI=ZaR$tC+SZw0^2Z?vn= zGRu&|+zZ9=SJ`7Iy!k0~$Z*Oqv!+5s1!=|V#H=niaMJ0B zV#ZkzS&@OoTd{pDsFf4~}y%SNAaN##4-R>uAa zp54H~D4!kNoooW1u|&G&0tK9%a>R`k5S0g(Or4Ci^z{6In~XFYv{3`C_zgRjyI>H@ zh7Vi$gQQn#OfCG1JU0+gfZC!8q=1{un8*DHX7633&n&~v^d$)s@H>nlj!ob6gqs?L zW3O^LT3Gu$J$YY!Zfrf%TYj3L?X_mr_f$aOHFXyMv$^+c0HgON-TzbF|FarsLAKRQ ztPNVr(Mn-XQq!~vc@{0JR`4=As{jSa(=JBYv4isN!i%>$^&o5SU|_LZQu(1LImyd7 z|Ede~kAPn$9-sn?-%GAMprrAktG|SKr*k;a zk!OH|G<*5H+ewc9uD|p1$rk!s%I!&Kqe;Qg!(%2j%moEL-oJsD)HE0FOb%t3ZcK%b z3L+7B@K9ajFvcNSx$xLS1#eLU{lPIe5F}kZFb~~`G8?aAoI{v97NN;SeXLDO4z9L% zy5nGa(Uj)5;F~|U#Z8BlX;T{l9={n7H{RG8LolO2pn`5UsJ((>X=?UhZ$LP3P3Z5; zX-^l!d{X=2!K-wQ613{?ZGRxv)CHMQZQD;(Ug3Z)Hopphp5f>REjhox3mP^bc-Sj%0yMt2n)#F9E`pAYfY7aT~dMGZ9Qel&y*oKun$d> zm-M1Hzgi>h%zED@whQAFYQ~6$?7qBZXgdbKMa%=>KJY>i63=D3AILn>Ry=!XXg9r~ z231TV&5USD&KD_+Y)FPq?Zsg`gb9s#nX~>|a>Iv+QC|d7q6@2>=27yy7wVu&+a-Wb zwhqYv06K>)qjqWD=Y~8~J`x}SpU*tFl>^75Oy|jy0NMlHEq*+-NwbWnw`b{HV=mjS zF_9rTPy4&O2&=7ZKRe3aLuc>+Ckh9lI(6HNNZWQ?9lL=ohycBb%%BC7;AkD%gX~9a z@MUnGFkbUd=Y6b%shG+sc+Fb|O3vKh=UDR2sv|?PNC&GMlF}9TDaKVG@SJMX&uBX|?{Q z#u4mlzV8)CIm6Dkwf^hd=vCXNyZu_7;eniF(o2D8Nd?Su3CMo?ruNs6P_ajZSV*5! zC8l!ENWive1%|HeL?&hT3U>cQ=A46ndb2T8-JcM(l>=_7%p8o`kUI7bCSD^Y`etL7 z%Kl7(^?ht#xMFIviFxE%bX2^H`F{sY{47+$DnC!w~*#2T!%e^)Hv?~gpF2q-qg zqeI*C*A?jB+jFiJ#lYd_EiPFevSKEVZDsQ(=x<8hQ%nBjg*FnS!*oRpw5Dp$s6GV2 zw~)eyMsf4=>sBE$TzOX2icx4Ir8j{DW*@_osy|fB4+k3O@>m`56TyF4jAdA5_3{=F z!E1M9beFPg+fK({k9B-k+P~jcqUCswf!v-casH3Q=&EJvP!-y$pk^u&EDr8T&D+CS zcS^X{av{o4>e6n9RdXXOCsvA_2+K7eXl(ETODQuGpcHOGCkmWKnfjCv?oB z7(M$7V?}{tQ2UDyZ)3@?9s+4XZ?8~+@jlEyhC{iTstPZXf}c0FGkzwr9Fuyp`-fHs zZ{L&24Xrf<-CI3I`_Hqe@8gZM;yIby%p%Jl1qzw?KL+5T?UEDh8QWaLe2RC&$Sj^w zK_DahReq!7UN9H%;vh~M(*gs5psp-dK|;Zsav@q>)Iu&<_B8YZXu?8_E%`1jY9=hU zEU|h>VFFK&jn-MGH1Dq>Hv9y|I~ukLDEBD)E5uJWib2^;cR#5H(}!3I7}xMkK6+`_ zmBqZ{W{1k@sEe7F@oYFl(vipLw>+vlEoSsswrj6}w*P2%uY=}m&u2*gmtum?o55DV zQ|BNspH24!la??}YwK`EEET?LDY{M0ayX@u{K(6puc1iNqWnh>g@&-{d!d%#LyDWU zrYL}JubWx7KnD^B^$Be-nf##0A|j97l;AZo651IUbD}f1yu9r6UmrB_)jsma3~AY_ zS?ylLOSQsuUa2U<_8u}K#k&yqvFyt z2wMuCCvV-AZ{mhkq>jE0|;7HKBFZYBMdK~69u7IDdh{ht#RxHKc^Y2UP8y?F z(7KnLvyj`xVz$##{|mPbMhr34R9zS?{gIes(Pw1sAMi?T#9juY8;y+3UVP0*^|Z=# zz@cq}U}7x_(VK5EtL^kqAhV?bWzu`t<3xryGa|F;2%`@ap!GPe1###w*z@g<;dy38v!YH2fV~;x9nNAy`CA(Y`Xw z^!kS~zs)vw_6C6*J4gH*5BrkpCxK+;bmJ1b$m}O6ef|psBVPZ0B(cO6rA~#|4|ERn zwSpV!uRJIQ+aPw~0JZEOh9FaIg*8)$4vnG9ft>@bNg4Il(Pfdn zkRdbd3Q#D zN%F!{I~ylynmOs|kG;D60y(JG_PvMb{O%K6)xVjs26#-(k8U{t<6sEbEzv-%G*~#p zSFH!)AZpz541riN{SYkX^`uX~S{X!Aettx(Cw8J9PyIxc)-I%&QFjz~G!RHZZ zPbu-WI=6|NLAss$HViXSu7%zPSz@%ei>Lpl?XSRGotK&P?1pRH+APtf&CSh-A`945 zZnTPsJOAW}Va4B945o4Z(|r0ew2;__QDC>%SfB**co;mho{~ev>&$WwSFGj;NIk0c zmqGlHKKx;=O-^ReAH0zp<8jsdo!1seTl>ytDq*N{>a6O z%B>Vx?O;ztLF=x@MroF z0^OThbKjIVKDN?-Qj3iJoQ0UN^VY>(E*L^P%XP4n{9xYOCWO7wXUiU=gTDB^lM-24 zIAjcaI%9if;pFXWSnzx_2De!rql0bdzxgw^GuDI#<&ujX10XXFL6_Q&q?iwVbstSt z@EEv|-~9aWQI=gJun!sUb1*C5^-uHrBfq!%wk|I(?+HXJTQM^GuZ>z;1+~u-j+{Im z+OBt`sdu7Vc0PqFS5>C7o6W0;2_{*ajyUEKt2HNyYZnu859a0_Vg%G>%g*uyS1+<- z8_>wll7bx+u_r96p^IlScPR17NDr6#AHyWPZ&JzhTjxME8VSo78{C>4@hTsJNb}r+ z$;1N!OwT+7YYoVF6mr> zDm2!H>Ba|Zpl@VbX-8%aW$CG{!;xBFnX8WTSRpNS$B0 z#4W+ug2P-}sLCJfOb^wO?7D3v=k1L#jWaA**KAi`KR%BI27?h87PIX;g}vT^N*6E^ z0r?YO^NEn~H#es}@tc zjLNF9dwH%6G9?~~9B0xG^sB9H-5U5-BkdT$i(?i-*slJS57~y#u?ck-3BwApxmd8^ zAAlOw7_^2t%{fI5+M!|uAM{h4S(w07Qh|<+uxG_K19D5^@jVX{S5a4w&>SasmzyKB zLOnQLc(p}>Dyd`^E}co+%t*lIZe$j+cp{oYdnb2wrGHjTCET>jDGej35a>Ftraa3P ze1&SVQ&hqqH1F<5{jSCfsNpaZQKlXLJDx*SZ^&S`m5jiBXDH5Jb%;agJuv6RN~~?@ zFeroG=Bk8+CukH2Q~*|@smazFy@9T+^?>?x446-}a3i}u__*(82W`@p#A7X#!>>zO zYU^mpDRC)+-qMn3?pDw`3~hpY##XE%BWT#yBSXn z`J>gZ+bTS0BN&IMu^f?@=anhWvcAq`mY?}=16im6mq8X7r}Q%Q-= zJNN~jNY2HQuIb-LdpGYNI{`fp1Erm-RYhk)*UO}Gg1qitlT0wmFzJ6wR;4TB@ATqH z|ALq{h&7ZP(S+QUFF3dX4QAbaloKi?-TLthDuXEVoY}S$sLh-yV_E`COols8Ev=s= zot^J!uuAC%%fN0Hn?YVrfB}Cw!Ss?XBKIgHLk??{7OO7B90j}d2oHt+Hw6}H2yAPQ z1nk;fp5hQr+US8w(F*!_bPu@isOgY%L5FV$`c!6zLPn;oo1mZriite77J_(5JkFNF zOQ4brKImEoex0(<+%lGejlbctOE_oTQeVnO?(ks+!+LWa$s$ZGQPx+)nuQUdaOym23SR`JNU{wK_$o?WjHH{Rs_I}GsOAzV%eb6(s-L0a(| zVzn`2YU3p|PmsUIum{IZF#Ye|J|DL!@EB0e)g3<^?GsTL1jPK^)@x1AmZ%POxnkep z{Qe4EQ&F91E&U@CQO<`1XXwX^D~ByhB4jt-4W=UbuM&mH^(^ISt2SQM!fw?}(&bI% z!yEs|vA`DT4)cg2J7bX`PTtr8O4Zfw4{pug^nSjb1S8PbJk;hcqPhQBMI=!IH+Eu- z3m1kYE~_Ks*~ZYm(uB``hy&AFj;v>To^4_-&kR7h-WA>|V@Q$C5J{-eIE6VYfnvq@t)Q^qgQY{; zx*kG%;u@sQ5XgiBn2^1wYa@l;|D4w$TOUWdK8Z8~TOD-6DcXssX#GJjFL_?RH46(r zVP#1s1XPb(+sgHzkE9K;UO}lPT&C^%@yBBZqq~oA<$p;hS*12D50p}U-@MK`tnxjd zZ@8Y9YE=uRQ}`-je)tIiI+>T6D`G|iHyEfE0izYB2HvEMR*GT(ORA@;jL2--wS+5! z=#jW-Z}{nV+Icv6Q^g;p9-S!_vxtvqcm|y%3iNa1fNR`eG6W0FzOnTyBc98j)=Jb^ zyWm5?x4gP1KK5YDxjubq=!&5V%Is1nI?0x8qmBFDD8s?-1j3!7YG`#Z*YR-5q<<4h zagkL+C}g9L=1cx1G_SU2b zco^U~{Gp3aTt9nngw;gaedM9V)lc+8%&3APD)A@?H$Cy<5UZ1(Slc#1BN0r-{l~md zV**`dM%!@G)V<2H6KUt+nkA9>_|qEum^7|YL_`bSmQaYkl`-1USdNQk0~##OhO9u% zM0>k&3zRq7W!c~$c8xL@OA_iWQqxtEvuR zkCx3>KFG`L(beBYsM|S8rT49(OlHR#k_cjcJC%P-fXn^C;Ov9pgTjH;>a8Ry04@?p z>g9w@KYjJERuX7m*{wBn7#;KF4Q-#b%2ve3yF_F5M?>>CjvjE+l{cw~2%JjJofk&o z4>-htDrVa{WE>q%rMkt`jjuT1deMZZt{Xxddbg}{l#LBF`&aO$4k=*`19Kh+26O}p z-jYYBS{#QN6O);z7stwj-0@B|SB1p!elyFWl@C@G;P2=C#r z2%W8Jc=8Nuuj75Ms--|-Vn>cocRa6@Q#%&9%&!}tw648YJ9*7=`xP2vdx~_LMq#|1h~1m|$T{SWrz3z$_Q9?gC*5faNAln~K!xu^ z7?h2C#KDyh!?N?tr{?KOW2O-@Ys%ZlPQgZIt4vRbUJB_pdU8yOv1<$z`e!Ht zmJpf^jhuTYQrYeeY}Ttgi>*tpUH?2X%m$C(D1bfF_C_Ydcn23~f6AJW>F@D3BART^ zCyYU2W3IkFU|e)8YLFF3tJcwDbw=aQcm%Mps+lD@IgoHx<2dGlb*$Q8G}k@$!+0_1 zRWsif=|DSzz4(RwSiO*vzu3kyN`IC={^L1{10&v(8OriMVX-y`5S!EWvz;JIj_~^I z@|+UTs;o{0J-W@I>TD?D-0}uP&By|kd6PRzX0~k>SM>LxYj5DV97X@)xImn24)$sL zr1xB-v<*@VL>89+3CqGnu{7b5G`m$8$V zW)x>!PiaKb+<%lhmn8C8qX2U3#fO(YJWux@bHqipDyk~`S`PcpU3m%rdo(RC!t_JT zx&|;o>GzDD!RZ*S6+e}1iOh%UZF23ttugail`U|jII}qG{H7MXri!07Y4@D^C!sSN z3KgxD{%~k?Ov?G36<@D@re6-z=zYgG+?|?JVnma=Ial;!0*`Zu;SdM?m;APKK3^#} zd)&2sH)|{tD|tERJwSjKLEx_K3m5>B-m`;WMCqiVntuL+$ItWk-Vfouu?&w6(Yf=7 zYMz{m<2m+)v1w;J$b6YW3T){yI0{m4;EmY^){iaRw1HTQaS>v$89+{@Z!=Wv*&F_1 z$=;MgPWgsftA5*|yFjdWIq$VpV%>j=@`xdh1&f_itcxler=CrlhmDL(tmAgMoac~b zzb25Hp1ZB)Rl`$C_5S}_fWG;>8}?a7NJyR5{LMuu^OvyKeP%24QfYXcPjuIMl?9v3 zs<(BUwAJn_R{+S1wztq_eKk|j47E(-AsqIl2ocdQkGzrP@g^3dtgl|!6UpIwbBgdE zEgV8k!+cOPZ)grSR^UCj+K6QN*>cX#^v9>EM4gSI%u)w-@#B9$%Xu}NAPeDdj;V)> zjhT5UfMH>J{o=->8NXJkrr&w9o=duNB?yZ%XcJ`5M}%_TaoSkB0oO6H`Bw#cwfm_# zd$)HGwYz(+r5VBys}wdZ^O6O{jqgTqvuTy-?6Z>Kn@anZ(yWOIC7A;&C}AUbngbNz zNJPp^O<6<-IDdfBX}<4=c>6h)uj^B0ekLM{o--BSk8v~?_w>kvX%4m7`~NEghL7{B zDSGaXl<@Gp)kweyi8ZX5U@&sL(J691#B?90uusoV$)gVBbO|}HD_-lw_X;w$dZ6bm zf&jKqZ@eFNi{n9dR1aY@m?al=@@8X$E6dQM|IvJxP?AoY+B}kp)-zV#Vqx7TmQO5U zlPrlc5sOqCYph2UnIbYyOY`*nQ$u<_P}5mL$KP@=+;1gx_QA9b+KLsLi^f zkFp^L67_zY%jHA&ZpQlM{tA1CRUl6HhD>1leOQ<2WZJgd7*Y5Z2t!Tm1MuiFTc=f= zsnO@H%E_01+gB!!$TSIBZk;NA7mGLa2h?WZ zCOGfqRl~g4sl$aW??K(ue*Mmpq~Wv~JzPEYT^163D+gl2C zYN@GNv|2`1uU=%(qG-Z@wBSGCnu$dUq8G*#i?PWu@YvDu{qCIErL*_xo?7>-+;V=O z?z*+THrjrRUC>?MfO_qbuT~p;_NDK9(eZt>mQOl6Y4jFq3}*@xEA9{SbUqqY*R8qs z`W}5q+q_EH$+J^&6*DWFw8dW(c(&ZyOWx!mYAv61^V|+A7$rGz9h#o*DtU69&#YT< z;UdI-+6cLe^a72p?(@pkv%(>B~wO6*4Te|IVH9XV_BFo0_7H86;i|c3{8}? z?p4wii;;jkvza}-I`qyWO7~a9V18B`mmcIraB^X69ULn8$pf^c`GiaVODi+$*GU^G zo;zIYWSU&WI6bS$F+|e}4^!>RC;l=3Oym!IQl_fH#t)U7H#%Ud0-^I4txtJ^7msK9 z_?ZV!5nkG(+RjY=Pwjl`DQ1n_j;3|hZzh)Y&XzbuJ~oj`53RNV5}ySUFR{6fUDMD4 zO4e*Taq$v4roS+r86M9vf@kueB26FYe|H3PPniht_2i@=>e5qgHrXZh7L= z!w*#a50t-y#oZ{R?d;Pp@7|3*8c3+#Oj&+j*juI~x$rARg;T-kjlYs)p`%+{tC2UA zuFY{5FE7wVr7w02wA5!wG!`ohF&`zN*9RQx1iE1$z9Y517H|DM$N|tEgm!l=n~XCYnG`}9zqZ>0-0q^E!n2-{?$AWhM2I(|F+8W#~Vy zeM<>>VyZDfc8tDEl%-a2!MEzScPQeDzmU=#fG&z9m>a#2%!^>qio$Dl< z92&-erV@RlpWDA;x^reKky+puU~0wVAcJRv|5X?oEm*osW(|U{uXuJ;)7f_*;SEOK zX9o~aO!%_z2@Ds9xCpgEF5MliG1JN&)feEmf~l%yCeX9hIPNc{JOizE4;c5yvw)jc zS_j{jdb)*YVq$JSzFZ&xP?$i!*Y9(nMJktK!X(4^bcxX%Yybgya@L3p(fu|JyD%u) zok03eT)*b?Pd=h(#InO0ZzZA+kQ^`@baPXJh6#v`o!SLJC-$vT%f;?l4TSa{8MY9k z{;7AZFj+Q&hs-x8A--%QzFhS*)z2%NQ*K_OM&%%Y@l7XR5PtA!lH7(n4N?PLjO1wB z6g%yi2d#?`tIo7?hq86i)%*%W)~9#M4%}(YUss7L%P~gkq2jpSlo%`*9GlBfZ|nS4W(K@aIAyG zMw+h(-lJ)$YrvCbpkbbuPC8&LxZUyYj3`eMsLJU24+%lAa}&m)(_P(QntQ*Y7FMxr zV&FHGgkxK<;?ixqj@c!LH7KKC`O~PyTE`qRt<|!V1HzBW7I2o^) zXF6^h80U;!XC_+1x+t$u7>L0*qk8Dl{Hp<*5KK0j-7x16nq^<;+ek^7z#-O(_gb=h z;s&qWb%0V1TexLv_ci*+Q(-TjH2`Hdf1UZ2$Sqbz7t->$Z}&Yt0%Loyz|MOKo@2Jd z>M~MBM__aww4zkZ!gsES(nRztziywCDsS?vQe&UA5G^dkFog2q!KHDthUa`<9Dl-5 zoS@&X^O{v=zRRB-&-_#s{H%N##FX-Y3#wZImZ4pPAQl0&R&-7Dm zId8gA0{0k`d#3|6Jhl58$Qc&`Hy2{lctp&&+xASLXt0 z(5V{pCYM-OZ{PlN=V9QXLs+{m0K@V)YZKjqh3yWWV$VoBuBecSe#XjQKf7jrZ7!xnXAf zaU)J9H2(%-MkU28h`PAHSzEKY$!=MtkjIalPe!MKc7R@()s36kh z@sJg*)|VwVwZE99gcGbny?yn0dmNEYHdT!aJqhnP=^Y=zp~1X{#WPDzLFby^PP21l zb^Ni}#s-OfG<S zoK>%EJ<4tEHwqQKkw&%KP7>W>BH>##w&3e7^IcCrYGnJ_!EMXf)Cvm{+lKRZ+oUd) zkwoh?>NR2|ZG>!4l1Tf><@`9Zr{^}et2-|nSUMTDLw`PRhJQY-Cj&_qavwtYwxO&z z@JU`jNrdXxhA(@+i@y$dc1926Y0Gt}8Vwhr+HKMTzx3QR8;bEA#{#=9$if?4T6KrP z_@_^)o5w3Lj==dgZ+!E`s>6=oefFaB(}WRr^aVa5XD6~;aZY5%A{s=sH zD^rj9NVhH6tkka*X|TbWW%25Hh7JA+{-C69{1e#!fOz)1^TM%*(=?h3lxP4Z(o;vM zfnL-KC$_2;$i3ak#U{FMt)bg=vVp})cnGgxDqH{R+-J|gmZFrBEB(p)JPt=xpZnR+ zmjHEdW?Rr$oJCcTO5e0T+yG4<9YAWr(kT6vcd4V+2qxr(C^6yg;e~;>c8_&$;{-5JkE460;>wTM;yMN~R%HnTdHVwPG?khr>mRDffY>zYo0M zNu(pspWm8cP&&8_#3@pxX|5scu2k-ZcxmEGPK5Z09ckp<#CZ9pZc#nbTgL253x2W~ zH!g1*ErQkp040##qu*!8z~m=d!;H~;WL^fLRv+E>XgFI@fr(lK=r>-NP6!FY8fJf1 zqC7c?GTeSTEVLxWQ6@?9jXlM)y{4^!K^QTej3f&o8k_kjVbo4=J5rXBK9-lj+DyU( zo3h{h`HM9|flBq!QtdMG?xDc08wgf7xjr{7s7?Rs(ev`|s8pFyqIOH1Jr-fcVwC%c z<|K+FE}|sH&6lW+?#JxUH|5)?9B^N8rtvY1>UhhMW|h{$ms12wqF=IMnLXG*LhN>EO{(F9U}=4@mXbs zaz6f6VFRAIb_0IexdYlSg&LmL)HGQ@R79{Fw~Y~2!EUMk;3i*DCR4(zI_-0co@Skt z;I38}fl;51HmMR&gZ4)13J-JbS0&NOwVLLFGZ$v7Y;)7H7)cI*E+z}7c(2^#yt`Ie zWoV{^8F%CBdYja_mfnhp{0~jn6kS)_b=$^iY}>ZcxUsFqw%w+&ZQC{*+vbUFJO6&a zG5(u#ajwod`+3%$Yp%KGZSMAbs{XdvM1$JHt1s~tbfJ01x^<^#2^8Q?K*l%Nd3 z>>I}Fy6RO(yKTm9Z?~#Qkvq0#-1AR?r~YVz^*Ej8MM?>TYTt-uLU zAn_4)Xc{6d3diMyejkeKpEYlq_RQa95B+&~joFFevaPUe89RS!Tn9)n=oGP#o3yZu z*7Nz7vWA8P9Fh>GaCdY+z1!xCiEs$cuhGv<`Crd+fes`5T#H zQbBb+7s8C2;aH^_H)X?_UB;}AP)^QOegCmqcNzMvE9kU@Ng%Ps7sQ|HDK)m@%^pIP zd%wcP9ULKfl|6xLiFnGCcT-OC<_oy%VCO}`M4}^KQi`Q|wN7u36Cik3t)=v~C zEmEP0>xaptEsR;|*|{$(hq0Cl&RMolm>zU=jB46;DUNs|a9N?B3IvS=AXPi<6S>Bv z20@mxBP;gS{HWIy9#FChp2>SMq{beQ5|*W-!7!pDX;QvmGchEQyi#O2X=}?P5GtDd z;So?;DpVDPniOJR##m8%Ro-9s7up>rIwht8)>5>ODIwE!^>_rxgLAnOTqFJV4{L7n zAMKC4sHl$;Erw8fwFGpvRPf0tl!vT^O2fKk? z<=9LVN3RwF*P@&#XRd=9zDQ-#1UK;^=fZ)C)~3mXTW5Af>oG$s=Iat9f#aE$F;Xq+ zNg|UUs?h!v1_{Jf_fGx}X&NxXV3g+i{nX;)n9!o7My*qAhjOE2XUP40$DHo7<@M`U z27egX_Z*e`6_ZRmdx5|_Bf2*M#YL_CZo_>6i&}A#I);m81FST1=_J0C#dn*z1|PGi zrh()Jm$I>aNB)5jo*#~C3Dsu&u0O-&5t(`sjm8-3eI>AT9NOmf!{`X3P<)f`SJr+? z{HTQO$iuGBMrN3rY*180GvbfpoLeV`5xeKzd1645)L0w69VkdgJb<@T+9%erE1eKe z8+`O60vKo4Vaih4`(oiVw-=d@X(-9lSq|EB7g2%0gyH0x*I8Zn^K!ZAcsY0MaJN;( z{>Ps7+<%ck8ojbG@)24hft!L{f@9k&}q)^Y><(tlVfA7uP=O&&K|^PKuy-j7A~4h ztT=c$(djzb=b}+~K?ztZ$;SIAj9a2tXFMWu^$65*T+@j!G>KA`nL>ZDMbPq| zXrA)ea_71?$6rdv8_>g!m>xF{chL^h#;>COHO6*D#YFo4)nmu!-6pntfCz-+Q9)oX!32n^qN0~Ppga}MdbepdYx>(j?0fm5=5OY zTP%r+w1YV4iistISlSl8%AFBFx_E zBzz%LM2nv~42ciWO+Ewf>Q4QmsM;Yz#J_@&cnpveC55w5e7K+UyV^KjYR=BKg zYw9ctr@f-261O2siES{j05W|plmcv^WNNsG+}F1&aF^yO6F-_yjKQSH@QpQ@V;fUddCB~ zVCN~S-FRiil$-QyKl`V-TZ)vZ$KqOVTg&F9n^brc%$%SL`jZVmwmnx&84C-~WR>sOdpxmI0E_x31`8 z5k$cQ@bldOYl=PqN_=|gaE9xQ6xF4=jXdab+PRqCoLXxR z-82zKSmn#Qr$AJf3Q^gliLOnw&y_C zVxNS&abP$`obus=Ea4yRt^J5Pd_ zr0whwCF7>88<#DH@SQ(>9`h1es++mKCY^UmQ1ap6;~s_|`UXmO`+nsvZ%CV%|H2a* z+eG}P9qsz0kzPjfEtt@w1c=Wy;CuVm=XuZ0Y2PJ7mgbGEFf`R znT<)#zeg1x{2MJDpMkJAd5=8pMLw0`$J_ImbfMDiWlxE1w9N&w%?D;rjt-+N#wx7U z!p*|6W3=jI6dmJj0EZ%aV7cMGTDdow4A(R(ngjaVT+7`%YZ<`zk^Dg+0CB3~_{K}& zBzZu`$JI2b7ImxcNRjwvpS%%JU|eVQLBp{2n%4Hgo6ep4Oc{9_#*y>VF9OK})CgW9 z+p}qDMLYt3EfGhw^VX>~y3@e#K`XXB-Un0JW9vXXo3k4#K)skM z6SzMqzK@cF_JWt*W!lEb?V^DmpOdw15NpTtFHlfJJe-1^9WABW zXid8OCg1@?=X=w4X|mb-oR<~c50kc7MlR{Ukecq%`!gnj_w7!W3}zNMbxS_<bqT zIN7*lHj6DXRWtR-%k)#<6Hg*~s}&DyoT{`6)UMm=YH+9rphT5$LF&Y7h@*!Y^3dN4 z>rgzK!O#^8rKhH!DAxlrGCtT}mF!uE?^B$j8yg$B({rT!1FsLnP~vS%7bGSyWOhDT++x(I3 zN0b4fQeJ{nWOjY&xJbieLWk9%XIxU z(+e?j=5h-6|xX)T=dd@?W$Z}p` z|Ju>Vl&4nR&V9|-2K#&{Rvofnq}MfiX~Hi|WD5WPS^zH@1cw}`cHHd`l|aKrnx+0E zFg_7ZTh=a)+6wihbJIi`FWh6aj?D~>6HAQ?c89$D)oqV+Ahr_mTe^+qn&;a@`Q_JD zU6Sgyld`2uAl6j<@!^3&GR}Rgw@RO_$%xU#y81B~W{S#N@Nh=O&4|2ZHp&kc)mAQn z*GYif0dkbe`kS29fH(hXEtidhy);=yhVCwz!NS&8RKsUWqm;QgSoqKp(vG&o7_nzP zjy~LTHbI7}T+Ee@ztLYS+be=IQM97DD-Gn~lox0u2pvlFT=vOx)U+Z^gIU2D89K~^ zMX>q=v##|zFU-2*fXdqMz*3(-<=N0}S1<8epK|?e+6=661OC)MAMIOh5?55{E|wMg z6svLlO4OwD>w4J&j=JBgmFHP4W9%V~xlOio0@+KL^5%HP{L34AuW2>mT0ON{`c;`RNh(mY8DHMbv9%92? z4Q&IedruL{;-Y0Xmccf7T+P}W>>aX$d1bk{R*}EyiioR28ypNPGLVAVPLOo;p|y(r zM^6F1*B#+ctNWl>snQTxR2Co`%x_bNf2Zc}F!uQd-*ScOs*oO2j^hI|%vFwe><@dScDeSCd{DB-GD@^g=J zZa7i0^xk3_iSlcMbKzD5!LaG}%d>N3^V1D|t_KYKI6(8?QqT`+HWs6RW)S{-+^W5bRLR z_BD+JdWPLn07W^V;i+mF^v92>kCI07&%~zO^G92Al^7E{|6oX8 zAi5%leiIc9*MJcjt({66b`x#DCdEM3Of?kslv&z(eq3^}xeUi^dJoG>OP&g11g>?vKwMR9x3mW28W-CwUp0Q3ByI^olK{Uv z+bsX8Nu%lP)w%X^;ytu3HCP_KhlM@!mA3=H{i&GYPqX0@<|Zs$#nxntHrxt?p9Ut$ zzXt`Lg_!uj-8(JYqtn_u@ck|sIWELZNW}CZ4IrxGb|e!4A6M$au1QgzY4|RP#N*)%_I?E zaz&}sFXRDwi-9R`ozMLZv!6aYjLB9lR%&L!_)YOv)on7Mhp5L>d^Zr#uhuO(WYOdz#N$HrUpJ*t2L3647x*tZke-)Ls7LVF|rP8$0m7C zYKQPfMyk1zHj*z$CXP?C`Jv1bn)EOMwlgZ?IySmXK1-)os{8RTp)@cd9mEos7^$%3=wyca(T zm5@hFGqCNPf>6}miY%4CUos)`&GiU=`H2!|8a{Gtu>lmow8m5Cfl)Je%K@pRYVWk- zY)#q988GylmJ3UeCSK%;-vfqBq_W2BR*pWibict>FS zvT@nCVl5d*Mz`t9E5jgBv|{LKnFdAZ8==Ed50SK|R(s;)R~<}XHetm;_Y($m}awYK7gYl zm;qgot8N`CAihgfk>GUsvU$i0sC-oIVqm@j_7%G~MauFsm+x0RwjIY9Sz5JHgM*Yu zG<%$wegbyf4rQg6@7n z2Z-OlDRv6ESmRmIuUH3FW!Zk|wREuxUFl{L4Iq;{s!E_j4_7H?2M!ix*IOZyI9eO* zy89gcOTY41;qt-)vnWZDX?;MWKwz&ZkDIogh8p}ckuiy24_Ko>lCkcfdy|gbZ(0QX zYN;ORuE`oXaFamMOkcl(h?BnvJV!})PoKU&+1G}V8u|Y?w&IzxU{^GfKw}3;7)8_W z28bqLjB>}C1k!w>P|&dpBMim(iYMCjCpDIJ6eijyQ0_To%}E7*MxeNI_F7mTVM@09 za2cTm>Uvip*Yrd)x7ZSU{4&aWBU0oj5mWexX_CFSP)_Z^k z=3Dk(gk^O^!M!h~YUOC{wBDCcOV`#}4&@7C`HB>9-&Mj$-7U(?Cz5l=KGa}Qt)ai` zprN~`RtKE0tzB-VV55DbP$N(s>lsw&^$_u^8+9yC08c8fOJ;uKyUuFt-EEy5y5+C> zn7M!JqozdOs{^#Ue3lX)q9Ds2I|16OzS|%TU}*mR5OI!knvx4-)+uxam7nP1^u7<%7 zokmR5rAk!_)XJ~3+_Iu1Mb;<@{iOWv(?OD@8tZg)U_DxhQ)HqBQH{H|REgrlheac0 z$3LJm*@==icc{!F%~}>)LlQJ?Vf;4Fb)D-(CB79L*0$zWv1A^&BJ;QBM=JK)?;-`TkV;8+2-$>i3HvN3%$*`APJO3{Fuy%Ox; zvTyq{_@C&;uuCZKm+|@)yXrz7piFzV(Vo>VU0C2`BB%tYh8XQM@fbIXOSn<*k84RDuye?H*#9Pcd!meph?>n#>T3vYgtXeQ!3y2C$r=(2?#n=zcM$Of>&zmb)`Ul0 zh=IpRvCfpzIZ*yn;B&U!_s5{xbR5dNPY|*SUom~Y1u#9;>n#wTM1}8*0Tk8mJ@|c~ z=;398HKp8Gsxa7-4jh}%(Kxd3T7uJl!{2$mInWZ=2B{9;o^6P#^6Vqbh6~$ALMDGZ zzW-4CRSF2jP;LG$ByoywMC{c6B~;Eze28|ynup0UD20y47|qMb!MhyHe4(53%RL3L?} zHtcDW^HbW-{an7~vPS|dI|WV2SFT-61|0VJ8`yg%kD2|C*H_Qd1nJB!14TSmcK%A> zrUHKzGY_zX=d5a4AGpR@dHD(l3=t_m&pSO&tmIJgVjjCr&;NTXiYm&q>g+78UB=+r zb+PXw$#(zBaT}W%On>q8g7$`z!Gz@|@Usl;IGArfe%60$xbWKYMOipu!1BHo z)T(CnLEg0DaNjY;=ce9XCCDr}Mod{E?3N4EKd(Gt1A9%&Vo0Roo__HQC)9&w`+dhd z$)6w@8o=%y<*_Xm{>!RMl6$Sh>lKfmMmdFU zFEflV$o6??t@pwCtQ>ivjk3W>la-ZCoWs&YRmE8TP3Ir|J(p#t1rhAL%svyj*Ul+) zhi#3c-N@ui04x-i2!c6D#Lr&C342GCZ7HJ>{;^j&1fuBKsSpJW%ipyECxKI0y&ywi z8U_4^l!EPw1nyVJ8%EI^d%J|5h~3@q2)doFop<-Hx5K;g!^xAK8lSrr^3z!Or{?a0 zpxiQJCr!D}L&PJus+ZWi$9rND@UFu;?W|mlGZJX;IO8DtPTM|lbqa&?aV1n_Bk-w~ zbhh}Bf{3jEF1Zj?b?|huB(f+?0FHUQ3i>HLYP6MK)B>^g&5v1*TOVV9BL0JlAAJdb z_k>VysCI~8Lf?o#9J>^uE-T88;Z4-m6yk5zQTc zGF>+n$xyA|iOXWh`YSQ!{)2!lqjTj;SMy=2z>L}F?GCd$Nev6d_*)ymKU^m=9?uNn z(=Wu%V6Gp{l-UpMJcAT4x?lG{T<47mjX^$letYn z$h}3`6$+vN5*A09_93Fhc<{(g*r0dRF#m+R)IVAKe=ZE>zcH_Ox%$TH7!nZ~LU(UM zSz#Nw(HAAh*^<7A?~+}&wY8nakxLUuRzH8@h>(4WkB{F#_~p!Lz#*GIB`3r&kKWK5 z(GmT%jY%ffxxs&RsfsT2cF87J)vr~#b3tAd`)_)I@Q7wO2`FslD zf;)zwK`pz9BSqLAOt%Fzod4ZW48~PC=|U2tT~xk@e&srYUFbw97!a490!93BO=0sB z)4#8)a|<{ISToAhb8B;z{QMhm2zShUZXtoUhqd-SOr882bSZ-BI1?0sS+;t(-ZAc^^GLm$}P+R)T#;|bqKiA)hpgVSqs zvb1p6IC#@`e(&ENw3?Ru#-Jy<>V&rDEE`+)#`L#pND&i{V-;ZozmZxkodSJ7fN)%` z2-hgSme_|0=F@B|T?$L11X%}#r{8kXf5OQo$h3oxkQvvz_^yoCNTSDYN)OzvHZQrw zP2SUlLJuhQdn~@czvBi62QL*Yz4-+bTeeUA_e4*^a#fJT$%&CD6^VYR=}2{FR$W4h z1}60$*5S6s@Yq!$?uJkQE$_865{=e0f!ggTYts-QhS0;nWrC3xyI=*Aq>Xim$1fmq zu_oSyVRu3MTKfS+`*ib3*i;|AF0B)pb=*g`8DBrtt<93(+!Fe!3qA(k-g^ml$kic; zAc@_tGiFiWWB#ya9h@%VP7REo~gOHLc}Fw(#cTN#@EjmN ztpC+hqcSx3sV!det}97v+~^?_Qsz|+(N^!0Wtq@AhocuetCm$KHRr@K z%AVO;y_h;ZasrJrR0{yx>dT9ZEV-%e|DJ5LW#ek)NlkJE>|+{5Gno|C^uIcqKi^5l zT4<87my|rc6dC9%4!wG~9LmFS4VNY2DRDxBgP6QP}Skung5idnrJ0L zkc?vnlS;mPl%q<21F}pa@++j^{)}uH2W5fYsH)Ha-blF1``YATC%lVHnKb|I{$V(o z@r|Uo>w(PY5lWES0PXs(m2WSvyk9)vT}NWkpf#n9B_moQFAG#pX_IObFAvFl$?^r^ zbcS*52J_svp;W@%fZeD?x6aF5zN@QzCFDi)sm_823wBTj8P#8C zsW|7a5PK+SBO#cgiZkKRkfLacJ>Vfkaew{8$YSXXphc4SSjU-io!s`F9`rzjuPSbc z-$;leHtqQ3`B^uLPgGnMs$3TJx@(k5=QVnr7J*&PlEssXwwthwn+3)rAt+zr8~k(g z$}E}38Wq|F6YtP}PJSIB^&@5F75x#%bIzM@5K2^gfS^o*z$Y&HqHY)bV0I0NiJxkM zdU#r{s=Jk215$^aoF7P|Yt64wJiT5$fGtUngH}wk^of`;6aKNb!oqZWD{>l_-kq+( zv--m-fFjBi7f6mDy*kpC6aI(QQ(xRJ4Ok~(mgVPbSW(Mn_3vZq=XSRpIUb)Kxkp;m z>ru@{u%w;Vck4gk- z4w0w|B*UgeP+su=qE|0SOF*K_bHJP1*ysfJ&VT6)y4cp069rs*A&~X84v?}~Wup*Z zxtUIK{{9}yC`2^D+MjOw`)_uk2^_{sW+rB2!qrs!DU^$Y^`optd)G5rJfrO7^Ac=<_Dj>~%ZFq6#jN z6Vm*IS19H`*mLrodaV32_A{HC6y=q5aRQIM;baW!AGY2AvMs*wImhnIY65QV?;Zc)XNJ03+ceHve-! zqtv{$Qneb)Cb0;I8U8{nOIg(H9>GQHyDk4mIVizf;E|`yRZRB>yIg&nVwq*jB=^Y^ zamd%J5@laxUJhn|a??OhUfe+Tk0fl8YGRyzG-Y^O^Mh7_C~s1&E%d-{GEivQO96U% z%l&g%V^XPdF$1`Q{NYD;Vu&c2wGS1-Wo});^cdkfwAax28;PyiyLgyUdeWp!rndzQ zMM!B$O;FKugw?LU!n4t0_t_%4S1zQrVzj36)}Txa++#0FjW(%i9khBu=W|PlI+@^h z0zNLY|1@z|RklTrE~}2JO>y~rw2Pf1ANxl*s#t7>?}7ZrB?GU%)>=;Nrmvc|m+bT6 z2<4I9D}Ip!n!Iz{d-2YFTmZ8_GjU?9qSS!D_v;#^!gXdA#Il8Ey4& z8ml@bXof+tP|D+%eTBulY5smw%WN2HoFX4mk?=uxM~rD>k)j7PPCCE0+pa%x4!CO3gA}*b(^51xw-K zy%3hQT}S6@edSN1(UI#~;S5Uq^=(_k5cyxi`xRwgR2>lpFAy2z{;*bH_kI?Qr!-KL zUo8afLl&=RSKNyd4}x|G1d^(lLP#SGXI0zMAUF`=oigfvZm&E4c0r8Hafq9q67kOTR`W3`0;m7WyIum*i3~XPdj$ zFLNIwHdZdU`&+-L zXy_VS%YOHD?JTZ+^6Fw~%EGs@2(mfZc;jgfD1|Pq5fb6^vsKvcyH9Cgf&`hVXwWSp z2K`#MvI<03OL9 zu(_o_ey2k{<|W7}yaIwC$j;`wR@rV6zi{}|k)8`323O z!+?btF=kOy4XtFS7FA$fB;)uYn}9gPU1Sxu(hm+}IPFiVE&Ty;*sO}`x6~#t^31#7 zd#W$_K}g>G1xrywP$4Sx*1L1HXLLY_WGO8Ev;0mhkh4&EgL6iP$d@hxfdrv&3cIl6{=FJ6w}OZ>nj>CYa{@oE$y^Syd3BRIPe! z`YI&t-`gBtX)`WKos6Rmd2eV_4ZZ*F8q8rw))`dsd(5Txr-YKF6`?eT&@X^*o7vb% zwQt@$dd-YkkGE_i+9j?3S6zpZ9&YFM|5|_+mYKj_KyM<(jz{2fQ$A1PIQXb!CqLaB zwdh5JR7zs$E@o)CV?OtzvXLTr?AXgD>GQ;0U0e#=D5I=yI|x_c83>SdsiduH*~y0@ zaNi0%KR*YoBYb+Uu4)ujVS1S`NR6)in_(53X7NHIa&@`E_;>8)`YBU+Gt}|J-Idgv ze-yi$`@vkrD9hZWRP6~6U$rQ|sE*(=qGXU|IoMo9MXB&OC}ObYI^Yw*<$5#hKDpbl z+?JEG49>(g4AvHw*NZT;?a_QZz2Y`-d7w;~24LmtRZUkcffg7~cnQi1hNgig1kIgSE1xTOKQ&leLOxs zHiNuxC)(d0n!EY0K3*?B?d|OB1d_)rauw_swTu*REK}CT?QN}}4XjuCu^?65#oiM| z|5zCgp{Q1c@U#CSy`DJa+Z2D8UG{o%<@_bR>m>=lB~ZM6kJbT zb?;l@IXB*n`kw+r)r_riA0J;mJ=+HP`yd)13yA8QL~w=g>fS`S^2A)+d#MjaH|^S> z`Eo~P)l$zB6ay1YyeIf8NBJkO%{OAC_F-Vb0t`g!j|a9GdG`B!@p z=eZ2byv=mR@d zdP@76#e9`gyft~yov>47gt?6hi5_KqXMEgCp>;m>@o|s^SD1?+@{?D?*{+gO$EZk; zMYdrQqMbY{!~H~c-%0%+>yE5TZyVwhNXRzW&gEqqR$xXE+7^Y0dfZQ2f2Tg@iv?Uh zW+VvT$Ho}i&!(%Q5NOr@{t+fDPDf!)BNmJ@IxrTwk!0Wd{bMU6h?BRS2sdKIcy({N z__XE^a+Ex-A%3?4%vF!4%-0rf-Y7#DUyk{-`xLNmeYF-T^W#oe)6VAlGQ`#~>V0Q9 zqsp_!^s?8l72E$t(Fh{u?$Dr^4Qu5_jA#27A@%w9+ijBdeeij0nQ{^XO#Q3d|-qPd@ECs?nqs0Z{NGe>GkEYPFv>g zvVU8;OUBftAVT}@anWhl)WPM}wdsCNe%YgcAKj-VT~+_rN&YjI!QG_xu7S;HL?Q5P zG3lX`)L-YfI8tW>VV6Y$ms~zgfl_2QD_=dsKj<@btVQNs$6|%IpQp&^4CCNNB4`jG z(lpI*ZZ0%Am>X}t$y=qeo_v}L2H$@>hr)fi)O|6iwh6E+HKEGHKbuTXfq3xztx7i# zjpvixP9zpEzYZh=0B zNz#X8s>|+LX!uShk>+px1oQL8bdkhxT>`Y39Uq2njz>cFZj7vKz=r1AB>n zRd2XS=A3oQ7gds}Sh3yZ^hNClY1_t+#CtLgGI6nui{skn@cKDxSG+I%`>><*zM*G6 z?2)*8`_7mc^}u0xItWQe6*LbUMPxTTC8O-oBy`LDw;bAF2AOkq#77%!<~{!l38q5- zV*um=CH8ljuRNj}Q%~8+ayex(s87F<7v&bu1SaGdX`WW=S4A7 z5G&RcfUx19-!6lpo-iZ-S=hIAy@>nprx^=ebb?FuJHl>K_c}EI!c-C+3wtZ*Ip(6R zZ-q*u-a6q!IHSLZR_-bX6ZFY8ZxSmP%ZRIDX+%3hCghNHg=4#POTn6k?^bdrP=2lINu!U`V9zfHDTCY5j$IL+#W zjN7;YijEa7RyMCPhJ+;Ud3&yZ?4>z=RazoCK^4#GA8*lE9rr0gb;ZK2&6KQ_kBDPP5GJ1T`obJ_e)R(os|JD%#%e}I9bA^qeN7IbGXjLw&gDIj8T5pyU-zJ>l~C8_cs5UEgj+hwC;4Vk+?}YhS~f4_*Wx(+2UnRfa zKPqKig?+Pw|20d#D;yWX-asw|AL9rwQ|fXW?xgX-@(?dj7+A>h78P(BhY4Z!rLl2b*OKn4|9hG7 zLE3p21{y8Mvek!yhp#`hC0vN_dJrppxJMnj=4C;D%#7fQqJD68+GJ#yuAgfCp(QFy zI;zkb9EYPO!x^G7-Ijx4E$aZp1AxPH4s%55_c2n3{?j1oAl5H`$%?BOJ5=@Pr``^5 z;nwvwY!k#LQe^VQQ_};2jT0kk%AhfaMj;psm3#wQT=3Q5SySQu1H8{Q-Zo=f{1?cb ziUo7Wj`b@ADm2`O8C)ukI9I0a-5jL~9)$p( zj@zd@>1CsDi0|h#nkzd-oSXYwpUw(A%0m${)G?(-l1Ei!A^vmL;HRA(q2T9!^?d|u z7k!mhU&`Z0zM?81dST>$_1AOUzZ^bvq|=*~=E*G57>;)Mvl<4$&~}D{O@KY~ee%7w zVAkTK5vQDR2*1vC zI_-Txd>8$Bp`kw#56(S@<^4C%#6R6XESToizs1AKdCqFK%6@E-<4qRo8Og*XYZds1 zr1dfy4uLWVkETg`^Nc5Gm~v_yg{e!y9(nd1iZgID4@z@?gYfT4_4^RZcd@OGsNj_0 z(l-%#nOu#H$^JXvw#$xV^k(HJj=3$_SUo^o;f-E*kcqIUifD>}&kJp|z{`<15Lc>K zU0F$wfm~Y_C*)33;uJaWo|APbiF=ixW%$d4*_(~YnG9VPUw=}da1;f~=gD`5_GEi9 z@Wcaqs}%=7l=X*5BIZ13>T7wZ1fJIUARWO6V;Iw%yLx9UEa0`Lb!rYS4&gG6Qm0(m8x-{ zM%dLqW28&EIN#aSXM#qq5=)ehjt}x~{ql#rkI4XKNC=Dqo}n@wK~^urpY5UJ82unp zI!ESKgkkHgf7FqOaE;u=6uTj5nwE_Sj>kBqD){(ozeAcsBkSPUZ>>@O!68>b$6rOB zc&SkD(%xg`5;6*E*}uw;Ke;OzEzTeYfw zw>EsQ)ln6#%edhX6>XR?2M4_!aLV()p4H0_?>nYVnCjvU3H{#cXW1y^Ze>n9-zBNX zffvAlr7Q$X;}MABLF4K&02%>a${%-O@?eBzLmeP!!C~;Rlt^{-#pJ}waN$FnnN@z2kV-t&l{u?VmY-dz@F zOO+{{UN;`4coK2y_W?PCA*l0lT0iTyZz=E@R9>?fCsizYSO*AO%}z@}g3XgAuQn`m_%Ud ztc+fUz#^?uD^F!#(z%UYvu(Gjm!a6z0#&=c%liHWVmo$Vh)K%IP=bhyvpi4K8M@zY zGFMkuli&~#APukvF;wTT;zaRMd{VPbG3&jkWO^ubKlT>OH-GvCLGzNCPmhf>;1XroC|;?PyDs=+-P&=qSuUXu zaaBM0;UQM)*`}Z|=|L&fEXz;v!~1n3Zpw7%a8Lp{p7QZ%N$v@+X-0zqh3@H*KgtO9 z+-Cm71%dbG_9gQ2Lt%AQDir4*$xcZ^dnqSuMjEndm>azAOribPmb#os(dHHvq}}2s zEnGv{@(GD^TEReW)rC@Pa-X_mgv?hyzF3y&Z__An#=}pSON%@6U<;|C* ztaU05Y$VHw8Li5}uTXgQdKB>vi>vk?(!wPO+fRB zo5Y}tgJV>IwaWtS5nxyJ@+@gVkbewofjRw6MqB(DBRZlHDfqX;Ux&Ur(_Q)$mFqY} z*6l~sXw8I(2tvD=ZzOLc6avXTx4fgUf2m541}yX{kE4wMz()yex0b#gjb{Tle~{-j zt3EQ&A$-O7|7g0#;JVsq9Xn}k+qP}nW@Fp7(*{|8XYF1bG z3Ju$v*Oo;dA#>u?Y1PZK<89yTmE5OQBc|Tt=Y_?kEW1VeWlL-v{owxZd~aoGq2OY@ zv@~HjjF6J^mAjQ8BZMquw`L(upzh9_SmAD!IATV6`JE`kw11XD@zH3gLX7w(8ZVSm zcT(qMRmTx`M-{@=+eFU@tw1^q7kCxwt--rG1y=v@;G-E>?;vfu&;AS=( ziQBHK>+$*F=y!l;MDt^^%&_h!F#?0|&xjI*sk2u|KZd3-bkU!`K&fCh$C$TaE2OUzGz~KJp#d;_?NDq;&b>->1i> zrRwL4J!m58$HG0X-kN$>ze~6Xb|2l1}bM811Y>9}GiN#ODf+oaE-&W!9{M zXIjC+^id1nxJ__)WsO87ydOItc226Pa@uj{7@cz6H?Q$iU5gK{f{5#nT(S}a-w&`9 zve|8%ylWzic_e{68f1uN9xHW@m-`>Re*d5aZ-YC9-qvOf`5m{rGyr47{_04uO7d?w zA=&3UVdVq5cp1OGV-%GfqOI6D^f?7Gc#1iJV|{ z2?3ilKTE>=?&qQUTxTwVA((h`+Iopp@?VH$#Ni68A=c656;%$T=m1!x$bF^`@WFrS zjCd5YmvB{@Uh^I`C>67E+qPngqnc*;HOdsTs>{Dlt%~5*?4Za*@%1LsLAP7y|M|?U zm8ZgiovW~GA~t(Tp0hhU0r@&&nUniPQQR>NH$mgNbS#!lI3a!sZMp}+Z$t}nbsZTO zwY@q(-j$6~iz!+pktVGHHb=>jirOAyIX<*J1_@+!DTxgPRxn<;q)`waN(kpLhrCOJ zvz@H42-~L3x!E|ZpIX*oc}|?x9LP8B(wUd++*N3w1)5aICumJ{C52ZHH5QXlw~q7f zt}ZV3)%kf9f!jXtK*$69?jx3rNz2Cz3Avw`5hk0!QW$Rl3Vz=IR~NlJJzFtQoM*B? z3gg5i);iaMX_WT48U|y`o{5b^#r=06KJ5qs-DQzhS(vNS(Wx-_wai-7%3v>vur)bS zUWWPzW}H(s)`JAV25}{+cd>>n8V+#EyM&NEz9C$VpI18hph z_Xx74mSP{zAls0zJZfrE1O%jpFCgp`Ap8E3$cP_IgjkijfY znI0qA4Oh<*ISe$g{fhD9sE+Aiyga{j-fm%WYj|cOFtZrx4IRLb6)WGd(TE2!1*EW{ zq_IV@Lz@3G8Ww4NJ|(`N9rrptWtBIc$IwMRJNU#7chaIpn7im;PUY4G0@*DA&yjdu zr^%xwfMJjp^YyLpM~o|pSSXt{cPeotp`j2zJFqp}f~pHy)4Rl{ zy)qJiVxsk$fh`An0DWaw*EB2mwyLJcbA*5*g8>)Lop4YeR6whrMOdO@hn%Qr+A&!l z9-KZjm`8J0GKNJeryyWcEoWZ6qo#=J21W9dor>TSQBUJtd5k0ewqN*k;31JTO-ItJ z>U%u8->;@B zJ%p+$E!4DZ>1H3XPD?%zP=O|Jj9P4}(<6Yv8RXLE@EI|6?|W_rJa^OUEYMLh^O~)z zTlyQTdF*&YFB^I?ta*&u9}VgSCKdLR)NtXtd3Z?IYJ=F4q?4x4dKW3;tJN*JdwGQg z_mf~U>JBu`b_20cvwPcbM5cR1!+>9u`|j}OoTB&NxSnE1r7Si?TwL7!a9x0yW5*+s zPGiXrH(m+KRFVWU+H_jm>XsPBtE*J%1QeDi?jp;)2*fqI(rS_c4$ba^(19pg1>;6J zl}6B`>e5f^Al;bEb_T*TDt8EaQgP&BAYF9C_CW;qlZ92M#@|C#Zz#%CwphwmIUP!!_Kt1Lr^ zqi#BHdZrTqOdto?{?@X9xz&bdPGt>PWL48MS5Q7qzV?Zy0$TRr)Zs?3uf_VmQBJa8 z;Vmup4;ah8B;NT6oUB=;x|aCSjW2wrR@6W@A_=>#IgrSVmvpDjXa+$mEM~nUHIVE^ z_%p_D8E}q%3`71N1-**o7_&d^0Q>WHCv`e&)K7)E; zhpsO+_@IE`ne(;C=9aJIcgfKIvE9No37qpUyg(-@VZhFTJemQZ5C)<8EJFX6a;M5m z-#IJtpW|L~Mk*Od3dMXAY*bd6c3}@>quvb%J$BrZdmttom;A>N9B?zcDYJ4zDs?}8 zCCC?2X4I8~^(4!>n*YOKR6v}s0L7Fl0A>0%wac7(+*Ms348t%AGf6vAH>v%j2Ai{H z)Q^)LKLxIhxe;Gqs{>B#P|lzG+uDX~DXLw^S=sX@fc5m|RnzE4|L;)4aL$2tt98{h z7TU4vi^Pe}b-Vv$v^f5lVy@kWQB7C?{&x3|pEpW~!CU1b&oinNpHeTZA!o zjkybts3 z&CaENSZiYIdFFV7%3sEFeUB_<#-d;M)9qvWUpty&#t*D6QQmaaTejT+<~|-NwwQVG zTt|rPcuL7IOEyzC^RN@c%qoa}J=CFpA}NT=IqeqOeh(7`o8I2@!&(^QUK+$M zj+YFzZ{2#54B}1*&6pg%$Ng#n7`>VrU0+|{qXBp({@qZb_uVV1JgA_c69X#q8jhodek>`F9-(xNSwL7BnYdRuo4%oyM z_~m~X^zlsMyefri|5?YmhX;b`u|hsEtAhUwtgkRQ36<# z#|l!B^Ej9+`IxvrRHv)W-K?C_6~g)dy#VH-z5ccu0#Lne4!;v} zLxN~nk>UlcbGK2*1dyG7q#hQAc$$ABJl4_3xSF~4ywpZX=!fG`sfpF z+K}((01=8>MbeT1=sti>y!Ms!m0ahy*W@$sBO%?gJ0bpnYmG}agc$iGN7ohy7xxLx zC=~)-DubB0n#0vy;85-DzbInaw-UlXi)avL-2Z?C-`9+dzk$7X26_KBTvh20$1T|o zA9#wWivo?Km|<4E2Ho9n1U$`Jb6d=gmNZe~`j@}|snpOfWzDM|402!5QOll=a`^%g zQT6ZMf-!sy&_0nqaoOa1Lov+eGrDibk9pi^q#mp#skZa`Vq#$4@u^y__97D;W-Asd z($ORv1~WA}B0tHGN}KhVUY{q{6T1eq|hi#8HGJiT$wwJiEBztmtKFbuIw*XmNc`t5hjrBZYxhHJ5K7WdCeBD(D3Ji=HsFAO zuxY%(3ggq7;3BHtKy;o+<=d6eL0b8rhN=?n=I$Tx7D?BNVx#kjA&S^Nv?VTDK{Ud@ z2-|T8^Ny=nF!U=U#u3?Q)JTx?lXPLr>jHzopFcA5+>brU5fG+*4v3!O|L`od(-)^# z8oC0D&Yy!Lwd-#&iny0e8;EUd8*n%LoZwrwzhl}97%bp3EXEaCNPA0$`APA0$7Y-| z&Af2{RgfRx{fMw`c>gMjTt1sTXIcQuOS|euhvk!T{+)N*n?_W?&il9;A3k1t90@exU;us>D zbz?UgfTqp7ta(ZEGTW4lS9y_@_dF_G?QKW|MB?nxwOQHY57Yt>HG)qtK8*=-`<81 zqFJ1)p2n2|?)MeAw*Cf_CGy`dL=yT~FUq7+4)*|S;HpBR$N(|gv!?$#e%xFfNAgS~ zGxT1N%+4|l303UA1m6L~Hn!fX=t+zJotN+INIlpArHoRbM7l6WYp2dCtkf%0t1*T~ z^lyx^FL$50h&qlmYRNAm;`$Di8Fq;P!x>lCX2}lbu|s*ti!QCjLm2^mm!sy!->pW0 z{@XffF^~WhbIgnzdI?LHD?geeL)xDc6N;``S>mMv5}?R`f}U7sR5@}JwD2=a>opKl z`CrBQU7w0I1EnyYMLw5x{Llv#&ZY^j0V@M)WX_s%6egukk^AfWtCTJKoNJ(OhS2*N zCJja`x0MPOt11)gAoW{#0E8P%0L5RHWgir zS7u;@*Vg_7DZTib%!8d#{*31X{4YqBsD0#R*f7xW$Ds0DW`$j~V3OSWcPSMhjP{gX zj}rDA>7-P$2kc=nr`jDoc34(VZ-e^XS6%!oIT|KotFor^x?^@5@XItQ^4|>+<+#r9 zjQ(zJZda$ZsA&cjmPLGU3Ej*}cu_Q?RnyNF6PpBF&^79Pnm`;tbD4brs@E#bXm|uT z+M|D!iNCb9b|L0nFU=rI9dAn{ZcDY4ACJY0=l}_vFS?p)WiPxl1`M3B#;cP%>y`N$ z^VN&bspSt*;0XZy6YD^EB!7ttWjf zIH`+vgM78mDp6W*Cc%!5XzTE6H%O+L%~~e-m4V?Fz>N%*$jc9wag$TvW8i*=!$F~! zok;(sZh4!2X_}Pj!HSQJdw!0$#8lB(fg*_M^Jh7fP?Wf*QIL_n+GO`UdvFh%$xFPZ zS(P4B3NBpq)xcM6$MSuYvPd(`1e@oc2#j6^umX5kSr_K#=TU&$ zjTYIu4?`rYv(fd{!;Ch+-hPTTgE{zA&rqDfcg+TjE=t)s`i%@$gGhS|W()RGsCx1# zqbzo=PF5&Em?X1A`~jgk5?ocM(8vrazJptiufmE!2NuS8^N4?dtK>ErSr8RBVEOXc zmJD>bg`xy9TSZ<;i|Se6CCYEVkF4!H<-yvKZsFGw%NH?DH}8Dc@=c!>nj*oOx^!)3 zBy#~Pb4jAmBlfph?+_`Xz!aNXX|V$b6cKU z!T!(dV!R!E6$pu4bmL<`yDhYtJ$nqDr*CL5-~%ji2Bf>iRdx&Jm{aCh`vSL>_>_93 zCts2vBU|mJ$^5u!@5IXnG7FZtLx>}|B?xzR38BCq6;5PKVY49>IT4XDpixLqDJ}k}G2>Wo_S{4DI(m z571EJ6tdH~69)gIHL~M5*G~&){f^mwyg$|jZZ(#dQ+dqblz;EwFqWo|JjP_MTNA@} zPtgdCVk~~VWA2XYe`N^wht~j>q<%_{?tLyRkMbKbGKWx1i5^`C-G4weh%(gMnY!lu z$s8)sK0lX&nU!B%CqKba%vf@^u^9dp3LXK*yPlDRL_bYk*4Y#aO&E~&A^3q)+wsEh zvW5U~e=V*2%esxP_#&K>(vCu&JA8WoaR=93Cjo|jHhF->Ry@p2l_cCmnifNrHy8J3 zOC@!@q}p}ht%*bLo8e6NuIk6kIMwaEfk)E$+- z8#T}(AMK)7o4sRJSZMCxw#*fo{{BT6@>TujWM>5*_dUBjddxJ49EBuJC*xD(eznNi z?#9KB2|XaeLj2X4pP{@Zq2xlI#JQP$!>&+3eq(^C-b{cYl9BbAp*ncwzjl<+1&D zZicPNrSNciBwX}AyK@$Qo_R*l)F$UDA6?;;_^TASmqScCC4}k0DM^R)q^vcPglRUC z>XysrC54B)QmQIikx@oyppX(bYp{5iqaM^F%2k{r=1#R)J5G9_c}{Q5qI1do(i=X6 z(99Nf`1ri`UVr?xr3%n%-Lf~@y67W2EI-kwo)!2?DN&`NboXRwFaK%UAlyA~Y;B!M z%uGwl%d1=FXq>d&0~!$_RX@{-(A9N|v39dNJHZt2NjL5JyZ312#quk`j&pFJ{o#@8 zhoM4+`Vgh|A)EgL;5w3nRNV0?sJTv&asgnl|051*#K=4F=PL^8)c?b}z zMzFQ2mKK0j4hs{#|CZ_!{wvolP<6UqR6EaHXkS!7u||Oqxy)e`K_2t$dB{a;0Z0SB z1c9ej__wz=Dt8Z$5fyaEAastBR3?V4lnf<68+>09Du||*jC~;D6cCgJjjqO72YCvRk9?Q@vtJ1(=q z2ptodlNQ1v*rEDZvc50F#yO(bh(w?Gf6*Hnwn_VR zbVeP(EG6j{gY360!lm3}p-NYx1g|x?ClGQ8nzf8WXl)kM+w&k&KB{RdAw-ra@cUna4 z)T~rl<^S)jd?gTIzd8Ny$Emooxg59Gl>{FDd|xb6@I5MuW&HQT_ZslUc1Hxe z)0>s~>o8v^@bhmmpxGDcId;2u0^BP$^9+xf^ay6O*_$NRn#(uZgi`9jh91%NRhTLY zgv;>rQT>%*g7|tMwB^88`XSe-`XRU8yR1LuDO~)V=m%>8Wbu|?$J+E$;X?FWu2T}B z^Ms6o98-O*7et+bAFL4E`!=nd`l}c5lxqaELc9z?tPEbP1_?m~{nf?6FEOD~tGqjg z3Z%q$wGE;Z{8!Utru-L%k3FRsz=qZVJLn|eP&>n-TY);tQdQenblq{_aoIh}X!qWO zNHJwOA8}JFgDs34XG@Ze0{#L1uTRYNW!hrJX)E6$LzSiO+m<_7D%GPjy2K5&-%SqL z&*}Sx_aE+W2G6D2)=@H+Y3Y+&>Q%WA`@asLZLbNj>9@C^al-X#gjy0{^dJY!QCPih z?sxR0z%42x*zZF#O^J9o#5v8KoFMT73I=s0Pe+W>2%Q2D6*%jgSf-?*C@cS&AbavU zX0(VAcL}|A`DnDy(tP8!K&@9He&>l0trYPcr3>N`r28@1U#-)0-F{jW>ygW5HDmcr z@MRa%^?1tri*1&_X|G5kR+Ik6B&aZU1TYkFm?VbOemliB`z{Z-ntQc48^2Cyi!|lc z@9Taq$i~)oKdoOW@m>8+o#j4=Q)A7FJ9gp08+JwpE5vn}`pZ_Wu&tFPe+ZV1`(RYK zSI=AkujFG(`#wxVC6xgefVUi>M7;>+r5*mN=_N|qp{y)M8>ORbZ?))y_2c_c)Atus z+JfpHd8NIdJE4lN*2L8$tenO!-&M=L)@iF^ZinOQ|6uZXnX|?}Ntg#wCuIHEnr52$ ziU*orncnyGS-JMWT?Lxwnr3v~_1CSN&tF13Ctz8E_HfI(``nbw5dfxFHHoRQw6Fc@ zUb(*P8k9QC6V&*Pqf)0?(R#_po=kko{k0keFbd}71|GY`eD2UU?DcZI> z&Pd6Xrj0l={BQa`Z_p`gb}Y_0Wtz5PL-GBOde9LJXqt}&YL?MS1Avxyi3)>9B|`?W z3={TB`?u1`6t}|;OQ->Z5a~O5JOw8U-uLV;xUONl^566^=|7fKpg6yU5x#2*QVk%G z@uTTLtj=REbEYI&gp(#f*RayDSJU2}z*^dZ%(N5d&pw=6~!vRMMpF&8`* zV-7G`Akd-#k7C+jcgc#snK^ZLgwQ()WoJ5ag$LHb!DI+V=et zQ>DeIk486h`#n|5XJ0-_=nnJK{!8#?5TR zQ7Gb(7x+00;76e?F9mms*icCKcX^If|=)7v#;M}$;&;4vOA5T#h z&tVynpe_P^mQFQuWkUPSATo@?HwYt(h}}=7%P_F<%SXf>=Ub;0`oNglFJKUan8zQA|fIK1lT3P;mtsxe&(@7m$YOG21V=@^thKU=E4 z7O-L=1;5fA$BZ04V=?*oq7PQE%=t6`s&uxOTPxq|9LJ@-S_yYVxmKO~wJLu_j!95U zuzuJQNQg*-jvRra_!N5T;hTv$_xs{GnVP!tz47p%2_Y_^$3zZQCB?i%)? zXCu$fb7XHIU$z!!i4LI%Qt5g6rJ@`f1#0)#5OcN1*Eg5vd&65cxDw^Z&!%3)f zv)l7}Du-{j>&QFbGf+%uB$MHOgl8d|d|`gos%VxWOc)4qHQI6EDgpO%;EWi<{BD+D zG(UUCojOw-56FG-mz37!Rz6uWzFeA!Pz9550(s9Y4`gW4Y7MScNCh5OdXeGBc zrc-m@&d#h2Br1I8LL`I(N9K0lr~Q?%P}ZZ6LBt4P?NmVcabCc4heZ(LSW0cOhq1-@ z39Z6KMWksspzYLob1{>pOJ3Kk@;}PJMLQn;G5sNX#f`$JLtOItdpiGkn5vZLyr{rs zkUwsoGP(l@b^AGPXVXP>Eo%$r!zOU8`xg$Dm_}b6ZnAoeCAGHii zw+QJ0fxw@jt}@$Q4RkXyXzNaa;>**>jJflJIR4tD5j28YZI#kxE*h)7M?44_7u%|B zKd^SRgX4l48xB(n^vaVxB_dJ8;ok zfvZC+@@A85p>r!akombw-}N!0s)-4QNwowPRD0s#-CdQvOBrc!1X-XLJ7EBR{LXL& z5@xV(CCwIY6Hfb=lEB=&J`@Hr5^1-#3hB)f4H_e#X^Sz^;L>SO7qlP*f>yc@SfyCv zX=1;o_(wduz?aonURq(X#3s9|xL9oR!~e@6<-R(#w>3UwU-FBp-5kx_3&Q3 zeFBD4PaR6rUus&JP$;Mwk0ZQvxFR}`A%>FD2>og#S2yrT*f*q$VE+^MS812lXZPff zaE7ZP%hP4iduF}4?l=g;`4lgsLu99QRS+mu9%-9_le;21T(h0m{EH;%vT>Y?kK_ko z5Mu5Y&g-DF!?62TFrVkj@{8U;#3}8r(|JD~1=(n09he&?|9pq94{&;X#?Z7aV)3EE z3murfiFGySBvG5JEz-^v_|xSoT7t9!<2sU@XUhU*G2DRkDzUM#qGUbmLIb%k{1&YZ z6;Vxkf^+0Z29Bc8!W27}mfmyN_qkTIB0p%t+vWm|$z&~S%*?Wdv$WIGQ)CSd4TTj$ z-z)pc3w4al$E1-O+~l~!Z$viV{m8^M?}8JG!ssBlRrwv-^2jTH z3%Sf|@&ZD9g3Td&Nof8E#8}Lq9VNCij6vUGK zp>^u)Zi-XZNE~^Z#LjcYz0^>2#~4x7nLFc%D0W5UOc>UtPT@c>jFI*C`03(qv`yU3 zWxD8aNV|NmK#a3UyY{@SuCujxFaC9qSEqu(QaU%yN3i+nIfr{veU?#yC|0U^(TgA$ z%>T-erF^;FllpuB=D&LF=8Ggk0UjlTXk}%EODUK4FXTwdvb;8<*y#vqtbyBkrYw%8S8F;kd`$uQGDU zFG_}zLA}u|#A~$=(cf=!lF-8Yp@bP^!hikYYEM?y8f&mLm%|_>5j+kYmc9R3Rn?YzATu0%-i?bPsTu1{G z6AWmAyd8PmP2g9#_Wd<9lGRxE()fuq-4Y0U^3Z}E4@mfqE1KLrLcDpZi6^Q_Lz9P@ z3nr4YOOVJu=V|{Cr$EA}pL6xDcUV+$j!lYdf*T`MN0C7nziOVq*ZV_$7BF4rUo07~ zJH% z8xjl>l_OKLq`_`JCsKd!Ek*e>X_H|=qOBJHc9SI%7+{W6RZZOhWPI;MN#!SUe zC%htWPEYW`vqu|`<*m=t$RiBbXH+`C-On<1BsDZJ%5*AGM#e>2H(brEYe8H}j$x@umLd_R>E>hPS&;n4~cB~g`S zfejJCH=Gui%*?EPWlbJ;&q;yis=Ye5oWz z$$uvi-kvy9n!oeHhY9-wV?#1+s<7oNK5f=c{I?Aznhyjq>SNOCzx6*|n5y-7`K{SL1^#Dr_$;ELrwo}JQ5|lDz z@E+KUP;y_Y55gBZ&Y|!eNtCM?9F2q&eI;n$?cU(dPj;IHq8VVWp`0{X8m_*%733{~ zCd?Sb>7u4}8#!Kb(6Ek6$acBfF&>xWJah_%{w63MdfAw%e!6<9qAl?;8?v-hxAo2JEW}a{DOi+Fh;Q0e6lkG;f5i!SefCU8nBhbSMo;H zo^nl?s;5MpNuE3b0_~PW3zhf&E-D4y-2>Bk&#PvM{%VWtE*)U(Bblh8(kCxGC;B}= z6iBg=*Isxmhr*!1^CsKt??px5aY&LUX75?<30w*>0KDZn@Zja>9UaS?E7| zf)Y!ro2&6{wieB}!)=usw1y$9!Y+p5jPmA*V5R;dz6QYoui$5Q%(r9yGb_f5G{R1; zA>4;&n!N5>;riz0)unNIX*W8UI6W-jxdSF^b?NUi7G9q32L?s>CqJW3e|m=Jz??YW z(~H}Pu!$a?unwzZC6q6~m+RxW#aD-UH*(8=MmR0__$qNd4^23n;r8`=7MB;d1)5`U zYym-@jkhqmaZ2lHe@CJ!E#%ym|2MZfla|BgQX!62ay3X|agWJa+T_tDU9iBV{ndVW z=Dv#TixiMN`Em))pAvP3-Ro3JdGDu(MZn~c({eZuct<)N9)LTwxlX+Zk>UaXJ z=X55JhH>Tp@9)~4-LtESE^zVW&*2LgH#Ie}y^oP4>SMq_5SJbX)tJyT5U9e!SW<~^ zY5Xzy5zIG!TnVDcv-6tEQzG4c=*?BC3xZ4$(tl~+_OR3lfv}J80Q#F1UOgquB2H`^ zA1v8SUTl%noF>RQ$cDHIK7DDl-uwit@&2bawR%KvH*5Zf^@hhEO|AZ`2bYPR%LKgu z8NC+Hg*lQ?9nPO`3+j_;i$i$T)Kb2#bOv8Sy(6i!U1PMQCU5U3`M2M({@)7#LX8Yt zdFG1P%s=1hFTg-;SO~&Dt4eoXP zXb3@3Ga88;zTwPW#+Ydt;qas`sxu`@7_exmSY9q>Mq3^{Xz-hm-|J8`8WA@z`+lsh zp)s6$fG!R}684AvimZ>g6kQai-Dz*YEDvz#E#3f=+C+xlN9o@%;2;xCu)X_`nzd?l zIZlD5UC6ON1hTxwp!4ZgoOF+jG+mn9Yp2Jb?|3HHcj4vk;Oo=dTcq@f1KD5H)%t!J zfC6kL7t1x>Rh5+`7CJMCDa0ia9>cdS9jOjD*ov`-? z605RdVp#?v&U#|o>0wypd*w)H^WDe@tKG`B=uU0uE!ENus+9d+)JB**DQvRNrdx&R zD4~TI%1gGWw%adstO)TOt-tq_*uF%$Hs9eh>}^78FLnW{Pkt}cEWy;QhX<}*{9Y~@ zPgR_lW)Bg9*bQ-3=z;WOpR3jEA;Z49vIsvzZowREA+E4-wwe0dv-yM_xEYjm88R*2 z7;m?~45kP32m&=T#JjU!5`w6(SdD8Ys>jb!pV-?a&;#GM=td6LG7&M32Bm?X^gY4$_Jo5_}M|)0fPU|-1ANCVzG|%yPT-S&O zD-=PdFOSZ}MFxrQ%X*FI&u=!1Wm~0bs1YWIt&Tf}&%pJS{|az`^7<`yYb@jD zst<1;%n0D-o^AKBj2+h71fH8L%{@Im0796yfq$*eaGV{zb+23d{7DyGnD(D>rT!Fn zGWbVsp6T>@!-H9(@&oW;f(w^uj*db9fOE?<2_#^H zDwxN{#gURG9?{rYf{7 zRnFZMz!*Ip%24J0r3E=YOnid%$xrvcLh%Q4HCTS$_H{h{&roSrSyctl7M(*hC6{Gm zj<#TG9EMXQ+V#=VI}Fl)%@G#e61I`BhCyzl)V;=3+t!e=$8{QJ=^=NY%Z5-T+BC3E zr!V3ACNE{kZ)gTsC0{7rs?(uL@&K?ef%)E_mr-jIz*@m)=5RyN$1dDbC^fsNGUi~_ zK6FuHV{sXCV>n8vo}$Qif{<}_8IpDn4@un0k^y@A?_<~STZ{c_9JH07V&ZhxsOcTV z#GnQ7oI{>6dVgipT+?%V+&Ct(E1TofPGJgl6?U-KTa&org07jc`eczNlvB${Y}it9 ze|_p@KTIMH?f_CaDg4h>uinyICHPEx>!=v8&m#9d8U|1rIGE7XM7)$V)CMnoWR!?+ zM4#Ihz}lS~pd)%v=f+nA-`c}E(Z7y8Ihg1Ga}wi#4Ki)eeg!ZZ^rpv#Jy-AJvK|#6 zGzXWDj@HQ6c=o zErHIhvkJFftSLa>mKApQlc+cs#YK33{>$QM(0<9V;Qj?u4?B-m0uJ+cEMK zXQd>A)N%-!gu<#MvZiq6bf~ z%~joyJ1xZ+bc=u(E3VR~Ww3x|dek1@z?!bNd` z&m1LUh5rK%R%~+j%kRodyad?_Qxv(xIdMu(Zm7s0d-NcgD%WjcQcK@?*x+C!-q&D} zeSiw4H-}Zg6?F*}1ekJ}0QNHl4#YC`c=ZF?h2my|nN)l21v(5wCcL1kY+;(IEX~k? zvooMq2?fpyJnI_*juVsYvTJJ2027eIb>KX>Pu>t2kyxsv7uE$J+(m|8Wss8QdrZxq z1q|y}vH^`G43MlIPyX9T?f?e?CcEfHDxv>OscZ<-Cq^=qxlVCrgQ7f}qv{uZ8US@| zt`niTS_6rhqmR7iO)7LbqW#AW_+MImt|?F30WJtzs(2seEb!RMIQ-*YY;QI623 zg<-#q~Bm`c}n1|(J?vL2>Yv34=)PBO)RaDFU1d)2mmm zI=gZQg(>9Er+kXeoiRw}XV#d|7+vveUl4SO1&{$|)?E@PNYgl9OnAz$ zmlcq4NIXw;6z~EYTt7jg%u$f2q|?iRjW2!4x~?ZmLtirzyy5vN%1Q}P?PK$E*=(V>gm0-H3ow5aNO{+(y7=(|pDfkPpz!tp^j z&7S|TE&ZpB^aXtb*x3t#t-pt*bzS!puXtPz{K7O6KuG~Oo&X3x{YCca?~aZuPzc!d zXM2%Ef}Q}CMI=kZ0n+IuGLv1Ol8*r5nzC&4;|BDt$SO{SHvK-|=e!~o7>}~WF_Wdy z=(t%pd5{4P@4{#M;?WW)R;)O|F$DU(irdna7D1FG`js z*VNR+{00>oZOojxmoq7lUS66uu!VI(!Z5io%X?9c<@0pOW4S*RT}O@@O-QIv&fTX5|+yK&&GrPZJJGA|Lv*f_|bH?zeF#TQvJC(zyxN%~`U9~49qTN;;^ zI4L5;n73r!{=+`xQooYIlHI8 z*&cB%`7G%bzY|vR@0FQ+iBl8^AVi3XS2yll6+tXW3I}r>1XFYNXWFGn3gq=3TTnhX zgX!9TXH@BRo?J2`Jok9?S64MIn;=mk${8=`VPuGZKquhu^?0sV; zg-m2b1`^Gz7v-6^purKYdayGrCzw%i*F|80iwj^Lyl@@84l?OfrP-p1oz+FC7~7_f zABxy;m%iKD7OOG#`i=alP`kNWYqE;HSOOu+52eXfMi7=Lo^J%g(HsT<9luVy!|iO$ zj&l;Ae*w@AR`3KNcwGofVlE{W=`l|6gWqouk?Z|;ZEbB$np#QP{tUapk4^13xF_!g zL9(|6t?A8Jq15pgNub7kzglnpsI06^Gr`_Wz5tni97+0uq}&ko3U9CaG~HO?c_C!7 zOgABFwg)rHVOJGBnTO37tn>0hW%!-^J1h5mM_wA1|^Zbs0b zKUQ69ZQ@HjNcAaHmN?B-*okjV0)iGjNw(})b4{rIT1)P7?M@U%)w1ytAjmDZg%0n- zf&|T8gs)tHT2)iO7&XQuK&uC-;y86S21_bffJhSizWBx|K>JaQ8kW3CPzYy%cL~#QAWP=%2`@j@l?p;_x^{W zrGFO*jDmYmRnW^a{uQ_pk$E{PgI%uC^V|g-0JYx7Syq%BoSe(p5hB6DAfhldrH2kK z-i{;KYQVn<{CeB@x{Bx8eo>p@ajh-xzLo+m_Lk=6B~C7`6;C&}KbUagpU^~pw~>+f zE@QU&kqM7K2%~}=m~}fnu7bbu-}Ze23bT;OMvJ|Bb{Ppa0_{vYzzNOpSg_(a|DBiW z<+We`{gcySle?s*rltiz(`C)g%{{>99w1XqiXFE`u~cn^e$6Eh+ zOoI0X?WREd(`xJp4%^9?2%qEK={#^3^w*6_Y-J@}TDa(~5C3jXLx@dcKI$uLm z1x`F)_FS7kl?OXVQ_GVgIjRi0JX#K$7Xo<1UhuKoS5;zAqM1BMB}i%Gt`ETkt&+I+ zBlb%-XwoGZjYaPI>wu4zP4scl6xfFMFi z9Mu7G)K>oMZg(`a4BQ=oH2jlKu%uLB#*DLcRqZyXyD~M-=2EnE$5NsM~tJ$Pox@Bt(jofgvHlf$-jPU-|G_|83)$QPi#qSD`+!v%PJ&vAViy1xQ%n z0Aa&tzSZH5sMqR1u(`0H`eWLX=LDZH5jw&I83Y{;5}?iO=DkRiH8m}l|NObU+-SXt z0P;iZ@VOi|>27-7uE%_@+fS(W8tdt)<-(+QpU--*L0ZuWy(Ccs9Ar0bVRD9fHgO9S z`;1Bk65&yIR(N_SxVw<3uZVX8;ZXUaZmGnKxvg#o@|=;t9Gb`t)8HQ5m@IwQ?~+;( zElR{xL5I2;PegiI20;_GXgurZsd)A<+ipckmPl#ZP#Wo}PXOkdc1>{woZ% z6%}XHpv3KUMKM3W_HyJ#uz`HQ8`oI@&!_*0oU4UWIVs*j6q!Z#voi3fXKn5i)*qA6 zfG>XQt(fTJ6zkz+R_G%1n;^FfaP>zz0B4}=>gsC1j%%SRyPwmv-R(;i2>_}mAVT}? z`9JGPJRmAo-@p33D*2%bLB7d?s@R~;`a6uLPz+j5W!?>98G7w?*>TzeYS=Kg8Uq8v zdPRBOzkA>-yqxXr?SF`QdP2SkngUNitMkDKC{e)6Ub0*UJ^{8iKKi7W0eXjyu3-Mjt6$G567VSR-Fbnup$86B`zhtvrHiBpNR#Yxc} z$8lfcD2tx8THPiezH<~pD zInB~RpV7pGG}9K6*3*s}v8pj+bkJI5s(w|NOaM8WUecspnEb31Z39}4EwQpQOmWSf zS!DlK;&sC3^$Lbi&EePEKSmdbsu@v>EuLH7E@N6aQfU2gh_T-?(Ivu)FHa49a;W-r zQgRon=bHgU2fD}ws=~iZ{lmMD`7vYfVq#+c4r3Ydn*j-wt z?|a^J_Fj9fwfCV5T-bXKJZf&}eb?zl)jXMIzSlok$lPx=?J*Np4SW4NhDu4_ zY3cQvIUz!~%CxI(;pY0Z)g6w`qyDucU67S?pfPiOs&XMDmFLw*ukVD?Fu*3nH%%7Q4*gcDp&%^rv>zR53kx2#DM8r~G&gD#B7HX_bl5 zfM%?XQ}TjK+hZSZMf&^UVg>3x*|X3r{#!K5DRb-#a>274?-n+5Q+~M+&qR>^QC3)F zea6n$DvwzCUKLDc|6w%HQUp(eN|2l*H7Bh_tnPK`J!gwL7oPA_Z@#zP{E29Slx-pi zVU^#X^Kr%p+c=yh(zv|1*`8)Q4iLW8i`*rQ_cD0Mk~-hBAkgN_$-=qID!PUG{^}~x z_!nBIUl^JVWxW=PQjGXhw(NVPoRQehB35r+b)vIisd-#p*$huTF%$pqaAfjHE5dy6 z<0oy$k5n_pccWxHSV;P_{I1V=ij>k_omzdbT(|KN4e^}?3*!@IFY&$Cb3$8AK{qfO z8yh3S!3$_3m0s+S%`?#^QcGLr{23g^aT@No5?s}Y-B5y$@#Q-kJlK&G+$z2t zq? zK}KsOgNM6-vX6<`_JE(j(1_F-MZ6%}eBR@sTOBQJ4X5txeBR$~uawmq4@B4I-H0yJ zU!1relRB@48g?7EY=vPi=F=qa=YIuZsrm2u!Xz9(fyoZaHuoT{CanHHySuLpYl)hye?xzQEJ?O)_5XlZSD%!H>yz^zD&FuskApLWY_9o1lc^+D$1LiiKm7)PSm z$ad}}?9=)m+cRq%c&q>U1jc6G$DuKjrAwZ9uW!lYn5x9-B$WNBcrK#59uHm7iOtA< zS`j_g(OrD7pF?hi-?~!g)*POcg;-xQeepSy2SMm-D9!}}uAdsGAy8T7?-E(2 z-q(H9Kxye)WPlf>iO$r0Z2II*tgCjRBEgKR5Haogyf1ceC@qNqDvI9|{(>Cy?uycx z%a9$tHMQy`kM7#fnfYIj_!L>+se@Vh9BxS6CF=~uH@dDwsEB4w8G0Xdjo+O0h#8*# zC$tv{>=C@NjLj+EH9!4F_trAt=p_$S=|6fN$)%;ImrgF|ye?$c6Mc1=!ew@smzVd` zv1hY%Zf?^HBfj4=bJu46y|rrpg;?nPJLq}PdRzz7n=CH>?wqK}XE=0I49~kQ_#{JZ z-Xm7xYu7ioOy8C`WEu5={pB44z+&}ZVFLw&?B|_VTl<8rAb1K{GcYQRGZ1pIbWsGz z5!|9NzrUH8#eczef61JHSx*92)zCd`NmRkKEg9_|@MvPLu);+0;-*C5Fbkv~wri0UVa-clI{L zmYE*Ee0Uw$rNWXQJ5WoLJ|F4riFT`DM@5TS9D|doEycvkRr-QCpx057=eA;EI~iq& z>`)(es3$6$C0dHs`?u+q{ff>lzjd*3rNAwKytCNu`nX8!>U_G&jI78}1 ze4vQ4?8s%VUKShGjKcGka4x$|oXu=4r~ZNBSz8L0@h{9@xoCHtj{*T3eUIs>KhlnHg3l>&N-BEaDp9 za>eeUXvYdk+-QGVPEesqiyKSrz3`Q|aswwgEGWgl#+0Qp>xb^b(K4mQZjiJAuSFSg z$8G5`>jo89+wd7pW-pn0D4#P*HQM{w&vSlD^CgQ5Hrz>FmM5v!qb(*Vv4` zyZoC&CSSXXyNe&Eg0*{RM41(*K-Qll9j<%EGg?;(rt2iYJ`S#Mux&>kEDHm0!t1VgRXL_jH+Tp^hj*iO`4Sv zE;Su6=Vk~t2EpmI$Yyb#q1c~bVnZq@@dBY@;{DJvY&~(N7OI!^{W=d)5gEg;f1}kF z19#HlS6{3RrMtBwKlzu(TIRx$yYOUDAI(DyrkABCe;=j^3JTHzRu}NR{&!yVNVjJF zJ8XqJgXY}8(YCD}NsROMk<-Wjz5i06fsr^E8|PRPbMnypD>WZ+{wMz1TA4rRi}nf# zt94I&))yRTKujj44T8TyfH&CkQ4u=!&QGR%fv@FUrW%ij1_BH^zeB2B$up% z{G}u|JZg<%chX0fne98ny*t9MQWYC}qsb&`NN&X`sc1@;P|<55Qm`7oKK^GBO0VL| z^6&cNhznjuwSWIKbJC^6AbyFzzG%3jBmLNW{8)v`0PfjLpJS8}$7amA6dX?>I=AZ>Eqk9v}Rc4_?w^Wmb=s zDlXsRO5No*Go}mthVeb`0nPk@8wvND`_*~)ksHcSDdIlIL|A;$iIXo-F*17a#>z^Y zEjSjo+7-A_?!Y`XLsmwvZf#C-4oHvxj%F9G|IVq&`%He)SLe@MTO*P~JvG=r&_&QqH||;%OVY0#hT~JYkCeix=fkEANB=p8Vg)Re51jp$Eu_7& zpsb8tMGa;44eC#f2dox^-v4~X?gLOOLx!HU7XmAZYaw{|{@PmSo<}SmdlUXO!jP)i zu_Io5Wx=?pWpkSEW@ctK>^AT5>KGKK?uR6nFTtWaeVVxuSSu*x08*)cBB zq=q~d?%tl;n^k_;M2ds}r{jsc<) zjazE+d{!gb56SfH+p`|EoGpfWK^1ok0oe?kK-5D+LnkvfeEHV8Ic|INdY8u=>Ku=W z2?43Qeuoh2dID0|Svma9u;I05Gz2OLb@@0Nwa8+Gng@b1SoOBuXO#ARPV{G3?Ph0f zEEtUH|5+dKXTylV9dV{+&3FBna+S6lQg=xrMv`3IVm}yka+G{2Ee)T9ySVR@yqAi% zWvoN@_f*Ofz3PFS*zm2aY~TS+?jQg8Q}-N9`G&{vmGv9E&ku8?G;DlS;1e2r!~VFknNpOdN;fmR{k~Otc^J>9i3aPkK_y>Wf+g549EU)(6P$42~Ox&T+aXh zn)RT>c{wzqOOrVx+6t!q3@O+X2ldvuaRttru9e3s0w|aB&V-lFdob`YmW1F!9$)8J*W0q zPD6$o>L{6J-_|T)>X<{K4U+Q4^v5bBMVvjAg-izd**T14o0pLp^kI z(yhd2))^PG|%%BZ%nphTg_6X3btUNqCUh-2#%h1JXvyjMBh8$G=$@%i2E8+qKvX94L zT0NAQ_Ngj}7pIY|o61ffv+y;V=s_ivd?huL*uO$aQ726b#;@q@I`E1zuuex<>5Bc`@mfhDT7d+qrs_gjJBrDf&Nzf4&B49VKSspg;Eh zODga&uC=e>(Ars!PPAZGO|}Z=_ZoN&@~_QkbF6|xYTvV2%lb~Qh%z&CH^+b^T%9vj z@33T;!fDcB)E!B26a%}7UF50PVye&Erk}}%IaNP?vB>~&Hw2;l!qvq^6W+`7Pu(ysRk)*UZ;i(=B!~dMy6nb-x+(kVhq9A#+)n)^0^N}2N~zFb`H*)n=AA4 zD|?m>`v3dyzkVdpidE~NZ6@uS#qIQ8*c-+`4RB{6fkwk*H{@Rs87q*C_+80m(Au~X zXKoUT+sIKo@ukf4M(eJIKRa%{J#c?$x~48G$%gS?!%38#w^<*ut?6u|DC^o8XhYp! zPgvwicbzuB7nwxQCstUe={Gp8WV3^!+y^QFTC?Hf26}t7l0?`61d=r1?VfT`VC|wf zb^6a=acaOyvE0AMkMnQ_FZTTHZ@RF#dj>GGX5H`DN4cr(a@C2yHcyMdgN8ma$dQjA&bM zl7rzO?rQc*=W;)c%7-?g!|IJMMpe>`v0}iHc-8X=rwV}04>*n6(vUJjM_#nxYY)~l z{x`G;uC3KfhpjM92I(jZiAq*Zri|R2 zwNF;RazDiW<%CEi5DbVEG#?H+%>u>mq(XoW=Mh`n;P;WA{I}14 z5ZM!=nq~068l&AO+>VeZmehvLJWEMQnG_A2W$kF*^Hzru39PrSjE||#u@jf=MO~a& z?_@Z609E{=3GSlW1b}cYZMZ(JOFs<)EV>LX#Qk6I$PCX$kR(FV>UaH^?Kk`9r`zlpQduDo z$Z}MST`^6v{`|1bQ*EcB8RK8;;8hYyAEHdM@EF^u0ht?}=)Dv_bxg7w#JesyTkgV! zmT8tWak6JHF@*-W(D*)c4-hV4JC;WEEVX!x;{WHN`b=-?#FSzRXC)kM|8GxN>=w^i zo%2YjJgbtaqM1K>m19laBgsz|eA#Nobm*~xiFf1s7vQD5()UO)rB=-DhB?8P$mwe;a7WO~<=PDxkguS$w4aWyUEn3WL+vDs zd1OJBD(rEX#~Afdy2A>nu6g&RC5mqcOm@&5i!Z)Wv;i==8c6`ZnXY_#^)*Nf+&&bdKy z*ao$)C;osR_sukjG7_1YnVPAQ>IYnrbm9$Uo;dtFD6$lZ{eAt0QzWlFskW9#uu6FA zv2&Wu=%3hKkd7a7w()Yt=ZB>XuPJ;)66G*UEBK|LkWd-YHw?_Dw*71F)Z^ezla&+C z+iLPAI!PagEaL`N2yijWtIT>@1mV&sFMtI%r4?IaGmj6fH3okXctZ^!f;5T$fcv6l z1m!$(tkdL~66lED*9@gSdDDU17CK1GH2vR^)NPr*d`^%x{p?!1K<23ZkEL+Ho7j8S_bdQp3ig{bGbXo~ zQPD`~dIFdijz}1oU_6bZD|VM($O&se?CrYe!W#@-!E@<-y;2 zGb8SK9WlV!dW9^{rofoc8&1l8#|R7rgj-XUV)+dX4KKJ;e>@!ciIEXJ!my^u%05(} z7Wv+>yZ*mKQl~VvCb15TDsdWud)(%|O`Rcldi<|{6#GSzbD7G4$nxE^YUJv4;ieO8 z3iilxlsdq87)((JJVQnK>?}A0?cne+M&kv^hJrreiN>FtoZLO-jTm!URIL3Bh&E2a zZy$zN>R_7y!ET*a2+gOP>+2&j(NyP?+cRH=Kb4fcZ~UXT;VOTDV*x;C1{UvPD-?9e zCN+6NT+9eWqH>W3Q-TT7bL9Y<%luR8vHVbQ=LpGT7w)Ec-(tV6^~r!P4kJ3J)E?XE z&&&_sw*BX0v(VCfdU3LauLC;UA9k%z0`Gm2j$O!Ru&UN-ihK{gL;FX%ZEqTbq{Cq{! z1F`D@xGcyUP;&3i9Z5!UCX|s30jHJ|DZY0S>Ux$E15$BJr^@h@E$|q2Kj$sKz`#I zwfg#W{@t0qWF*4L`{Crc%pWem{FK2gQi8QhX?(ds{|+$3!2~vK{wPC8dR7vV3eTEV z1?NH@OGBRByGvgITCxG_$?AJ<6pSOQq9zE?g8K5PYroh|xb>w2^&c@8Dra6GAA$+^ zc)g0^riv{~a6pOr{ySTP9WT-j{8kr};@u<&bGuO_Bf4suBFazt_3rSg<|$44LRrc-4B9;ele z1=eTzY5)FtJ0Ynl*z!f1Dk`yIKAkMlOZmTk>DRezO$uq1>rUttjmy1e%C8vMaedQz zwiw|6)BuH%u5M|qHV2WkYWURFN_a#>Ne{Ib$6L_09>5zt70Og*E+$_ROG43HzIFIH z$&X^xNI$F#|M!%AfLMv4*s8PGDrleea7LdqF*8pI!0M_4Qu4Y0$*Y!&Mi~Wx^X#CB z2!WYIBAvr1XBuYo<0?Fyc2rtg+IgZ>(+)}FCtRLvojx2s;*OJqvq1P0?u#j38_tTU zo85_L3Fkl{dK}aY4XI~8n{?e#?+S?IoZ(18Vv9@u;NU!!zSA`)2C)+0P zD%C+Em=*EMgRbO;>E7Y6Rb{SZw+B3#V}mnTSIS(0+wm+?&I}g-mPZ}=UsBW4-_V*6 zOBK*332IXQxXf(`{s>GpD=TXbC9hTN8Pe7WST8i6(9pE)F}_F{ z0o$#Q@zur2eH*^?2u|u+N>})oeK>o*EpRA4Qbu7?;9}||Su_?GhfTh&?eIrITa5x7 z_N-_;_40+e_zH0yVRq(V)$Gn>QevXE$H!Cc@=3bw^i?;#GcPZ{SLc3RVf|=n`1`;1 zqKVZo0_LkW-fuJ&7x!|)1E^xtHbT+1Fj1_!^jdl{>qb+x#Mx~Hp_UB0<&C4G<4}Hn zeo;bvJf&g1ea<{&*wVXXWJlq-h~cB`@eTh%>tmW-g73PT`uc+^B#dGwKh)*m3CqWCbVFyy|{Vx#i7?fxP}it34hfv8_5UVo^%jh4r9 z3$)I~9w1X+ghfY-US{LU`tXnPqllpVeSfd*#y{YL+lhPjg(lYR)Tg%;N^dER_qX_5 zrgiu9z{H}I>F-Mi+B!)mGE!3d8$U|M`(GOQxa9PivoS7RB=j7G4&2<6$yfGYb#1Bz z2;hAIZBI%SPzcepv$IR7l58S1zY#d!Lx3)3Z=O49m(3y&`|iSN-@XYqO>IrURAwV7 zDXDH?%tikJJH|{NB?HH(PrXpl;|)+jPB+Eo`SQ%VIWS$G1jmyV> z*KhR|S^Rf?=y<{S0QanR``4f)25gpZ^#QuxOWr5oDv-`o~U+@G- z1`e-{?+ZWvH%YY^gfN=ca-faejG-c5VccJkeVNyNgRtjFztzg#Wc4mL5xnw0UlFsGy`&zU$b4dpDvWAISRT zG`_cuUx0SKbQa7MSNw-Bzjnzb5KQ3SY$&5jGV;2%->&?Lq}$ioA?VG0J%763cT>s| zPC!ACDq!5^uLHv5hQs!BRT0&lP+Wv;xu7~pfjt%TD@)5ma9++_Lm}b%y;Xz+_xFP# zSogQk#NLdA#}m*G85k);-!b)!oqU;5lna|yDmf`B7q;yxa^|2jIPsRKow+J?YfIOT zH^#-9yw4rcdmLo*Mpb~n-Xb<;|FEzfMaBy+LIqx;4N@VxBIODV{~27}ppH~6u7QRf z(yM)4TMkc$MIB60Lh(vcQnKvq?Chz9j-{dR`HG#O+wQj=LzmbQ9DS^de@DYT5B2Nq zy+OL$kkG6-#E8-C8yd1$T3l>7UyOJ>kD#(wR855Ap4$$~x$>i?hL?HxsMnmEmSxtQ zv{0_I*-BcB4RY1S9MiAC1v>FeA4YI58A0wMc#Vp5XULuO{?df0$;1J(cSxWXp& zH>`ZAokU6=azGi?tyZqJ#Z8WD8@(hC(ec~>QjBZ73pp9tR~AuGUll8>Vh9ycKJ{(J zoHWgbm9p_t-L6yKpE=mc?%w4VyFN2Jy8BRf`-a^l;PQhX2R&W7rYeaAjE*q^kYgt$ zF7BT@YDIG5^4OfKgc$|~<$V4e8xs{}Pz%aW%V`?NVKjr>4YZ5HXDtR_Z4F+qD6mKH+0Fm6 zfl&y1P4%2f9o)F|Nb-LUl8MhCCvGmwtq(~_W8lQx8ZCQS>2g{cc!Di<3D4edxAwxq$BR?i0Lhl-Rj@!BH zWIfsygo>e|p}4;OuYTC7uDtM6WfP?mM)8~P zsFXG2s?-Dk;?ZTrd*N&L(JDMQ5wPU(IT%nyU%YsMuS(MOIDiND**Zr33*Qs1 z57{pc&b^(rwWp)hiyhKQ${d=jlh2wm$mQCP)eDUsRdcYf ztgcogMTzfl>imB0HSe7HX$*d9RBw?7Qwts&f&!Ae( zp^n>0U?YWskS9Q22P5j&(>G|@HmdlL*oQz%GISHn%$3QLfN-)o=v_MGLDmfC0fw(; zKt5WB!eTvg>yy2q#!@cQF#i{3iR^9I^M8+xCCK6!I0jf`bouZn0TVBPmKfw&t{SpT zKt)A0kLaS?Jn_^9#f`vUDDd5$)dL>oq5co-SVB~HvaQqdg(ZM-0NA9ctb8XuD{FXp zdHHi7jSA_+yHtXL@r_#`wojn&&{CI~c0V#`WUv8pG-7;%L?9YXE}onYZY!lXNz-Bx zfXt-Jad!jybEA-;@VSzDU=hs4pn!s|5gfuh_cN7?dXI$TEmBqgD1{Bia17LFl}Xdm z(slqKzCf030iJk?j{|A;Nu*g}M|o`k)YVB%PF|~lTN8|pvu&!h84~&!woW{yE2r3n zhmn5@W-7NY^f4vK-ND9=rMmOHL?$l)I`DWbzs#T^J_9sraTi8tS2Ov4V~}$OsK<_t z3McZ>*IB%HhI0qHe;g_#7!(u~9}f8a-t_%rTzSwF7RJCJM??vfUMWQL6NtXK402SG zVcjLv#Tgk?T1ggNHhgP`!h8P>55E$o#s{Dw|Ki1(=jm=U2Ol3_fK7|zTZHkICkQ43 zlh95s!oVO!A|0oWmi8z)f|Y~A{PykJGhkFsE3{srjwglBhp@2e(OUIqC>TlA%)r^+ zU3M14P$dzNAaIY8L3*6aFjVeRs*PSU439uL%jp3W@*X@op&hSQJVW#ne{X+(>EeMq zKkW6xdr>y4_k~=Yzd5eRCxXPmWr+%LX?7? zL+eYAG~iDQ$;b zJ{dM&FlyclE3_`i&i3b`jA}_qNg2<{$tlTYIV>qDc>>>+x`&8B?Uj}W$-6Nt-q`qf zwHGAP^7l8CKhi3iL+g7G01IIbetvBt>9`&<%(TMx6-OOtAk&NMiV6FTEUx|n^X~iG zox%T9rmF-Y}j44{`SRBFN~0I06AcgF@xM62fzD(G;Fn$_vOeMtiLdr4sxT z$HsKk(tCnTJ;eWc0pOw^*^g_apQ>qelC0s3{irk&iyOp4WCJpJ^z*Bo5qoGS?XAEz z6U@TpwKX2srnw2|B2f z`T-8{&4BtqZ9P6?p8w|dAFk504>;QlWZvOC(qH9+eR z+Y(7#ekG}4(Usc=KN85fE?`%nUtC%WT^#bnBHo<&vgQU9n;#DgOR-9=P6YDYZvwfZ zy?5g2p~=!=kjt(!2;OFcR%pD6VSGz~TU=kCvVn~D@PTPWq@*9@f5tMr7j;@y%!CcB zbh(mjWICXi`gvmptn0i~JsuNpO<5J><>gC7U<;?-jaV^kDBWv(b^G^Areh>-LyoIh zownh_FndVaw?dkhgtPAnOx$ZMd}^CFyp#bcmq&+(DL}e;j~2bidIEtiL42Tk5DZnT zyl*BsOg6>}f@*ck>5>g}We*=VxCk2He0;D0tnxkJ&o{4?i_~G?xfsky7*sm7wO9SiL@vcxgapwB4Mt6}@yoC1Af-UO z+Z`(yC`GE;N{a|N)kH^Ng|k7~RWyWTbdOi*8H5Apy?GxuZEfv3O7tEdHJK>NlL??= zWS~I8c)(xNvxJh+r%^g;rH@UE_mMd@mqoQ~;ub*r`ve3kznul`v?>j+{(+y5j^(mc zLvuz#cs#><#-aoUIo$K6Tt1))%0fd!$@nvsrG8@L<3l3~Pa4QCDq|tJEF|ssWqkr^ z#32mf&)V>jP*XpG9qlRvgykp%dYo*L=~o9C0?y<3Qmg_T!aTntA&Qd}{wX?|nA`SR|Une9C{clUamFEc6t z@?vD;(XnZ#TmAg#o8}(Hd(b~fA7b*kxjHq#xgzyuElewf%BT<$Y#AJ677h-@?zggc zBQRXnM@$~1;!8#(8+ux2_L)l(5)!Jxm)f@HS1H%6wOw6XyXPPj$sqTsF$z*s&F*CR zY+bUEug3|L^3T|F4F@8a2Ik-%JgoX?TDNvy>jD@~49q#nRivbXWhZM#t)zl5@h@_tuFcAlOvM>o1b$ z{|TsQp(QnEAy+$lq=l8e;-<;*0VoQ}4(6)Q+kq^_&`TGfjy`QE*F2ApkI zuR8g6hYB*dowDJ%$P?&A5Q(agx_!^?8`on728Ne!189rRAp7g?&3s{=H0MT%3n33t z1?(~h;T;b|;hv?@Z5$7_?U8%hpVPC{B|o4C*SX?QCT-~avBfM9f8)M*$F7F z49uGe(+DXS>K*8h!7@UzX3vj}R>%^L8YPp5OU`Lr)%l0z3~vfIoXQaW>=n5T(;Gb! zX=IyHRE_RK8k=Kri~76AyK_R+*@I7S4@WFIHt-?=_)B@63nPryAO<;8;4hegQ=su( z%fUW5d+tABjsn!n)(#TWD`m`ahV^KasI6>5@js8c|G4_r`xo|6;l7<(oE9gW6Cyx8 zQ$)DfVhV9_BMBQGAnN7Z9b`#sCB!w$CejRA3{**S)C%OMek`e$nFEH(Ft62R*XS#* zHoq|bI{T&1%bi{Y1W9<~V*|;Lr+6u&#zZs(#prM0uUzb3=xgT)^j*+MWhpPp2t7Uh)z@LuRWvwSVv#8N;@G1= z3@Ixp+yJtu%j?=+*RKBY zw3dt4RwZdTd=T7ER8_^Hz!B(H|3SaS`@9ON&s23t>Lh9<10bI^>6iPt?W5&#%YiRs9!8*{nEbFHq4hut1NJ_dZ2zQlop`f5#zXNOu z^>hdPl=-i-JMZEds{DfY^YJRD24JGwkw~WUb7k$|a)w3F;$%vQMiyQkJYr?7D!zHa zrvxwT#nRt@3up+WC%_3zt<3kDb04vVqrx$g+B;=FPXnpF2CBstcvbB2`Mc{u90U8% zZJ4EgigKfbhOn8f+vx&GP7^t~E}R z@Ia7F{t0^|ah5~#;t*UL;1NjkS-Ug`@`U%_nO6_)i}`+|9;kNXvRRy$U@$m2MIeyy zn2dwbISGAWlzud?-_K)=gph6xNMA! z1$+ms?I%3IFmFzldqoOA-%NvT`y1kB4IVCTVqb1)wjmTd8OpCP#VPp9N3Fhrw6!h* zK=l-|wt2iEnpeX3YZ)|^y59oPK|gEXWT~E#@|ehxNWVVaof|e|QsgiiDN!$SL0A-t zz|%W8RQWj-r_83_p| z63OTk$bU$0U9o?DCn(tGk@+>#Y0fy|3(4#0$WZJ zgb%dCqoW%^pn85_;Y+KAS?I9R(+44)Bl%29X;fa?!lhR$dzdBH>G&wO0YR@6 z>(;(bq7;q>@Bnm)ZnLM0TR!UkwXqPdBsLH!i39`$;^LN!92~d;RCl<5Hs+IT4V*bG zMpL2S#|9`a64m}%0Mh!MTelNS*lg;Da6k|EmjdO8W$IrYgqXl z*?lSXQ^;Xa%Ajamhlq&C5i8sF%M6Jk^AnYFD4(+T_w9#)kD;INf#OcoyFOsZ@)@~Q zQczHkDg=!qZf@>C0=9@$-3CXCO89#E<69>zbaavoCo+(p0f$a*omj07rqXkXkY^1) ze`5RVEOE}3xke>JnHj#0t}@(J_t`Ug@97P)Xn^Uoz0UkpNK~R4aJtrywzs?hOzjr& z3*k6Q5|U-CJ)HYM8QvA`O`|pbhvZwj5G@N~1VluEfIF70CqK>1!17sWRp`&$Cn3oP zuAGJ{JmA<<1l}nfA{+Lea@SVSLorbaf?{Y<$YONva4Lb{m5qjVhZn3?b=_TE5395} zs^@H5^B$y71G8lX!!j0%r;a+0J@Peq7I#~LkuU@V zHh+uJ1jN9s8S|yOI#4dG!)LRQJOl;!cN~O0oZ>cU3w!&423sb{!UDAzP}4Y|D5Lw7 zl%8{IM{^DVc{JDr)YKm-2?^trThvdz;D5>dX82fDH5MoXkdAN6c_|~n-T$;kvrOwf z?ChVLgM)+pQ2Kp;g>fMKm=w55UZp0eyMFqBxc3Ea+5+w7fuM7-H7Gtc%0aUGNl4k|LMqHytHKh&+6Za?}Kh(~f=1qDAk1_r4jj4=TC zf8Z6T5dR$$6SE)ksvj8hJc6o@wyu8Ypd&uR5xJw-a5)AzLW01V?2&$PW%Qt@fhLw|3<<_lR@xX)82no$f%&QNxBif+Ns4UH?i>T0VtYPpozM`N68^ zo+G%O;-KP9A^vR^)qsB~8q%uYjO7>BQ?)(~A9MGQp}&9s2H6NHE_(t&&kVeL!)?V! zj~-d(=H+|4W!h?tX=Ds61tm+;DxWW*aty6we-q7(56#cz6<`D%vO@}AmE?-OmXV|s5uc*?MvD4noHWV?SP zyLJ0E9(aGQKY4%5@{-k;WmV8%g8JGp3x3-&oUb0DNdI=6oriZ7=7F;?n%r;>2ko#c zTkv-aU=IRPQWxs+VY@L*1i#OPb0%mdp6L0{r{4&Oh&1c^`ufIp{)vU_10(yttFk?Js!iGZ(O+a zE6dB;5N+o16iRWEN{cb^D7OLG_NgMWL8AbE{YNNaiPy#PhMgD~-{oK!{s~)nu2m@B zy*iG^kB8Ob;`~3=A{V}Qa1fVvjQsq5`frQs!p|p49ZSgo^>SG{fHpe=`M_5wdh;cZ zg&%7Se%uoaPI5;8M%mFh8nu-`5Eg^{*9Ep#>O_Y3MGqFY6JtQjxNQROM|(2LzAh>Q49mq&Tae|_^9M}o+r>Mvh*Ub91o%pY|%was{?XUTJdC=iR3 zDzwUfJ{jBfx#&kAY<|{kbU~(;BE~mX8-{wX)l%m!l!y2TPUwx11Nr>LI^1L#NnCc1 z)IuSuu={6~D?CaI6fV_|tCE!t_MjImi6M z!pZN0NlaSVkXbUTXLqF030ldN-X~Vu%v2|jkBxOeeS;H$qDo={nIg;5(h{=^lE}u- z+O5AZHRJI&_XAUx3HLaT0cVhVUP1JG{DwD;2zd9y=62&)WG*oK9!BVNAhdFN3j^^P zR8hXy*0|&Nw83pVt~^liUcZo#KuJ$d?pXy^-}Y-d?A)dUP^V+`0LVu$s2LUzp>=Y` zGsuZA4#+-$nl>Ul9C=RnoOgb@$SEuTXAnXztx68j6lp+A1yZZEz50{=-4=g0<%=K0ZE;Lrd<4+h@*dQ3QJsS%}c4V zn^^crzg`8uhz7;K`qwbv-EC;wE&K*<6|vsxK{zXobGF3Mq(=&(!o$yCKe~*)h2QuD zF!;?sr{_;ZQTE?#zJzk5UjGqi`j8|rUv>CUxGDpqMV5#ix z-Mg5v(8|!G0qTbo_si4xFoHUN6lvdOLne%4AB9H~wC;4u|>{=H{MBl+~ z^9aE71?zsx9wN5Kd&q@mDldz;tobfMt)O+B4G(G?<;~#nSFSv zrXb8Kttt;*^9ex8l%-Fifd;_Egv*DI$C;7z<|+ubNOQwQK7jU49S|Y=&AHp55)*~0 zRbUdRjwSreTHNI4@q~opf>3|1gWfp0IUZO{0aZ>a!UDmdG)_Vojw}Z$TkNuS-;(hmlQ@h!obWN z04R@UvULB%-45j82=#mn%!$`KGf8!}~NcZ+k1N;J&b zHiGd09M)GS7GS4|hzSpGlwS{+kZcH)eu093Nc9DL3dX#YqzL0MUKr&6LGfojz7hp| zB7jC;XUHk`i^>v{ZOmET+@^pGcA%(LXND^jndOMgILQ+M*I|-9HuU}k^ykYkJw&(v zG+6%1`H`L{C=-0r>xp2KYL{!BfrwcMw{87fgG9qn)?E46@SFA(NPjLG*Uz+t3ceR8 zB)eM8HJ-Fwa%tEI{D7h-$uHzn39z9l9UYQ*?;m%%QCT#i{c;A!!Hx`F;@No3 zKM<~b?V&2TkjYb}D}B!r9@%Bumj$t3JZtzB2P-RANf0@E?rn9~VKTiEwE}in{d@VY z$4(9UArO5KgM%!G_j(-SwaQMOnh$6|n^WN$>|-(8+uH=SN6a0*iokJNFOOc1Xz0RZi~&HWG4#fw%8f7ZD7cps zpnffa9+MibI4vLy2_>P&#Tm)}FF7{2Cs|zi@@bnEie8^BaX|u|Hv;*)JHq_y?Ar|Z z$R6_woJ{-&qQAa@a6n%}tNalPwM&?9)ErZsLHg*)&S_XXACirB7_jv3NAL5B)&$WY92LlQa=|+tfKcI5J9K)=f5k?K{7~99S zE4dMaM`U`SHAH}r^57jouKUN+o$0E)p>G~X-Ahp`ZlW9@lY-HEAKX{pcR*JBN+eAP ziSqsZ#xi4=frq;Ub1YP|03GYsQot%r!$S_Sy${Ppj#8*Tk0Ih~LAe zZO9=UJWxp2BIQzTGd5J+Jxyd~{)0gsG&n$Kx@=8kN}v|bz$hZF+d#?mRx6ssm{tHD zzJyYnmjXcpOzN|t_0LP$)qXeEbq0;j=N>S-Xbv)&E%8${ggCHl*PCUPhFc>!om{FU zXfRo=nu7<}aUem7G_d8quXjLPeE92)I!cNX~jtb@#QeN*DSPnC}r%MU6m?D2DiqG2#6M z<}l4VLk%ehu;f4^gRrCZE0`m}zgPp`dJQ>n8_W=&*1_y@!YL;c!$40bO(Jc8VWh6@ zGWNZzt2}Ri=#KDkP`fe0JCmz`&&6~rBt9;#9hSyp07CwJ*8=eopLzuf!vHtH))cTd zJw~-P!^Lr+Vx|UdR269zg@pYR7|_5=aEL4!LLa9BM1gk*1PJZX$i`We;iGFlS@~h4 zQ+WdL=>!4+)Cga#xmJ4{p(Hj`UEh0pPVHcv0#0sVY}H*75^d-pikZ*}Eb9aF2()H8 zM!`YJNU%u#!>dKnvm!{FS6ts%^b zz^IFxD@eYVuWNO0cs9@KTJwZ87H9SzVg}{JHa?HH4>4)8;$;IV3)WlC@{3Lg_ZK0B zPul#wDu3ODk^YFM=Do3YNso=L>8jcgNzN)5g=}esf1r2z9%}R#p`$a6b*U{;i%^$tB)Kw<_pDlVrY2T9zJ}sZBO$O2@f9badvS_-586MOe;kk6!s7l@7{xIW9w@4l! zJ%;04;Rlti>ou^yLl!!P5UEfQBqSv0rgA-5cT(0o>H(npNLGWd^#amzG}`F1d}ohSM>!n-IbYS=UKUhZCxrFlIhfAaN4AQuQ@UtP?AHLHR|j&y8q#O z30|DT*fOO@ta&X%*kRiRgw#dW0+6`n9X>y>8?2LSLs-z;88kei&#d;C+OwDY!9zDe zN;8JEor>zffM~0maX}!(uiDW|*kVQ`c4t(h+Xg#`7dpmYedY=+QLn*kgZBEq?uo}0UeK5CkSH+?`UxzPK|0z9-; zu&_BFkvkvrkq~Y}*6XW$7mD3!ocH1bEWs_EVd7i;M?GubCB5djUOUlt#_(@UysVlF zrwsD_v{DOoITc^La)Ov*WGL1}Z+2~rG^BEQ%O@xc-ZXQj_KguV5k__>fskQ0TIW;V$=*Ki<(hu( zxm39~E+wE#p4>mX*y^`Rh>_Cz>eYGLAPfViN=OtvWz8l*1D1NM7i8b~JS!GD_-&NW zi68!?dInMv-^P(AOi{878Cl!*n?8@?bfl}$v2ANAgb;;@4JQj&@8%*eCpT)dbfJ0sjF$KMvdQ6(cI9@Zsag?GPMky$#38yUJc~%2z&8MuY!r*~ zT}XdSOibwK<{!$Fi-Y%g3bGEJ-S=oZqS8SGr31eyDefsL^s%d&QX5dPJ9tUl>U)XHXB);{i@;id^&_34mBp7**e)Z&& zw^2DLCHhlA-|g6r<`3SxPM$&8&lOeR`1okD(o{`Tq#Tj28(WV083P26)#R-vzQLNb2g-CXrA|%9Vd; zsp505b_`MwYd>qSeKK1HQ6JM}C0Z^I?}Z`UJenmEZCMe=~ zbOQH7x9QdL^XHnH8e)#n56hTN?kE1rlEKjn4+6r44?z>?#-QsdVwWrc?5hN~_?DSq zbUYT=3c6(A09{eGDqxb-lX_6mqM1KZ>uUom3XvJCJI-# zphqrOQZ|zgnrzG}UJ$rJE7@aizDV?WQK%VQC;Vd`_4gxA^KnLnO+toy{F`{oOIc8~ z@>T6F0flVuziBy8NAsS>Qrvk*qy|HH(h<9_ZaVBo>ifc8|8nMIl&~J9&9nNU6IuZe zlz9Ft1qG8;zie$$%p5eCl-OmD*T0f65|5Eun|0`i?c<)3FBNr6QrM4y06TPiCSm-`36ws>YRyNKpqC=`qWCw=lG1!Oqf+ z(|U{KRQ&Gyj~Nb!)1}a!?rbEjP|$vzHdq93D9_Ty=5G8NogyEn=kB@|jQg+9sIy;l zm-43J^zSl3AMZn$>(v+%tC~;Mu3Dc{(O3vnsSJgDE77aSuUWH2oWv2UnDZ6im!eOf zKJ~iHwqW+#yWjnY~Odt!itmCFh%Rq9x>&dwAu&2~m(_K3?NrO<05eI0=)wBiyT9#{DJ>+k*BJ+? zdgCs_XNW^i$mT!X=vln9lSH#4Ksp!@aBm>MD2?Wg1!~)c#l*A;9(>Cbhfr3J%dT8| z+1#p>p^X2LhO52r<^15$p5*N~-{24s5RgvuejHbVCtcr}c((pSIvgN8=Qu(qttqho zVrB|!ZpKujdtcRy;Il-IDS(%Jw-SQxMV$W9K^os09;;%H>l9P_wee2 zmn=;F;vBduwBWy^H`gSlsfsMr(9_d%8o%F`wY{!ig_NDHoyc+YjJA>A<7owG&j1y) z0nC>23vnLYz#}LH2Y?S;SSAZp6ObOCjWkz7WeSGN_@vz7VLhLZK-C zpOoOD8*sG&oqq+c5RnB-kc-?F^*A<`ikLs}G4yMmGFg8^$jhuUShz(Bt0WzY5)PW0#mLDnn|sM(-8@8X^T0u zSljj{Tj>k1xM_sYay=)C{IR0Vxq-J>`#TD;NVKkK`qdUA_tPf34OU$2c<9U-M3F8nfjHO z_;4Hf_;_wECd>aNP!dvfWBZfbik)Bjc7z?6S$FoQWJV6bKT|@#IZi|Lx`A3I0;~V* z@}n0=od;sd-q_7L#WDFi642ZTOXB`@W)Tr>X6 z`9}GSC(cezU0)^yHz#gC6p*SK&&Qk^WfnX-XyuAtssulBu8qmc{%OSuVM54u9FQ3Rh)QIabo0!>Et_ zPW3oGrH9rAA5miXV&UqU4#Y;eo4Rqdn}pcw4UVR+nelXT-P21XPHF0=hUsRqy0Hwq zR!H3{BRffU-BCv{IK<{8&|P8%HBOZo-7L@B(1vo5_>ItTiRi`hMs7(sm2)jFQSas6 zH&YPd`#hexL|1P%V?@npi7j`Q+S7LUZ2Hukb)vNi&2Gs()# LdQa6Z_t^gc0>PFJ From 49a6c56a7ccc1d3648dfeb8b3b6f3070509fea55 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 22:19:54 +0100 Subject: [PATCH 304/580] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5efd1683..c7e13cf55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### [1.7.3] 2018-11-01 + + * Fixed handling of replace/conflict rules. This may affect dependency resolution in some edge cases. + * Fixed Bitbucket API support and migrated all calls to API v2 as v1 is deprecated + * Fixed support for lib-openssl 1.1.1 having only lowercase algorithm names + * Fixed escaping of URLs in Perforce and Svn drivers + * Fixed `show` command not respecting `--path` when a single package name was given + * Fixed regression in 1.7.2's handling of metapackages + ### [1.7.2] 2018-08-16 * Fixed reporting of authentication/rate limiting issues for GitHub API access @@ -687,6 +696,7 @@ * Initial release +[1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3 [1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1 [1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0 From 856df56bdd05f4ce2a5f437796ab6a1a222c5f3a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 22:20:40 +0100 Subject: [PATCH 305/580] Revert #7755 --- src/Composer/Command/DiagnoseCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 10340a56b..3c4c3bb32 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -122,8 +122,8 @@ EOT if (!is_array($rate)) { $this->outputResult($rate); } elseif (10 > $rate['remaining']) { - $io->writeError('WARNING'); - $io->writeError(sprintf( + $io->write('WARNING'); + $io->write(sprintf( 'Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL From 008475dee7a589f1f94f42d434ca29300fe040bf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 6 Nov 2018 14:55:46 +0100 Subject: [PATCH 306/580] Use lowercase sha in docs --- doc/faqs/how-to-install-composer-programmatically.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faqs/how-to-install-composer-programmatically.md b/doc/faqs/how-to-install-composer-programmatically.md index 02ca21d2b..8a35e34d7 100644 --- a/doc/faqs/how-to-install-composer-programmatically.md +++ b/doc/faqs/how-to-install-composer-programmatically.md @@ -11,7 +11,7 @@ An alternative is to use this script which only works with UNIX utilities: EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" -ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")" +ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] then From 38a34159ef6e2faf49d55fde3b4f571e206ff987 Mon Sep 17 00:00:00 2001 From: Mathias Brodala Date: Mon, 12 Nov 2018 11:32:19 +0100 Subject: [PATCH 307/580] Dispatch "post-package-update" event after writing lock (#7766) Fixes #7765 --- src/Composer/Installer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index a729710c0..2ace251af 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -610,14 +610,14 @@ class Installer } } + if ($this->executeOperations || $this->writeLock) { + $localRepo->write(); + } + $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); if (defined($event) && $this->runScripts) { $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } - - if ($this->executeOperations || $this->writeLock) { - $localRepo->write(); - } } if ($this->executeOperations) { From 2a13bb2649151932407aae1a37a356179b1f5199 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Mon, 12 Nov 2018 12:23:32 -0200 Subject: [PATCH 308/580] Fixes from PHPStan (#7687) * fix docblocks * remove redundant conditional * fix wrong variable name * fix wrong namespaces * add missing private members * remove unused/redundant arguments * move testcase class * exclude TestCase.php * Tweak RuleWatchGraph type hints * Tweak doc comment --- .travis.yml | 2 +- src/Composer/Command/BaseCommand.php | 2 +- src/Composer/Command/ShowCommand.php | 54 +++++++++---------- .../DependencyResolver/RuleSetGenerator.php | 4 +- .../DependencyResolver/RuleWatchGraph.php | 6 +-- src/Composer/Downloader/GitDownloader.php | 6 +-- .../Downloader/PearPackageExtractor.php | 6 +-- .../EventDispatcher/EventDispatcher.php | 2 +- .../Package/Archiver/GitExcludeFilter.php | 2 +- .../Package/Archiver/HgExcludeFilter.php | 2 +- src/Composer/Package/Archiver/ZipArchiver.php | 2 +- .../Repository/ArtifactRepository.php | 2 +- .../Repository/Pear/BaseChannelReader.php | 8 +-- .../Repository/Pear/ChannelReader.php | 6 +-- .../Repository/Pear/ChannelRest10Reader.php | 18 +++---- .../Repository/Pear/ChannelRest11Reader.php | 10 ++-- .../Pear/PackageDependencyParser.php | 16 +++--- src/Composer/Util/Filesystem.php | 4 +- src/Composer/Util/NoProxyPattern.php | 2 +- src/Composer/Util/Perforce.php | 6 +-- src/Composer/Util/StreamContextFactory.php | 2 +- src/Composer/Util/TlsHelper.php | 2 +- tests/Composer/Test/AllFunctionalTest.php | 2 +- tests/Composer/Test/ApplicationTest.php | 2 +- .../Test/Autoload/AutoloadGeneratorTest.php | 2 +- .../Test/Autoload/ClassMapGeneratorTest.php | 2 +- tests/Composer/Test/CacheTest.php | 2 +- .../Composer/Test/Command/InitCommandTest.php | 2 +- .../Test/Command/RunScriptCommandTest.php | 2 +- tests/Composer/Test/ComposerTest.php | 2 +- .../Test/Config/JsonConfigSourceTest.php | 4 +- tests/Composer/Test/ConfigTest.php | 2 +- tests/Composer/Test/DefaultConfigTest.php | 2 +- .../DependencyResolver/DefaultPolicyTest.php | 2 +- .../Test/DependencyResolver/PoolTest.php | 2 +- .../Test/DependencyResolver/RequestTest.php | 2 +- .../Test/DependencyResolver/RuleSetTest.php | 2 +- .../Test/DependencyResolver/RuleTest.php | 2 +- .../Test/DependencyResolver/SolverTest.php | 5 +- .../Test/Downloader/FileDownloaderTest.php | 2 +- .../Test/Downloader/FossilDownloaderTest.php | 2 +- .../Test/Downloader/GitDownloaderTest.php | 2 +- .../Test/Downloader/HgDownloaderTest.php | 2 +- .../Downloader/PearPackageExtractorTest.php | 2 +- .../Downloader/PerforceDownloaderTest.php | 2 +- .../Test/Downloader/XzDownloaderTest.php | 2 +- .../Test/Downloader/ZipDownloaderTest.php | 8 +-- .../EventDispatcher/EventDispatcherTest.php | 2 +- tests/Composer/Test/IO/ConsoleIOTest.php | 2 +- tests/Composer/Test/IO/NullIOTest.php | 2 +- .../Test/Installer/LibraryInstallerTest.php | 2 +- tests/Composer/Test/InstallerTest.php | 2 +- tests/Composer/Test/Mock/FactoryMock.php | 2 +- .../Archiver/ArchivableFilesFinderTest.php | 2 +- .../Test/Package/Archiver/ArchiverTest.php | 2 +- .../Test/Package/CompletePackageTest.php | 2 +- .../Test/Package/RootAliasPackageTest.php | 2 +- .../Test/Plugin/PluginInstallerTest.php | 4 +- .../StrictConfirmationQuestionTest.php | 2 +- .../Test/Repository/ArrayRepositoryTest.php | 2 +- .../Repository/ArtifactRepositoryTest.php | 8 +-- .../Repository/ComposerRepositoryTest.php | 2 +- .../Repository/CompositeRepositoryTest.php | 2 +- .../Repository/FilesystemRepositoryTest.php | 2 +- .../Test/Repository/PathRepositoryTest.php | 14 ++--- .../Repository/Pear/ChannelReaderTest.php | 2 +- .../Pear/ChannelRest10ReaderTest.php | 4 +- .../Pear/ChannelRest11ReaderTest.php | 4 +- .../Pear/PackageDependencyParserTest.php | 2 +- .../Test/Repository/PearRepositoryTest.php | 2 +- .../Test/Repository/RepositoryFactoryTest.php | 2 +- .../Test/Repository/RepositoryManagerTest.php | 2 +- .../Test/Repository/Vcs/FossilDriverTest.php | 2 +- .../Repository/Vcs/GitBitbucketDriverTest.php | 2 +- .../Test/Repository/Vcs/GitHubDriverTest.php | 2 +- .../Test/Repository/Vcs/GitLabDriverTest.php | 4 +- .../Test/Repository/Vcs/HgDriverTest.php | 2 +- .../Repository/Vcs/PerforceDriverTest.php | 2 +- .../Test/Repository/Vcs/SvnDriverTest.php | 2 +- .../Test/Repository/VcsRepositoryTest.php | 2 +- tests/Composer/{ => Test}/TestCase.php | 2 +- .../Test/Util/ConfigValidatorTest.php | 2 +- tests/Composer/Test/Util/ErrorHandlerTest.php | 2 +- tests/Composer/Test/Util/FilesystemTest.php | 2 +- .../Test/Util/ProcessExecutorTest.php | 2 +- tests/bootstrap.php | 2 +- 86 files changed, 163 insertions(+), 166 deletions(-) rename tests/Composer/{ => Test}/TestCase.php (99%) diff --git a/.travis.yml b/.travis.yml index 064782fa2..c1511b6c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ before_script: script: # run test suite directories in parallel using GNU parallel - - ls -d tests/Composer/Test/* | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' + - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' before_deploy: - php -d phar.readonly=0 bin/compile diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index d6b014d7d..888b2a7f2 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -33,7 +33,7 @@ use Symfony\Component\Console\Command\Command; abstract class BaseCommand extends Command { /** - * @var Composer + * @var Composer|null */ private $composer; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index e73d6ead9..cc0fe0154 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -737,7 +737,7 @@ EOT /** * Display the tree * - * @param $arrayTree + * @param array $arrayTree */ protected function displayPackageTree(array $arrayTree) { @@ -782,7 +782,7 @@ EOT /** * Generate the package tree * - * @param PackageInterface|string $package + * @param PackageInterface $package * @param RepositoryInterface $installedRepo * @param RepositoryInterface $distantRepos * @return array @@ -792,38 +792,36 @@ EOT RepositoryInterface $installedRepo, RepositoryInterface $distantRepos ) { - if (is_object($package)) { - $requires = $package->getRequires(); - ksort($requires); - $children = array(); - foreach ($requires as $requireName => $require) { - $packagesInTree = array($package->getName(), $requireName); + $requires = $package->getRequires(); + ksort($requires); + $children = array(); + foreach ($requires as $requireName => $require) { + $packagesInTree = array($package->getName(), $requireName); - $treeChildDesc = array( - 'name' => $requireName, - 'version' => $require->getPrettyConstraint(), - ); - - $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree); - - if ($deepChildren) { - $treeChildDesc['requires'] = $deepChildren; - } - - $children[] = $treeChildDesc; - } - $tree = array( - 'name' => $package->getPrettyName(), - 'version' => $package->getPrettyVersion(), - 'description' => $package->getDescription(), + $treeChildDesc = array( + 'name' => $requireName, + 'version' => $require->getPrettyConstraint(), ); - if ($children) { - $tree['requires'] = $children; + $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree); + + if ($deepChildren) { + $treeChildDesc['requires'] = $deepChildren; } - return $tree; + $children[] = $treeChildDesc; } + $tree = array( + 'name' => $package->getPrettyName(), + 'version' => $package->getPrettyVersion(), + 'description' => $package->getDescription(), + ); + + if ($children) { + $tree['requires'] = $children; + } + + return $tree; } /** diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 60617ba43..6117c1d95 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -50,7 +50,7 @@ class RuleSetGenerator * reason for generating this rule * @param mixed $reasonData Any data, e.g. the requirement name, * that goes with the reason - * @return Rule The generated rule or null if tautological + * @return Rule|null The generated rule or null if tautological */ protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) { @@ -117,7 +117,7 @@ class RuleSetGenerator * reason for generating this rule * @param mixed $reasonData Any data, e.g. the package name, that * goes with the reason - * @return Rule The generated rule + * @return Rule|null The generated rule */ protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) { diff --git a/src/Composer/DependencyResolver/RuleWatchGraph.php b/src/Composer/DependencyResolver/RuleWatchGraph.php index a9f7414b2..31a22414d 100644 --- a/src/Composer/DependencyResolver/RuleWatchGraph.php +++ b/src/Composer/DependencyResolver/RuleWatchGraph.php @@ -127,9 +127,9 @@ class RuleWatchGraph * * The rule node's watched literals are updated accordingly. * - * @param $fromLiteral mixed A literal the node used to watch - * @param $toLiteral mixed A literal the node should watch now - * @param $node mixed The rule node to be moved + * @param int $fromLiteral A literal the node used to watch + * @param int $toLiteral A literal the node should watch now + * @param RuleWatchNode $node The rule node to be moved */ protected function moveWatch($fromLiteral, $toLiteral, $node) { diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 740c4e3ec..869d5330b 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -433,7 +433,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface } /** - * @param $path + * @param string $path * @throws \RuntimeException */ protected function discardChanges($path) @@ -447,7 +447,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface } /** - * @param $path + * @param string $path * @throws \RuntimeException */ protected function stashChanges($path) @@ -461,7 +461,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface } /** - * @param $path + * @param string $path * @throws \RuntimeException */ protected function viewDiff($path) diff --git a/src/Composer/Downloader/PearPackageExtractor.php b/src/Composer/Downloader/PearPackageExtractor.php index 44267d558..5eaf3edcd 100644 --- a/src/Composer/Downloader/PearPackageExtractor.php +++ b/src/Composer/Downloader/PearPackageExtractor.php @@ -73,8 +73,8 @@ class PearPackageExtractor * Perform copy actions on files * * @param array $files array of copy actions ('from', 'to') with relative paths - * @param $source string path to source dir. - * @param $target string path to destination dir + * @param string $source path to source dir. + * @param string $target path to destination dir * @param array $roles array [role => roleRoot] relative root for files having that role * @param array $vars list of values can be used for replacement tasks */ @@ -135,7 +135,7 @@ class PearPackageExtractor */ private function buildCopyActions($source, array $roles, $vars) { - /** @var $package \SimpleXmlElement */ + /** @var \SimpleXmlElement $package */ $package = simplexml_load_string(file_get_contents($this->combine($source, 'package.xml'))); if (false === $package) { throw new \RuntimeException('Package definition file is not valid.'); diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 145944b07..98c9a6c66 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -264,7 +264,7 @@ class EventDispatcher $finder = new PhpExecutableFinder(); $phpPath = $finder->find(); if (!$phpPath) { - throw new \RuntimeException('Failed to locate PHP binary to execute '.$scriptName); + throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath); } $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); diff --git a/src/Composer/Package/Archiver/GitExcludeFilter.php b/src/Composer/Package/Archiver/GitExcludeFilter.php index 0cdc98c81..f79734855 100644 --- a/src/Composer/Package/Archiver/GitExcludeFilter.php +++ b/src/Composer/Package/Archiver/GitExcludeFilter.php @@ -64,7 +64,7 @@ class GitExcludeFilter extends BaseExcludeFilter * * @param string $line A line from .gitattributes * - * @return array An exclude pattern for filter() + * @return array|null An exclude pattern for filter() */ public function parseGitAttributesLine($line) { diff --git a/src/Composer/Package/Archiver/HgExcludeFilter.php b/src/Composer/Package/Archiver/HgExcludeFilter.php index 3e2b25d5c..b83b7756b 100644 --- a/src/Composer/Package/Archiver/HgExcludeFilter.php +++ b/src/Composer/Package/Archiver/HgExcludeFilter.php @@ -54,7 +54,7 @@ class HgExcludeFilter extends BaseExcludeFilter * * @param string $line A line from .hgignore * - * @return array An exclude pattern for filter() + * @return array|null An exclude pattern for filter() */ public function parseHgIgnoreLine($line) { diff --git a/src/Composer/Package/Archiver/ZipArchiver.php b/src/Composer/Package/Archiver/ZipArchiver.php index d1d7573f3..65694cb88 100644 --- a/src/Composer/Package/Archiver/ZipArchiver.php +++ b/src/Composer/Package/Archiver/ZipArchiver.php @@ -37,7 +37,7 @@ class ZipArchiver implements ArchiverInterface if ($res === true) { $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); foreach ($files as $file) { - /** @var $file \SplFileInfo */ + /** @var \SplFileInfo $file */ $filepath = strtr($file->getPath()."/".$file->getFilename(), '\\', '/'); $localname = str_replace($sources.'/', '', $filepath); if ($file->isDir()) { diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 0184cb4d5..079d34c54 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -84,7 +84,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito * Find a file by name, returning the one that has the shortest path. * * @param \ZipArchive $zip - * @param $filename + * @param string $filename * @return bool|int */ private function locateFile(\ZipArchive $zip, $filename) diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php index d11a96d7b..9b26eb9db 100644 --- a/src/Composer/Repository/Pear/BaseChannelReader.php +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -44,8 +44,8 @@ abstract class BaseChannelReader /** * Read content from remote filesystem. * - * @param $origin string server - * @param $path string relative path to content + * @param string $origin server + * @param string $path relative path to content * @throws \UnexpectedValueException * @return \SimpleXMLElement */ @@ -63,8 +63,8 @@ abstract class BaseChannelReader /** * Read xml content from remote filesystem * - * @param $origin string server - * @param $path string relative path to content + * @param string $origin server + * @param string $path relative path to content * @throws \UnexpectedValueException * @return \SimpleXMLElement */ diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php index 00b62b89c..73cc9152e 100644 --- a/src/Composer/Repository/Pear/ChannelReader.php +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -44,7 +44,7 @@ class ChannelReader extends BaseChannelReader /** * Reads PEAR channel through REST interface and builds list of packages * - * @param $url string PEAR Channel url + * @param string $url PEAR Channel url * @throws \UnexpectedValueException * @return ChannelInfo */ @@ -70,8 +70,8 @@ class ChannelReader extends BaseChannelReader /** * Reads channel supported REST interfaces and selects one of them * - * @param $channelXml \SimpleXMLElement - * @param $supportedVersions string[] supported PEAR REST protocols + * @param \SimpleXMLElement $channelXml + * @param string[] $supportedVersions supported PEAR REST protocols * @return array|null hash with selected version and baseUrl */ private function selectRestVersion($channelXml, $supportedVersions) diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php index 92498dae9..489914d5d 100644 --- a/src/Composer/Repository/Pear/ChannelRest10Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -39,7 +39,7 @@ class ChannelRest10Reader extends BaseChannelReader /** * Reads package descriptions using PEAR Rest 1.0 interface * - * @param $baseUrl string base Url interface + * @param string $baseUrl base Url interface * * @return PackageInfo[] */ @@ -52,7 +52,7 @@ class ChannelRest10Reader extends BaseChannelReader * Read list of packages from * {baseUrl}/p/packages.xml * - * @param $baseUrl string + * @param string $baseUrl * @return PackageInfo[] */ private function readPackages($baseUrl) @@ -75,8 +75,8 @@ class ChannelRest10Reader extends BaseChannelReader * Read package info from * {baseUrl}/p/{package}/info.xml * - * @param $baseUrl string - * @param $packageName string + * @param string $baseUrl + * @param string $packageName * @return PackageInfo */ private function readPackage($baseUrl, $packageName) @@ -105,8 +105,8 @@ class ChannelRest10Reader extends BaseChannelReader * Read package releases from * {baseUrl}/p/{package}/allreleases.xml * - * @param $baseUrl string - * @param $packageName string + * @param string $baseUrl + * @param string $packageName * @throws \Composer\Downloader\TransportException|\Exception * @return ReleaseInfo[] hash array with keys as version numbers */ @@ -146,9 +146,9 @@ class ChannelRest10Reader extends BaseChannelReader * Read package dependencies from * {baseUrl}/p/{package}/deps.{version}.txt * - * @param $baseUrl string - * @param $packageName string - * @param $version string + * @param string $baseUrl + * @param string $packageName + * @param string $version * @return DependencyInfo[] */ private function readPackageReleaseDependencies($baseUrl, $packageName, $version) diff --git a/src/Composer/Repository/Pear/ChannelRest11Reader.php b/src/Composer/Repository/Pear/ChannelRest11Reader.php index 22cd61cc0..f9e05f5be 100644 --- a/src/Composer/Repository/Pear/ChannelRest11Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest11Reader.php @@ -35,7 +35,7 @@ class ChannelRest11Reader extends BaseChannelReader /** * Reads package descriptions using PEAR Rest 1.1 interface * - * @param $baseUrl string base Url interface + * @param string $baseUrl base Url interface * * @return PackageInfo[] */ @@ -48,7 +48,7 @@ class ChannelRest11Reader extends BaseChannelReader * Read list of channel categories from * {baseUrl}/c/categories.xml * - * @param $baseUrl string + * @param string $baseUrl * @return PackageInfo[] */ private function readChannelPackages($baseUrl) @@ -70,8 +70,8 @@ class ChannelRest11Reader extends BaseChannelReader * Read packages from * {baseUrl}/c/{category}/packagesinfo.xml * - * @param $baseUrl string - * @param $categoryName string + * @param string $baseUrl + * @param string $categoryName * @return PackageInfo[] */ private function readCategoryPackages($baseUrl, $categoryName) @@ -92,7 +92,7 @@ class ChannelRest11Reader extends BaseChannelReader /** * Parses package node. * - * @param $packageInfo \SimpleXMLElement xml element describing package + * @param \SimpleXMLElement $packageInfo xml element describing package * @return PackageInfo */ private function parsePackage($packageInfo) diff --git a/src/Composer/Repository/Pear/PackageDependencyParser.php b/src/Composer/Repository/Pear/PackageDependencyParser.php index dc6879d6d..24f8fb9f9 100644 --- a/src/Composer/Repository/Pear/PackageDependencyParser.php +++ b/src/Composer/Repository/Pear/PackageDependencyParser.php @@ -22,7 +22,7 @@ class PackageDependencyParser /** * Builds dependency information. It detects used package.xml format. * - * @param $depArray array + * @param array $depArray * @return DependencyInfo */ public function buildDependencyInfo($depArray) @@ -46,7 +46,7 @@ class PackageDependencyParser * { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes" * channel="channelName" name="extName|packageName" } * - * @param $depArray array Dependency data in package.xml 1.0 format + * @param array $depArray Dependency data in package.xml 1.0 format * @return DependencyConstraint[] */ private function buildDependency10Info($depArray) @@ -115,7 +115,7 @@ class PackageDependencyParser /** * Builds dependency information from package.xml 2.0 format * - * @param $depArray array Dependency data in package.xml 1.0 format + * @param array $depArray Dependency data in package.xml 1.0 format * @return DependencyInfo */ private function buildDependency20Info($depArray) @@ -187,8 +187,8 @@ class PackageDependencyParser /** * Builds dependency constraint of 'extension' type * - * @param $depItem array dependency constraint or array of dependency constraints - * @param $depType string target type of building constraint. + * @param array $depItem dependency constraint or array of dependency constraints + * @param string $depType target type of building constraint. * @return DependencyConstraint[] */ private function buildDepExtensionConstraints($depItem, $depType) @@ -217,8 +217,8 @@ class PackageDependencyParser /** * Builds dependency constraint of 'package' type * - * @param $depItem array dependency constraint or array of dependency constraints - * @param $depType string target type of building constraint. + * @param array $depItem dependency constraint or array of dependency constraints + * @param string $depType target type of building constraint. * @return DependencyConstraint[] */ private function buildDepPackageConstraints($depItem, $depType) @@ -287,7 +287,7 @@ class PackageDependencyParser /** * Softened version parser * - * @param $version + * @param string $version * @return null|string */ private function parseVersion($version) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index a3af3b825..ebb7dfbd3 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -264,8 +264,8 @@ class Filesystem /** * Copies a file or directory from $source to $target. * - * @param $source - * @param $target + * @param string $source + * @param string $target * @return bool */ public function copy($source, $target) diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php index 5cc6e361b..a6cb112be 100644 --- a/src/Composer/Util/NoProxyPattern.php +++ b/src/Composer/Util/NoProxyPattern.php @@ -35,7 +35,7 @@ class NoProxyPattern * * @param string $url * - * @return true if the URL matches one of the rules. + * @return bool true if the URL matches one of the rules. */ public function test($url) { diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index d7c6816cc..b064feec4 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -515,7 +515,7 @@ class Perforce } /** - * @param $reference + * @param string $reference * @return mixed|null */ protected function getChangeList($reference) @@ -537,8 +537,8 @@ class Perforce } /** - * @param $fromReference - * @param $toReference + * @param string $fromReference + * @param string $toReference * @return mixed|null */ public function getCommitLogs($fromReference, $toReference) diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index f5f12d315..4e9b7f480 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -160,7 +160,7 @@ final class StreamContextFactory * This method fixes the array by moving the content-type header to the end * * @link https://bugs.php.net/bug.php?id=61548 - * @param $header + * @param string|array $header * @return array */ private static function fixHttpHeaderField($header) diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index e04c7157e..34336d06c 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -164,7 +164,7 @@ final class TlsHelper * * @param string $certName CN/SAN * - * @return callable|null + * @return callable|void */ private static function certNameMatcher($certName) { diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 1fc552daf..7a3ef3ee0 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -12,7 +12,7 @@ namespace Composer\Test; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\Process; diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 37d434fa8..5f491440b 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -13,7 +13,7 @@ namespace Composer\Test; use Composer\Console\Application; -use Composer\TestCase; +use Composer\Test\TestCase; use Symfony\Component\Console\Output\OutputInterface; class ApplicationTest extends TestCase diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index b8eec2f43..4d672084e 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -17,7 +17,7 @@ use Composer\Package\Link; use Composer\Util\Filesystem; use Composer\Package\AliasPackage; use Composer\Package\Package; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Script\ScriptEvents; use Composer\Repository\InstalledRepositoryInterface; use Composer\Installer\InstallationManager; diff --git a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php index 05f0d0530..ae7b597b7 100644 --- a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php +++ b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php @@ -19,7 +19,7 @@ namespace Composer\Test\Autoload; use Composer\Autoload\ClassMapGenerator; -use Composer\TestCase; +use Composer\Test\TestCase; use Symfony\Component\Finder\Finder; use Composer\Util\Filesystem; diff --git a/tests/Composer/Test/CacheTest.php b/tests/Composer/Test/CacheTest.php index 9830fd7de..50c767752 100644 --- a/tests/Composer/Test/CacheTest.php +++ b/tests/Composer/Test/CacheTest.php @@ -12,7 +12,7 @@ namespace Composer\Test; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class CacheTest extends TestCase diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index d355b6cd1..06d8a004b 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Command; use Composer\Command\InitCommand; -use Composer\TestCase; +use Composer\Test\TestCase; class InitCommandTest extends TestCase { diff --git a/tests/Composer/Test/Command/RunScriptCommandTest.php b/tests/Composer/Test/Command/RunScriptCommandTest.php index 53478b323..97acc6d26 100644 --- a/tests/Composer/Test/Command/RunScriptCommandTest.php +++ b/tests/Composer/Test/Command/RunScriptCommandTest.php @@ -15,7 +15,7 @@ namespace Composer\Test\Command; use Composer\Composer; use Composer\Config; use Composer\Script\Event as ScriptEvent; -use Composer\TestCase; +use Composer\Test\TestCase; class RunScriptCommandTest extends TestCase { diff --git a/tests/Composer/Test/ComposerTest.php b/tests/Composer/Test/ComposerTest.php index aabe1deab..c2c425e76 100644 --- a/tests/Composer/Test/ComposerTest.php +++ b/tests/Composer/Test/ComposerTest.php @@ -13,7 +13,7 @@ namespace Composer\Test; use Composer\Composer; -use Composer\TestCase; +use Composer\Test\TestCase; class ComposerTest extends TestCase { diff --git a/tests/Composer/Test/Config/JsonConfigSourceTest.php b/tests/Composer/Test/Config/JsonConfigSourceTest.php index e558932c2..8c5d641de 100644 --- a/tests/Composer/Test/Config/JsonConfigSourceTest.php +++ b/tests/Composer/Test/Config/JsonConfigSourceTest.php @@ -10,11 +10,11 @@ * file that was distributed with this source code. */ -namespace Composer\Test\Json; +namespace Composer\Test\Config; use Composer\Config\JsonConfigSource; use Composer\Json\JsonFile; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class JsonConfigSourceTest extends TestCase diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index f84d5d35f..57c368988 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -13,7 +13,7 @@ namespace Composer\Test; use Composer\Config; -use PHPUnit\Framework\TestCase; +use Composer\Test\TestCase; class ConfigTest extends TestCase { diff --git a/tests/Composer/Test/DefaultConfigTest.php b/tests/Composer/Test/DefaultConfigTest.php index 23de8741f..04298e5a1 100644 --- a/tests/Composer/Test/DefaultConfigTest.php +++ b/tests/Composer/Test/DefaultConfigTest.php @@ -13,7 +13,7 @@ namespace Composer\Test; use Composer\Config; -use PHPUnit\Framework\TestCase; +use Composer\Test\TestCase; class DefaultConfigTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index a73139d54..2611d772f 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -19,7 +19,7 @@ use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\AliasPackage; use Composer\Semver\Constraint\Constraint; -use Composer\TestCase; +use Composer\Test\TestCase; class DefaultPolicyTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 14b24fc9f..aa38fa31d 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -15,7 +15,7 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Pool; use Composer\Repository\ArrayRepository; use Composer\Package\BasePackage; -use Composer\TestCase; +use Composer\Test\TestCase; class PoolTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index 08e0cae96..dfa411ed9 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Request; use Composer\Repository\ArrayRepository; -use Composer\TestCase; +use Composer\Test\TestCase; class RequestTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index cecae613d..bd6efbc1b 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -17,7 +17,7 @@ use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\Pool; use Composer\Repository\ArrayRepository; -use Composer\TestCase; +use Composer\Test\TestCase; class RuleSetTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index a0339f27a..19f5fddea 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -17,7 +17,7 @@ use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\Pool; use Composer\Repository\ArrayRepository; -use Composer\TestCase; +use Composer\Test\TestCase; class RuleTest extends TestCase { diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 28c439b9e..24147e6ad 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -20,7 +20,7 @@ use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Semver\Constraint\MultiConstraint; class SolverTest extends TestCase @@ -30,6 +30,7 @@ class SolverTest extends TestCase protected $repoInstalled; protected $request; protected $policy; + protected $solver; public function setUp() { @@ -37,7 +38,7 @@ class SolverTest extends TestCase $this->repo = new ArrayRepository; $this->repoInstalled = new ArrayRepository; - $this->request = new Request($this->pool); + $this->request = new Request(); $this->policy = new DefaultPolicy; $this->solver = new Solver($this->policy, $this->pool, $this->repoInstalled, new NullIO()); } diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index d4f6b7ad7..476b9a8f7 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\FileDownloader; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class FileDownloaderTest extends TestCase diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index ca941fe20..623f7dec2 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\FossilDownloader; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index ff1c5c201..c3cd31a4a 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\GitDownloader; use Composer\Config; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 714388f2c..c71d463cb 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\HgDownloader; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; diff --git a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php index 92004d0f1..23334f303 100644 --- a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php +++ b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\PearPackageExtractor; -use Composer\TestCase; +use Composer\Test\TestCase; class PearPackageExtractorTest extends TestCase { diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 3fae0f7af..ebb1f0456 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -16,7 +16,7 @@ use Composer\Downloader\PerforceDownloader; use Composer\Config; use Composer\Repository\VcsRepository; use Composer\IO\IOInterface; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; /** diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index fc33adcf4..6df782ddb 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\XzDownloader; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\RemoteFilesystem; diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 3d4ebac7b..466fd35c7 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Downloader; use Composer\Downloader\ZipDownloader; use Composer\Package\PackageInterface; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class ZipDownloaderTest extends TestCase @@ -24,6 +24,8 @@ class ZipDownloaderTest extends TestCase */ private $testDir; private $prophet; + private $io; + private $config; public function setUp() { @@ -46,9 +48,9 @@ class ZipDownloaderTest extends TestCase $reflectedProperty = $reflectionClass->getProperty($name); $reflectedProperty->setAccessible(true); if ($obj === null) { - $reflectedProperty = $reflectedProperty->setValue($value); + $reflectedProperty->setValue($value); } else { - $reflectedProperty = $reflectedProperty->setValue($obj, $value); + $reflectedProperty->setValue($obj, $value); } } diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 7f0327d9c..689462bbd 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -17,7 +17,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallerEvents; use Composer\Config; use Composer\Composer; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\IO\BufferIO; use Composer\Script\ScriptEvents; use Composer\Script\CommandEvent; diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index 57809769c..ef5096300 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\IO; use Composer\IO\ConsoleIO; -use Composer\TestCase; +use Composer\Test\TestCase; use Symfony\Component\Console\Output\OutputInterface; class ConsoleIOTest extends TestCase diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php index 6902f08bd..d37fb2a4f 100644 --- a/tests/Composer/Test/IO/NullIOTest.php +++ b/tests/Composer/Test/IO/NullIOTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\IO; use Composer\IO\NullIO; -use Composer\TestCase; +use Composer\Test\TestCase; class NullIOTest extends TestCase { diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 41581f712..772bb05c8 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Installer; use Composer\Installer\LibraryInstaller; use Composer\Util\Filesystem; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Composer; use Composer\Config; diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 8614495ee..c3b957afb 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -29,7 +29,7 @@ use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatter; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\IO\BufferIO; class InstallerTest extends TestCase diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index a6f88b9cb..47683afcd 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -19,7 +19,7 @@ use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; use Composer\Installer; use Composer\IO\IOInterface; -use Composer\TestCase; +use Composer\Test\TestCase; class FactoryMock extends Factory { diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index 5856efbf5..f6afe10f1 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Package\Archiver; use Composer\Package\Archiver\ArchivableFilesFinder; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Symfony\Component\Process\Process; diff --git a/tests/Composer/Test/Package/Archiver/ArchiverTest.php b/tests/Composer/Test/Package/Archiver/ArchiverTest.php index 1d08e8873..8926d760b 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiverTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiverTest.php @@ -12,7 +12,7 @@ namespace Composer\Test\Package\Archiver; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\ProcessExecutor; use Composer\Package\Package; diff --git a/tests/Composer/Test/Package/CompletePackageTest.php b/tests/Composer/Test/Package/CompletePackageTest.php index aa3127071..fe1a783e8 100644 --- a/tests/Composer/Test/Package/CompletePackageTest.php +++ b/tests/Composer/Test/Package/CompletePackageTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Package; use Composer\Package\Package; use Composer\Semver\VersionParser; -use Composer\TestCase; +use Composer\Test\TestCase; class CompletePackageTest extends TestCase { diff --git a/tests/Composer/Test/Package/RootAliasPackageTest.php b/tests/Composer/Test/Package/RootAliasPackageTest.php index 8124ca865..a5fe9172d 100644 --- a/tests/Composer/Test/Package/RootAliasPackageTest.php +++ b/tests/Composer/Test/Package/RootAliasPackageTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Package; use Composer\Package\Link; use Composer\Package\RootAliasPackage; -use Composer\TestCase; +use Composer\Test\TestCase; use Prophecy\Argument; class RootAliasPackageTest extends TestCase diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 26fc63efa..aa1f9ddbb 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Composer\Test\Installer; +namespace Composer\Test\Plugin; use Composer\Composer; use Composer\Config; @@ -20,7 +20,7 @@ use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Plugin\PluginManager; use Composer\Autoload\AutoloadGenerator; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class PluginInstallerTest extends TestCase diff --git a/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php b/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php index a69d1aeef..1f0dad72b 100644 --- a/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php +++ b/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Composer\Question\Test; +namespace Composer\Test\Question; use Composer\Question\StrictConfirmationQuestion; use PHPUnit\Framework\TestCase; diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index bd19b979c..1c7972c96 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository; use Composer\Repository\ArrayRepository; use Composer\Repository\RepositoryInterface; -use Composer\TestCase; +use Composer\Test\TestCase; class ArrayRepositoryTest extends TestCase { diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php index d919d180b..506a033c4 100644 --- a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository; use Composer\Repository\ArtifactRepository; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\IO\NullIO; use Composer\Config; use Composer\Package\BasePackage; @@ -42,7 +42,7 @@ class ArtifactRepositoryTest extends TestCase ); $coordinates = array('type' => 'artifact', 'url' => __DIR__ . '/Fixtures/artifacts'); - $repo = new ArtifactRepository($coordinates, new NullIO(), new Config()); + $repo = new ArtifactRepository($coordinates, new NullIO()); $foundPackages = array_map(function (BasePackage $package) { return "{$package->getPrettyName()}-{$package->getPrettyVersion()}"; @@ -58,7 +58,7 @@ class ArtifactRepositoryTest extends TestCase { $absolutePath = __DIR__ . '/Fixtures/artifacts'; $coordinates = array('type' => 'artifact', 'url' => $absolutePath); - $repo = new ArtifactRepository($coordinates, new NullIO(), new Config()); + $repo = new ArtifactRepository($coordinates, new NullIO()); foreach ($repo->getPackages() as $package) { $this->assertSame(strpos($package->getDistUrl(), strtr($absolutePath, '\\', '/')), 0); @@ -69,7 +69,7 @@ class ArtifactRepositoryTest extends TestCase { $relativePath = 'tests/Composer/Test/Repository/Fixtures/artifacts'; $coordinates = array('type' => 'artifact', 'url' => $relativePath); - $repo = new ArtifactRepository($coordinates, new NullIO(), new Config()); + $repo = new ArtifactRepository($coordinates, new NullIO()); foreach ($repo->getPackages() as $package) { $this->assertSame(strpos($package->getDistUrl(), $relativePath), 0); diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 38b459730..3e29e8023 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -16,7 +16,7 @@ use Composer\IO\NullIO; use Composer\Repository\ComposerRepository; use Composer\Repository\RepositoryInterface; use Composer\Test\Mock\FactoryMock; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Package\Loader\ArrayLoader; use Composer\Semver\VersionParser; diff --git a/tests/Composer/Test/Repository/CompositeRepositoryTest.php b/tests/Composer/Test/Repository/CompositeRepositoryTest.php index d9f8b70e3..978587133 100644 --- a/tests/Composer/Test/Repository/CompositeRepositoryTest.php +++ b/tests/Composer/Test/Repository/CompositeRepositoryTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository; use Composer\Repository\CompositeRepository; use Composer\Repository\ArrayRepository; -use Composer\TestCase; +use Composer\Test\TestCase; class CompositeRepositoryTest extends TestCase { diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index 841a54d7b..be8b0d0a9 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository; use Composer\Repository\FilesystemRepository; -use Composer\TestCase; +use Composer\Test\TestCase; class FilesystemRepositoryTest extends TestCase { diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php index 7f1532a00..a9594257c 100644 --- a/tests/Composer/Test/Repository/PathRepositoryTest.php +++ b/tests/Composer/Test/Repository/PathRepositoryTest.php @@ -15,7 +15,7 @@ namespace Composer\Test\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Repository\PathRepository; use Composer\Semver\VersionParser; -use Composer\TestCase; +use Composer\Test\TestCase; class PathRepositoryTest extends TestCase { @@ -25,11 +25,10 @@ class PathRepositoryTest extends TestCase ->getMock(); $config = new \Composer\Config(); - $loader = new ArrayLoader(new VersionParser()); $versionGuesser = null; $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-version')); - $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config); $repository->getPackages(); $this->assertEquals(1, $repository->count()); @@ -42,11 +41,10 @@ class PathRepositoryTest extends TestCase ->getMock(); $config = new \Composer\Config(); - $loader = new ArrayLoader(new VersionParser()); $versionGuesser = null; $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'without-version')); - $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config); $packages = $repository->getPackages(); $this->assertEquals(1, $repository->count()); @@ -64,11 +62,10 @@ class PathRepositoryTest extends TestCase ->getMock(); $config = new \Composer\Config(); - $loader = new ArrayLoader(new VersionParser()); $versionGuesser = null; $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*')); - $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config); $packages = $repository->getPackages(); $names = array(); @@ -93,7 +90,6 @@ class PathRepositoryTest extends TestCase ->getMock(); $config = new \Composer\Config(); - $loader = new ArrayLoader(new VersionParser()); $versionGuesser = null; // realpath() does not fully expand the paths @@ -103,7 +99,7 @@ class PathRepositoryTest extends TestCase // PHP Bug https://bugs.php.net/bug.php?id=73797 $relativeUrl = ltrim(substr($repositoryUrl, strlen(realpath(realpath(getcwd())))), DIRECTORY_SEPARATOR); - $repository = new PathRepository(array('url' => $relativeUrl), $ioInterface, $config, $loader); + $repository = new PathRepository(array('url' => $relativeUrl), $ioInterface, $config); $packages = $repository->getPackages(); $this->assertEquals(1, $repository->count()); diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index 27b7af2a3..e766065a7 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -17,7 +17,7 @@ use Composer\Repository\Pear\DependencyConstraint; use Composer\Repository\Pear\DependencyInfo; use Composer\Repository\Pear\PackageInfo; use Composer\Repository\Pear\ReleaseInfo; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Semver\VersionParser; use Composer\Semver\Constraint\Constraint; use Composer\Package\Link; diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php index 263a80420..4aa7bbba2 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php @@ -12,7 +12,7 @@ namespace Composer\Test\Repository\Pear; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Test\Mock\RemoteFilesystemMock; class ChannelRest10ReaderTest extends TestCase @@ -31,7 +31,7 @@ class ChannelRest10ReaderTest extends TestCase $reader = new \Composer\Repository\Pear\ChannelRest10Reader($rfs); - /** @var $packages \Composer\Package\PackageInterface[] */ + /** @var \Composer\Package\PackageInterface[] $packages */ $packages = $reader->read('http://test.loc/rest10'); $this->assertCount(2, $packages); diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php index 562e000c8..04e48426e 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php @@ -12,7 +12,7 @@ namespace Composer\Test\Repository\Pear; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Test\Mock\RemoteFilesystemMock; class ChannelRest11ReaderTest extends TestCase @@ -27,7 +27,7 @@ class ChannelRest11ReaderTest extends TestCase $reader = new \Composer\Repository\Pear\ChannelRest11Reader($rfs); - /** @var $packages \Composer\Package\PackageInterface[] */ + /** @var \Composer\Package\PackageInterface[] $packages */ $packages = $reader->read('http://test.loc/rest11'); $this->assertCount(3, $packages); diff --git a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php index dd566055a..0ca9259d9 100644 --- a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php +++ b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Pear; use Composer\Repository\Pear\DependencyConstraint; use Composer\Repository\Pear\PackageDependencyParser; -use Composer\TestCase; +use Composer\Test\TestCase; class PackageDependencyParserTest extends TestCase { diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index c484820b6..b1a3c0b5e 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository; use Composer\Repository\PearRepository; -use Composer\TestCase; +use Composer\Test\TestCase; /** * @group legacy diff --git a/tests/Composer/Test/Repository/RepositoryFactoryTest.php b/tests/Composer/Test/Repository/RepositoryFactoryTest.php index e6d811fe9..e54624415 100644 --- a/tests/Composer/Test/Repository/RepositoryFactoryTest.php +++ b/tests/Composer/Test/Repository/RepositoryFactoryTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository; use Composer\Repository\RepositoryFactory; -use Composer\TestCase; +use Composer\Test\TestCase; class RepositoryFactoryTest extends TestCase { diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php index a5739f32d..3774dd268 100644 --- a/tests/Composer/Test/Repository/RepositoryManagerTest.php +++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository; use Composer\Repository\RepositoryManager; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; class RepositoryManagerTest extends TestCase diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index f9e73b5eb..cbb4342e9 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\FossilDriver; use Composer\Config; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 7547855bf..8d711e8f0 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Config; use Composer\Repository\Vcs\GitBitbucketDriver; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; /** diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index 35e2f64c8..ba9c6d4f7 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\GitHubDriver; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Config; diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index ced826672..a5eb799f2 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\GitLabDriver; use Composer\Config; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Prophecy\Argument; @@ -251,7 +251,7 @@ JSON; public function testGetSource_GivenPublicProject() { - $driver = $this->testInitializePublicProject('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v4/projects/mygroup%2Fmyproject', true); + $driver = $this->testInitializePublicProject('https://gitlab.com/mygroup/myproject', 'https://gitlab.com/api/v4/projects/mygroup%2Fmyproject'); $reference = 'c3ebdbf9cceddb82cd2089aaef8c7b992e536363'; $expected = array( diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php index 441ce19c2..11143b476 100644 --- a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\HgDriver; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Config; diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index 02a6e89e5..a5e5d4b4c 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\PerforceDriver; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Config; use Composer\Util\Perforce; diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 1106c9df4..029d20160 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\SvnDriver; use Composer\Config; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index 61e29be37..0cab2f8bb 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -12,7 +12,7 @@ namespace Composer\Test\Repository; -use Composer\TestCase; +use Composer\Test\TestCase; use Symfony\Component\Process\ExecutableFinder; use Composer\Package\Dumper\ArrayDumper; use Composer\Repository\VcsRepository; diff --git a/tests/Composer/TestCase.php b/tests/Composer/Test/TestCase.php similarity index 99% rename from tests/Composer/TestCase.php rename to tests/Composer/Test/TestCase.php index 656bf061b..656a477e6 100644 --- a/tests/Composer/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Composer; +namespace Composer\Test; use Composer\Semver\VersionParser; use Composer\Package\AliasPackage; diff --git a/tests/Composer/Test/Util/ConfigValidatorTest.php b/tests/Composer/Test/Util/ConfigValidatorTest.php index 157eba92e..8b6ac5132 100644 --- a/tests/Composer/Test/Util/ConfigValidatorTest.php +++ b/tests/Composer/Test/Util/ConfigValidatorTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Util; use Composer\IO\NullIO; use Composer\Util\ConfigValidator; -use Composer\TestCase; +use Composer\Test\TestCase; /** * ConfigValidator test case diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php index cb16a1e13..52de96af4 100644 --- a/tests/Composer/Test/Util/ErrorHandlerTest.php +++ b/tests/Composer/Test/Util/ErrorHandlerTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Util; use Composer\Util\ErrorHandler; -use Composer\TestCase; +use Composer\Test\TestCase; /** * ErrorHandler test case diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index ef3ab5bbe..9f684dbfb 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Util; use Composer\Util\Filesystem; -use Composer\TestCase; +use Composer\Test\TestCase; class FilesystemTest extends TestCase { diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index 4a18c24e7..e98898417 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Util; use Composer\Util\ProcessExecutor; -use Composer\TestCase; +use Composer\Test\TestCase; use Composer\IO\BufferIO; use Symfony\Component\Console\Output\StreamOutput; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 908861cf5..aaf431f0f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -17,4 +17,4 @@ if (function_exists('date_default_timezone_set') && function_exists('date_defaul } require __DIR__.'/../src/bootstrap.php'; -require __DIR__.'/Composer/TestCase.php'; +require __DIR__.'/Composer/Test/TestCase.php'; From 86f59348f5fa308d3b9a911a8d03c30272c71e64 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 15:24:28 +0100 Subject: [PATCH 309/580] Fix TestCase import --- tests/Composer/Test/FactoryTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php index 34d4518bb..6704e5b15 100644 --- a/tests/Composer/Test/FactoryTest.php +++ b/tests/Composer/Test/FactoryTest.php @@ -12,7 +12,6 @@ namespace Composer\Test; -use PHPUnit\Framework\TestCase; use Composer\Factory; class FactoryTest extends TestCase From 5a56bb69710cbc2d8fd8fc83fc0654de11059e7f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 15:57:44 +0100 Subject: [PATCH 310/580] Remove BC event and constraint classes --- .../EventDispatcher/EventDispatcher.php | 38 ------------------ .../LinkConstraint/EmptyConstraint.php | 24 ------------ .../LinkConstraintInterface.php | 24 ------------ .../LinkConstraint/MultiConstraint.php | 24 ------------ .../LinkConstraint/SpecificConstraint.php | 24 ------------ .../LinkConstraint/VersionConstraint.php | 24 ------------ src/Composer/Script/CommandEvent.php | 22 ----------- src/Composer/Script/PackageEvent.php | 24 ------------ src/Composer/Util/SpdxLicense.php | 24 ------------ src/Composer/XdebugHandler.php | 31 --------------- .../EventDispatcher/EventDispatcherTest.php | 39 ++----------------- 11 files changed, 3 insertions(+), 295 deletions(-) delete mode 100644 src/Composer/Package/LinkConstraint/EmptyConstraint.php delete mode 100644 src/Composer/Package/LinkConstraint/LinkConstraintInterface.php delete mode 100644 src/Composer/Package/LinkConstraint/MultiConstraint.php delete mode 100644 src/Composer/Package/LinkConstraint/SpecificConstraint.php delete mode 100644 src/Composer/Package/LinkConstraint/VersionConstraint.php delete mode 100644 src/Composer/Script/CommandEvent.php delete mode 100644 src/Composer/Script/PackageEvent.php delete mode 100644 src/Composer/Util/SpdxLicense.php delete mode 100644 src/Composer/XdebugHandler.php diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index f0fcdaef6..324190f19 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -321,44 +321,6 @@ class EventDispatcher $expected = $typehint->getName(); - // BC support - if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') { - trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); - $event = new \Composer\Script\CommandEvent( - $event->getName(), - $event->getComposer(), - $event->getIO(), - $event->isDevMode(), - $event->getArguments() - ); - } - if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') { - trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); - $event = new \Composer\Script\PackageEvent( - $event->getName(), - $event->getComposer(), - $event->getIO(), - $event->isDevMode(), - $event->getPolicy(), - $event->getRepositorySet(), - $event->getInstalledRepo(), - $event->getRequest(), - $event->getOperations(), - $event->getOperation() - ); - } - if (!$event instanceof $expected && $expected === 'Composer\Script\Event') { - trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED); - $event = new \Composer\Script\Event( - $event->getName(), - $event->getComposer(), - $event->getIO(), - $event->isDevMode(), - $event->getArguments(), - $event->getFlags() - ); - } - return $event; } diff --git a/src/Composer/Package/LinkConstraint/EmptyConstraint.php b/src/Composer/Package/LinkConstraint/EmptyConstraint.php deleted file mode 100644 index 33f9e2e82..000000000 --- a/src/Composer/Package/LinkConstraint/EmptyConstraint.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Package\LinkConstraint; - -use Composer\Semver\Constraint\EmptyConstraint as SemverEmptyConstraint; - -trigger_error('The ' . __NAMESPACE__ . '\EmptyConstraint class is deprecated, use Composer\Semver\Constraint\EmptyConstraint instead.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Semver\Constraint\EmptyConstraint instead - */ -class EmptyConstraint extends SemverEmptyConstraint implements LinkConstraintInterface -{ -} diff --git a/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php b/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php deleted file mode 100644 index b8903ea8f..000000000 --- a/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Package\LinkConstraint; - -use Composer\Semver\Constraint\ConstraintInterface; - -trigger_error('The ' . __NAMESPACE__ . '\LinkConstraintInterface interface is deprecated, use Composer\Semver\Constraint\ConstraintInterface instead.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Semver\Constraint\ConstraintInterface instead - */ -interface LinkConstraintInterface extends ConstraintInterface -{ -} diff --git a/src/Composer/Package/LinkConstraint/MultiConstraint.php b/src/Composer/Package/LinkConstraint/MultiConstraint.php deleted file mode 100644 index 10a996568..000000000 --- a/src/Composer/Package/LinkConstraint/MultiConstraint.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Package\LinkConstraint; - -use Composer\Semver\Constraint\MultiConstraint as SemverMultiConstraint; - -trigger_error('The ' . __NAMESPACE__ . '\MultiConstraint class is deprecated, use Composer\Semver\Constraint\MultiConstraint instead.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Semver\Constraint\MultiConstraint instead - */ -class MultiConstraint extends SemverMultiConstraint implements LinkConstraintInterface -{ -} diff --git a/src/Composer/Package/LinkConstraint/SpecificConstraint.php b/src/Composer/Package/LinkConstraint/SpecificConstraint.php deleted file mode 100644 index 12d3194b6..000000000 --- a/src/Composer/Package/LinkConstraint/SpecificConstraint.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Package\LinkConstraint; - -use Composer\Semver\Constraint\AbstractConstraint; - -trigger_error('The ' . __NAMESPACE__ . '\SpecificConstraint abstract class is deprecated, there is no replacement for it.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Semver\Constraint\AbstractConstraint instead - */ -abstract class SpecificConstraint extends AbstractConstraint implements LinkConstraintInterface -{ -} diff --git a/src/Composer/Package/LinkConstraint/VersionConstraint.php b/src/Composer/Package/LinkConstraint/VersionConstraint.php deleted file mode 100644 index d6b1cbe1d..000000000 --- a/src/Composer/Package/LinkConstraint/VersionConstraint.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Package\LinkConstraint; - -use Composer\Semver\Constraint\Constraint; - -trigger_error('The ' . __NAMESPACE__ . '\VersionConstraint class is deprecated, use Composer\Semver\Constraint\Constraint instead.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Semver\Constraint\Constraint instead - */ -class VersionConstraint extends Constraint implements LinkConstraintInterface -{ -} diff --git a/src/Composer/Script/CommandEvent.php b/src/Composer/Script/CommandEvent.php deleted file mode 100644 index 84c52008c..000000000 --- a/src/Composer/Script/CommandEvent.php +++ /dev/null @@ -1,22 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Script; - -/** - * The Command Event. - * - * @deprecated use Composer\Script\Event instead - */ -class CommandEvent extends Event -{ -} diff --git a/src/Composer/Script/PackageEvent.php b/src/Composer/Script/PackageEvent.php deleted file mode 100644 index 531b86a40..000000000 --- a/src/Composer/Script/PackageEvent.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Script; - -use Composer\Installer\PackageEvent as BasePackageEvent; - -/** - * The Package Event. - * - * @deprecated Use Composer\Installer\PackageEvent instead - */ -class PackageEvent extends BasePackageEvent -{ -} diff --git a/src/Composer/Util/SpdxLicense.php b/src/Composer/Util/SpdxLicense.php deleted file mode 100644 index be4efdc54..000000000 --- a/src/Composer/Util/SpdxLicense.php +++ /dev/null @@ -1,24 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Util; - -use Composer\Spdx\SpdxLicenses; - -trigger_error('The ' . __NAMESPACE__ . '\SpdxLicense class is deprecated, use Composer\Spdx\SpdxLicenses instead.', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\Spdx\SpdxLicenses instead - */ -class SpdxLicense extends SpdxLicenses -{ -} diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php deleted file mode 100644 index eb94e93f4..000000000 --- a/src/Composer/XdebugHandler.php +++ /dev/null @@ -1,31 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer; - -use Symfony\Component\Console\Output\OutputInterface; - -trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead,', E_USER_DEPRECATED); - -/** - * @deprecated use Composer\XdebugHandler\XdebugHandler instead - */ -class XdebugHandler extends XdebugHandler\XdebugHandler -{ - const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; - const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION'; - - public function __construct(OutputInterface $output) - { - parent::__construct('composer', '--ansi'); - } -} diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 54460d705..590711d79 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -20,7 +20,7 @@ use Composer\Composer; use Composer\TestCase; use Composer\IO\BufferIO; use Composer\Script\ScriptEvents; -use Composer\Script\CommandEvent; +use Composer\Script\Event as ScriptEvent; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Output\OutputInterface; @@ -51,29 +51,6 @@ class EventDispatcherTest extends TestCase $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } - /** - * @expectedException PHPUnit_Framework_Error_Deprecated - */ - public function testDispatcherCanConvertScriptEventToCommandEventForListener() - { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $dispatcher = $this->getDispatcherStubForListenersTest(array( - 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent', - ), $io); - - $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false)); - } - - public function testDispatcherDoesNotAttemptConversionForListenerWithoutTypehint() - { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $dispatcher = $this->getDispatcherStubForListenersTest(array( - 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent', - ), $io); - - $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false)); - } - /** * @dataProvider getValidCommands * @param string $command @@ -265,7 +242,7 @@ class EventDispatcherTest extends TestCase return array(); })); - $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io)); + $dispatcher->dispatch('root', new ScriptEvent('root', $composer, $io)); $expected = '> root: @group'.PHP_EOL. '> group: echo -n foo'.PHP_EOL. '> group: @subgroup'.PHP_EOL. @@ -305,7 +282,7 @@ class EventDispatcherTest extends TestCase return array(); })); - $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io)); + $dispatcher->dispatch('root', new ScriptEvent('root', $composer, $io)); } private function getDispatcherStubForListenersTest($listeners, $io) @@ -424,16 +401,6 @@ class EventDispatcherTest extends TestCase throw new \RuntimeException(); } - public static function expectsCommandEvent(CommandEvent $event) - { - return false; - } - - public static function expectsVariableEvent($event) - { - return false; - } - public static function someMethod() { return true; From 04098153c8c4f3c2b2f329aa13f868bb5735032a Mon Sep 17 00:00:00 2001 From: Michele Locati Date: Mon, 26 Nov 2018 12:32:31 +0100 Subject: [PATCH 311/580] Add support for running composer with phpdbg (#7798) --- bin/composer | 2 +- src/Composer/EventDispatcher/EventDispatcher.php | 7 ++++--- src/Composer/Util/StreamContextFactory.php | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/composer b/bin/composer index 7a80288b6..5e142662c 100755 --- a/bin/composer +++ b/bin/composer @@ -1,7 +1,7 @@ #!/usr/bin/env php find(); + $phpPath = $finder->find(false); if (!$phpPath) { throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath); } - + $phpArgs = $finder->findArguments(); + $phpArgs = $phpArgs ? ' ' . implode(' ', $phpArgs) : ''; $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')); $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')); - return ProcessExecutor::escape($phpPath) . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; + return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; } /** diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 4e9b7f480..8dfd6624a 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -40,7 +40,7 @@ final class StreamContextFactory )); // Handle HTTP_PROXY/http_proxy on CLI only for security reasons - if (PHP_SAPI === 'cli' && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { + if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); } From b0b00ad1fa1abe2601774b49f0355dcbf533cf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Mon, 26 Nov 2018 12:35:41 +0100 Subject: [PATCH 312/580] Call a script recursively with extra parameters (#7720) * Added support for calling scripts recursively (fixes #7562) --- doc/articles/scripts.md | 11 +++++ .../EventDispatcher/EventDispatcher.php | 8 +++- .../EventDispatcher/EventDispatcherTest.php | 40 +++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 79ea01519..e0c27b10f 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -238,6 +238,17 @@ one by prefixing the command name with `@`: } ``` +You can also refer a script and pass it new arguments: + +```json +{ + "scripts": { + "tests": "phpunit", + "testsVerbose": "@tests -vvv" + } +} +``` + ## Calling Composer commands To call Composer commands, you can use `@composer` which will automatically diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 7f62f6890..0fb978fd4 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -176,8 +176,12 @@ class EventDispatcher $return = false === call_user_func($callable, $event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE); - $scriptName = substr($callable, 1); - $args = $event->getArguments(); + + $script = explode(' ', substr($callable, 1)); + $scriptName = $script[0]; + unset($script[0]); + + $args = array_merge($script, $event->getArguments()); $flags = $event->getFlags(); if (substr($callable, 0, 10) === '@composer ') { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9); diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 689462bbd..5e68ebcc9 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\EventDispatcher; use Composer\EventDispatcher\Event; use Composer\EventDispatcher\EventDispatcher; +use Composer\EventDispatcher\ScriptExecutionException; use Composer\Installer\InstallerEvents; use Composer\Config; use Composer\Composer; @@ -274,6 +275,45 @@ class EventDispatcherTest extends TestCase $this->assertEquals($expected, $io->getOutput()); } + public function testRecursionInScriptsNames() + { + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') + ->setConstructorArgs(array( + $composer = $this->createComposerInstance(), + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE), + $process + )) + ->setMethods(array( + 'getListeners' + )) + ->getMock(); + + $process->expects($this->exactly(1)) + ->method('execute') + ->will($this->returnValue(0)); + + $dispatcher->expects($this->atLeastOnce()) + ->method('getListeners') + ->will($this->returnCallback(function (Event $event) { + if($event->getName() === 'hello') { + return array('echo Hello'); + } + + if($event->getName() === 'helloWorld') { + return array('@hello World'); + } + + return array(); + })); + + $dispatcher->dispatch('helloWorld', new CommandEvent('helloWorld', $composer, $io)); + $expected = "> helloWorld: @hello World".PHP_EOL. + "> hello: echo Hello " .escapeshellarg('World').PHP_EOL; + + $this->assertEquals($expected, $io->getOutput()); + } + /** * @expectedException RuntimeException */ From f9234222d17bcd9f7a57a30d3c3b38d0c70e60cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 26 Nov 2018 13:37:56 +0200 Subject: [PATCH 313/580] add lib-imagick to show -p output (#7762) --- src/Composer/Repository/PlatformRepository.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 0a2e79f35..5ebb6c9db 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -157,6 +157,18 @@ class PlatformRepository extends ArrayRepository break; + case 'imagick': + $reflector = new \ReflectionExtension('imagick'); + + ob_start(); + $reflector->info(); + $output = ob_get_clean(); + + preg_match('/^Imagick using ImageMagick library version => ImageMagick ([\d.]+)-(\d+)/m', $output, $matches); + $prettyVersion = "{$matches[1]}.{$matches[2]}"; + + break; + case 'libxml': $prettyVersion = LIBXML_DOTTED_VERSION; break; From 42e88ac27afd15293c6a7bbfe8406364c32ade03 Mon Sep 17 00:00:00 2001 From: Shalvah Date: Mon, 26 Nov 2018 12:57:38 +0100 Subject: [PATCH 314/580] Add interactive option to install dependencies after running init command (#7521) * Add interactive option to install dependencies after running init command * Only ask to install dependencies when dependencies where defined --- src/Composer/Command/InitCommand.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 760826d22..515a16943 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -22,6 +22,7 @@ use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\ProcessExecutor; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -145,6 +146,11 @@ EOT } } } + + $question = 'Would you like to install dependencies now [yes]? '; + if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) { + $this->installDependencies($output); + } } /** @@ -767,4 +773,23 @@ EOT return array_keys(array_slice($similarPackages, 0, 5)); } + + private function installDependencies($output) + { + try { + $installCommand = $this->getApplication()->find('install'); + $installCommand->run(new ArrayInput(array()), $output); + } catch (\Exception $e) { + $this->getIO()->writeError('Could not install dependencies. Run `composer install` to see more information.'); + } + + } + + private function hasDependencies($options) + { + $requires = (array) $options['require']; + $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : array(); + + return !empty($requires) || !empty($devRequires); + } } From 6e6fb844ddb35868f04c6db9d40b6b24cc6ad31a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 26 Nov 2018 12:59:54 +0100 Subject: [PATCH 315/580] Avoid validating package name if it has a newline at the end --- src/Composer/Command/InitCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 515a16943..3022360ee 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -214,7 +214,7 @@ EOT } $name = strtolower($name); } else { - if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $name)) { + if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $name)) { throw new \InvalidArgumentException( 'The package name '.$name.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); @@ -228,7 +228,7 @@ EOT return $name; } - if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $value)) { + if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $value)) { throw new \InvalidArgumentException( 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); From acdf8f83f1c5e0cbdd16be2325206c4b42e43679 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 26 Nov 2018 13:12:18 +0100 Subject: [PATCH 316/580] Bypass version check for explicitly versioned packages in require command when --no-update is given, fixes #7800 --- src/Composer/Command/InitCommand.php | 4 ++-- src/Composer/Command/RequireCommand.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index be56b23fb..78628a085 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -381,7 +381,7 @@ EOT return $this->repos; } - protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable') + protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true) { if ($requires) { $requires = $this->normalizeRequirements($requires); @@ -404,7 +404,7 @@ EOT )); } else { // check that the specified version/constraint exists before we proceed - list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $requirement['version'], 'dev'); + list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev'); // replace package name from packagist.org $requirement['name'] = $name; diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 01439207f..262519315 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -61,7 +61,7 @@ class RequireCommand extends InitCommand <<repos->findPackage('php', '*')->getPrettyVersion(); - $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability); + $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update')); $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; From 55c40e8853038e337364c3205cab9ad1832a2a3f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 26 Nov 2018 13:43:24 +0100 Subject: [PATCH 317/580] Add note about CS fixes to contribution notes --- .github/CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5f85c6bd9..a0fd0ce54 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,6 +21,11 @@ If your issue involves installing, updating or resolving dependencies, the chance of us being able to reproduce your issue will be much higher if you share your `composer.json` with us. +Coding Style Fixes +------------------ + +We do not accept CS fixes pull requests. Fixes are done by the project maintainers when appropriate to avoid causing too many unnecessary conflicts between branches and pull requests. + Security Reports ---------------- From b89daf5322711ac154d5583ce4443588e43c1716 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 26 Nov 2018 19:52:05 +0100 Subject: [PATCH 318/580] Add support.chat to schema, refs #7714 --- res/composer-schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/composer-schema.json b/res/composer-schema.json index 2ff405f1e..ce80c209b 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -490,6 +490,11 @@ "description": "IRC channel for support, as irc://server/channel.", "format": "uri" }, + "chat": { + "type": "string", + "description": "URL to the support chat.", + "format": "uri" + }, "source": { "type": "string", "description": "URL to browse or download the sources.", From 66d84f60c6e6c869ba739db239f23366c66cb144 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 26 Nov 2018 20:09:26 +0100 Subject: [PATCH 319/580] Fix pattern matching for remove wildcard, refs #7715 --- src/Composer/Command/RemoveCommand.php | 5 +++-- src/Composer/Installer.php | 18 +++--------------- src/Composer/Package/BasePackage.php | 13 +++++++++++++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 9646d6db1..27be1a0ca 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -22,6 +22,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; +use Composer\Package\BasePackage; /** * @author Pierre du Plessis @@ -100,11 +101,11 @@ EOT $json->removeLink($altType, $composer[$altType][$package]); } } - } elseif (isset($composer[$type]) && $matches = preg_grep('#^'.$package.'#', array_keys($composer[$type]))) { + } elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) { foreach ($matches as $matchedPackage) { $json->removeLink($type, $matchedPackage); } - } elseif (isset($composer[$altType]) && $matches = preg_grep('#^'.$package.'#', array_keys($composer[$altType]))) { + } elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) { foreach ($matches as $matchedPackage) { $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 2ace251af..bd0d22e3c 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -33,6 +33,7 @@ use Composer\Installer\NoopInstaller; use Composer\Installer\SuggestedPackagesReporter; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; +use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\Loader\ArrayLoader; @@ -1254,7 +1255,7 @@ class Installer } foreach ($this->updateWhitelist as $whiteListedPattern => $void) { - $patternRegexp = $this->packageNameToRegexp($whiteListedPattern); + $patternRegexp = BasePackage::packageNameToRegexp($whiteListedPattern); if (preg_match($patternRegexp, $package->getName())) { return true; } @@ -1263,19 +1264,6 @@ class Installer return false; } - /** - * Build a regexp from a package name, expanding * globs as required - * - * @param string $whiteListedPattern - * @return string - */ - private function packageNameToRegexp($whiteListedPattern) - { - $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); - - return "{^" . $cleanedWhiteListedPattern . "$}i"; - } - /** * @param array $links * @return array @@ -1341,7 +1329,7 @@ class Installer // check if the name is a glob pattern that did not match directly if (!$nameMatchesRequiredPackage) { - $whitelistPatternRegexp = $this->packageNameToRegexp($packageName); + $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { $nameMatchesRequiredPackage = true; diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 1e3081c7e..65ea6860f 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -234,4 +234,17 @@ abstract class BasePackage implements PackageInterface $this->repository = null; $this->id = -1; } + + /** + * Build a regexp from a package name, expanding * globs as required + * + * @param string $whiteListedPattern + * @return string + */ + public static function packageNameToRegexp($whiteListedPattern) + { + $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); + + return "{^" . $cleanedWhiteListedPattern . "$}i"; + } } From 17fd933fd59fc5c8dad6fffb77f795739293b9ec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 27 Nov 2018 12:22:32 +0100 Subject: [PATCH 320/580] Update dependencies --- composer.lock | 92 +++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/composer.lock b/composer.lock index db4aacb10..b1fcfa36d 100644 --- a/composer.lock +++ b/composer.lock @@ -126,16 +126,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b" + "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/cb17687e9f936acd7e7245ad3890f953770dec1b", - "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", "shasum": "" }, "require": { @@ -183,7 +183,7 @@ "spdx", "validator" ], - "time": "2018-04-30T10:33:04+00:00" + "time": "2018-11-01T09:45:54+00:00" }, { "name": "composer/xdebug-handler", @@ -297,16 +297,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -340,7 +340,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "seld/jsonlint", @@ -437,16 +437,16 @@ }, { "name": "symfony/console", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc" + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", - "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", "shasum": "" }, "require": { @@ -494,20 +494,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-11-20T15:55:20+00:00" }, { "name": "symfony/debug", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "cbb8a5f212148964efbc414838c527229f9951b7" + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/cbb8a5f212148964efbc414838c527229f9951b7", - "reference": "cbb8a5f212148964efbc414838c527229f9951b7", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", "shasum": "" }, "require": { @@ -551,20 +551,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-08-03T09:45:57+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c" + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/0b252f4e25b7da17abb5a98eb60755b71d082c9c", - "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080", "shasum": "" }, "require": { @@ -601,20 +601,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-08-07T09:12:42+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/finder", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e" + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f0de0b51913eb2caab7dfed6413b87e14fca780e", - "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", "shasum": "" }, "require": { @@ -650,11 +650,11 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -712,16 +712,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -767,20 +767,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/process", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4be278e19064c3492095de50c9e375caae569ae1" + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4be278e19064c3492095de50c9e375caae569ae1", - "reference": "4be278e19064c3492095de50c9e375caae569ae1", + "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8", "shasum": "" }, "require": { @@ -816,7 +816,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-08-03T09:45:57+00:00" + "time": "2018-11-11T11:18:13+00:00" } ], "packages-dev": [ @@ -1736,16 +1736,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.45", + "version": "v2.8.48", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "fbf876678e29dc634430dcf0096e216eb0004467" + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/fbf876678e29dc634430dcf0096e216eb0004467", - "reference": "fbf876678e29dc634430dcf0096e216eb0004467", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", "shasum": "" }, "require": { @@ -1782,7 +1782,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:03:18+00:00" + "time": "2018-11-11T11:18:13+00:00" } ], "aliases": [], From 489e0d4b124905a26310c9026d83eb82eb0e57ce Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 27 Nov 2018 14:26:03 +0100 Subject: [PATCH 321/580] Add support for imagemagick <3.3, refs #7762 --- src/Composer/Repository/PlatformRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 5ebb6c9db..1e680e3b5 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -164,7 +164,7 @@ class PlatformRepository extends ArrayRepository $reflector->info(); $output = ob_get_clean(); - preg_match('/^Imagick using ImageMagick library version => ImageMagick ([\d.]+)-(\d+)/m', $output, $matches); + preg_match('/^(Imagick using ImageMagick library version|ImageMagick version) => ImageMagick ([\d.]+)-(\d+)/m', $output, $matches); $prettyVersion = "{$matches[1]}.{$matches[2]}"; break; From 5ce5560040052bcb6a23bb5de2eb5e7c9ead10e4 Mon Sep 17 00:00:00 2001 From: meyerbaptiste Date: Tue, 27 Nov 2018 16:27:01 +0100 Subject: [PATCH 322/580] Fix support for imagemagick <3.3, refs #7762 --- src/Composer/Repository/PlatformRepository.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 1e680e3b5..6d6e04d2f 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -158,15 +158,10 @@ class PlatformRepository extends ArrayRepository break; case 'imagick': - $reflector = new \ReflectionExtension('imagick'); - - ob_start(); - $reflector->info(); - $output = ob_get_clean(); - - preg_match('/^(Imagick using ImageMagick library version|ImageMagick version) => ImageMagick ([\d.]+)-(\d+)/m', $output, $matches); + $imagick = new \Imagick(); + $imageMagickVersion = $imagick->getVersion(); + preg_match('/^ImageMagick ([\d.]+)-(\d+)/', $imageMagickVersion['versionString'], $matches); $prettyVersion = "{$matches[1]}.{$matches[2]}"; - break; case 'libxml': From 7ab633a2af81ba12c5200c98d172f6467b9c1669 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 28 Nov 2018 08:44:45 +0100 Subject: [PATCH 323/580] Prepare 1.8.0 changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e13cf55..ec7c72ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +### [1.8.0] 2018-11-XX + + * Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before + * Added support for removing packages using a wildcard with the `remove` command, e.g. `composer remove foo/*` + * Added `chat` to the list of `support` channels you can list in composer.json + * Added signal handling on require command to restore the composer.json in case of abort + * Added `--ignore` to `outdated` command to pass one or more packages that you do not want to be listed + * Added `--no-dev` to `check-platform-reqs` command to skip dev requirements even if they are installed + * Added support for running plugin commands from sub-directories within a project much like other Composer commands + * Added support for running Composer via phpdbg + * Added `lib-imagick` platform package + * Fixed validate command always checking for disabled checks when used with `--strict` + ### [1.7.3] 2018-11-01 * Fixed handling of replace/conflict rules. This may affect dependency resolution in some edge cases. @@ -696,6 +709,7 @@ * Initial release +[1.8.0]: https://github.com/composer/composer/compare/1.7.3...1.8.0 [1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3 [1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2 [1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1 From ab165cfcd6c00a46dbfb712807086fcec786f166 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Thu, 29 Nov 2018 14:25:01 +0000 Subject: [PATCH 324/580] Update xdebug-handler, fixes #7807 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index d4e7f847f..13638dbbc 100644 --- a/composer.lock +++ b/composer.lock @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "dc523135366eb68f22268d069ea7749486458562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", + "reference": "dc523135366eb68f22268d069ea7749486458562", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-08-31T19:20:00+00:00" + "time": "2018-11-29T10:59:02+00:00" }, { "name": "justinrainbow/json-schema", From 02ee50ac1d240dbbb3507734ef20dacb8094b000 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 3 Dec 2018 10:21:52 +0100 Subject: [PATCH 325/580] Prepare 1.8.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7c72ad4..cf06a0fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### [1.8.0] 2018-11-XX +### [1.8.0] 2018-12-03 * Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before * Added support for removing packages using a wildcard with the `remove` command, e.g. `composer remove foo/*` From 4301c19ce3b2468ad841ca9f5f35ca5558fb005f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 3 Dec 2018 10:39:36 +0100 Subject: [PATCH 326/580] Update target for master --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3a1efaa17..41048903b 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { From bf33eec912fcdd2cdfc903b5656596ad0202c718 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 3 Dec 2018 10:59:04 +0100 Subject: [PATCH 327/580] Fix tests --- tests/Composer/Test/EventDispatcher/EventDispatcherTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index c3c20e7e8..7786e7807 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -284,7 +284,7 @@ class EventDispatcherTest extends TestCase return array(); })); - $dispatcher->dispatch('helloWorld', new CommandEvent('helloWorld', $composer, $io)); + $dispatcher->dispatch('helloWorld', new ScriptEvent('helloWorld', $composer, $io)); $expected = "> helloWorld: @hello World".PHP_EOL. "> hello: echo Hello " .escapeshellarg('World').PHP_EOL; From 554805197766b5f90d711e233706a92634f84536 Mon Sep 17 00:00:00 2001 From: Ahammar Yassine Date: Tue, 4 Dec 2018 16:03:16 +0100 Subject: [PATCH 328/580] Ask confirmation when is run as admin --- src/Composer/Console/Application.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index e6ff7da9d..9478561fb 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -207,6 +207,12 @@ class Application extends BaseApplication if (function_exists('posix_getuid') && posix_getuid() === 0) { if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); + + if ($io->isInteractive()) { + if (!$io->askConfirmation('Continue as root/super user [yes]? ', true)) { + exit(0); + } + } } if ($uid = (int) getenv('SUDO_UID')) { // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on From 0fd4ef6d8eaa29d887704d0fcc0eaf2b302dddbe Mon Sep 17 00:00:00 2001 From: Ahammar Yassine Date: Tue, 4 Dec 2018 17:47:45 +0100 Subject: [PATCH 329/580] Ask confirmation when is run as admin Use return instead of exit for the Application to run cleanly --- src/Composer/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 9478561fb..c148c73fa 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -210,7 +210,7 @@ class Application extends BaseApplication if ($io->isInteractive()) { if (!$io->askConfirmation('Continue as root/super user [yes]? ', true)) { - exit(0); + return 0; } } } From b4fae00db24cc08ad5d5c3b5e2dec8c6156d5d39 Mon Sep 17 00:00:00 2001 From: Ahammar Yassine Date: Tue, 4 Dec 2018 18:54:57 +0100 Subject: [PATCH 330/580] Change return code to 1 --- src/Composer/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index c148c73fa..3398cef69 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -210,7 +210,7 @@ class Application extends BaseApplication if ($io->isInteractive()) { if (!$io->askConfirmation('Continue as root/super user [yes]? ', true)) { - return 0; + return 1; } } } From 3c01faf0e2823ce00c81b839977cae14567c42d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Auri=C3=A8res?= Date: Thu, 13 Dec 2018 10:26:29 +0100 Subject: [PATCH 331/580] Use parameter with default value to set schema file path. --- src/Composer/Json/JsonFile.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index b84791420..45d638a96 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -34,6 +34,8 @@ class JsonFile const JSON_PRETTY_PRINT = 128; const JSON_UNESCAPED_UNICODE = 256; + const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; + private $path; private $rfs; private $io; @@ -144,10 +146,11 @@ class JsonFile * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant + * @param string $schemaFile a path to the schema file * @throws JsonValidationException * @return bool true on success */ - public function validateSchema($schema = self::STRICT_SCHEMA) + public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = self::COMPOSER_SCHEMA_PATH) { $content = file_get_contents($this->path); $data = json_decode($content); @@ -156,8 +159,6 @@ class JsonFile self::validateSyntax($content, $this->path); } - $schemaFile = __DIR__ . '/../../../res/composer-schema.json'; - // Prepend with file:// only when not using a special schema already (e.g. in the phar) if (false === strpos($schemaFile, '://')) { $schemaFile = 'file://' . $schemaFile; From a8f27bf097448160cd6a9433ad3398afde3c9689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Auri=C3=A8res?= Date: Thu, 13 Dec 2018 11:36:57 +0100 Subject: [PATCH 332/580] Fix constant usage to be compatible with PHP 5.3 --- src/Composer/Json/JsonFile.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 45d638a96..ad73a8169 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -34,7 +34,7 @@ class JsonFile const JSON_PRETTY_PRINT = 128; const JSON_UNESCAPED_UNICODE = 256; - const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json'; + const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json'; private $path; private $rfs; @@ -150,7 +150,7 @@ class JsonFile * @throws JsonValidationException * @return bool true on success */ - public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = self::COMPOSER_SCHEMA_PATH) + public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null) { $content = file_get_contents($this->path); $data = json_decode($content); @@ -159,6 +159,10 @@ class JsonFile self::validateSyntax($content, $this->path); } + if (null === $schemaFile) { + $schemaFile = __DIR__ . self::COMPOSER_SCHEMA_PATH; + } + // Prepend with file:// only when not using a special schema already (e.g. in the phar) if (false === strpos($schemaFile, '://')) { $schemaFile = 'file://' . $schemaFile; From 6725d1d244836b6a1a199278c93f3531d9ba4823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Auri=C3=A8res?= Date: Thu, 13 Dec 2018 11:39:20 +0100 Subject: [PATCH 333/580] Fix docblock. --- src/Composer/Json/JsonFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index ad73a8169..f2d950004 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -146,7 +146,7 @@ class JsonFile * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant - * @param string $schemaFile a path to the schema file + * @param string|null $schemaFile a path to the schema file * @throws JsonValidationException * @return bool true on success */ From 767462b4095d655145c8e71f801c9f2c6f9ad9b3 Mon Sep 17 00:00:00 2001 From: bugreportuser <37939393+bugreportuser@users.noreply.github.com> Date: Thu, 13 Dec 2018 12:15:45 -0600 Subject: [PATCH 334/580] Move config check after config read --- src/Composer/Factory.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 1aac934a1..6df73ceb3 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -164,6 +164,16 @@ class Factory 'data-dir' => self::getDataDir($home), ))); + // load global config + $file = new JsonFile($config->get('home').'/config.json'); + if ($file->exists()) { + if ($io && $io->isDebug()) { + $io->writeError('Loading config file ' . $file->getPath()); + } + $config->merge($file->read()); + } + $config->setConfigSource(new JsonConfigSource($file)); + $htaccessProtect = (bool) $config->get('htaccess-protect'); if ($htaccessProtect) { // Protect directory against web access. Since HOME could be @@ -180,16 +190,6 @@ class Factory } } - // load global config - $file = new JsonFile($config->get('home').'/config.json'); - if ($file->exists()) { - if ($io && $io->isDebug()) { - $io->writeError('Loading config file ' . $file->getPath()); - } - $config->merge($file->read()); - } - $config->setConfigSource(new JsonConfigSource($file)); - // load global auth file $file = new JsonFile($config->get('home').'/auth.json'); if ($file->exists()) { From 2739fc05e9c40e7bae15fc40b229890db086ea3e Mon Sep 17 00:00:00 2001 From: bugreportuser <37939393+bugreportuser@users.noreply.github.com> Date: Thu, 13 Dec 2018 12:22:31 -0600 Subject: [PATCH 335/580] Read htaccess-protect as a bool --- src/Composer/Config.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 7b4220724..ad820ce3a 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -216,7 +216,6 @@ class Config case 'cache-vcs-dir': case 'cafile': case 'capath': - case 'htaccess-protect': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); @@ -230,6 +229,13 @@ class Config return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); + case 'htaccess-protect': + $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT'); + if (false === $value) { + $value = $this->config[$key]; + } + return $value !== 'false' && (bool) $value; + case 'cache-ttl': return (int) $this->config[$key]; From 618122f8979bb67764c4d6a733f9ec07833ba20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Mon, 24 Dec 2018 10:45:06 +0100 Subject: [PATCH 336/580] Fix: Keep environment variables sorted by name --- doc/03-cli.md | 156 +++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 74374ec6b..300d4837f 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -796,58 +796,40 @@ COMPOSER=composer-other.json php composer.phar install The generated lock file will use the same name: `composer-other.lock` in this example. -### COMPOSER_ROOT_VERSION +### COMPOSER_ALLOW_SUPERUSER -By setting this var you can specify the version of the root package, if it can -not be guessed from VCS info and is not present in `composer.json`. +If set to 1, this env disables the warning about running commands as root/super user. +It also disables automatic clearing of sudo sessions, so you should really only set this +if you use Composer as super user at all times like in docker containers. -### COMPOSER_VENDOR_DIR +### COMPOSER_AUTH -By setting this var you can make Composer install the dependencies into a -directory other than `vendor`. +The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable. +The contents of the variable should be a JSON formatted object containing http-basic, +github-oauth, bitbucket-oauth, ... objects as needed, and following the +[spec from the config](06-config.md#gitlab-oauth). ### COMPOSER_BIN_DIR By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md)) directory to something other than `vendor/bin`. -### http_proxy or HTTP_PROXY +### COMPOSER_CACHE_DIR -If you are using Composer from behind an HTTP proxy, you can use the standard -`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy. -Many operating systems already set this variable for you. +The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, +which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. -Using `http_proxy` (lowercased) or even defining both might be preferable since -some tools like git or curl will only use the lower-cased `http_proxy` version. -Alternatively you can also define the git proxy using -`git config --global http.proxy `. +By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and +`C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. -If you are using Composer in a non-CLI context (i.e. integration into a CMS or -similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY` -environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further -details. +### COMPOSER_CAFILE -### no_proxy or NO_PROXY +By setting this environmental value, you can set a path to a certificate bundle +file to be used during SSL/TLS peer verification. -If you are behind a proxy and would like to disable it for certain domains, you -can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of -domains the proxy should *not* be used for. +### COMPOSER_DISCARD_CHANGES -The env var accepts domains, IP addresses, and IP address blocks in CIDR -notation. You can restrict the filter to a particular port (e.g. `:80`). You -can also set it to `*` to ignore the proxy for all HTTP requests. - -### HTTP_PROXY_REQUEST_FULLURI - -If you use a proxy but it does not support the request_fulluri flag, then you -should set this env var to `false` or `0` to prevent Composer from setting the -request_fulluri option. - -### HTTPS_PROXY_REQUEST_FULLURI - -If you use a proxy but it does not support the request_fulluri flag for HTTPS -requests, then you should set this env var to `false` or `0` to prevent Composer -from setting the request_fulluri option. +This env var controls the [`discard-changes`](06-config.md#discard-changes) config option. ### COMPOSER_HOME @@ -873,45 +855,10 @@ This file allows you to set [repositories](05-repositories.md) and In case global configuration matches _local_ configuration, the _local_ configuration in the project's `composer.json` always wins. -### COMPOSER_CACHE_DIR +### COMPOSER_HTACCESS_PROTECT -The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, -which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. - -By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and -`C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. - -### COMPOSER_PROCESS_TIMEOUT - -This env var controls the time Composer waits for commands (such as git -commands) to finish executing. The default value is 300 seconds (5 minutes). - -### COMPOSER_CAFILE - -By setting this environmental value, you can set a path to a certificate bundle -file to be used during SSL/TLS peer verification. - -### COMPOSER_AUTH - -The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable. -The contents of the variable should be a JSON formatted object containing http-basic, -github-oauth, bitbucket-oauth, ... objects as needed, and following the -[spec from the config](06-config.md#gitlab-oauth). - -### COMPOSER_DISCARD_CHANGES - -This env var controls the [`discard-changes`](06-config.md#discard-changes) config option. - -### COMPOSER_NO_INTERACTION - -If set to 1, this env var will make Composer behave as if you passed the -`--no-interaction` flag to every command. This can be set on build boxes/CI. - -### COMPOSER_ALLOW_SUPERUSER - -If set to 1, this env disables the warning about running commands as root/super user. -It also disables automatic clearing of sudo sessions, so you should really only set this -if you use Composer as super user at all times like in docker containers. +Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the +composer home, cache, and data directories. ### COMPOSER_MEMORY_LIMIT @@ -923,9 +870,62 @@ If set to 1, this env changes the default path repository strategy to `mirror` i of `symlink`. As it is the default strategy being set it can still be overwritten by repository options. -### COMPOSER_HTACCESS_PROTECT +### COMPOSER_NO_INTERACTION -Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the -composer home, cache, and data directories. +If set to 1, this env var will make Composer behave as if you passed the +`--no-interaction` flag to every command. This can be set on build boxes/CI. + +### COMPOSER_PROCESS_TIMEOUT + +This env var controls the time Composer waits for commands (such as git +commands) to finish executing. The default value is 300 seconds (5 minutes). + +### COMPOSER_ROOT_VERSION + +By setting this var you can specify the version of the root package, if it can +not be guessed from VCS info and is not present in `composer.json`. + +### COMPOSER_VENDOR_DIR + +By setting this var you can make Composer install the dependencies into a +directory other than `vendor`. + +### http_proxy or HTTP_PROXY + +If you are using Composer from behind an HTTP proxy, you can use the standard +`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy. +Many operating systems already set this variable for you. + +Using `http_proxy` (lowercased) or even defining both might be preferable since +some tools like git or curl will only use the lower-cased `http_proxy` version. +Alternatively you can also define the git proxy using +`git config --global http.proxy `. + +If you are using Composer in a non-CLI context (i.e. integration into a CMS or +similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY` +environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further +details. + +### HTTP_PROXY_REQUEST_FULLURI + +If you use a proxy but it does not support the request_fulluri flag, then you +should set this env var to `false` or `0` to prevent Composer from setting the +request_fulluri option. + +### HTTPS_PROXY_REQUEST_FULLURI + +If you use a proxy but it does not support the request_fulluri flag for HTTPS +requests, then you should set this env var to `false` or `0` to prevent Composer +from setting the request_fulluri option. + +### no_proxy or NO_PROXY + +If you are behind a proxy and would like to disable it for certain domains, you +can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of +domains the proxy should *not* be used for. + +The env var accepts domains, IP addresses, and IP address blocks in CIDR +notation. You can restrict the filter to a particular port (e.g. `:80`). You +can also set it to `*` to ignore the proxy for all HTTP requests. ← [Libraries](02-libraries.md) | [Schema](04-schema.md) → From e5989fcfe0c8c24e1378215b15463e7e095148e1 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 26 Dec 2018 20:37:46 +0100 Subject: [PATCH 337/580] adding PHP_BINARY as env var to script execution --- src/Composer/EventDispatcher/EventDispatcher.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 0fb978fd4..ee02381e5 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -244,6 +244,12 @@ class EventDispatcher if (substr($exec, 0, 5) === '@php ') { $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5); + } else { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if ($phpPath) { + putenv('PHP_BINARY=' . $phpPath); + } } if (0 !== ($exitCode = $this->process->execute($exec))) { From 894b3da970c92360f26004eb5382e600e488e1fe Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 26 Dec 2018 21:21:45 +0100 Subject: [PATCH 338/580] updating lock file hash --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 957382bc6..6d0d93512 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e46280c4cfd37bf3ec8be36095feb20e", + "content-hash": "0367be765bb2ea718da11dbb9b3ed793", "packages": [ { "name": "composer/ca-bundle", From 4fc305456aa8e940cfdbfe4b8bb7810f1c1361a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:12:27 +0100 Subject: [PATCH 339/580] Enhancement: Validate composer.json on Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c1511b6c3..ddd7917de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,7 @@ before_install: # disable default memory limit - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo memory_limit = -1 >> $INI + - composer validate install: # flags to pass to install From 9f2dd5c612d3d2d75815dc0f4291201775d90ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:38:07 +0100 Subject: [PATCH 340/580] Fix: Consistently indent with 2 spaces --- .travis.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index c1511b6c3..1ff8a5221 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,33 +37,33 @@ matrix: - php: nightly before_install: - # disable xdebug if available - - phpenv config-rm xdebug.ini || echo "xdebug not available" - # disable default memory limit - - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - echo memory_limit = -1 >> $INI + # disable xdebug if available + - phpenv config-rm xdebug.ini || echo "xdebug not available" + # disable default memory limit + - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - echo memory_limit = -1 >> $INI install: - # flags to pass to install - - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" - # update deps to latest in case of high deps build - - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi - # install dependencies using system provided composer binary - - composer install $flags - # install dependencies using composer from source - - bin/composer install $flags + # flags to pass to install + - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" + # update deps to latest in case of high deps build + - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi + # install dependencies using system provided composer binary + - composer install $flags + # install dependencies using composer from source + - bin/composer install $flags before_script: - # make sure git tests do not complain about user/email not being set - - git config --global user.name travis-ci - - git config --global user.email travis@example.com + # make sure git tests do not complain about user/email not being set + - git config --global user.name travis-ci + - git config --global user.email travis@example.com script: - # run test suite directories in parallel using GNU parallel - - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' + # run test suite directories in parallel using GNU parallel + - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' before_deploy: - - php -d phar.readonly=0 bin/compile + - php -d phar.readonly=0 bin/compile deploy: provider: releases From 8f3946fc3aaf87202a19e62728cd720dc6e61b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:40:17 +0100 Subject: [PATCH 341/580] Enhancement: Reference phpunit.xsd as installed with composer --- phpunit.xml.dist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4c6749521..20eaefb12 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,8 @@ - Date: Wed, 2 Jan 2019 15:45:25 +0100 Subject: [PATCH 342/580] Enhancement: Explicitly configure build matrix to maintain order --- .travis.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index c1511b6c3..df2110057 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,22 +16,20 @@ addons: packages: - parallel -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - nightly - matrix: include: - php: 5.3 dist: precise + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 - php: 7.3 env: deps=high + - php: nightly fast_finish: true allow_failures: - php: nightly From 05e86c2c071e1c9b7ca1bdfe09b0b44c5aaf729a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:41:33 +0100 Subject: [PATCH 343/580] Fix: Remove non-existent attribute --- phpunit.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 20eaefb12..bb3b94774 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,7 +10,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="tests/bootstrap.php" > From bd2a46cf5dcc33af229834a4b817383252d14ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:52:53 +0100 Subject: [PATCH 344/580] Fix: Remove sudo configuration --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c1511b6c3..e9bc30034 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: php -sudo: false - dist: trusty git: From 4b2e63704bffe8d5a3699499d660d014078f0318 Mon Sep 17 00:00:00 2001 From: fancyweb Date: Wed, 2 Jan 2019 23:36:53 +0100 Subject: [PATCH 345/580] fix(application): use precise helper set --- src/Composer/Console/Application.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index e6ff7da9d..c25ee3fe0 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -17,6 +17,8 @@ use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -111,7 +113,9 @@ class Application extends BaseApplication { $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); - $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); + $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array( + new QuestionHelper(), + ))); ErrorHandler::register($io); // switch working dir From 45a7b8e1c2e68a450ac053d0f96bc5df3d7ef399 Mon Sep 17 00:00:00 2001 From: fancyweb Date: Wed, 2 Jan 2019 23:38:11 +0100 Subject: [PATCH 346/580] feat(buffer-io): add question helper set --- src/Composer/IO/BufferIO.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index d47d4eaa5..83a1a9820 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -12,6 +12,7 @@ namespace Composer\IO; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Input\StringInput; @@ -34,7 +35,9 @@ class BufferIO extends ConsoleIO $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter); - parent::__construct($input, $output, new HelperSet(array())); + parent::__construct($input, $output, new HelperSet(array( + new QuestionHelper(), + ))); } public function getOutput() From db4ec12a24e0091c861c43da7e05cd1df1a34d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 2 Jan 2019 15:16:42 +0100 Subject: [PATCH 347/580] Enhancement: Add .editorconfig --- .editorconfig | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..033f8a6da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.yml] +indent_size = 2 From ea48bad401339a650bb8a445238bca747b3b72c9 Mon Sep 17 00:00:00 2001 From: Pete Cooper Date: Thu, 3 Jan 2019 17:05:46 +0000 Subject: [PATCH 348/580] Fix spelling mistake on Cygwin --- src/Composer/Installer/BinaryInstaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 0f709f60b..a14755bf1 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -197,7 +197,7 @@ class BinaryInstaller dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd) if [ -d /proc/cygdrive ] && [[ \$(which php) == \$(readlink -n /proc/cygdrive)/* ]]; then - # We are in Cgywin using Windows php, so the path must be translated + # We are in Cygwin using Windows php, so the path must be translated dir=\$(cygpath -m "\$dir"); fi From a9d6068c57652f8c5290d4668f1377387f308a76 Mon Sep 17 00:00:00 2001 From: fancyweb Date: Thu, 3 Jan 2019 10:35:51 +0100 Subject: [PATCH 349/580] feat(buffer-io): add the possibility to set user inputs for interactive questions --- src/Composer/IO/BufferIO.php | 24 ++++++++++++++ tests/Composer/Test/IO/BufferIOTest.php | 43 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/Composer/Test/IO/BufferIOTest.php diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index d47d4eaa5..742c3f8a4 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -14,6 +14,7 @@ namespace Composer\IO; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Helper\HelperSet; @@ -56,4 +57,27 @@ class BufferIO extends ConsoleIO return $output; } + + public function setUserInputs(array $inputs) + { + if (!$this->input instanceof StreamableInputInterface) { + throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $this->input->setStream($this->createStream($inputs)); + $this->input->setInteractive(true); + } + + private function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.PHP_EOL); + } + + rewind($stream); + + return $stream; + } } diff --git a/tests/Composer/Test/IO/BufferIOTest.php b/tests/Composer/Test/IO/BufferIOTest.php new file mode 100644 index 000000000..013a3c100 --- /dev/null +++ b/tests/Composer/Test/IO/BufferIOTest.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\IO; + +use Composer\IO\BufferIO; +use Composer\Test\TestCase; +use Symfony\Component\Console\Input\StreamableInputInterface; + +class BufferIOTest extends TestCase +{ + public function testSetUserInputs() + { + $bufferIO = new BufferIO(); + + $refl = new \ReflectionProperty($bufferIO, 'input'); + $refl->setAccessible(true); + $input = $refl->getValue($bufferIO); + + if (!$input instanceof StreamableInputInterface) { + $this->setExpectedException('\RuntimeException', 'Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $bufferIO->setUserInputs(array( + 'yes', + 'no', + '', + )); + + $this->assertTrue($bufferIO->askConfirmation('Please say yes!', 'no')); + $this->assertFalse($bufferIO->askConfirmation('Now please say no!', 'yes')); + $this->assertSame('default', $bufferIO->ask('Empty string last', 'default')); + } +} From 750692227fde2d9a9e97d116c8f16365ff641dea Mon Sep 17 00:00:00 2001 From: Dzhuneyt Ahmed Date: Mon, 7 Jan 2019 17:46:33 +0200 Subject: [PATCH 350/580] Added no-cache argument to "composer install" --- src/Composer/Command/InstallCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index cc590d8c9..b2d2e4a54 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -45,6 +45,7 @@ class InstallCommand extends BaseCommand new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), + new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), 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('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), From 8c30b12bd9002de9fc65eafe1b2cbdea764b6857 Mon Sep 17 00:00:00 2001 From: Dzhuneyt Ahmed Date: Mon, 7 Jan 2019 18:36:21 +0200 Subject: [PATCH 351/580] Added no-cache argument to "composer install" and "composer update" --- src/Composer/Command/InstallCommand.php | 15 ++++++++++++++- src/Composer/Command/UpdateCommand.php | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index b2d2e4a54..a2e55b6c8 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -15,6 +15,7 @@ namespace Composer\Command; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Util\Platform; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -40,12 +41,12 @@ class InstallCommand extends BaseCommand new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), + new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), - new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), 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('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), @@ -94,6 +95,18 @@ EOT $install = Installer::create($io, $composer); $config = $composer->getConfig(); + + if ($input->getOption('no-cache')) { + $io->write('Skipping cache directory'); + $config->merge( + array( + 'config' => array( + 'cache-dir' => Platform::isWindows() ? 'nul' : '/dev/null', + ) + ) + ); + } + list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 34420b747..a912cc373 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -17,6 +17,7 @@ use Composer\Installer; use Composer\IO\IOInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Util\Platform; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -44,6 +45,7 @@ class UpdateCommand extends BaseCommand new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), + new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), @@ -128,6 +130,18 @@ EOT $install = Installer::create($io, $composer); $config = $composer->getConfig(); + + if ($input->getOption('no-cache')) { + $io->write('Skipping cache directory'); + $config->merge( + array( + 'config' => array( + 'cache-dir' => Platform::isWindows() ? 'nul' : '/dev/null', + ) + ) + ); + } + list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); From cc4e5ec281391db709ddb607dfb937db71dbac2e Mon Sep 17 00:00:00 2001 From: Marko Kaznovac Date: Thu, 10 Jan 2019 15:00:56 +0100 Subject: [PATCH 352/580] add compile script call to composer.json expose call for compiling `composer.phar` in composer json, along with description --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 41048903b..5ed969969 100644 --- a/composer.json +++ b/composer.json @@ -72,8 +72,13 @@ "bin/composer" ], "scripts": { + "compile": "@php -dphar.readonly=0 bin/compile", "test": "phpunit" }, + "scripts-descriptions": { + "compile": "Compile composer.phar", + "test": "Run all tests" + }, "support": { "issues": "https://github.com/composer/composer/issues", "irc": "irc://irc.freenode.org/composer" From 56805ecafe8597a07b4576b45e5532b9862fd65a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Sep 2018 18:58:54 +0200 Subject: [PATCH 353/580] Add HttpDownloader to wrap/replace RemoteFilesystem with a new curl multi implementation --- composer.json | 3 +- composer.lock | 46 ++- src/Composer/Downloader/FileDownloader.php | 20 +- src/Composer/Downloader/GzipDownloader.php | 6 +- src/Composer/Downloader/RarDownloader.php | 6 +- src/Composer/Downloader/XzDownloader.php | 6 +- src/Composer/Downloader/ZipDownloader.php | 6 +- src/Composer/Factory.php | 14 +- src/Composer/Plugin/PreFileDownloadEvent.php | 20 +- .../Repository/ComposerRepository.php | 220 +++++++++++--- src/Composer/Repository/RepositoryFactory.php | 6 +- src/Composer/Repository/RepositoryManager.php | 6 +- src/Composer/Util/Http/CurlDownloader.php | 282 ++++++++++++++++++ src/Composer/Util/Http/Response.php | 75 +++++ src/Composer/Util/HttpDownloader.php | 246 +++++++++++++++ src/Composer/Util/RemoteFilesystem.php | 108 +------ src/Composer/Util/StreamContextFactory.php | 105 +++++++ 17 files changed, 985 insertions(+), 190 deletions(-) create mode 100644 src/Composer/Util/Http/CurlDownloader.php create mode 100644 src/Composer/Util/Http/Response.php create mode 100644 src/Composer/Util/HttpDownloader.php diff --git a/composer.json b/composer.json index 41048903b..1b75131bc 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0" + "symfony/process": "^2.7 || ^3.0 || ^4.0", + "react/promise": "^1.2" }, "conflict": { "symfony/console": "2.8.38" diff --git a/composer.lock b/composer.lock index 957382bc6..63b8033b9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e46280c4cfd37bf3ec8be36095feb20e", + "content-hash": "d356b92e869790db1e9d2c0f4b10935e", "packages": [ { "name": "composer/ca-bundle", @@ -342,6 +342,50 @@ ], "time": "2018-11-20T15:27:04+00:00" }, + { + "name": "react/promise", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "eefff597e67ff66b719f8171480add3c91474a1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/eefff597e67ff66b719f8171480add3c91474a1e", + "reference": "eefff597e67ff66b719f8171480add3c91474a1e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "React\\Promise": "src/" + }, + "files": [ + "src/React/Promise/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "time": "2016-03-07T13:46:50+00:00" + }, { "name": "seld/jsonlint", "version": "1.7.1", diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 6596d9c8b..6b1349bb8 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -24,7 +24,7 @@ use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Util\Url as UrlUtil; /** @@ -39,7 +39,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface { protected $io; protected $config; - protected $rfs; + protected $httpDownloader; protected $filesystem; protected $cache; protected $outputProgress = true; @@ -52,16 +52,16 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface * @param IOInterface $io The IO instance * @param Config $config The config * @param EventDispatcher $eventDispatcher The event dispatcher - * @param Cache $cache Optional cache instance - * @param RemoteFilesystem $rfs The remote filesystem + * @param Cache $cache Cache instance + * @param HttpDownloader $httpDownloader The remote filesystem * @param Filesystem $filesystem The filesystem */ - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher, Cache $cache, HttpDownloader $httpDownloader, Filesystem $filesystem = null) { $this->io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; - $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader; $this->filesystem = $filesystem ?: new Filesystem(); $this->cache = $cache; @@ -125,13 +125,12 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $fileName = $this->getFileName($package, $path); $processedUrl = $this->processUrl($package, $url); - $hostname = parse_url($processedUrl, PHP_URL_HOST); - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $processedUrl); if ($this->eventDispatcher) { $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $rfs = $preFileDownloadEvent->getRemoteFilesystem(); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); try { $checksum = $package->getDistSha1Checksum(); @@ -150,7 +149,8 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $retries = 3; while ($retries--) { try { - $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); + // TODO handle this->outputProgress + $httpDownloader->copy($processedUrl, $fileName, $package->getTransportOptions()); break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index 19e4a45e1..bb86f6267 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -18,7 +18,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; /** @@ -30,10 +30,10 @@ class GzipDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); + parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index 40cd09896..c1ed8d60b 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -18,7 +18,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use RarArchive; @@ -33,10 +33,10 @@ class RarDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); + parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index 4a9b854d3..1f5947997 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -17,7 +17,7 @@ use Composer\Cache; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; /** @@ -30,11 +30,11 @@ class XzDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); + parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 6534db3d8..10518e026 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -19,7 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use Symfony\Component\Process\ExecutableFinder; use ZipArchive; @@ -36,10 +36,10 @@ class ZipDownloader extends ArchiveDownloader protected $process; private $zipArchiveObject; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); + parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); } /** diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 1aac934a1..97d507b10 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -23,7 +23,7 @@ use Composer\Repository\WritableRepositoryInterface; use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; @@ -325,7 +325,7 @@ class Factory $io->loadConfiguration($config); } - $rfs = self::createRemoteFilesystem($io, $config); + $rfs = self::createHttpDownloader($io, $config); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); @@ -451,7 +451,7 @@ class Factory * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ - public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) + public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) { $cache = null; if ($config->get('cache-files-ttl') > 0) { @@ -579,10 +579,10 @@ class Factory /** * @param IOInterface $io IO instance * @param Config $config Config instance - * @param array $options Array of options passed directly to RemoteFilesystem constructor - * @return RemoteFilesystem + * @param array $options Array of options passed directly to HttpDownloader constructor + * @return HttpDownloader */ - public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array()) + public static function createHttpDownloader(IOInterface $io, Config $config = null, $options = array()) { static $warned = false; $disableTls = false; @@ -607,7 +607,7 @@ class Factory $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options); } try { - $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls); + $remoteFilesystem = new HttpDownloader($io, $config, $remoteFilesystemOptions, $disableTls); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); diff --git a/src/Composer/Plugin/PreFileDownloadEvent.php b/src/Composer/Plugin/PreFileDownloadEvent.php index 7ae6821ce..076449484 100644 --- a/src/Composer/Plugin/PreFileDownloadEvent.php +++ b/src/Composer/Plugin/PreFileDownloadEvent.php @@ -13,7 +13,7 @@ namespace Composer\Plugin; use Composer\EventDispatcher\Event; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; /** * The pre file download event. @@ -23,7 +23,7 @@ use Composer\Util\RemoteFilesystem; class PreFileDownloadEvent extends Event { /** - * @var RemoteFilesystem + * @var HttpDownloader */ private $rfs; @@ -36,10 +36,10 @@ class PreFileDownloadEvent extends Event * Constructor. * * @param string $name The event name - * @param RemoteFilesystem $rfs + * @param HttpDownloader $rfs * @param string $processedUrl */ - public function __construct($name, RemoteFilesystem $rfs, $processedUrl) + public function __construct($name, HttpDownloader $rfs, $processedUrl) { parent::__construct($name); $this->rfs = $rfs; @@ -47,21 +47,17 @@ class PreFileDownloadEvent extends Event } /** - * Returns the remote filesystem - * - * @return RemoteFilesystem + * @return HttpDownloader */ - public function getRemoteFilesystem() + public function getHttpDownloader() { return $this->rfs; } /** - * Sets the remote filesystem - * - * @param RemoteFilesystem $rfs + * @param HttpDownloader $rfs */ - public function setRemoteFilesystem(RemoteFilesystem $rfs) + public function setHttpDownloader(HttpDownloader $rfs) { $this->rfs = $rfs; } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 09e2179d8..c4a570c75 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -21,7 +21,7 @@ use Composer\Cache; use Composer\Config; use Composer\Factory; use Composer\IO\IOInterface; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; @@ -40,7 +40,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected $url; protected $baseUrl; protected $io; - protected $rfs; + protected $httpDownloader; protected $cache; protected $notifyUrl; protected $searchUrl; @@ -60,8 +60,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private $rootData; private $hasPartialPackages; private $partialPackagesByName; + private $versionParser; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher, HttpDownloader $httpDownloader) { parent::__construct(); if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) { @@ -98,12 +99,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); - $this->loader = new ArrayLoader(); - if ($rfs && $this->options) { - $rfs = clone $rfs; - $rfs->setOptions($this->options); + $this->versionParser = new VersionParser(); + $this->loader = new ArrayLoader($this->versionParser); + if ($httpDownloader && $this->options) { + // TODO solve this somehow - should be sent a request time not on the instance + $httpDownloader = clone $httpDownloader; + $httpDownloader->setOptions($this->options); } - $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $this->config, $this->options); + $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; } @@ -129,8 +132,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($constraint); + $constraint = $this->versionParser->parseConstraints($constraint); } foreach ($this->getProviderNames() as $providerName) { @@ -161,8 +163,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $name = strtolower($name); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($constraint); + $constraint = $this->versionParser->parseConstraints($constraint); } $packages = array(); @@ -196,8 +197,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) { + if ($this->lazyProvidersUrl) { + return $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable); + } if (!$this->hasProviders()) { - // TODO build more efficient version of this return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable); } @@ -235,9 +238,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl); - $hostname = parse_url($url, PHP_URL_HOST) ?: $url; - $json = $this->rfs->getContents($hostname, $url, false); - $search = JsonFile::parseJson($json, $url); + $search = $this->httpDownloader->get($url)->decodeJson(); if (empty($search['results'])) { return array(); @@ -496,6 +497,85 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->configurePackageTransportOptions($package); } + private function loadAsyncPackages(array $packageNames, $isPackageAcceptableCallable) + { + $this->loadRootServerFile(); + + $packages = array(); + $repo = $this; + + $createPackageIfAcceptable = function ($version, $constraint) use (&$packages, $isPackageAcceptableCallable, $repo) { + if (!call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { + return; + } + + if (isset($version['version_normalized']) && $constraint && !$constraint->matches(new Constraint('==', $version['version_normalized']))) { + return; + } + + // load acceptable packages in the providers + $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); + $package->setRepository($repo); + + // if there was no version_normalized, then we need to check now for the constraint + if (!$constraint || isset($version['version_normalized']) || $constraint->matches(new Constraint('==', $package->getVersion()))) { + $packages[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + }; + + if ($this->lazyProvidersUrl) { + foreach ($packageNames as $name => $constraint) { + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + + $lastModified = null; + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + $lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null; + } + + $this->asyncFetchFile($url, $cacheKey, $lastModified) + ->then(function ($response) use (&$packages, $contents, $name, $constraint, $createPackageIfAcceptable) { + if (true === $response) { + $response = $contents; + } + + $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); + foreach ($response['packages'][$name] as $version) { + if (isset($version['versions'])) { + $baseVersion = $version; + foreach ($uniqKeys as $key) { + unset($baseVersion[$key.'s']); + } + + foreach ($version['versions'] as $index => $dummy) { + $unpackedVersion = $baseVersion; + foreach ($uniqKeys as $key) { + $unpackedVersion[$key] = $version[$key.'s'][$index]; + } + + $createPackageIfAcceptable($unpackedVersion, $constraint); + } + } else { + $createPackageIfAcceptable($version, $constraint); + } + } + }, function ($e) { + // TODO use ->done() above instead with react/promise 2.0 + var_dump('Uncaught Ex', $e->getMessage()); + }); + } + } + + $this->httpDownloader->wait(); + + return $packages; + // RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed + } + protected function loadRootServerFile() { if (null !== $this->rootData) { @@ -691,15 +771,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename); - if ($this->eventDispatcher) { - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - } + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; - $rfs = $preFileDownloadEvent->getRemoteFilesystem(); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); - $json = $rfs->getContents($hostname, $filename, false); + $response = $httpDownloader->get($filename); + $json = $response->getBody(); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow if ($this->allowSslDowngrade) { @@ -718,7 +796,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); } - $data = JsonFile::parseJson($json, $filename); + $data = $response->decodeJson(); if (!empty($data['warning'])) { $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); } @@ -728,7 +806,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($cacheKey) { if ($storeLastModifiedTime) { - $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); + $lastModifiedDate = $response->getHeader('last-modified'); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = json_encode($data); @@ -737,8 +815,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->cache->write($cacheKey, $json); } + $response->collect(); + break; } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } @@ -775,20 +859,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename); - if ($this->eventDispatcher) { - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - } + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; - $rfs = $preFileDownloadEvent->getRemoteFilesystem(); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); - $json = $rfs->getContents($hostname, $filename, false, $options); - if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) { + $response = $httpDownloader->get($filename, $options); + $json = $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { return true; } - $data = JsonFile::parseJson($json, $filename); + $data = $response->decodeJson(); if (!empty($data['warning'])) { $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); } @@ -796,7 +878,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->io->writeError('Info from '.$this->url.': '.$data['info'].''); } - $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = json_encode($data); @@ -805,6 +888,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $data; } catch (\Exception $e) { + if ($e instanceof \LogicException) { + throw $e; + } + if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } @@ -825,6 +912,69 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } + protected function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null) + { + $retries = 3; + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + $options = $lastModifiedTime ? array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))) : array(); + + $io = $this->io; + $url = $this->url; + $cache = $this->cache; + $degradedMode =& $this->degradedMode; + + $accept = function ($response) use ($io, $url, $cache, $cacheKey) { + $json = $response->getBody(); + if ($json === '' && $response->getStatusCode() === 304) { + return true; + } + + $data = $response->decodeJson(); + if (!empty($data['warning'])) { + $io->writeError('Warning from '.$url.': '.$data['warning'].''); + } + if (!empty($data['info'])) { + $io->writeError('Info from '.$url.': '.$data['info'].''); + } + + $lastModifiedDate = $response->getHeader('last-modified'); + $response->collect(); + if ($lastModifiedDate) { + $data['last-modified'] = $lastModifiedDate; + $json = JsonFile::encode($data, JsonFile::JSON_UNESCAPED_SLASHES | JsonFile::JSON_UNESCAPED_UNICODE); + } + $cache->write($cacheKey, $json); + + return $data; + }; + + $reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, $cache, &$degradedMode) { + var_dump('Caught8', $e->getMessage()); + if ($e instanceof TransportException && $e->getStatusCode() === 404) { + return false; + } + + if (--$retries) { + usleep(100000); + + return $httpDownloader->add($filename, $options)->then($accept, $reject); + } + + if (!$degradedMode) { + $io->writeError(''.$e->getMessage().''); + $io->writeError(''.$url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); + } + $degradedMode = true; + + return true; + }; + + return $httpDownloader->add($filename, $options)->then($accept, $reject); + } + /** * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url * diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index ca479a7fd..5737a5359 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -16,7 +16,7 @@ use Composer\Factory; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Json\JsonFile; /** @@ -108,10 +108,10 @@ class RepositoryFactory * @param IOInterface $io * @param Config $config * @param EventDispatcher $eventDispatcher - * @param RemoteFilesystem $rfs + * @param HttpDownloader $rfs * @return RepositoryManager */ - public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) + public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) { $rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 87b82d14d..64568514c 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -16,7 +16,7 @@ use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; /** * Repositories manager. @@ -35,7 +35,7 @@ class RepositoryManager private $eventDispatcher; private $rfs; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) { $this->io = $io; $this->config = $config; @@ -127,7 +127,7 @@ class RepositoryManager $reflMethod = new \ReflectionMethod($class, '__construct'); $params = $reflMethod->getParameters(); - if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') { + if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\HttpDownloader') { return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs); } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php new file mode 100644 index 000000000..846c41883 --- /dev/null +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -0,0 +1,282 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\CaBundle\CaBundle; +use Psr\Log\LoggerInterface; +use React\Promise\Promise; + +/** + * @author Jordi Boggiano + * @author Nicolas Grekas + */ +class CurlDownloader +{ + private $multiHandle; + private $shareHandle; + private $jobs = array(); + private $io; + private $selectTimeout = 5.0; + protected $multiErrors = array( + CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'), + CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."), + CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'), + CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!') + ); + + private static $options = array( + 'http' => array( + 'method' => CURLOPT_CUSTOMREQUEST, + 'content' => CURLOPT_POSTFIELDS, + 'proxy' => CURLOPT_PROXY, + ), + 'ssl' => array( + 'ciphers' => CURLOPT_SSL_CIPHER_LIST, + 'cafile' => CURLOPT_CAINFO, + 'capath' => CURLOPT_CAPATH, + ), + ); + + private static $timeInfo = array( + 'total_time' => true, + 'namelookup_time' => true, + 'connect_time' => true, + 'pretransfer_time' => true, + 'starttransfer_time' => true, + 'redirect_time' => true, + ); + + public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) + { + $this->io = $io; + + $this->multiHandle = $mh = curl_multi_init(); + if (function_exists('curl_multi_setopt')) { + curl_multi_setopt($mh, CURLMOPT_PIPELINING, /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3); + if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { + curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); + } + } + + if (function_exists('curl_share_init')) { + $this->shareHandle = $sh = curl_share_init(); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); + } + } + + public function download($resolve, $reject, $origin, $url, $options, $copyTo = null) + { + $ch = curl_init(); + $hd = fopen('php://temp/maxmemory:32768', 'w+b'); + + // TODO auth & other context + // TODO cleanup + + if ($copyTo && !$fd = @fopen($copyTo.'~', 'w+b')) { + // TODO throw here probably? + $copyTo = null; + } + if (!$copyTo) { + $fd = @fopen('php://temp/maxmemory:524288', 'w+b'); + } + + if (!isset($options['http']['header'])) { + $options['http']['header'] = array(); + } + + $headers = array_diff($options['http']['header'], array('Connection: close')); + + // TODO + $degradedMode = false; + if ($degradedMode) { + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + } else { + $headers[] = 'Connection: keep-alive'; + $version = curl_version(); + $features = $version['features']; + if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features)) { + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + } + } + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + //curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); // TODO increase + curl_setopt($ch, CURLOPT_WRITEHEADER, $hd); + curl_setopt($ch, CURLOPT_FILE, $fd); + if (function_exists('curl_share_init')) { + curl_setopt($ch, CURLOPT_SHARE, $this->shareHandle); + } + + foreach (self::$options as $type => $curlOptions) { + foreach ($curlOptions as $name => $curlOption) { + if (isset($options[$type][$name])) { + curl_setopt($ch, $curlOption, $options[$type][$name]); + } + } + } + + $progress = array_diff_key(curl_getinfo($ch), self::$timeInfo); + + $this->jobs[(int) $ch] = array( + 'progress' => $progress, + 'ch' => $ch, + //'callback' => $params['notification'], + 'file' => $copyTo, + 'hd' => $hd, + 'fd' => $fd, + 'resolve' => $resolve, + 'reject' => $reject, + ); + + $this->io->write('Downloading '.$url, true, IOInterface::DEBUG); + + $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $ch)); + //$params['notification'](STREAM_NOTIFY_RESOLVE, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false); + } + + public function tick() + { + // TODO check we have active handles before doing this + if (!$this->jobs) { + return; + } + + $active = true; + try { + $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); + if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { + // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select + usleep(150); + } + + while ($progress = curl_multi_info_read($this->multiHandle)) { + $h = $progress['handle']; + $i = (int) $h; + if (!isset($this->jobs[$i])) { + continue; + } + $progress = array_diff_key(curl_getinfo($h), self::$timeInfo); + $job = $this->jobs[$i]; + unset($this->jobs[$i]); + curl_multi_remove_handle($this->multiHandle, $h); + $error = curl_error($h); + $errno = curl_errno($h); + curl_close($h); + + try { + //$this->onProgress($h, $job['callback'], $progress, $job['progress']); + if ('' !== $error) { + throw new TransportException(curl_error($h)); + } + + if ($job['file']) { + if (CURLE_OK === $errno) { + fclose($job['fd']); + rename($job['file'].'~', $job['file']); + call_user_func($job['resolve'], true); + } + // TODO otherwise show error? + } else { + rewind($job['hd']); + $headers = explode("\r\n", rtrim(stream_get_contents($job['hd']))); + fclose($job['hd']); + rewind($job['fd']); + $contents = stream_get_contents($job['fd']); + fclose($job['fd']); + $this->io->writeError('['.$progress['http_code'].'] '.$progress['url'], true, IOInterface::DEBUG); + call_user_func($job['resolve'], new Response(array('url' => $progress['url']), $progress['http_code'], $headers, $contents)); + } + } catch (TransportException $e) { + fclose($job['hd']); + fclose($job['fd']); + if ($job['file']) { + @unlink($job['file'].'~'); + } + call_user_func($job['reject'], $e); + } + } + + foreach ($this->jobs as $i => $h) { + if (!isset($this->jobs[$i])) { + continue; + } + $h = $this->jobs[$i]['ch']; + $progress = array_diff_key(curl_getinfo($h), self::$timeInfo); + + if ($this->jobs[$i]['progress'] !== $progress) { + $previousProgress = $this->jobs[$i]['progress']; + $this->jobs[$i]['progress'] = $progress; + try { + //$this->onProgress($h, $this->jobs[$i]['callback'], $progress, $previousProgress); + } catch (TransportException $e) { + var_dump('Caught '.$e->getMessage());die; + unset($this->jobs[$i]); + curl_multi_remove_handle($this->multiHandle, $h); + curl_close($h); + + fclose($job['hd']); + fclose($job['fd']); + if ($job['file']) { + @unlink($job['file'].'~'); + } + call_user_func($job['reject'], $e); + } + } + } + } catch (\Exception $e) { + var_dump('Caught2', get_class($e), $e->getMessage(), $e);die; + } + +// TODO finalize / resolve +// if ($copyTo && !isset($this->exceptions[(int) $ch])) { +// $fd = fopen($copyTo, 'rb'); +// } +// + } + + private function onProgress($ch, callable $notify, array $progress, array $previousProgress) + { + if (300 <= $progress['http_code'] && $progress['http_code'] < 400) { + return; + } + if (!$previousProgress['http_code'] && $progress['http_code'] && $progress['http_code'] < 200 || 400 <= $progress['http_code']) { + $code = 403 === $progress['http_code'] ? STREAM_NOTIFY_AUTH_RESULT : STREAM_NOTIFY_FAILURE; + $notify($code, STREAM_NOTIFY_SEVERITY_ERR, curl_error($ch), $progress['http_code'], 0, 0, false); + } + if ($previousProgress['download_content_length'] < $progress['download_content_length']) { + $notify(STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['download_content_length'], false); + } + if ($previousProgress['size_download'] < $progress['size_download']) { + $notify(STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, (int) $progress['size_download'], (int) $progress['download_content_length'], false); + } + } + + private function checkCurlResult($code) + { + if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) { + throw new \RuntimeException(isset($this->multiErrors[$code]) + ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" + : 'Unexpected cURL error: ' . $code + ); + } + } +} diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php new file mode 100644 index 000000000..ff48fdb40 --- /dev/null +++ b/src/Composer/Util/Http/Response.php @@ -0,0 +1,75 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util\Http; + +use Composer\Json\JsonFile; + +class Response +{ + private $request; + private $code; + private $headers; + private $body; + + public function __construct(array $request, $code, array $headers, $body) + { + $this->request = $request; + $this->code = $code; + $this->headers = $headers; + $this->body = $body; + } + + public function getStatusCode() + { + return $this->code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function getHeader($name) + { + $value = null; + foreach ($this->headers as $header) { + if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) { + $value = $match[1]; + } elseif (preg_match('{^HTTP/}i', $header)) { + // TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary + // + // In case of redirects, http_response_headers contains the headers of all responses + // so we reset the flag when a new response is being parsed as we are only interested in the last response + $value = null; + } + } + + return $value; + } + + + public function getBody() + { + return $this->body; + } + + public function decodeJson() + { + return JsonFile::parseJson($this->body, $this->request['url']); + } + + public function collect() + { + $this->request = $this->code = $this->headers = $this->body = null; + } +} diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php new file mode 100644 index 000000000..31c615e0c --- /dev/null +++ b/src/Composer/Util/HttpDownloader.php @@ -0,0 +1,246 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; +use Composer\CaBundle\CaBundle; +use Psr\Log\LoggerInterface; +use React\Promise\Promise; + +/** + * @author Jordi Boggiano + */ +class HttpDownloader +{ + const STATUS_QUEUED = 1; + const STATUS_STARTED = 2; + const STATUS_COMPLETED = 3; + const STATUS_FAILED = 4; + + private $io; + private $config; + private $jobs = array(); + private $index; + private $progress; + private $lastProgress; + private $disableTls = false; + private $curl; + private $rfs; + private $idGen = 0; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + * @param Config $config The config + * @param array $options The options + * @param bool $disableTls + */ + public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) + { + $this->io = $io; + + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $logger = $io instanceof LoggerInterface ? $io : null; + $this->options = StreamContextFactory::getTlsDefaults($options, $logger); + } else { + $this->disableTls = true; + } + + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); + $this->config = $config; + + if (extension_loaded('curl')) { + $this->curl = new Http\CurlDownloader($io, $config, $options, $disableTls); + } + + $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); + } + + public function get($url, $options = array()) + { + list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false), true); + $this->wait($job['id']); + + return $this->getResponse($job['id']); + } + + public function add($url, $options = array()) + { + list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false)); + + return $promise; + } + + public function copy($url, $to, $options = array()) + { + list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => $to), true); + $this->wait($job['id']); + + return $this->getResponse($job['id']); + } + + public function addCopy($url, $to, $options = array()) + { + list($job, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => $to)); + + return $promise; + } + + private function addJob($request, $sync = false) + { + $job = array( + 'id' => $this->idGen++, + 'status' => self::STATUS_QUEUED, + 'request' => $request, + 'sync' => $sync, + ); + + $curl = $this->curl; + $rfs = $this->rfs; + $io = $this->io; + + $origin = $this->getOrigin($job['request']['url']); + + // TODO only send http/https through curl + if ($curl) { + $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { + // start job + $url = $job['request']['url']; + $options = $job['request']['options']; + + $job['status'] = HttpDownloader::STATUS_STARTED; + + if ($job['request']['copyTo']) { + $curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); + } else { + $curl->download($resolve, $reject, $origin, $url, $options); + } + }; + } else { + $resolver = function ($resolve, $reject) use (&$job, $rfs, $curl, $origin) { + // start job + $url = $job['request']['url']; + $options = $job['request']['options']; + + $job['status'] = HttpDownloader::STATUS_STARTED; + + if ($job['request']['copyTo']) { + if ($curl) { + $result = $curl->download($origin, $url, $options, $job['request']['copyTo']); + } else { + $result = $rfs->copy($origin, $url, $job['request']['copyTo'], false /* TODO progress */, $options); + } + + $resolve($result); + } else { + $body = $rfs->getContents($origin, $url, false /* TODO progress */, $options); + $headers = $rfs->getLastHeaders(); + $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); + + $resolve($response); + } + }; + } + + $canceler = function () {}; + + $promise = new Promise($resolver, $canceler); + $promise->then(function ($response) use (&$job) { + $job['status'] = HttpDownloader::STATUS_COMPLETED; + $job['response'] = $response; + // TODO look for more jobs to start once we throttle to max X jobs + }, function ($e) use ($io, &$job) { + var_dump(__CLASS__ . __LINE__); + var_dump(gettype($e)); + var_dump($e->getMessage()); + die; + $job['status'] = HttpDownloader::STATUS_FAILED; + $job['exception'] = $e; + }); + $this->jobs[$job['id']] =& $job; + + return array($job, $promise); + } + + public function wait($index = null, $progress = false) + { + while (true) { + if ($this->curl) { + $this->curl->tick(); + } + + if (null !== $index) { + if ($this->jobs[$index]['status'] === self::STATUS_COMPLETED || $this->jobs[$index]['status'] === self::STATUS_FAILED) { + return; + } + } else { + $done = true; + foreach ($this->jobs as $job) { + if (!in_array($job['status'], array(self::STATUS_COMPLETED, self::STATUS_FAILED), true)) { + $done = false; + break; + } elseif (!$job['sync']) { + unset($this->jobs[$job['id']]); + } + } + if ($done) { + return; + } + } + + usleep(1000); + } + } + + private function getResponse($index) + { + if (!isset($this->jobs[$index])) { + throw new \LogicException('Invalid request id'); + } + + if ($this->jobs[$index]['status'] === self::STATUS_FAILED) { + throw $this->jobs[$index]['exception']; + } + + if (!isset($this->jobs[$index]['response'])) { + throw new \LogicException('Response not available yet, call wait() first'); + } + + $resp = $this->jobs[$index]['response']; + + unset($this->jobs[$index]); + + return $resp; + } + + private function getOrigin($url) + { + $origin = parse_url($url, PHP_URL_HOST); + + if ($origin === 'api.github.com') { + return 'github.com'; + } + + if ($origin === 'repo.packagist.org') { + return 'packagist.org'; + } + + return $origin ?: $url; + } +} diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index ea18a9e30..f4a211acb 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -60,7 +60,8 @@ class RemoteFilesystem // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { - $this->options = $this->getTlsDefaults($options); + $logger = $io instanceof LoggerInterface ? $io : null; + $this->options = StreamContextFactory::getTlsDefaults($options, $logger); } else { $this->disableTls = true; } @@ -891,111 +892,6 @@ class RemoteFilesystem return false; } - /** - * @param array $options - * - * @return array - */ - private function getTlsDefaults(array $options) - { - $ciphers = implode(':', array( - 'ECDHE-RSA-AES128-GCM-SHA256', - 'ECDHE-ECDSA-AES128-GCM-SHA256', - 'ECDHE-RSA-AES256-GCM-SHA384', - 'ECDHE-ECDSA-AES256-GCM-SHA384', - 'DHE-RSA-AES128-GCM-SHA256', - 'DHE-DSS-AES128-GCM-SHA256', - 'kEDH+AESGCM', - 'ECDHE-RSA-AES128-SHA256', - 'ECDHE-ECDSA-AES128-SHA256', - 'ECDHE-RSA-AES128-SHA', - 'ECDHE-ECDSA-AES128-SHA', - 'ECDHE-RSA-AES256-SHA384', - 'ECDHE-ECDSA-AES256-SHA384', - 'ECDHE-RSA-AES256-SHA', - 'ECDHE-ECDSA-AES256-SHA', - 'DHE-RSA-AES128-SHA256', - 'DHE-RSA-AES128-SHA', - 'DHE-DSS-AES128-SHA256', - 'DHE-RSA-AES256-SHA256', - 'DHE-DSS-AES256-SHA', - 'DHE-RSA-AES256-SHA', - 'AES128-GCM-SHA256', - 'AES256-GCM-SHA384', - 'AES128-SHA256', - 'AES256-SHA256', - 'AES128-SHA', - 'AES256-SHA', - 'AES', - 'CAMELLIA', - 'DES-CBC3-SHA', - '!aNULL', - '!eNULL', - '!EXPORT', - '!DES', - '!RC4', - '!MD5', - '!PSK', - '!aECDH', - '!EDH-DSS-DES-CBC3-SHA', - '!EDH-RSA-DES-CBC3-SHA', - '!KRB5-DES-CBC3-SHA', - )); - - /** - * CN_match and SNI_server_name are only known once a URL is passed. - * They will be set in the getOptionsForUrl() method which receives a URL. - * - * cafile or capath can be overridden by passing in those options to constructor. - */ - $defaults = array( - 'ssl' => array( - 'ciphers' => $ciphers, - 'verify_peer' => true, - 'verify_depth' => 7, - 'SNI_enabled' => true, - 'capture_peer_cert' => true, - ), - ); - - if (isset($options['ssl'])) { - $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); - } - - $caBundleLogger = $this->io instanceof LoggerInterface ? $this->io : null; - - /** - * Attempt to find a local cafile or throw an exception if none pre-set - * The user may go download one if this occurs. - */ - if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { - $result = CaBundle::getSystemCaRootBundlePath($caBundleLogger); - - if (is_dir($result)) { - $defaults['ssl']['capath'] = $result; - } else { - $defaults['ssl']['cafile'] = $result; - } - } - - if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $caBundleLogger))) { - throw new TransportException('The configured cafile was not valid or could not be read.'); - } - - if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) { - throw new TransportException('The configured capath was not valid or could not be read.'); - } - - /** - * Disable TLS compression to prevent CRIME attacks where supported. - */ - if (PHP_VERSION_ID >= 50413) { - $defaults['ssl']['disable_compression'] = true; - } - - return $defaults; - } - /** * Fetch certificate common name and fingerprint for validation of SAN. * diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 8dfd6624a..72d12115d 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -13,6 +13,8 @@ namespace Composer\Util; use Composer\Composer; +use Composer\CaBundle\CaBundle; +use Psr\Log\LoggerInterface; /** * Allows the creation of a basic context supporting http proxy @@ -153,6 +155,109 @@ final class StreamContextFactory return stream_context_create($options, $defaultParams); } + /** + * @param array $options + * + * @return array + */ + public static function getTlsDefaults(array $options, LoggerInterface $logger = null) + { + $ciphers = implode(':', array( + 'ECDHE-RSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256', + 'DHE-DSS-AES128-GCM-SHA256', + 'kEDH+AESGCM', + 'ECDHE-RSA-AES128-SHA256', + 'ECDHE-ECDSA-AES128-SHA256', + 'ECDHE-RSA-AES128-SHA', + 'ECDHE-ECDSA-AES128-SHA', + 'ECDHE-RSA-AES256-SHA384', + 'ECDHE-ECDSA-AES256-SHA384', + 'ECDHE-RSA-AES256-SHA', + 'ECDHE-ECDSA-AES256-SHA', + 'DHE-RSA-AES128-SHA256', + 'DHE-RSA-AES128-SHA', + 'DHE-DSS-AES128-SHA256', + 'DHE-RSA-AES256-SHA256', + 'DHE-DSS-AES256-SHA', + 'DHE-RSA-AES256-SHA', + 'AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'AES128-SHA256', + 'AES256-SHA256', + 'AES128-SHA', + 'AES256-SHA', + 'AES', + 'CAMELLIA', + 'DES-CBC3-SHA', + '!aNULL', + '!eNULL', + '!EXPORT', + '!DES', + '!RC4', + '!MD5', + '!PSK', + '!aECDH', + '!EDH-DSS-DES-CBC3-SHA', + '!EDH-RSA-DES-CBC3-SHA', + '!KRB5-DES-CBC3-SHA', + )); + + /** + * CN_match and SNI_server_name are only known once a URL is passed. + * They will be set in the getOptionsForUrl() method which receives a URL. + * + * cafile or capath can be overridden by passing in those options to constructor. + */ + $defaults = array( + 'ssl' => array( + 'ciphers' => $ciphers, + 'verify_peer' => true, + 'verify_depth' => 7, + 'SNI_enabled' => true, + 'capture_peer_cert' => true, + ), + ); + + if (isset($options['ssl'])) { + $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); + } + + /** + * Attempt to find a local cafile or throw an exception if none pre-set + * The user may go download one if this occurs. + */ + if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { + $result = CaBundle::getSystemCaRootBundlePath($logger); + + if (is_dir($result)) { + $defaults['ssl']['capath'] = $result; + } else { + $defaults['ssl']['cafile'] = $result; + } + } + + if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) { + throw new TransportException('The configured cafile was not valid or could not be read.'); + } + + if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) { + throw new TransportException('The configured capath was not valid or could not be read.'); + } + + /** + * Disable TLS compression to prevent CRIME attacks where supported. + */ + if (PHP_VERSION_ID >= 50413) { + $defaults['ssl']['disable_compression'] = true; + } + + return $defaults; + } + /** * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and * NOT at the end of the array From 713bc4de1d506a83f341e033569ded22bff7d228 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Oct 2018 12:44:54 +0100 Subject: [PATCH 354/580] Minor fixes and updated the rest of the code/tests to use HttpDownloader --- doc/articles/plugins.md | 4 +- src/Composer/Command/ArchiveCommand.php | 2 +- src/Composer/Downloader/FileDownloader.php | 4 +- src/Composer/Downloader/GzipDownloader.php | 4 +- src/Composer/Downloader/RarDownloader.php | 4 +- src/Composer/Downloader/XzDownloader.php | 4 +- src/Composer/Downloader/ZipDownloader.php | 4 +- src/Composer/Factory.php | 32 +++--- src/Composer/Package/Loader/ArrayLoader.php | 3 +- .../Repository/ComposerRepository.php | 17 ++- .../Repository/Pear/BaseChannelReader.php | 16 ++- .../Repository/Pear/ChannelReader.php | 10 +- .../Repository/Pear/ChannelRest10Reader.php | 5 +- .../Repository/Pear/ChannelRest11Reader.php | 6 +- src/Composer/Repository/PearRepository.php | 10 +- src/Composer/Repository/RepositoryFactory.php | 6 +- src/Composer/Repository/RepositoryManager.php | 8 +- .../Repository/Vcs/BitbucketDriver.php | 25 +++-- .../Repository/Vcs/GitBitbucketDriver.php | 2 +- src/Composer/Repository/Vcs/GitHubDriver.php | 53 +++++---- src/Composer/Repository/Vcs/GitLabDriver.php | 55 ++++----- .../Repository/Vcs/HgBitbucketDriver.php | 2 +- src/Composer/Repository/Vcs/VcsDriver.php | 17 +-- src/Composer/Util/Bitbucket.php | 12 +- src/Composer/Util/GitHub.php | 10 +- src/Composer/Util/GitLab.php | 12 +- src/Composer/Util/Http/Response.php | 3 + src/Composer/Util/HttpDownloader.php | 10 +- src/Composer/Util/RemoteFilesystem.php | 4 +- .../Test/Downloader/ArchiveDownloaderTest.php | 6 +- .../Test/Downloader/FileDownloaderTest.php | 6 +- .../Test/Downloader/XzDownloaderTest.php | 4 +- .../Test/Downloader/ZipDownloaderTest.php | 38 +++---- tests/Composer/Test/InstallerTest.php | 5 +- ...esystemMock.php => HttpDownloaderMock.php} | 12 +- .../Package/Archiver/ArchiveManagerTest.php | 9 +- .../Repository/ComposerRepositoryTest.php | 32 ++++-- .../Test/Repository/PathRepositoryTest.php | 2 +- .../Repository/Pear/ChannelReaderTest.php | 10 +- .../Pear/ChannelRest10ReaderTest.php | 4 +- .../Pear/ChannelRest11ReaderTest.php | 4 +- .../Test/Repository/PearRepositoryTest.php | 4 +- .../Test/Repository/RepositoryFactoryTest.php | 4 +- .../Test/Repository/RepositoryManagerTest.php | 6 +- .../Repository/Vcs/GitBitbucketDriverTest.php | 68 ++++++----- .../Test/Repository/Vcs/GitHubDriverTest.php | 85 +++++++------- .../Test/Repository/Vcs/GitLabDriverTest.php | 106 +++++++----------- .../Repository/Vcs/PerforceDriverTest.php | 14 +-- tests/Composer/Test/Util/BitbucketTest.php | 72 ++++++------ tests/Composer/Test/Util/GitHubTest.php | 31 +++-- tests/Composer/Test/Util/GitLabTest.php | 31 +++-- 51 files changed, 461 insertions(+), 436 deletions(-) rename tests/Composer/Test/Mock/{RemoteFilesystemMock.php => HttpDownloaderMock.php} (73%) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 228cbac9e..59e2a2f15 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -176,8 +176,8 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface if ($protocol === 's3') { $awsClient = new AwsClient($this->io, $this->composer->getConfig()); - $s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); - $event->setRemoteFilesystem($s3RemoteFilesystem); + $s3Downloader = new S3Downloader($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); + $event->setHttpdownloader($s3Downloader); } } } diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 29858c6fc..f893ed679 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -104,7 +104,7 @@ EOT $archiveManager = $composer->getArchiveManager(); } else { $factory = new Factory; - $downloadManager = $factory->createDownloadManager($io, $config); + $downloadManager = $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config)); $archiveManager = $factory->createArchiveManager($config, $downloadManager); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 6b1349bb8..8b196e60c 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -51,12 +51,12 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface * * @param IOInterface $io The IO instance * @param Config $config The config + * @param HttpDownloader $httpDownloader The remote filesystem * @param EventDispatcher $eventDispatcher The event dispatcher * @param Cache $cache Cache instance - * @param HttpDownloader $httpDownloader The remote filesystem * @param Filesystem $filesystem The filesystem */ - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher, Cache $cache, HttpDownloader $httpDownloader, Filesystem $filesystem = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $filesystem = null) { $this->io = $io; $this->config = $config; diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index bb86f6267..f65fcf27d 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -30,10 +30,10 @@ class GzipDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index c1ed8d60b..6fe4cf27c 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -33,10 +33,10 @@ class RarDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index 1f5947997..bd7b028e2 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -30,11 +30,11 @@ class XzDownloader extends ArchiveDownloader { protected $process; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } protected function extract($file, $path) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 10518e026..9eceab250 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -36,10 +36,10 @@ class ZipDownloader extends ArchiveDownloader protected $process; private $zipArchiveObject; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, HttpDownloader $downloader = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $eventDispatcher, $cache, $downloader); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } /** diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 97d507b10..00aa499d0 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -325,14 +325,14 @@ class Factory $io->loadConfiguration($config); } - $rfs = self::createHttpDownloader($io, $config); + $httpDownloader = self::createHttpDownloader($io, $config); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); $composer->setEventDispatcher($dispatcher); // initialize repository manager - $rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs); + $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher); $composer->setRepositoryManager($rm); // load local repository @@ -357,7 +357,7 @@ class Factory if ($fullLoad) { // initialize download manager - $dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs); + $dm = $this->createDownloadManager($io, $config, $httpDownloader, $dispatcher); $composer->setDownloadManager($dm); // initialize autoload generator @@ -451,7 +451,7 @@ class Factory * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ - public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) + public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { $cache = null; if ($config->get('cache-files-ttl') > 0) { @@ -484,14 +484,14 @@ class Factory $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs)); $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs)); $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config)); - $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); - $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); - $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs)); - $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); - $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs)); - $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs)); - $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs)); - $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); + $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); + $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); + $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); + $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); + $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); + $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); + $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); return $dm; } @@ -501,14 +501,8 @@ class Factory * @param Downloader\DownloadManager $dm Manager use to download sources * @return Archiver\ArchiveManager */ - public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null) + public function createArchiveManager(Config $config, Downloader\DownloadManager $dm) { - if (null === $dm) { - $io = new IO\NullIO(); - $io->loadConfiguration($config); - $dm = $this->createDownloadManager($io, $config); - } - $am = new Archiver\ArchiveManager($dm); $am->addArchiver(new Archiver\ZipArchiver); $am->addArchiver(new Archiver\PharArchiver); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 303cc3c13..49ba45aa8 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -18,7 +18,6 @@ use Composer\Package\Link; use Composer\Package\RootAliasPackage; use Composer\Package\RootPackageInterface; use Composer\Package\Version\VersionParser; -use Composer\Semver\VersionParser as SemverVersionParser; /** * @author Konstantin Kudryashiv @@ -29,7 +28,7 @@ class ArrayLoader implements LoaderInterface protected $versionParser; protected $loadOptions; - public function __construct(SemverVersionParser $parser = null, $loadOptions = false) + public function __construct(VersionParser $parser = null, $loadOptions = false) { if (!$parser) { $parser = new VersionParser; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index c4a570c75..a0036e4fd 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -102,7 +102,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); if ($httpDownloader && $this->options) { - // TODO solve this somehow - should be sent a request time not on the instance + // TODO solve this somehow - should be sent at request time not on the instance $httpDownloader = clone $httpDownloader; $httpDownloader->setOptions($this->options); } @@ -543,6 +543,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $response = $contents; } + if (!isset($response['packages'][$name])) { + return; + } + $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); foreach ($response['packages'][$name] as $version) { if (isset($version['versions'])) { @@ -566,6 +570,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito }, function ($e) { // TODO use ->done() above instead with react/promise 2.0 var_dump('Uncaught Ex', $e->getMessage()); + throw $e; }); } } @@ -644,6 +649,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->hasProviders = true; } + // TODO this is for testing only, remove once packagist reports v2 protocol support + if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url)) { + $this->repoConfig['force-lazy-providers'] = true; + } + // force values for packagist if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) { $this->url = 'https://repo.packagist.org'; @@ -927,6 +937,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $degradedMode =& $this->degradedMode; $accept = function ($response) use ($io, $url, $cache, $cacheKey) { + // package not found is acceptable for a v2 protocol repository + if ($response->getStatusCode() === 404) { + return array('packages' => array()); + } + $json = $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { return true; diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php index 9b26eb9db..b778bf08b 100644 --- a/src/Composer/Repository/Pear/BaseChannelReader.php +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -12,7 +12,7 @@ namespace Composer\Repository\Pear; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; /** * Base PEAR Channel reader. @@ -33,12 +33,12 @@ abstract class BaseChannelReader const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases'; const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package'; - /** @var RemoteFilesystem */ - private $rfs; + /** @var HttpDownloader */ + private $httpDownloader; - protected function __construct(RemoteFilesystem $rfs) + protected function __construct(HttpDownloader $httpDownloader) { - $this->rfs = $rfs; + $this->httpDownloader = $httpDownloader; } /** @@ -52,7 +52,11 @@ abstract class BaseChannelReader protected function requestContent($origin, $path) { $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); - $content = $this->rfs->getContents($origin, $url, false); + try { + $content = $this->httpDownloader->get($url)->getBody(); + } catch (\Exception $e) { + throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.', 0, $e); + } if (!$content) { throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.'); } diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php index 73cc9152e..14d48ad86 100644 --- a/src/Composer/Repository/Pear/ChannelReader.php +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -12,7 +12,7 @@ namespace Composer\Repository\Pear; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; /** * PEAR Channel package reader. @@ -26,12 +26,12 @@ class ChannelReader extends BaseChannelReader /** @var array of ('xpath test' => 'rest implementation') */ private $readerMap; - public function __construct(RemoteFilesystem $rfs) + public function __construct(HttpDownloader $httpDownloader) { - parent::__construct($rfs); + parent::__construct($httpDownloader); - $rest10reader = new ChannelRest10Reader($rfs); - $rest11reader = new ChannelRest11Reader($rfs); + $rest10reader = new ChannelRest10Reader($httpDownloader); + $rest11reader = new ChannelRest11Reader($httpDownloader); $this->readerMap = array( 'REST1.3' => $rest11reader, diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php index 489914d5d..93969043a 100644 --- a/src/Composer/Repository/Pear/ChannelRest10Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Pear; use Composer\Downloader\TransportException; +use Composer\Util\HttpDownloader; /** * Read PEAR packages using REST 1.0 interface @@ -29,9 +30,9 @@ class ChannelRest10Reader extends BaseChannelReader { private $dependencyReader; - public function __construct($rfs) + public function __construct(HttpDownloader $httpDownloader) { - parent::__construct($rfs); + parent::__construct($httpDownloader); $this->dependencyReader = new PackageDependencyParser(); } diff --git a/src/Composer/Repository/Pear/ChannelRest11Reader.php b/src/Composer/Repository/Pear/ChannelRest11Reader.php index f9e05f5be..18b1b10f3 100644 --- a/src/Composer/Repository/Pear/ChannelRest11Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest11Reader.php @@ -12,6 +12,8 @@ namespace Composer\Repository\Pear; +use Composer\Util\HttpDownloader; + /** * Read PEAR packages using REST 1.1 interface * @@ -25,9 +27,9 @@ class ChannelRest11Reader extends BaseChannelReader { private $dependencyReader; - public function __construct($rfs) + public function __construct(HttpDownloader $httpDownloader) { - parent::__construct($rfs); + parent::__construct($httpDownloader); $this->dependencyReader = new PackageDependencyParser(); } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index c4f0b83e7..aef5c0381 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -21,7 +21,7 @@ use Composer\Repository\Pear\ChannelInfo; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Config; use Composer\Factory; @@ -38,7 +38,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn { private $url; private $io; - private $rfs; + private $httpDownloader; private $versionParser; private $repoConfig; @@ -47,7 +47,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn */ private $vendorAlias; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher, HttpDownloader $httpDownloader) { parent::__construct(); if (!preg_match('{^https?://}', $repoConfig['url'])) { @@ -61,7 +61,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn $this->url = rtrim($repoConfig['url'], '/'); $this->io = $io; - $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader; $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null; $this->versionParser = new VersionParser(); $this->repoConfig = $repoConfig; @@ -78,7 +78,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn $this->io->writeError('Initializing PEAR repository '.$this->url); - $reader = new ChannelReader($this->rfs); + $reader = new ChannelReader($this->httpDownloader); try { $channelInfo = $reader->read($this->url); } catch (\Exception $e) { diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 5737a5359..515908f64 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -108,12 +108,12 @@ class RepositoryFactory * @param IOInterface $io * @param Config $config * @param EventDispatcher $eventDispatcher - * @param HttpDownloader $rfs + * @param HttpDownloader $httpDownloader * @return RepositoryManager */ - public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) + public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { - $rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs); + $rm = new RepositoryManager($io, $config, $eventDispatcher, $httpDownloader); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 64568514c..8fc01cb08 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -33,14 +33,14 @@ class RepositoryManager private $io; private $config; private $eventDispatcher; - private $rfs; + private $httpDownloader; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, HttpDownloader $rfs = null) + public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher, HttpDownloader $httpDownloader) { $this->io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; - $this->rfs = $rfs; + $this->httpDownloader = $httpDownloader; } /** @@ -128,7 +128,7 @@ class RepositoryManager $reflMethod = new \ReflectionMethod($class, '__construct'); $params = $reflMethod->getParameters(); if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\HttpDownloader') { - return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs); + return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->httpDownloader); } return new $class($config, $this->io, $this->config, $this->eventDispatcher); diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 24a4af4dd..bde4fc1b7 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -16,6 +16,7 @@ use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Util\Bitbucket; +use Composer\Util\Http\Response; abstract class BitbucketDriver extends VcsDriver { @@ -92,7 +93,7 @@ abstract class BitbucketDriver extends VcsDriver ) ); - $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson(); if ($this->fallbackDriver) { return false; } @@ -204,7 +205,7 @@ abstract class BitbucketDriver extends VcsDriver $file ); - return $this->getContentsWithOAuthCredentials($resource); + return $this->fetchWithOAuthCredentials($resource)->getBody(); } /** @@ -222,7 +223,7 @@ abstract class BitbucketDriver extends VcsDriver $this->repository, $identifier ); - $commit = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson(); return new \DateTime($commit['date']); } @@ -284,7 +285,7 @@ abstract class BitbucketDriver extends VcsDriver ); $hasNext = true; while ($hasNext) { - $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($tagsData['values'] as $data) { $this->tags[$data['name']] = $data['target']['hash']; } @@ -328,7 +329,7 @@ abstract class BitbucketDriver extends VcsDriver ); $hasNext = true; while ($hasNext) { - $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($branchData['values'] as $data) { // skip headless branches which seem to be deleted branches that bitbucket nevertheless returns in the API if ($this->vcsType === 'hg' && empty($data['heads'])) { @@ -354,14 +355,14 @@ abstract class BitbucketDriver extends VcsDriver * @param string $url The URL of content * @param bool $fetchingRepoData * - * @return mixed The result + * @return Response The result */ - protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false) + protected function fetchWithOAuthCredentials($url, $fetchingRepoData = false) { try { return parent::getContents($url); } catch (TransportException $e) { - $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem); + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); if (403 === $e->getCode() || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) { if (!$this->io->hasAuthentication($this->originUrl) @@ -371,7 +372,9 @@ abstract class BitbucketDriver extends VcsDriver } if (!$this->io->isInteractive() && $fetchingRepoData) { - return $this->attemptCloneFallback(); + if ($this->attemptCloneFallback()) { + return new Response(array('url' => 'dummy'), 200, array(), 'null'); + } } } @@ -390,6 +393,8 @@ abstract class BitbucketDriver extends VcsDriver { try { $this->setupFallbackDriver($this->generateSshUrl()); + + return true; } catch (\RuntimeException $e) { $this->fallbackDriver = null; @@ -433,7 +438,7 @@ abstract class BitbucketDriver extends VcsDriver $this->repository ); - $data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $data = $this->fetchWithOAuthCredentials($resource)->decodeJson(); if (isset($data['mainbranch'])) { return $data['mainbranch']; } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index c8a5c9905..dd69e753a 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -76,7 +76,7 @@ class GitBitbucketDriver extends BitbucketDriver $this->io, $this->config, $this->process, - $this->remoteFilesystem + $this->httpDownloader ); $this->fallbackDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index d0b721af9..f1ad253d8 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -18,6 +18,8 @@ use Composer\Json\JsonFile; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Util\GitHub; +use Composer\Util\Http\Response; +use Composer\Util\RemoteFilesystem; /** * @author Jordi Boggiano @@ -184,7 +186,7 @@ class GitHubDriver extends VcsDriver } $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); - $resource = JsonFile::parseJson($this->getContents($resource)); + $resource = $this->getContents($resource)->decodeJson(); if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } @@ -202,7 +204,7 @@ class GitHubDriver extends VcsDriver } $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); - $commit = JsonFile::parseJson($this->getContents($resource), $resource); + $commit = $this->getContents($resource)->decodeJson(); return new \DateTime($commit['commit']['committer']['date']); } @@ -220,12 +222,13 @@ class GitHubDriver extends VcsDriver $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; do { - $tagsData = JsonFile::parseJson($this->getContents($resource), $resource); + $response = $this->getContents($resource); + $tagsData = $response->decodeJson(); foreach ($tagsData as $tag) { $this->tags[$tag['name']] = $tag['commit']['sha']; } - $resource = $this->getNextPage(); + $resource = $this->getNextPage($response); } while ($resource); } @@ -247,7 +250,8 @@ class GitHubDriver extends VcsDriver $branchBlacklist = array('gh-pages'); do { - $branchData = JsonFile::parseJson($this->getContents($resource), $resource); + $response = $this->getContents($resource); + $branchData = $response->decodeJson(); foreach ($branchData as $branch) { $name = substr($branch['ref'], 11); if (!in_array($name, $branchBlacklist)) { @@ -255,7 +259,7 @@ class GitHubDriver extends VcsDriver } } - $resource = $this->getNextPage(); + $resource = $this->getNextPage($response); } while ($resource); } @@ -315,7 +319,7 @@ class GitHubDriver extends VcsDriver try { return parent::getContents($url); } catch (TransportException $e) { - $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem); + $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: @@ -330,16 +334,18 @@ class GitHubDriver extends VcsDriver } if (!$this->io->isInteractive()) { - return $this->attemptCloneFallback(); + if ($this->attemptCloneFallback()) { + return new Response(array('url' => 'dummy'), 200, array(), 'null'); + } } $scopesIssued = array(); $scopesNeeded = array(); if ($headers = $e->getHeaders()) { - if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) { + if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-OAuth-Scopes')) { $scopesIssued = explode(' ', $scopes); } - if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { + if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { $scopesNeeded = explode(' ', $scopes); } } @@ -358,7 +364,9 @@ class GitHubDriver extends VcsDriver } if (!$this->io->isInteractive() && $fetchingRepoData) { - return $this->attemptCloneFallback(); + if ($this->attemptCloneFallback()) { + return new Response(array('url' => 'dummy'), 200, array(), 'null'); + } } $rateLimited = $gitHubUtil->isRateLimited($e->getHeaders()); @@ -404,7 +412,7 @@ class GitHubDriver extends VcsDriver $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; - $this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl); + $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson(); if (null === $this->repoData && null !== $this->gitDriver) { return; } @@ -434,7 +442,7 @@ class GitHubDriver extends VcsDriver // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($this->generateSshUrl()); - return; + return true; } catch (\RuntimeException $e) { $this->gitDriver = null; @@ -450,22 +458,19 @@ class GitHubDriver extends VcsDriver $this->io, $this->config, $this->process, - $this->remoteFilesystem + $this->httpDownloader ); $this->gitDriver->initialize(); } - protected function getNextPage() + protected function getNextPage(Response $response) { - $headers = $this->remoteFilesystem->getLastHeaders(); - foreach ($headers as $header) { - if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) { - $links = explode(',', $match[1]); - foreach ($links as $link) { - if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { - return $match[1]; - } - } + $header = $response->getHeader('link'); + + $links = explode(',', $header); + foreach ($links as $link) { + if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; } } } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 2044ff702..6a1aa8ac2 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -17,8 +17,9 @@ use Composer\Cache; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Downloader\TransportException; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Util\GitLab; +use Composer\Util\Http\Response; /** * Driver for GitLab API, use the Git driver for local checkouts. @@ -110,14 +111,14 @@ class GitLabDriver extends VcsDriver } /** - * Updates the RemoteFilesystem instance. + * Updates the HttpDownloader instance. * Mainly useful for tests. * * @internal */ - public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem) + public function setHttpDownloader(HttpDownloader $httpDownloader) { - $this->remoteFilesystem = $remoteFilesystem; + $this->httpDownloader = $httpDownloader; } /** @@ -140,7 +141,7 @@ class GitLabDriver extends VcsDriver $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier; try { - $content = $this->getContents($resource); + $content = $this->getContents($resource)->getBody(); } catch (TransportException $e) { if ($e->getCode() !== 404) { throw $e; @@ -297,7 +298,8 @@ class GitLabDriver extends VcsDriver $references = array(); do { - $data = JsonFile::parseJson($this->getContents($resource), $resource); + $response = $this->getContents($resource); + $data = $response->decodeJson(); foreach ($data as $datum) { $references[$datum['name']] = $datum['commit']['id']; @@ -308,7 +310,7 @@ class GitLabDriver extends VcsDriver } if (count($data) >= $perPage) { - $resource = $this->getNextPage(); + $resource = $this->getNextPage($response); } else { $resource = false; } @@ -321,7 +323,7 @@ class GitLabDriver extends VcsDriver { // we need to fetch the default branch from the api $resource = $this->getApiUrl(); - $this->project = JsonFile::parseJson($this->getContents($resource, true), $resource); + $this->project = $this->getContents($resource, true)->decodeJson(); if (isset($this->project['visibility'])) { $this->isPrivate = $this->project['visibility'] !== 'public'; } else { @@ -344,7 +346,7 @@ class GitLabDriver extends VcsDriver // are not interactive) then we fallback to GitDriver. $this->setupGitDriver($url); - return; + return true; } catch (\RuntimeException $e) { $this->gitDriver = null; @@ -375,7 +377,7 @@ class GitLabDriver extends VcsDriver $this->io, $this->config, $this->process, - $this->remoteFilesystem + $this->httpDownloader ); $this->gitDriver->initialize(); } @@ -386,10 +388,10 @@ class GitLabDriver extends VcsDriver protected function getContents($url, $fetchingRepoData = false) { try { - $res = parent::getContents($url); + $response = parent::getContents($url); if ($fetchingRepoData) { - $json = JsonFile::parseJson($res, $url); + $json = $response->decodeJson(); // force auth as the unauthenticated version of the API is broken if (!isset($json['default_branch'])) { @@ -401,9 +403,9 @@ class GitLabDriver extends VcsDriver } } - return $res; + return $response; } catch (TransportException $e) { - $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem); + $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: @@ -418,7 +420,9 @@ class GitLabDriver extends VcsDriver } if (!$this->io->isInteractive()) { - return $this->attemptCloneFallback(); + if ($this->attemptCloneFallback()) { + return new Response(array('url' => 'dummy'), 200, array(), 'null'); + } } $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); @@ -431,7 +435,9 @@ class GitLabDriver extends VcsDriver } if (!$this->io->isInteractive() && $fetchingRepoData) { - return $this->attemptCloneFallback(); + if ($this->attemptCloneFallback()) { + return new Response(array('url' => 'dummy'), 200, array(), 'null'); + } } throw $e; @@ -471,17 +477,14 @@ class GitLabDriver extends VcsDriver return true; } - private function getNextPage() + protected function getNextPage(Response $response) { - $headers = $this->remoteFilesystem->getLastHeaders(); - foreach ($headers as $header) { - if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) { - $links = explode(',', $match[1]); - foreach ($links as $link) { - if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { - return $match[1]; - } - } + $header = $response->getHeader('link'); + + $links = explode(',', $header); + foreach ($links as $link) { + if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; } } } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 8324f22ac..4a00f2da0 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -76,7 +76,7 @@ class HgBitbucketDriver extends BitbucketDriver $this->io, $this->config, $this->process, - $this->remoteFilesystem + $this->httpDownloader ); $this->fallbackDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 5227630f6..17ed706d2 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -19,8 +19,9 @@ use Composer\Factory; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Util\Filesystem; +use Composer\Util\Http\Response; /** * A driver implementation for driver with authentication interaction. @@ -41,8 +42,8 @@ abstract class VcsDriver implements VcsDriverInterface protected $config; /** @var ProcessExecutor */ protected $process; - /** @var RemoteFilesystem */ - protected $remoteFilesystem; + /** @var HttpDownloader */ + protected $httpDownloader; /** @var array */ protected $infoCache = array(); /** @var Cache */ @@ -55,9 +56,9 @@ abstract class VcsDriver implements VcsDriverInterface * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking - * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ - final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) + final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null) { if (Filesystem::isLocalPath($repoConfig['url'])) { $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); @@ -69,7 +70,7 @@ abstract class VcsDriver implements VcsDriverInterface $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); - $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } /** @@ -156,13 +157,13 @@ abstract class VcsDriver implements VcsDriverInterface * * @param string $url The URL of content * - * @return mixed The result + * @return Response */ protected function getContents($url) { $options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array(); - return $this->remoteFilesystem->getContents($this->originUrl, $url, false, $options); + return $this->httpDownloader->get($url, $options); } /** diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index 1fc286ac4..d9f569b1b 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -25,7 +25,7 @@ class Bitbucket private $io; private $config; private $process; - private $remoteFilesystem; + private $httpDownloader; private $token = array(); private $time; @@ -37,15 +37,15 @@ class Bitbucket * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking - * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking * @param int $time Timestamp, injectable for mocking */ - public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null) + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null, $time = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); - $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); $this->time = $time; } @@ -90,7 +90,7 @@ class Bitbucket private function requestAccessToken($originUrl) { try { - $json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array( + $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, array( 'retry-auth-failure' => false, 'http' => array( 'method' => 'POST', @@ -98,7 +98,7 @@ class Bitbucket ), )); - $this->token = json_decode($json, true); + $this->token = $response->decodeJson(); } catch (TransportException $e) { if ($e->getCode() === 400) { $this->io->writeError('Invalid OAuth consumer provided.'); diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 1eca1a9bb..c3046cb77 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -25,7 +25,7 @@ class GitHub protected $io; protected $config; protected $process; - protected $remoteFilesystem; + protected $httpDownloader; /** * Constructor. @@ -33,14 +33,14 @@ class GitHub * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking - * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ - public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); - $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } /** @@ -104,7 +104,7 @@ class GitHub try { $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/'; - $this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl, false, array( + $this->httpDownloader->get('https://'. $apiUrl, array( 'retry-auth-failure' => false, )); } catch (TransportException $e) { diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 475c5e7ee..2a4867954 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -26,7 +26,7 @@ class GitLab protected $io; protected $config; protected $process; - protected $remoteFilesystem; + protected $httpDownloader; /** * Constructor. @@ -34,14 +34,14 @@ class GitLab * @param IOInterface $io The IO instance * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking - * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ - public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); - $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); + $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } /** @@ -154,10 +154,10 @@ class GitLab ), ); - $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options); + $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson(); $this->io->writeError('Token successfully created'); - return JsonFile::parseJson($json); + return $token; } } diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index ff48fdb40..f76057f3e 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -23,6 +23,9 @@ class Response public function __construct(array $request, $code, array $headers, $body) { + if (!isset($request['url'])) { + throw new \LogicException('url key missing from request array'); + } $this->request = $request; $this->code = $code; $this->headers = $headers; diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 31c615e0c..631c0df7d 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -117,8 +117,8 @@ class HttpDownloader $origin = $this->getOrigin($job['request']['url']); - // TODO only send http/https through curl - if ($curl) { + // TODO experiment with allowing file:// through curl too + if ($curl && preg_match('{^https?://}i', $job['request']['url'])) { $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { // start job $url = $job['request']['url']; @@ -141,11 +141,7 @@ class HttpDownloader $job['status'] = HttpDownloader::STATUS_STARTED; if ($job['request']['copyTo']) { - if ($curl) { - $result = $curl->download($origin, $url, $options, $job['request']['copyTo']); - } else { - $result = $rfs->copy($origin, $url, $job['request']['copyTo'], false /* TODO progress */, $options); - } + $result = $rfs->copy($origin, $url, $job['request']['copyTo'], false /* TODO progress */, $options); $resolve($result); } else { diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index f4a211acb..00fe35294 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -147,7 +147,7 @@ class RemoteFilesystem * @param string $name header name (case insensitive) * @return string|null */ - public function findHeaderValue(array $headers, $name) + public static function findHeaderValue(array $headers, $name) { $value = null; foreach ($headers as $header) { @@ -167,7 +167,7 @@ class RemoteFilesystem * @param array $headers array of returned headers like from getLastHeaders() * @return int|null */ - public function findStatusCode(array $headers) + public static function findStatusCode(array $headers) { $value = null; foreach ($headers as $header) { diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index 68852d8e0..ddf21c64b 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -156,7 +156,11 @@ class ArchiveDownloaderTest extends TestCase { return $this->getMockForAbstractClass( 'Composer\Downloader\ArchiveDownloader', - array($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getMockBuilder('Composer\Config')->getMock()) + array( + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $config = $this->getMockBuilder('Composer\Config')->getMock(), + new \Composer\Util\HttpDownloader($io, $config), + ) ); } } diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 476b9a8f7..12edfe19d 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -18,13 +18,13 @@ use Composer\Util\Filesystem; class FileDownloaderTest extends TestCase { - protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $rfs = null, $filesystem = null) + protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $httpDownloader = null, $filesystem = null) { $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); - $rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); + $httpDownloader = $httpDownloader ?: $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); - return new FileDownloader($io, $config, $eventDispatcher, $cache, $rfs, $filesystem); + return new FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $filesystem); } /** diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index 6df782ddb..451592d37 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -16,7 +16,7 @@ use Composer\Downloader\XzDownloader; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; class XzDownloaderTest extends TestCase { @@ -66,7 +66,7 @@ class XzDownloaderTest extends TestCase ->method('get') ->with('vendor-dir') ->will($this->returnValue($this->testDir)); - $downloader = new XzDownloader($io, $config, null, null, null, new RemoteFilesystem($io)); + $downloader = new XzDownloader($io, $config, new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null); try { $downloader->download($packageMock, $this->getUniqueTmpDirectory()); diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 466fd35c7..0c1311427 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -16,6 +16,7 @@ use Composer\Downloader\ZipDownloader; use Composer\Package\PackageInterface; use Composer\Test\TestCase; use Composer\Util\Filesystem; +use Composer\Util\HttpDownloader; class ZipDownloaderTest extends TestCase { @@ -32,6 +33,8 @@ class ZipDownloaderTest extends TestCase $this->testDir = $this->getUniqueTmpDirectory(); $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->config = $this->getMockBuilder('Composer\Config')->getMock(); + $dlConfig = $this->getMockBuilder('Composer\Config')->getMock(); + $this->httpDownloader = new HttpDownloader($this->io, $dlConfig); } public function tearDown() @@ -64,18 +67,6 @@ class ZipDownloaderTest extends TestCase } $this->config->expects($this->at(0)) - ->method('get') - ->with('disable-tls') - ->will($this->returnValue(false)); - $this->config->expects($this->at(1)) - ->method('get') - ->with('cafile') - ->will($this->returnValue(null)); - $this->config->expects($this->at(2)) - ->method('get') - ->with('capath') - ->will($this->returnValue(null)); - $this->config->expects($this->at(3)) ->method('get') ->with('vendor-dir') ->will($this->returnValue($this->testDir)); @@ -94,7 +85,7 @@ class ZipDownloaderTest extends TestCase ->will($this->returnValue(array())) ; - $downloader = new ZipDownloader($this->io, $this->config); + $downloader = new ZipDownloader($this->io, $this->config, $this->httpDownloader); $this->setPrivateProperty('hasSystemUnzip', false); @@ -118,8 +109,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', false); $this->setPrivateProperty('hasZipArchive', true); - $downloader = new MockedZipDownloader($this->io, $this->config); - + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader); $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') @@ -144,8 +134,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', false); $this->setPrivateProperty('hasZipArchive', true); - $downloader = new MockedZipDownloader($this->io, $this->config); - + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader); $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') @@ -169,8 +158,7 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', false); $this->setPrivateProperty('hasZipArchive', true); - $downloader = new MockedZipDownloader($this->io, $this->config); - + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader); $zipArchive = $this->getMockBuilder('ZipArchive')->getMock(); $zipArchive->expects($this->at(0)) ->method('open') @@ -200,7 +188,7 @@ class ZipDownloaderTest extends TestCase ->method('execute') ->will($this->returnValue(1)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader->extract('testfile.zip', 'vendor/dir'); } @@ -217,7 +205,7 @@ class ZipDownloaderTest extends TestCase ->method('execute') ->will($this->returnValue(0)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader->extract('testfile.zip', 'vendor/dir'); } @@ -244,7 +232,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(true)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract('testfile.zip', 'vendor/dir'); } @@ -276,7 +264,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract('testfile.zip', 'vendor/dir'); } @@ -304,7 +292,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract('testfile.zip', 'vendor/dir'); } @@ -336,7 +324,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract('testfile.zip', 'vendor/dir'); } diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index f21007281..2e7d70639 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -63,7 +63,9 @@ class InstallerTest extends TestCase ->getMock(); $config = $this->getMockBuilder('Composer\Config')->getMock(); - $repositoryManager = new RepositoryManager($io, $config); + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); + $repositoryManager = new RepositoryManager($io, $config, $eventDispatcher, $httpDownloader); $repositoryManager->setLocalRepository(new InstalledArrayRepository()); if (!is_array($repositories)) { @@ -76,7 +78,6 @@ class InstallerTest extends TestCase $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); $installationManager = new InstallationManagerMock(); - $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock(); $installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator); diff --git a/tests/Composer/Test/Mock/RemoteFilesystemMock.php b/tests/Composer/Test/Mock/HttpDownloaderMock.php similarity index 73% rename from tests/Composer/Test/Mock/RemoteFilesystemMock.php rename to tests/Composer/Test/Mock/HttpDownloaderMock.php index 5d4f52e54..1e2774af0 100644 --- a/tests/Composer/Test/Mock/RemoteFilesystemMock.php +++ b/tests/Composer/Test/Mock/HttpDownloaderMock.php @@ -12,13 +12,11 @@ namespace Composer\Test\Mock; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; +use Composer\Util\Http\Response; use Composer\Downloader\TransportException; -/** - * Remote filesystem mock - */ -class RemoteFilesystemMock extends RemoteFilesystem +class HttpDownloaderMock extends HttpDownloader { protected $contentMap; @@ -30,10 +28,10 @@ class RemoteFilesystemMock extends RemoteFilesystem $this->contentMap = $contentMap; } - public function getContents($originUrl, $fileUrl, $progress = true, $options = array()) + public function get($fileUrl, $options = array()) { if (!empty($this->contentMap[$fileUrl])) { - return $this->contentMap[$fileUrl]; + return new Response(array('url' => $fileUrl), 200, array(), $this->contentMap[$fileUrl]); } throw new TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index f9fe308fa..b9f08e693 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -12,9 +12,11 @@ namespace Composer\Test\Package\Archiver; +use Composer\IO\NullIO; use Composer\Factory; use Composer\Package\Archiver\ArchiveManager; use Composer\Package\PackageInterface; +use Composer\Test\Mock\FactoryMock; class ArchiveManagerTest extends ArchiverTest { @@ -30,7 +32,12 @@ class ArchiveManagerTest extends ArchiverTest parent::setUp(); $factory = new Factory(); - $this->manager = $factory->createArchiveManager($factory->createConfig()); + $dm = $factory->createDownloadManager( + $io = new NullIO, + $config = FactoryMock::createConfig(), + $factory->createHttpDownloader($io, $config) + ); + $this->manager = $factory->createArchiveManager($factory->createConfig(), $dm); $this->targetDir = $this->testDir.'/composer_archiver_tests'; } diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 05e1afada..1fe60f589 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -18,7 +18,7 @@ use Composer\Repository\RepositoryInterface; use Composer\Test\Mock\FactoryMock; use Composer\Test\TestCase; use Composer\Package\Loader\ArrayLoader; -use Composer\Semver\VersionParser; +use Composer\Package\Version\VersionParser; class ComposerRepositoryTest extends TestCase { @@ -37,6 +37,8 @@ class ComposerRepositoryTest extends TestCase $repoConfig, new NullIO, FactoryMock::createConfig(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() )) ->getMock(); @@ -179,21 +181,29 @@ class ComposerRepositoryTest extends TestCase ), ); - $rfs = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->disableOriginalConstructor() + ->getMock(); + $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->disableOriginalConstructor() ->getMock(); - $rfs->expects($this->at(0)) - ->method('getContents') - ->with('example.org', 'http://example.org/packages.json', false) - ->willReturn(json_encode(array('search' => '/search.json?q=%query%&type=%type%'))); + $httpDownloader->expects($this->at(0)) + ->method('get') + ->with($url = 'http://example.org/packages.json') + ->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode(array('search' => '/search.json?q=%query%&type=%type%')))); - $rfs->expects($this->at(1)) - ->method('getContents') - ->with('example.org', 'http://example.org/search.json?q=foo&type=composer-plugin', false) - ->willReturn(json_encode($result)); + $httpDownloader->expects($this->at(1)) + ->method('get') + ->with($url = 'http://example.org/search.json?q=foo&type=composer-plugin') + ->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode($result))); - $repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), null, $rfs); + $httpDownloader->expects($this->at(2)) + ->method('get') + ->with($url = 'http://example.org/search.json?q=foo&type=library') + ->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode(array()))); + + $repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), $eventDispatcher, $httpDownloader); $this->assertSame( array(array('name' => 'foo', 'description' => null)), diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php index a9594257c..abe6063f4 100644 --- a/tests/Composer/Test/Repository/PathRepositoryTest.php +++ b/tests/Composer/Test/Repository/PathRepositoryTest.php @@ -14,8 +14,8 @@ namespace Composer\Test\Repository; use Composer\Package\Loader\ArrayLoader; use Composer\Repository\PathRepository; -use Composer\Semver\VersionParser; use Composer\Test\TestCase; +use Composer\Package\Version\VersionParser; class PathRepositoryTest extends TestCase { diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index e766065a7..7ad30825d 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -22,13 +22,13 @@ use Composer\Semver\VersionParser; use Composer\Semver\Constraint\Constraint; use Composer\Package\Link; use Composer\Package\CompletePackage; -use Composer\Test\Mock\RemoteFilesystemMock; +use Composer\Test\Mock\HttpDownloaderMock; class ChannelReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new RemoteFilesystemMock(array( + $rfs = new HttpDownloaderMock(array( 'http://pear.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), @@ -50,11 +50,15 @@ class ChannelReaderTest extends TestCase public function testShouldSelectCorrectReader() { - $rfs = new RemoteFilesystemMock(array( + $rfs = new HttpDownloaderMock(array( 'http://pear.1.0.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.0.xml'), 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), + 'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'), + 'http://test.loc/rest10/r/http_client/deps.1.2.1.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_deps.1.2.1.txt'), 'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'), + 'http://test.loc/rest10/r/http_request/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_allreleases.xml'), + 'http://test.loc/rest10/r/http_request/deps.1.4.0.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_deps.1.4.0.txt'), 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php index 4aa7bbba2..5a40915e1 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php @@ -13,13 +13,13 @@ namespace Composer\Test\Repository\Pear; use Composer\Test\TestCase; -use Composer\Test\Mock\RemoteFilesystemMock; +use Composer\Test\Mock\HttpDownloaderMock; class ChannelRest10ReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new RemoteFilesystemMock(array( + $rfs = new HttpDownloaderMock(array( 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), 'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'), diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php index 04e48426e..08c3a2998 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php @@ -13,13 +13,13 @@ namespace Composer\Test\Repository\Pear; use Composer\Test\TestCase; -use Composer\Test\Mock\RemoteFilesystemMock; +use Composer\Test\Mock\HttpDownloaderMock; class ChannelRest11ReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new RemoteFilesystemMock(array( + $rfs = new HttpDownloaderMock(array( 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index b1a3c0b5e..6046fefb4 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -133,7 +133,7 @@ class PearRepositoryTest extends TestCase $config = new \Composer\Config(); - $this->remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + $this->httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') ->disableOriginalConstructor() ->getMock(); @@ -143,6 +143,6 @@ class PearRepositoryTest extends TestCase protected function tearDown() { $this->repository = null; - $this->remoteFilesystem = null; + $this->httpDownloader = null; } } diff --git a/tests/Composer/Test/Repository/RepositoryFactoryTest.php b/tests/Composer/Test/Repository/RepositoryFactoryTest.php index e54624415..e0a854d46 100644 --- a/tests/Composer/Test/Repository/RepositoryFactoryTest.php +++ b/tests/Composer/Test/Repository/RepositoryFactoryTest.php @@ -21,7 +21,9 @@ class RepositoryFactoryTest extends TestCase { $manager = RepositoryFactory::manager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), - $this->getMockBuilder('Composer\Config')->getMock() + $this->getMockBuilder('Composer\Config')->getMock(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); $ref = new \ReflectionProperty($manager, 'repositoryClasses'); diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php index 3774dd268..c4f09de87 100644 --- a/tests/Composer/Test/Repository/RepositoryManagerTest.php +++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php @@ -38,7 +38,8 @@ class RepositoryManagerTest extends TestCase $rm = new RepositoryManager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getMockBuilder('Composer\Config')->getMock(), - $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() ); $repository1 = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); @@ -61,7 +62,8 @@ class RepositoryManagerTest extends TestCase $rm = new RepositoryManager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config = $this->getMockBuilder('Composer\Config')->setMethods(array('get'))->getMock(), - $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() ); $tmpdir = $this->tmpdir; diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 8d711e8f0..a3a9219d9 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\Repository\Vcs\GitBitbucketDriver; use Composer\Test\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Http\Response; /** * @group bitbucket @@ -26,8 +27,8 @@ class GitBitbucketDriverTest extends TestCase private $io; /** @type \Composer\Config */ private $config; - /** @type \Composer\Util\RemoteFilesystem|\PHPUnit_Framework_MockObject_MockObject */ - private $rfs; + /** @type \Composer\Util\HttpDownloader|\PHPUnit_Framework_MockObject_MockObject */ + private $httpDownloader; /** @type string */ private $home; /** @type string */ @@ -46,7 +47,7 @@ class GitBitbucketDriverTest extends TestCase ), )); - $this->rfs = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + $this->httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') ->disableOriginalConstructor() ->getMock(); } @@ -68,7 +69,7 @@ class GitBitbucketDriverTest extends TestCase $this->io, $this->config, null, - $this->rfs + $this->httpDownloader ); $driver->initialize(); @@ -83,15 +84,14 @@ class GitBitbucketDriverTest extends TestCase 'https://bitbucket.org/user/repo.git does not appear to be a git repository, use https://bitbucket.org/user/repo if this is a mercurial bitbucket repository' ); - $this->rfs->expects($this->once()) - ->method('getContents') + $this->httpDownloader->expects($this->once()) + ->method('get') ->with( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', - false + $url = 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', + array() ) ->willReturn( - '{"scm":"hg","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo","name":"https"},{"href":"ssh:\/\/hg@bitbucket.org\/user\/repo","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}' + new Response(array('url' => $url), 200, array(), '{"scm":"hg","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo","name":"https"},{"href":"ssh:\/\/hg@bitbucket.org\/user\/repo","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}') ); $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); @@ -103,47 +103,43 @@ class GitBitbucketDriverTest extends TestCase { $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); - $this->rfs->expects($this->any()) - ->method('getContents') + $urls = array( + 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', + 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=mainbranch', + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/tags?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext&sort=-target.date', + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/branches?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cvalues.heads%2Cnext&sort=-target.date', + 'https://api.bitbucket.org/2.0/repositories/user/repo/src/master/composer.json', + 'https://api.bitbucket.org/2.0/repositories/user/repo/commit/master?fields=date', + ); + $this->httpDownloader->expects($this->any()) + ->method('get') ->withConsecutive( array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', - false, + $urls[0], array() ), array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=mainbranch', - false, + $urls[1], array() ), array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/tags?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext&sort=-target.date', - false, + $urls[2], array() ), array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/branches?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cvalues.heads%2Cnext&sort=-target.date', - false, + $urls[3], array() ), array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/src/master/composer.json', - false, + $urls[4], array() ), array( - $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/commit/master?fields=date', - false, + $urls[5], array() ) ) ->willReturnOnConsecutiveCalls( - '{"scm":"git","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo.git","name":"https"},{"href":"ssh:\/\/git@bitbucket.org\/user\/repo.git","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}', - '{"mainbranch": {"name": "master"}}', - '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}', - '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}', - '{"name": "user/repo","description": "test repo","license": "GPL","authors": [{"name": "Name","email": "local@domain.tld"}],"require": {"creator/package": "^1.0"},"require-dev": {"phpunit/phpunit": "~4.8"}}', - '{"date": "2016-05-17T13:19:52+00:00"}' + new Response(array('url' => $urls[0]), 200, array(), '{"scm":"git","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo.git","name":"https"},{"href":"ssh:\/\/git@bitbucket.org\/user\/repo.git","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}'), + new Response(array('url' => $urls[1]), 200, array(), '{"mainbranch": {"name": "master"}}'), + new Response(array('url' => $urls[2]), 200, array(), '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}'), + new Response(array('url' => $urls[3]), 200, array(), '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}'), + new Response(array('url' => $urls[4]), 200, array(), '{"name": "user/repo","description": "test repo","license": "GPL","authors": [{"name": "Name","email": "local@domain.tld"}],"require": {"creator/package": "^1.0"},"require-dev": {"phpunit/phpunit": "~4.8"}}'), + new Response(array('url' => $urls[5]), 200, array(), '{"date": "2016-05-17T13:19:52+00:00"}') ); $this->assertEquals( diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index ba9c6d4f7..1f721a4c7 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -16,6 +16,7 @@ use Composer\Downloader\TransportException; use Composer\Repository\Vcs\GitHubDriver; use Composer\Test\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Http\Response; use Composer\Config; class GitHubDriverTest extends TestCase @@ -53,8 +54,8 @@ class GitHubDriverTest extends TestCase ->method('isInteractive') ->will($this->returnValue(true)); - $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') - ->setConstructorArgs(array($io)) + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) ->getMock(); $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); @@ -62,9 +63,9 @@ class GitHubDriverTest extends TestCase ->method('execute') ->will($this->returnValue(1)); - $remoteFilesystem->expects($this->at(0)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) + $httpDownloader->expects($this->at(0)) + ->method('get') + ->with($this->equalTo($repoApiUrl)) ->will($this->throwException(new TransportException('HTTP/1.1 404 Not Found', 404))); $io->expects($this->once()) @@ -76,15 +77,15 @@ class GitHubDriverTest extends TestCase ->method('setAuthentication') ->with($this->equalTo('github.com'), $this->matchesRegularExpression('{sometoken}'), $this->matchesRegularExpression('{x-oauth-basic}')); - $remoteFilesystem->expects($this->at(1)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo('https://api.github.com/'), $this->equalTo(false)) - ->will($this->returnValue('{}')); + $httpDownloader->expects($this->at(1)) + ->method('get') + ->with($this->equalTo($url = 'https://api.github.com/')) + ->will($this->returnValue(new Response(array('url' => $url), 200, array(), '{}'))); - $remoteFilesystem->expects($this->at(2)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) - ->will($this->returnValue('{"master_branch": "test_master", "private": true, "owner": {"login": "composer"}, "name": "packagist"}')); + $httpDownloader->expects($this->at(2)) + ->method('get') + ->with($this->equalTo($url = $repoApiUrl)) + ->will($this->returnValue(new Response(array('url' => $url), 200, array(), '{"master_branch": "test_master", "private": true, "owner": {"login": "composer"}, "name": "packagist"}'))); $configSource = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); $authConfigSource = $this->getMockBuilder('Composer\Config\ConfigSourceInterface')->getMock(); @@ -95,7 +96,7 @@ class GitHubDriverTest extends TestCase 'url' => $repoUrl, ); - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $httpDownloader); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -124,21 +125,21 @@ class GitHubDriverTest extends TestCase ->method('isInteractive') ->will($this->returnValue(true)); - $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') - ->setConstructorArgs(array($io)) + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) ->getMock(); - $remoteFilesystem->expects($this->at(0)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) - ->will($this->returnValue('{"master_branch": "test_master", "owner": {"login": "composer"}, "name": "packagist"}')); + $httpDownloader->expects($this->at(0)) + ->method('get') + ->with($this->equalTo($repoApiUrl)) + ->will($this->returnValue(new Response(array('url' => $repoApiUrl), 200, array(), '{"master_branch": "test_master", "owner": {"login": "composer"}, "name": "packagist"}'))); $repoConfig = array( 'url' => $repoUrl, ); $repoUrl = 'https://github.com/composer/packagist.git'; - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $httpDownloader); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -167,31 +168,31 @@ class GitHubDriverTest extends TestCase ->method('isInteractive') ->will($this->returnValue(true)); - $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') - ->setConstructorArgs(array($io)) + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) ->getMock(); - $remoteFilesystem->expects($this->at(0)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) - ->will($this->returnValue('{"master_branch": "test_master", "owner": {"login": "composer"}, "name": "packagist"}')); + $httpDownloader->expects($this->at(0)) + ->method('get') + ->with($this->equalTo($url = $repoApiUrl)) + ->will($this->returnValue(new Response(array('url' => $url), 200, array(), '{"master_branch": "test_master", "owner": {"login": "composer"}, "name": "packagist"}'))); - $remoteFilesystem->expects($this->at(1)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo('https://api.github.com/repos/composer/packagist/contents/composer.json?ref=feature%2F3.2-foo'), $this->equalTo(false)) - ->will($this->returnValue('{"encoding":"base64","content":"'.base64_encode('{"support": {"source": "'.$repoUrl.'" }}').'"}')); + $httpDownloader->expects($this->at(1)) + ->method('get') + ->with($this->equalTo($url = 'https://api.github.com/repos/composer/packagist/contents/composer.json?ref=feature%2F3.2-foo')) + ->will($this->returnValue(new Response(array('url' => $url), 200, array(), '{"encoding":"base64","content":"'.base64_encode('{"support": {"source": "'.$repoUrl.'" }}').'"}'))); - $remoteFilesystem->expects($this->at(2)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo('https://api.github.com/repos/composer/packagist/commits/feature%2F3.2-foo'), $this->equalTo(false)) - ->will($this->returnValue('{"commit": {"committer":{ "date": "2012-09-10"}}}')); + $httpDownloader->expects($this->at(2)) + ->method('get') + ->with($this->equalTo($url = 'https://api.github.com/repos/composer/packagist/commits/feature%2F3.2-foo')) + ->will($this->returnValue(new Response(array('url' => $url), 200, array(), '{"commit": {"committer":{ "date": "2012-09-10"}}}'))); $repoConfig = array( 'url' => $repoUrl, ); $repoUrl = 'https://github.com/composer/packagist.git'; - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $httpDownloader); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -227,13 +228,13 @@ class GitHubDriverTest extends TestCase ->method('isInteractive') ->will($this->returnValue(false)); - $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') - ->setConstructorArgs(array($io)) + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) ->getMock(); - $remoteFilesystem->expects($this->at(0)) - ->method('getContents') - ->with($this->equalTo('github.com'), $this->equalTo($repoApiUrl), $this->equalTo(false)) + $httpDownloader->expects($this->at(0)) + ->method('get') + ->with($this->equalTo($repoApiUrl)) ->will($this->throwException(new TransportException('HTTP/1.1 404 Not Found', 404))); // clean local clone if present @@ -278,7 +279,7 @@ class GitHubDriverTest extends TestCase 'url' => $repoUrl, ); - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $httpDownloader); $gitHubDriver->initialize(); $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index a5eb799f2..f940733ae 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -17,6 +17,7 @@ use Composer\Config; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Prophecy\Argument; +use Composer\Util\Http\Response; /** * @author Jérôme Tamarelle @@ -27,7 +28,7 @@ class GitLabDriverTest extends TestCase private $config; private $io; private $process; - private $remoteFilesystem; + private $httpDownloader; public function setUp() { @@ -47,7 +48,7 @@ class GitLabDriverTest extends TestCase $this->io = $this->prophesize('Composer\IO\IOInterface'); $this->process = $this->prophesize('Composer\Util\ProcessExecutor'); - $this->remoteFilesystem = $this->prophesize('Composer\Util\RemoteFilesystem'); + $this->httpDownloader = $this->prophesize('Composer\Util\HttpDownloader'); } public function tearDown() @@ -87,13 +88,11 @@ class GitLabDriverTest extends TestCase } JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -126,13 +125,11 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -164,13 +161,11 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -206,12 +201,10 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents($domain, $apiUrl, false, array()) - ->willReturn(sprintf($projectData, $domain, $port, $namespace)) + $this->mockResponse($apiUrl, array(), sprintf($projectData, $domain, $port, $namespace)) ->shouldBeCalledTimes(1); - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -289,15 +282,11 @@ JSON; ] JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($tagData) + $this->mockResponse($apiUrl, array(), $tagData) ->shouldBeCalledTimes(1) ; - $this->remoteFilesystem->getLastHeaders() - ->willReturn(array()); - $driver->setRemoteFilesystem($this->remoteFilesystem->reveal()); + $driver->setHttpDownloader($this->httpDownloader->reveal()); $expected = array( 'v1.0.0' => '092ed2c762bbae331e3f51d4a17f67310bf99a81', @@ -344,26 +333,20 @@ JSON; $branchData = json_encode($branchData); - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($branchData) - ->shouldBeCalledTimes(1) - ; + $headers = array('Link: ; rel="next", ; rel="first", ; rel="last"'); + $this->httpDownloader + ->get($apiUrl, array()) + ->willReturn(new Response(array('url' => $apiUrl), 200, $headers, $branchData)) + ->shouldBeCalledTimes(1); - $this->remoteFilesystem - ->getContents('gitlab.com', "http://gitlab.com/api/v4/projects/mygroup%2Fmyproject/repository/tags?id=mygroup%2Fmyproject&page=2&per_page=20", false, array()) - ->willReturn($branchData) - ->shouldBeCalledTimes(1) - ; + $apiUrl = "http://gitlab.com/api/v4/projects/mygroup%2Fmyproject/repository/tags?id=mygroup%2Fmyproject&page=2&per_page=20"; + $headers = array('Link: ; rel="prev", ; rel="first", ; rel="last"'); + $this->httpDownloader + ->get($apiUrl, array()) + ->willReturn(new Response(array('url' => $apiUrl), 200, $headers, $branchData)) + ->shouldBeCalledTimes(1); - $this->remoteFilesystem->getLastHeaders() - ->willReturn( - array('Link: ; rel="next", ; rel="first", ; rel="last"'), - array('Link: ; rel="prev", ; rel="first", ; rel="last"') - ) - ->shouldBeCalledTimes(2); - - $driver->setRemoteFilesystem($this->remoteFilesystem->reveal()); + $driver->setHttpDownloader($this->httpDownloader->reveal()); $expected = array( 'mymaster' => '97eda36b5c1dd953a3792865c222d4e85e5f302e', @@ -401,15 +384,11 @@ JSON; ] JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($branchData) + $this->mockResponse($apiUrl, array(), $branchData) ->shouldBeCalledTimes(1) ; - $this->remoteFilesystem->getLastHeaders() - ->willReturn(array()); - $driver->setRemoteFilesystem($this->remoteFilesystem->reveal()); + $driver->setHttpDownloader($this->httpDownloader->reveal()); $expected = array( 'mymaster' => '97eda36b5c1dd953a3792865c222d4e85e5f302e', @@ -474,13 +453,11 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents('mycompany.com/gitlab', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -507,13 +484,11 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents('gitlab.com', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -540,13 +515,11 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents('mycompany.com/gitlab', $apiUrl, false, array()) - ->willReturn($projectData) + $this->mockResponse($apiUrl, array(), $projectData) ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -575,9 +548,7 @@ JSON; } JSON; - $this->remoteFilesystem - ->getContents(Argument::cetera(), $options) - ->willReturn($projectData) + $this->mockResponse(Argument::cetera(), $options, $projectData) ->shouldBeCalled(); $driver = new GitLabDriver( @@ -585,8 +556,15 @@ JSON; $this->io->reveal(), $this->config, $this->process->reveal(), - $this->remoteFilesystem->reveal() + $this->httpDownloader->reveal() ); $driver->initialize(); } + + private function mockResponse($url, $options, $return) + { + return $this->httpDownloader + ->get($url, $options) + ->willReturn(new Response(array('url' => $url), 200, array(), $return)); + } } diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index a5e5d4b4c..8225a4a54 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -26,7 +26,7 @@ class PerforceDriverTest extends TestCase protected $config; protected $io; protected $process; - protected $remoteFileSystem; + protected $httpDownloader; protected $testPath; protected $driver; protected $repoConfig; @@ -43,9 +43,9 @@ class PerforceDriverTest extends TestCase $this->repoConfig = $this->getTestRepoConfig(); $this->io = $this->getMockIOInterface(); $this->process = $this->getMockProcessExecutor(); - $this->remoteFileSystem = $this->getMockRemoteFilesystem(); + $this->httpDownloader = $this->getMockHttpDownloader(); $this->perforce = $this->getMockPerforce(); - $this->driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem); + $this->driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->httpDownloader); $this->overrideDriverInternalPerforce($this->perforce); } @@ -56,7 +56,7 @@ class PerforceDriverTest extends TestCase $fs->removeDirectory($this->testPath); $this->driver = null; $this->perforce = null; - $this->remoteFileSystem = null; + $this->httpDownloader = null; $this->process = null; $this->io = null; $this->repoConfig = null; @@ -99,9 +99,9 @@ class PerforceDriverTest extends TestCase return $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); } - protected function getMockRemoteFilesystem() + protected function getMockHttpDownloader() { - return $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); + return $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); } protected function getMockPerforce() @@ -113,7 +113,7 @@ class PerforceDriverTest extends TestCase public function testInitializeCapturesVariablesFromRepoConfig() { - $driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem); + $driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->httpDownloader); $driver->initialize(); $this->assertEquals(self::TEST_URL, $driver->getUrl()); $this->assertEquals(self::TEST_DEPOT, $driver->getDepot()); diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php index 4323a15f5..f50bdd818 100644 --- a/tests/Composer/Test/Util/BitbucketTest.php +++ b/tests/Composer/Test/Util/BitbucketTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Util; use Composer\Util\Bitbucket; +use Composer\Util\Http\Response; use PHPUnit\Framework\TestCase; /** @@ -30,8 +31,8 @@ class BitbucketTest extends TestCase /** @type \Composer\IO\ConsoleIO|\PHPUnit_Framework_MockObject_MockObject */ private $io; - /** @type \Composer\Util\RemoteFilesystem|\PHPUnit_Framework_MockObject_MockObject */ - private $rfs; + /** @type \Composer\Util\HttpDownloader|\PHPUnit_Framework_MockObject_MockObject */ + private $httpDownloader; /** @type \Composer\Config|\PHPUnit_Framework_MockObject_MockObject */ private $config; /** @type Bitbucket */ @@ -47,8 +48,8 @@ class BitbucketTest extends TestCase ->getMock() ; - $this->rfs = $this - ->getMockBuilder('Composer\Util\RemoteFilesystem') + $this->httpDownloader = $this + ->getMockBuilder('Composer\Util\HttpDownloader') ->disableOriginalConstructor() ->getMock() ; @@ -57,7 +58,7 @@ class BitbucketTest extends TestCase $this->time = time(); - $this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs, $this->time); + $this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->httpDownloader, $this->time); } public function testRequestAccessTokenWithValidOAuthConsumer() @@ -66,12 +67,10 @@ class BitbucketTest extends TestCase ->method('setAuthentication') ->with($this->origin, $this->consumer_key, $this->consumer_secret); - $this->rfs->expects($this->once()) - ->method('getContents') + $this->httpDownloader->expects($this->once()) + ->method('get') ->with( - $this->origin, Bitbucket::OAUTH2_ACCESS_TOKEN_URL, - false, array( 'retry-auth-failure' => false, 'http' => array( @@ -81,9 +80,14 @@ class BitbucketTest extends TestCase ) ) ->willReturn( - sprintf( - '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}', - $this->token + new Response( + array('url' => Bitbucket::OAUTH2_ACCESS_TOKEN_URL), + 200, + array(), + sprintf( + '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}', + $this->token + ) ) ); @@ -142,12 +146,10 @@ class BitbucketTest extends TestCase ->method('setAuthentication') ->with($this->origin, $this->consumer_key, $this->consumer_secret); - $this->rfs->expects($this->once()) - ->method('getContents') + $this->httpDownloader->expects($this->once()) + ->method('get') ->with( - $this->origin, Bitbucket::OAUTH2_ACCESS_TOKEN_URL, - false, array( 'retry-auth-failure' => false, 'http' => array( @@ -157,9 +159,14 @@ class BitbucketTest extends TestCase ) ) ->willReturn( - sprintf( - '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}', - $this->token + new Response( + array('url' => Bitbucket::OAUTH2_ACCESS_TOKEN_URL), + 200, + array(), + sprintf( + '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}', + $this->token + ) ) ); @@ -186,12 +193,10 @@ class BitbucketTest extends TestCase array('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url') ); - $this->rfs->expects($this->once()) - ->method('getContents') + $this->httpDownloader->expects($this->once()) + ->method('get') ->with( - $this->origin, Bitbucket::OAUTH2_ACCESS_TOKEN_URL, - false, array( 'retry-auth-failure' => false, 'http' => array( @@ -234,21 +239,24 @@ class BitbucketTest extends TestCase ) ->willReturnOnConsecutiveCalls($this->consumer_key, $this->consumer_secret); - $this->rfs + $this->httpDownloader ->expects($this->once()) - ->method('getContents') + ->method('get') ->with( - $this->equalTo($this->origin), - $this->equalTo(sprintf('https://%s/site/oauth2/access_token', $this->origin)), - $this->isFalse(), + $this->equalTo($url = sprintf('https://%s/site/oauth2/access_token', $this->origin)), $this->anything() ) ->willReturn( - sprintf( - '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refresh_token", "token_type": "bearer"}', - $this->token + new Response( + array('url' => $url), + 200, + array(), + sprintf( + '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refresh_token", "token_type": "bearer"}', + $this->token + ) ) - ) + ); ; $this->setExpectationsForStoringAccessToken(true); diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 28d00ce69..1893486e0 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Util; use Composer\Downloader\TransportException; use Composer\Util\GitHub; +use Composer\Util\Http\Response; use PHPUnit\Framework\TestCase; use RecursiveArrayIterator; use RecursiveIteratorIterator; @@ -45,17 +46,15 @@ class GitHubTest extends TestCase ->willReturn($this->password) ; - $rfs = $this->getRemoteFilesystemMock(); - $rfs + $httpDownloader = $this->getHttpDownloaderMock(); + $httpDownloader ->expects($this->once()) - ->method('getContents') + ->method('get') ->with( - $this->equalTo($this->origin), - $this->equalTo(sprintf('https://api.%s/', $this->origin)), - $this->isFalse(), + $this->equalTo($url = sprintf('https://api.%s/', $this->origin)), $this->anything() ) - ->willReturn('{}') + ->willReturn(new Response(array('url' => $url), 200, array(), '{}')); ; $config = $this->getConfigMock(); @@ -70,7 +69,7 @@ class GitHubTest extends TestCase ->willReturn($this->getConfJsonMock()) ; - $github = new GitHub($io, $config, null, $rfs); + $github = new GitHub($io, $config, null, $httpDownloader); $this->assertTrue($github->authorizeOAuthInteractively($this->origin, $this->message)); } @@ -85,10 +84,10 @@ class GitHubTest extends TestCase ->willReturn($this->password) ; - $rfs = $this->getRemoteFilesystemMock(); - $rfs + $httpDownloader = $this->getHttpDownloaderMock(); + $httpDownloader ->expects($this->exactly(1)) - ->method('getContents') + ->method('get') ->will($this->throwException(new TransportException('', 401))) ; @@ -99,7 +98,7 @@ class GitHubTest extends TestCase ->willReturn($this->getAuthJsonMock()) ; - $github = new GitHub($io, $config, null, $rfs); + $github = new GitHub($io, $config, null, $httpDownloader); $this->assertFalse($github->authorizeOAuthInteractively($this->origin)); } @@ -120,15 +119,15 @@ class GitHubTest extends TestCase return $this->getMockBuilder('Composer\Config')->getMock(); } - private function getRemoteFilesystemMock() + private function getHttpDownloaderMock() { - $rfs = $this - ->getMockBuilder('Composer\Util\RemoteFilesystem') + $httpDownloader = $this + ->getMockBuilder('Composer\Util\HttpDownloader') ->disableOriginalConstructor() ->getMock() ; - return $rfs; + return $httpDownloader; } private function getAuthJsonMock() diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 27f46b4ad..611b25256 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Util; use Composer\Downloader\TransportException; use Composer\Util\GitLab; +use Composer\Util\Http\Response; use PHPUnit\Framework\TestCase; /** @@ -49,17 +50,15 @@ class GitLabTest extends TestCase ->willReturn($this->password) ; - $rfs = $this->getRemoteFilesystemMock(); - $rfs + $httpDownloader = $this->getHttpDownloaderMock(); + $httpDownloader ->expects($this->once()) - ->method('getContents') + ->method('get') ->with( - $this->equalTo($this->origin), - $this->equalTo(sprintf('http://%s/oauth/token', $this->origin)), - $this->isFalse(), + $this->equalTo($url = sprintf('http://%s/oauth/token', $this->origin)), $this->anything() ) - ->willReturn(sprintf('{"access_token": "%s", "token_type": "bearer", "expires_in": 7200}', $this->token)) + ->willReturn(new Response(array('url' => $url), 200, array(), sprintf('{"access_token": "%s", "token_type": "bearer", "expires_in": 7200}', $this->token))); ; $config = $this->getConfigMock(); @@ -69,7 +68,7 @@ class GitLabTest extends TestCase ->willReturn($this->getAuthJsonMock()) ; - $gitLab = new GitLab($io, $config, null, $rfs); + $gitLab = new GitLab($io, $config, null, $httpDownloader); $this->assertTrue($gitLab->authorizeOAuthInteractively('http', $this->origin, $this->message)); } @@ -94,10 +93,10 @@ class GitLabTest extends TestCase ->willReturn($this->password) ; - $rfs = $this->getRemoteFilesystemMock(); - $rfs + $httpDownloader = $this->getHttpDownloaderMock(); + $httpDownloader ->expects($this->exactly(5)) - ->method('getContents') + ->method('get') ->will($this->throwException(new TransportException('', 401))) ; @@ -108,7 +107,7 @@ class GitLabTest extends TestCase ->willReturn($this->getAuthJsonMock()) ; - $gitLab = new GitLab($io, $config, null, $rfs); + $gitLab = new GitLab($io, $config, null, $httpDownloader); $gitLab->authorizeOAuthInteractively('https', $this->origin); } @@ -129,15 +128,15 @@ class GitLabTest extends TestCase return $this->getMockBuilder('Composer\Config')->getMock(); } - private function getRemoteFilesystemMock() + private function getHttpDownloaderMock() { - $rfs = $this - ->getMockBuilder('Composer\Util\RemoteFilesystem') + $httpDownloader = $this + ->getMockBuilder('Composer\Util\HttpDownloader') ->disableOriginalConstructor() ->getMock() ; - return $rfs; + return $httpDownloader; } private function getAuthJsonMock() From f946d8eb5a0104d3aefbd5b5fb8803355f482abf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 15:34:54 +0100 Subject: [PATCH 355/580] More RemoteFilesystem usage removals and some repository/vcs driver refactorings --- doc/articles/plugins.md | 2 +- doc/articles/scripts.md | 2 +- src/Composer/Command/DiagnoseCommand.php | 29 ++++++++-------- src/Composer/Command/SelfUpdateCommand.php | 8 ++--- src/Composer/Compiler.php | 1 + src/Composer/Factory.php | 12 +++---- src/Composer/Json/JsonFile.php | 20 +++++------ .../Package/Version/VersionGuesser.php | 3 +- .../Repository/ComposerRepository.php | 33 ++++++++++++------- src/Composer/Repository/PearRepository.php | 2 +- src/Composer/Repository/RepositoryFactory.php | 8 ++--- src/Composer/Repository/RepositoryManager.php | 8 ++--- .../Repository/Vcs/GitBitbucketDriver.php | 4 +-- src/Composer/Repository/Vcs/GitHubDriver.php | 4 +-- src/Composer/Repository/Vcs/GitLabDriver.php | 4 +-- .../Repository/Vcs/HgBitbucketDriver.php | 4 +-- src/Composer/Repository/Vcs/VcsDriver.php | 8 ++--- src/Composer/Repository/VcsRepository.php | 14 +++++--- src/Composer/SelfUpdate/Versions.php | 10 +++--- src/Composer/Util/HttpDownloader.php | 20 +++++++++++ .../Downloader/PerforceDownloaderTest.php | 3 +- tests/Composer/Test/InstallerTest.php | 2 +- .../Repository/ComposerRepositoryTest.php | 6 ++-- .../Test/Repository/PearRepositoryTest.php | 2 +- .../Test/Repository/RepositoryManagerTest.php | 8 ++--- .../Repository/Vcs/GitBitbucketDriverTest.php | 5 +-- .../Test/Repository/Vcs/GitHubDriverTest.php | 16 ++++++--- .../Test/Repository/Vcs/GitLabDriverTest.php | 18 +++++----- .../Repository/Vcs/PerforceDriverTest.php | 4 +-- .../Test/Repository/Vcs/SvnDriverTest.php | 3 +- 30 files changed, 156 insertions(+), 107 deletions(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 59e2a2f15..da20193f6 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -176,7 +176,7 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface if ($protocol === 's3') { $awsClient = new AwsClient($this->io, $this->composer->getConfig()); - $s3Downloader = new S3Downloader($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); + $s3Downloader = new S3Downloader($this->io, $event->getHttpDownloader()->getOptions(), $awsClient); $event->setHttpdownloader($s3Downloader); } } diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index e0c27b10f..17c83c373 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -61,7 +61,7 @@ Composer fires the following named events during its execution process: - **command**: occurs before any Composer Command is executed on the CLI. It provides you with access to the input and output objects of the program. - **pre-file-download**: occurs before files are downloaded and allows - you to manipulate the `RemoteFilesystem` object prior to downloading files + you to manipulate the `HttpDownloader` object prior to downloading files based on the URL to be downloaded. - **pre-command-run**: occurs before a command is executed and allows you to manipulate the `InputInterface` object's options and arguments to tweak diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 3c4c3bb32..481d58060 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -22,7 +22,7 @@ use Composer\Plugin\PluginEvents; use Composer\Util\ConfigValidator; use Composer\Util\IniHelper; use Composer\Util\ProcessExecutor; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Util\StreamContextFactory; use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Versions; @@ -35,8 +35,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DiagnoseCommand extends BaseCommand { - /** @var RemoteFilesystem */ - protected $rfs; + /** @var HttpDownloader */ + protected $httpDownloader; /** @var ProcessExecutor */ protected $process; @@ -85,7 +85,7 @@ EOT $config->merge(array('config' => array('secure-http' => false))); $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO); - $this->rfs = Factory::createRemoteFilesystem($io, $config); + $this->httpDownloader = Factory::createHttpDownloader($io, $config); $this->process = new ProcessExecutor($io); $io->write('Checking platform settings: ', false); @@ -226,7 +226,7 @@ EOT } try { - $this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false); + $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json'); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; @@ -253,11 +253,11 @@ EOT $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { - $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true); + $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->parseJson(); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); - $provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false); + $provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/'.$path)->getBody(); if (hash('sha256', $provider) !== $hash) { return 'It seems that your proxy is modifying http traffic on the fly'; @@ -285,10 +285,10 @@ EOT $url = 'http://repo.packagist.org/packages.json'; try { - $this->rfs->getContents('packagist.org', $url, false); + $this->httpDownloader->get($url); } catch (TransportException $e) { try { - $this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false))); + $this->httpDownloader->get($url, array('http' => array('request_fulluri' => false))); } catch (TransportException $e) { return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')'; } @@ -319,10 +319,10 @@ EOT $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0'; try { - $this->rfs->getContents('github.com', $url, false); + $this->httpDownloader->get($url); } catch (TransportException $e) { try { - $this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false))); + $this->httpDownloader->get($url, array('http' => array('request_fulluri' => false))); } catch (TransportException $e) { return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')'; } @@ -344,7 +344,7 @@ EOT try { $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; - return $this->rfs->getContents($domain, $url, false, array( + return $this->httpDownloader->get($url, array( 'retry-auth-failure' => false, )) ? true : 'Unexpected error'; } catch (\Exception $e) { @@ -374,8 +374,7 @@ EOT } $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; - $json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false)); - $data = json_decode($json, true); + $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->parseJson(); return $data['resources']['core']; } @@ -428,7 +427,7 @@ EOT return $result; } - $versionsUtil = new Versions($config, $this->rfs); + $versionsUtil = new Versions($config, $this->httpDownloader); $latest = $versionsUtil->getLatest(); if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 243755963..903b49d94 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -76,9 +76,9 @@ EOT } $io = $this->getIO(); - $remoteFilesystem = Factory::createRemoteFilesystem($io, $config); + $httpDownloader = Factory::createHttpDownloader($io, $config); - $versionsUtil = new Versions($config, $remoteFilesystem); + $versionsUtil = new Versions($config, $httpDownloader); // switch channel if requested foreach (array('stable', 'preview', 'snapshot') as $channel) { @@ -155,9 +155,9 @@ EOT $io->write(sprintf("Updating to version %s (%s channel).", $updateVersion, $versionsUtil->getChannel())); $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); - $signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false); + $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody(); $io->writeError(' ', false); - $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress')); + $httpDownloader->copy($remoteFilename, $tempFilename); $io->writeError(''); if (!file_exists($tempFilename) || !$signature) { diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 27b1f4816..86be2d7db 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -123,6 +123,7 @@ class Compiler ->in(__DIR__.'/../../vendor/composer/ca-bundle/') ->in(__DIR__.'/../../vendor/composer/xdebug-handler/') ->in(__DIR__.'/../../vendor/psr/') + ->in(__DIR__.'/../../vendor/react/') ->sort($finderSort) ; diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 00aa499d0..c96fd2188 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -590,18 +590,18 @@ class Factory throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } - $remoteFilesystemOptions = array(); + $httpDownloaderOptions = array(); if ($disableTls === false) { if ($config && $config->get('cafile')) { - $remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile'); + $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile'); } if ($config && $config->get('capath')) { - $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath'); + $httpDownloaderOptions['ssl']['capath'] = $config->get('capath'); } - $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options); + $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options); } try { - $remoteFilesystem = new HttpDownloader($io, $config, $remoteFilesystemOptions, $disableTls); + $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); @@ -614,7 +614,7 @@ class Factory throw $e; } - return $remoteFilesystem; + return $httpDownloader; } /** diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index b84791420..a61a75c34 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -15,7 +15,7 @@ namespace Composer\Json; use JsonSchema\Validator; use Seld\JsonLint\JsonParser; use Seld\JsonLint\ParsingException; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; @@ -35,25 +35,25 @@ class JsonFile const JSON_UNESCAPED_UNICODE = 256; private $path; - private $rfs; + private $httpDownloader; private $io; /** * Initializes json file reader/parser. * - * @param string $path path to a lockfile - * @param RemoteFilesystem $rfs required for loading http/https json files + * @param string $path path to a lockfile + * @param HttpDownloader $httpDownloader required for loading http/https json files * @param IOInterface $io * @throws \InvalidArgumentException */ - public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null) + public function __construct($path, HttpDownloader $httpDownloader = null, IOInterface $io = null) { $this->path = $path; - if (null === $rfs && preg_match('{^https?://}i', $path)) { - throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed'); + if (null === $httpDownloader && preg_match('{^https?://}i', $path)) { + throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed'); } - $this->rfs = $rfs; + $this->httpDownloader = $httpDownloader; $this->io = $io; } @@ -84,8 +84,8 @@ class JsonFile public function read() { try { - if ($this->rfs) { - $json = $this->rfs->getContents($this->path, $this->path, false); + if ($this->httpDownloader) { + $json = $this->httpDownloader->get($this->path)->getBody(); } else { if ($this->io && $this->io->isDebug()) { $this->io->writeError('Reading ' . $this->path); diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index e6ff84965..1c2fdf986 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -192,7 +192,8 @@ class VersionGuesser } // re-use the HgDriver to fetch branches (this properly includes bookmarks) - $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process); + $io = new NullIO(); + $driver = new HgDriver(array('url' => $path), $io, $this->config, new HttpDownloader($io, $this->config), $this->process); $branches = array_keys($driver->getBranches()); // try to find the best (nearest) version branch to assume this feature's version diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index a0036e4fd..b337ea106 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -62,7 +62,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private $partialPackagesByName; private $versionParser; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher, HttpDownloader $httpDownloader) + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { parent::__construct(); if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) { @@ -101,7 +101,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); - if ($httpDownloader && $this->options) { + if ($this->options) { // TODO solve this somehow - should be sent at request time not on the instance $httpDownloader = clone $httpDownloader; $httpDownloader->setOptions($this->options); @@ -781,10 +781,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $this->httpDownloader; - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + } $response = $httpDownloader->get($filename); $json = $response->getBody(); @@ -869,10 +872,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $this->httpDownloader; + + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + } - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); $response = $httpDownloader->get($filename, $options); $json = $response->getBody(); @@ -925,10 +932,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null) { $retries = 3; - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $this->httpDownloader; + + if ($this->eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); + $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + } - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); $options = $lastModifiedTime ? array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))) : array(); $io = $this->io; diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index aef5c0381..1bb22c0ed 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -47,7 +47,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn */ private $vendorAlias; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher, HttpDownloader $httpDownloader) + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null) { parent::__construct(); if (!preg_match('{^https?://}', $repoConfig['url'])) { diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 515908f64..9508f5886 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -36,7 +36,7 @@ class RepositoryFactory if (0 === strpos($repository, 'http')) { $repoConfig = array('type' => 'composer', 'url' => $repository); } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) { - $json = new JsonFile($repository, Factory::createRemoteFilesystem($io, $config)); + $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config)); $data = $json->read(); if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { $repoConfig = array('type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')); @@ -77,7 +77,7 @@ class RepositoryFactory */ public static function createRepo(IOInterface $io, Config $config, array $repoConfig) { - $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config)); + $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); $repos = static::createRepos($rm, array($repoConfig)); return reset($repos); @@ -98,7 +98,7 @@ class RepositoryFactory if (!$io) { throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); } - $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config)); + $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); } return static::createRepos($rm, $config->getRepositories()); @@ -113,7 +113,7 @@ class RepositoryFactory */ public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { - $rm = new RepositoryManager($io, $config, $eventDispatcher, $httpDownloader); + $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 8fc01cb08..c3ce0c24a 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -35,12 +35,12 @@ class RepositoryManager private $eventDispatcher; private $httpDownloader; - public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher, HttpDownloader $httpDownloader) + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { $this->io = $io; $this->config = $config; - $this->eventDispatcher = $eventDispatcher; $this->httpDownloader = $httpDownloader; + $this->eventDispatcher = $eventDispatcher; } /** @@ -127,8 +127,8 @@ class RepositoryManager $reflMethod = new \ReflectionMethod($class, '__construct'); $params = $reflMethod->getParameters(); - if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\HttpDownloader') { - return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->httpDownloader); + if (isset($params[3]) && $params[3]->getClass() && $params[3]->getClass()->getName() === 'Composer\Util\HttpDownloader') { + return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher); } return new $class($config, $this->io, $this->config, $this->eventDispatcher); diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index dd69e753a..5770a8326 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -75,8 +75,8 @@ class GitBitbucketDriver extends BitbucketDriver array('url' => $url), $this->io, $this->config, - $this->process, - $this->httpDownloader + $this->httpDownloader, + $this->process ); $this->fallbackDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index f1ad253d8..69eef200d 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -457,8 +457,8 @@ class GitHubDriver extends VcsDriver array('url' => $url), $this->io, $this->config, - $this->process, - $this->httpDownloader + $this->httpDownloader, + $this->process ); $this->gitDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 6a1aa8ac2..1e2775ff7 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -376,8 +376,8 @@ class GitLabDriver extends VcsDriver array('url' => $url), $this->io, $this->config, - $this->process, - $this->httpDownloader + $this->httpDownloader, + $this->process ); $this->gitDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 4a00f2da0..a919e7860 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -75,8 +75,8 @@ class HgBitbucketDriver extends BitbucketDriver array('url' => $url), $this->io, $this->config, - $this->process, - $this->httpDownloader + $this->httpDownloader, + $this->process ); $this->fallbackDriver->initialize(); } diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 17ed706d2..37946da23 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -55,10 +55,10 @@ abstract class VcsDriver implements VcsDriverInterface * @param array $repoConfig The repository configuration * @param IOInterface $io The IO instance * @param Config $config The composer configuration + * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking * @param ProcessExecutor $process Process instance, injectable for mocking - * @param HttpDownloader $httpDownloader Remote Filesystem, injectable for mocking */ - final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, HttpDownloader $httpDownloader = null) + final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process) { if (Filesystem::isLocalPath($repoConfig['url'])) { $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']); @@ -69,8 +69,8 @@ abstract class VcsDriver implements VcsDriverInterface $this->repoConfig = $repoConfig; $this->io = $io; $this->config = $config; - $this->process = $process ?: new ProcessExecutor($io); - $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); + $this->httpDownloader = $httpDownloader; + $this->process = $process; } /** diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index d6fb1bbee..d8e4b1501 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -20,6 +20,8 @@ use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; use Composer\Package\Loader\LoaderInterface; use Composer\EventDispatcher\EventDispatcher; +use Composer\Util\ProcessExecutor; +use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use Composer\Config; @@ -37,6 +39,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt protected $type; protected $loader; protected $repoConfig; + protected $httpDownloader; + protected $processExecutor; protected $branchErrorOccurred = false; private $drivers; /** @var VcsDriverInterface */ @@ -44,7 +48,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt /** @var VersionCacheInterface */ private $versionCache; - public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null) { parent::__construct(); $this->drivers = $drivers ?: array( @@ -67,6 +71,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->config = $config; $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; + $this->httpDownloader = $httpDownloader; + $this->processExecutor = new ProcessExecutor($io); } public function getRepoConfig() @@ -87,7 +93,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if (isset($this->drivers[$this->type])) { $class = $this->drivers[$this->type]; - $this->driver = new $class($this->repoConfig, $this->io, $this->config); + $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; @@ -95,7 +101,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url)) { - $this->driver = new $driver($this->repoConfig, $this->io, $this->config); + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; @@ -104,7 +110,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url, true)) { - $this->driver = new $driver($this->repoConfig, $this->io, $this->config); + $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; diff --git a/src/Composer/SelfUpdate/Versions.php b/src/Composer/SelfUpdate/Versions.php index b619bda16..431abecb5 100644 --- a/src/Composer/SelfUpdate/Versions.php +++ b/src/Composer/SelfUpdate/Versions.php @@ -12,7 +12,7 @@ namespace Composer\SelfUpdate; -use Composer\Util\RemoteFilesystem; +use Composer\Util\HttpDownloader; use Composer\Config; use Composer\Json\JsonFile; @@ -21,13 +21,13 @@ use Composer\Json\JsonFile; */ class Versions { - private $rfs; + private $httpDownloader; private $config; private $channel; - public function __construct(Config $config, RemoteFilesystem $rfs) + public function __construct(Config $config, HttpDownloader $httpDownloader) { - $this->rfs = $rfs; + $this->httpDownloader = $httpDownloader; $this->config = $config; } @@ -62,7 +62,7 @@ class Versions public function getLatest() { $protocol = extension_loaded('openssl') ? 'https' : 'http'; - $versions = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false)); + $versions = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson(); foreach ($versions[$this->getChannel()] as $version) { if ($version['min-php'] <= PHP_VERSION_ID) { diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 631c0df7d..920c5f75a 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -102,6 +102,26 @@ class HttpDownloader return $promise; } + /** + * Retrieve the options set in the constructor + * + * @return array Options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Merges new options + * + * @return array $options + */ + public function setOptions(array $options) + { + $this->options = array_replace_recursive($this->options, $options); + } + private function addJob($request, $sync = false) { $job = array( diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index ebb1f0456..1b5041d9f 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -17,6 +17,7 @@ use Composer\Config; use Composer\Repository\VcsRepository; use Composer\IO\IOInterface; use Composer\Test\TestCase; +use Composer\Factory; use Composer\Util\Filesystem; /** @@ -96,7 +97,7 @@ class PerforceDownloaderTest extends TestCase { $repository = $this->getMockBuilder('Composer\Repository\VcsRepository') ->setMethods(array('getRepoConfig')) - ->setConstructorArgs(array($repoConfig, $io, $config)) + ->setConstructorArgs(array($repoConfig, $io, $config, Factory::createHttpDownloader($io, $config))) ->getMock(); $repository->expects($this->any())->method('getRepoConfig')->will($this->returnValue($repoConfig)); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 2e7d70639..acaf8f1ff 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -65,7 +65,7 @@ class InstallerTest extends TestCase $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); - $repositoryManager = new RepositoryManager($io, $config, $eventDispatcher, $httpDownloader); + $repositoryManager = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher); $repositoryManager->setLocalRepository(new InstalledArrayRepository()); if (!is_array($repositories)) { diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 1fe60f589..0ffd70751 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -37,8 +37,8 @@ class ComposerRepositoryTest extends TestCase $repoConfig, new NullIO, FactoryMock::createConfig(), - $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() )) ->getMock(); @@ -203,7 +203,7 @@ class ComposerRepositoryTest extends TestCase ->with($url = 'http://example.org/search.json?q=foo&type=library') ->willReturn(new \Composer\Util\Http\Response(array('url' => $url), 200, array(), json_encode(array()))); - $repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), $eventDispatcher, $httpDownloader); + $repository = new ComposerRepository($repoConfig, new NullIO, FactoryMock::createConfig(), $httpDownloader, $eventDispatcher); $this->assertSame( array(array('name' => 'foo', 'description' => null)), diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index 6046fefb4..867d4978d 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -28,7 +28,7 @@ class PearRepositoryTest extends TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $remoteFilesystem; + private $httpDownloader; public function testComposerShouldSetIncludePath() { diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php index c4f09de87..35afd91e2 100644 --- a/tests/Composer/Test/Repository/RepositoryManagerTest.php +++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php @@ -38,8 +38,8 @@ class RepositoryManagerTest extends TestCase $rm = new RepositoryManager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getMockBuilder('Composer\Config')->getMock(), - $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); $repository1 = $this->getMockBuilder('Composer\Repository\RepositoryInterface')->getMock(); @@ -62,8 +62,8 @@ class RepositoryManagerTest extends TestCase $rm = new RepositoryManager( $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $config = $this->getMockBuilder('Composer\Config')->setMethods(array('get'))->getMock(), - $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(), - $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock() + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); $tmpdir = $this->tmpdir; diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index a3a9219d9..f0139970b 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\Repository\Vcs\GitBitbucketDriver; use Composer\Test\TestCase; use Composer\Util\Filesystem; +use Composer\Util\ProcessExecutor; use Composer\Util\Http\Response; /** @@ -68,8 +69,8 @@ class GitBitbucketDriverTest extends TestCase $repoConfig, $this->io, $this->config, - null, - $this->httpDownloader + $this->httpDownloader, + new ProcessExecutor($this->io) ); $driver->initialize(); diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index 1f721a4c7..977a4a7aa 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -96,7 +96,7 @@ class GitHubDriverTest extends TestCase 'url' => $repoUrl, ); - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $httpDownloader); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $process); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -139,7 +139,11 @@ class GitHubDriverTest extends TestCase ); $repoUrl = 'https://github.com/composer/packagist.git'; - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $httpDownloader); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor') + ->disableOriginalConstructor() + ->getMock(); + + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $process); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -192,7 +196,11 @@ class GitHubDriverTest extends TestCase ); $repoUrl = 'https://github.com/composer/packagist.git'; - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, null, $httpDownloader); + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor') + ->disableOriginalConstructor() + ->getMock(); + + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config,$httpDownloader, $process); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -279,7 +287,7 @@ class GitHubDriverTest extends TestCase 'url' => $repoUrl, ); - $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $process, $httpDownloader); + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $process); $gitHubDriver->initialize(); $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index f940733ae..0fd2fa956 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -92,7 +92,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -129,7 +129,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -165,7 +165,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -204,7 +204,7 @@ JSON; $this->mockResponse($apiUrl, array(), sprintf($projectData, $domain, $port, $namespace)) ->shouldBeCalledTimes(1); - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -457,7 +457,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -488,7 +488,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -519,7 +519,7 @@ JSON; ->shouldBeCalledTimes(1) ; - $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->httpDownloader->reveal()); + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->httpDownloader->reveal(), $this->process->reveal()); $driver->initialize(); $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); @@ -555,8 +555,8 @@ JSON; array('url' => 'https://gitlab.mycompany.local/mygroup/myproject', 'options' => $options), $this->io->reveal(), $this->config, - $this->process->reveal(), - $this->httpDownloader->reveal() + $this->httpDownloader->reveal(), + $this->process->reveal() ); $driver->initialize(); } diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index 8225a4a54..ff4e19121 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -45,7 +45,7 @@ class PerforceDriverTest extends TestCase $this->process = $this->getMockProcessExecutor(); $this->httpDownloader = $this->getMockHttpDownloader(); $this->perforce = $this->getMockPerforce(); - $this->driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->httpDownloader); + $this->driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->process); $this->overrideDriverInternalPerforce($this->perforce); } @@ -113,7 +113,7 @@ class PerforceDriverTest extends TestCase public function testInitializeCapturesVariablesFromRepoConfig() { - $driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->process, $this->httpDownloader); + $driver = new PerforceDriver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->process); $driver->initialize(); $this->assertEquals(self::TEST_URL, $driver->getUrl()); $this->assertEquals(self::TEST_DEPOT, $driver->getDepot()); diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 029d20160..946c198f2 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -46,6 +46,7 @@ class SvnDriverTest extends TestCase public function testWrongCredentialsInUrl() { $console = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); $output = "svn: OPTIONS of 'https://corp.svn.local/repo':"; $output .= " authorization failed: Could not authenticate to server:"; @@ -66,7 +67,7 @@ class SvnDriverTest extends TestCase 'url' => 'https://till:secret@corp.svn.local/repo', ); - $svn = new SvnDriver($repoConfig, $console, $this->config, $process); + $svn = new SvnDriver($repoConfig, $console, $this->config, $httpDownloader, $process); $svn->initialize(); } From 1cd9f4f9dbd5a2830e116b427076ce6c3d7f4b16 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 15:35:46 +0100 Subject: [PATCH 356/580] Disable request_fulluri by default for HTTPS connections --- src/Composer/Util/StreamContextFactory.php | 6 +++--- tests/Composer/Test/Util/StreamContextFactoryTest.php | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 72d12115d..b25b307a1 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -87,15 +87,15 @@ final class StreamContextFactory // enabled request_fulluri unless it is explicitly disabled switch (parse_url($url, PHP_URL_SCHEME)) { - case 'http': // default request_fulluri to true + case 'http': // default request_fulluri to true for HTTP $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI'); if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { $options['http']['request_fulluri'] = true; } break; - case 'https': // default request_fulluri to true + case 'https': // default request_fulluri to false for HTTPS $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI'); - if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { + if (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv) { $options['http']['request_fulluri'] = true; } break; diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index 9bb04aaa1..3d60a9a38 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -142,7 +142,6 @@ class StreamContextFactoryTest extends TestCase $expected = array( 'http' => array( 'proxy' => 'tcp://proxyserver.net:80', - 'request_fulluri' => true, 'method' => 'GET', 'header' => array('User-Agent: foo', "Proxy-Authorization: Basic " . base64_encode('username:password')), 'max_redirects' => 20, @@ -173,7 +172,6 @@ class StreamContextFactoryTest extends TestCase $expected = array( 'http' => array( 'proxy' => 'ssl://woopproxy.net:443', - 'request_fulluri' => true, 'method' => 'GET', 'max_redirects' => 20, 'follow_location' => 1, From 09fd239f243e53e989568802a18d5643f7ff2091 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 16:19:06 +0100 Subject: [PATCH 357/580] Fix factory test --- src/Composer/Util/HttpDownloader.php | 1 + tests/Composer/Test/FactoryTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 920c5f75a..d07823ec0 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -32,6 +32,7 @@ class HttpDownloader private $io; private $config; private $jobs = array(); + private $options = array(); private $index; private $progress; private $lastProgress; diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php index 6704e5b15..96b0e95d5 100644 --- a/tests/Composer/Test/FactoryTest.php +++ b/tests/Composer/Test/FactoryTest.php @@ -35,6 +35,6 @@ class FactoryTest extends TestCase ->with($this->equalTo('disable-tls')) ->will($this->returnValue(true)); - Factory::createRemoteFilesystem($ioMock, $config); + Factory::createHttpDownloader($ioMock, $config); } } From 346de47af23cbc8441bd1683491d5c25592b8a42 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 17:21:23 +0100 Subject: [PATCH 358/580] Small fixes --- src/Composer/Command/CreateProjectCommand.php | 2 +- .../Repository/ComposerRepository.php | 25 +++++++++++++++++-- .../Repository/RepositoryInterface.php | 11 ++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 1b58d59c5..a1c364539 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -374,7 +374,7 @@ EOT { $factory = new Factory(); - return $factory->createDownloadManager($io, $config); + return $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config)); } protected function createInstallationManager() diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index b337ea106..7b916d95a 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -28,6 +28,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Downloader\TransportException; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\EmptyConstraint; /** * @author Jordi Boggiano @@ -156,9 +157,19 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito */ public function findPackages($name, $constraint = null) { - if (!$this->hasProviders()) { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + // TODO we need a new way for the repo to report this v2 protocol somehow + if ($this->lazyProvidersUrl) { + return $this->loadAsyncPackages(array($name => new EmptyConstraint()), function ($name, $stability) { + return true; + }); + } + if (!$hasProviders) { return parent::findPackages($name, $constraint); } + // normalize name $name = strtolower($name); @@ -197,10 +208,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) { + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); + + // TODO we need a new way for the repo to report this v2 protocol somehow if ($this->lazyProvidersUrl) { return $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable); } - if (!$this->hasProviders()) { + if (!$hasProviders) { return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable); } @@ -225,6 +240,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $packages = array_merge($packages, $matches); } + return $packages; } @@ -528,6 +544,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->lazyProvidersUrl) { foreach ($packageNames as $name => $constraint) { + // skip platform packages, root package and composer-plugin-api + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { + continue; + } + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 55b76d33d..567455163 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -13,6 +13,7 @@ namespace Composer\Repository; use Composer\Package\PackageInterface; +use Composer\Semver\Constraint\ConstraintInterface; /** * Repository interface. @@ -38,8 +39,8 @@ interface RepositoryInterface extends \Countable /** * Searches for the first match of a package by name and version. * - * @param string $name package name - * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface|null */ @@ -48,8 +49,8 @@ interface RepositoryInterface extends \Countable /** * Searches for all packages matching a name and optionally a version. * - * @param string $name package name - * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface[] */ @@ -66,7 +67,7 @@ interface RepositoryInterface extends \Countable /** * Returns list of registered packages with the supplied name * - * @param bool[] $packageNameMap + * @param ConstraintInterface[] $packageNameMap package names pointing to constraints * @param $isPackageAcceptableCallable * @return PackageInterface[] */ From 655a784fac39230dcc5065aa37888ff614e6f5d4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 17:44:18 +0100 Subject: [PATCH 359/580] Fix findPackage(s) implementation --- src/Composer/Repository/ComposerRepository.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 7b916d95a..dc9f5a79c 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -127,15 +127,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito */ public function findPackage($name, $constraint) { - if (!$this->hasProviders()) { - return parent::findPackage($name, $constraint); - } + // this call initializes loadRootServerFile which is needed for the rest below to work + $hasProviders = $this->hasProviders(); $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } + // TODO we need a new way for the repo to report this v2 protocol somehow + if ($this->lazyProvidersUrl) { + return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { + return true; + }); + } + if (!$hasProviders) { + return parent::findPackage($name, $constraint); + } + foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { $packages = $this->whatProvides($providerName); @@ -162,7 +171,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // TODO we need a new way for the repo to report this v2 protocol somehow if ($this->lazyProvidersUrl) { - return $this->loadAsyncPackages(array($name => new EmptyConstraint()), function ($name, $stability) { + return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { return true; }); } From 4a8a1cb0c9d462c715b01a765df3bbc3b9ae69de Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 12 Nov 2018 17:58:28 +0100 Subject: [PATCH 360/580] Fix PHP 5.3 support --- .../Repository/ComposerRepository.php | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index dc9f5a79c..8c92d3e4d 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -529,28 +529,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $packages = array(); $repo = $this; - $createPackageIfAcceptable = function ($version, $constraint) use (&$packages, $isPackageAcceptableCallable, $repo) { - if (!call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { - return; - } - - if (isset($version['version_normalized']) && $constraint && !$constraint->matches(new Constraint('==', $version['version_normalized']))) { - return; - } - - // load acceptable packages in the providers - $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); - $package->setRepository($repo); - - // if there was no version_normalized, then we need to check now for the constraint - if (!$constraint || isset($version['version_normalized']) || $constraint->matches(new Constraint('==', $package->getVersion()))) { - $packages[spl_object_hash($package)] = $package; - if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { - $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); - } - } - }; - + // TODO what if not, then throw? if ($this->lazyProvidersUrl) { foreach ($packageNames as $name => $constraint) { // skip platform packages, root package and composer-plugin-api @@ -568,7 +547,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $this->asyncFetchFile($url, $cacheKey, $lastModified) - ->then(function ($response) use (&$packages, $contents, $name, $constraint, $createPackageIfAcceptable) { + ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { if (true === $response) { $response = $contents; } @@ -591,10 +570,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $unpackedVersion[$key] = $version[$key.'s'][$index]; } - $createPackageIfAcceptable($unpackedVersion, $constraint); + $repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $unpackedVersion, $constraint); } } else { - $createPackageIfAcceptable($version, $constraint); + $repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $version, $constraint); } } }, function ($e) { @@ -611,6 +590,34 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed } + /** + * TODO v3 should make this private once we can drop PHP 5.3 support + * + * @private + */ + public function createPackageIfAcceptable(&$packages, $isPackageAcceptableCallable, $version, $constraint) + { + if (!call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { + return; + } + + if (isset($version['version_normalized']) && $constraint && !$constraint->matches(new Constraint('==', $version['version_normalized']))) { + return; + } + + // load acceptable packages in the providers + $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); + $package->setRepository($this); + + // if there was no version_normalized, then we need to check now for the constraint + if (!$constraint || isset($version['version_normalized']) || $constraint->matches(new Constraint('==', $package->getVersion()))) { + $packages[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); + } + } + } + protected function loadRootServerFile() { if (null !== $this->rootData) { From fd11cf3618d960e4fd00e891b9e260a42ed92e0b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 14 Nov 2018 17:54:19 +0100 Subject: [PATCH 361/580] Port/extract most behavior of RemoteFilesystem to CurlDownloader --- src/Composer/Composer.php | 1 + src/Composer/Util/AuthHelper.php | 169 ++++++++++ src/Composer/Util/Http/CurlDownloader.php | 317 +++++++++++++----- src/Composer/Util/Http/Response.php | 22 +- src/Composer/Util/HttpDownloader.php | 35 +- src/Composer/Util/RemoteFilesystem.php | 197 +---------- src/Composer/Util/StreamContextFactory.php | 56 +++- .../Test/Util/RemoteFilesystemTest.php | 40 ++- 8 files changed, 518 insertions(+), 319 deletions(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index a3972f44f..c1a61545c 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -32,6 +32,7 @@ class Composer const VERSION = '@package_version@'; const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; const RELEASE_DATE = '@release_date@'; + const SOURCE_VERSION = '2.0-source'; /** * @var Package\RootPackageInterface diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index 72b23ba22..e80a5b0c3 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -14,6 +14,7 @@ namespace Composer\Util; use Composer\Config; use Composer\IO\IOInterface; +use Composer\Downloader\TransportException; /** * @author Jordi Boggiano @@ -60,4 +61,172 @@ class AuthHelper ); } } + + + public function promptAuthIfNeeded($url, $origin, $httpStatus, $reason = null, $warning = null, $headers = array()) + { + $storeAuth = false; + $retry = false; + + if (in_array($origin, $this->config->get('github-domains'), true)) { + $gitHubUtil = new GitHub($this->io, $this->config, null); + $message = "\n"; + + $rateLimited = $gitHubUtil->isRateLimited($headers); + if ($rateLimited) { + $rateLimit = $gitHubUtil->getRateLimit($headers); + if ($this->io->hasAuthentication($origin)) { + $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; + } else { + $message = 'Create a GitHub OAuth token to go over the API rate limit.'; + } + + $message = sprintf( + 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.', + $rateLimit['limit'], + $rateLimit['reset'] + )."\n"; + } else { + $message .= 'Could not fetch '.$url.', please '; + if ($this->io->hasAuthentication($origin)) { + $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; + } else { + $message .= 'create a GitHub OAuth token to access private repos'; + } + } + + if (!$gitHubUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { + $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit'); + $gitLabUtil = new GitLab($this->io, $this->config, null); + + if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $httpStatus); + } + + if (!$gitLabUtil->authorizeOAuth($origin) + && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message)) + ) { + throw new TransportException('Could not authenticate against '.$origin, 401); + } + } elseif ($origin === 'bitbucket.org') { + $askForOAuthToken = true; + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if ($auth['username'] !== 'x-token-auth') { + $bitbucketUtil = new Bitbucket($this->io, $this->config); + $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']); + if (!empty($accessToken)) { + $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); + $askForOAuthToken = false; + } + } else { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + + if ($askForOAuthToken) { + $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit'); + $bitBucketUtil = new Bitbucket($this->io, $this->config); + if (! $bitBucketUtil->authorizeOAuth($origin) + && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message)) + ) { + throw new TransportException('Could not authenticate against ' . $origin, 401); + } + } + } else { + // 404s are only handled for github + if ($httpStatus === 404) { + return; + } + + // fail if the console is not interactive + if (!$this->io->isInteractive()) { + if ($httpStatus === 401) { + $message = "The '" . $url . "' URL required authentication.\nYou must be using the interactive console to authenticate"; + } + if ($httpStatus === 403) { + $message = "The '" . $url . "' URL could not be accessed: " . $reason; + } + + throw new TransportException($message, $httpStatus); + } + // fail if we already have auth + if ($this->io->hasAuthentication($origin)) { + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $httpStatus); + } + + $this->io->overwriteError(''); + if ($warning) { + $this->io->writeError(' '.$warning.''); + } + $this->io->writeError(' Authentication required ('.parse_url($url, PHP_URL_HOST).'):'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthentication($origin, $username, $password); + $storeAuth = $this->config->get('store-auths'); + } + + $retry = true; + + return array('retry' => $retry, 'storeAuth' => $storeAuth); + } + + public function addAuthenticationHeader(array $headers, $origin, $url) + { + if ($this->io->hasAuthentication($origin)) { + $auth = $this->io->getAuthentication($origin); + if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { + $headers[] = 'Authorization: token '.$auth['username']; + } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { + if ($auth['password'] === 'oauth2') { + $headers[] = 'Authorization: Bearer '.$auth['username']; + } elseif ($auth['password'] === 'private-token') { + $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; + } + } elseif ( + 'bitbucket.org' === $origin + && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL + && 'x-token-auth' === $auth['username'] + ) { + if (!$this->isPublicBitBucketDownload($url)) { + $headers[] = 'Authorization: Bearer ' . $auth['password']; + } + } else { + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $headers[] = 'Authorization: Basic '.$authStr; + } + } + + return $headers; + } + + /** + * @link https://github.com/composer/composer/issues/5584 + * + * @param string $urlToBitBucketFile URL to a file at bitbucket.org. + * + * @return bool Whether the given URL is a public BitBucket download which requires no authentication. + */ + public function isPublicBitBucketDownload($urlToBitBucketFile) + { + $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); + if (strpos($domain, 'bitbucket.org') === false) { + // Bitbucket downloads are hosted on amazonaws. + // We do not need to authenticate there at all + return true; + } + + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); + + // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} + // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} + $pathParts = explode('/', $path); + + return count($pathParts) >= 4 && $pathParts[3] == 'downloads'; + } } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 846c41883..2accb7a0c 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -16,6 +16,9 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; +use Composer\Util\RemoteFilesystem; +use Composer\Util\StreamContextFactory; +use Composer\Util\AuthHelper; use Psr\Log\LoggerInterface; use React\Promise\Promise; @@ -28,8 +31,14 @@ class CurlDownloader private $multiHandle; private $shareHandle; private $jobs = array(); + /** @var IOInterface */ private $io; + /** @var Config */ + private $config; + /** @var AuthHelper */ + private $authHelper; private $selectTimeout = 5.0; + private $maxRedirects = 20; protected $multiErrors = array( CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'), CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."), @@ -42,6 +51,7 @@ class CurlDownloader 'method' => CURLOPT_CUSTOMREQUEST, 'content' => CURLOPT_POSTFIELDS, 'proxy' => CURLOPT_PROXY, + 'header' => CURLOPT_HTTPHEADER, ), 'ssl' => array( 'ciphers' => CURLOPT_SSL_CIPHER_LIST, @@ -62,6 +72,7 @@ class CurlDownloader public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) { $this->io = $io; + $this->config = $config; $this->multiHandle = $mh = curl_multi_init(); if (function_exists('curl_multi_setopt')) { @@ -77,79 +88,112 @@ class CurlDownloader curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); } + + $this->authHelper = new AuthHelper($io, $config); } public function download($resolve, $reject, $origin, $url, $options, $copyTo = null) { - $ch = curl_init(); - $hd = fopen('php://temp/maxmemory:32768', 'w+b'); + return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo); + } - // TODO auth & other context - // TODO cleanup + private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array()) + { + // TODO allow setting attributes somehow + $attributes = array_merge(array( + 'retryAuthFailure' => true, + 'redirects' => 1, + 'storeAuth' => false, + ), $attributes); - if ($copyTo && !$fd = @fopen($copyTo.'~', 'w+b')) { - // TODO throw here probably? - $copyTo = null; + $originalOptions = $options; + + // check URL can be accessed (i.e. is not insecure) + $this->config->prohibitUrlByConfig($url, $this->io); + + $curlHandle = curl_init(); + $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b'); + + if ($copyTo) { + $errorMessage = ''; + set_error_handler(function ($code, $msg) use (&$errorMessage) { + if ($errorMessage) { + $errorMessage .= "\n"; + } + $errorMessage .= preg_replace('{^fopen\(.*?\): }', '', $msg); + }); + $bodyHandle = fopen($copyTo.'~', 'w+b'); + restore_error_handler(); + if (!$bodyHandle) { + throw new TransportException('The "'.$url.'" file could not be written to '.$copyTo.': '.$errorMessage); + } + } else { + $bodyHandle = @fopen('php://temp/maxmemory:524288', 'w+b'); } - if (!$copyTo) { - $fd = @fopen('php://temp/maxmemory:524288', 'w+b'); + + curl_setopt($curlHandle, CURLOPT_URL, $url); + curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 20); + //curl_setopt($curlHandle, CURLOPT_DNS_USE_GLOBAL_CACHE, false); + curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curlHandle, CURLOPT_TIMEOUT, 10); // TODO increase + curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle); + curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle); + curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); + curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); + if (defined('CURLOPT_SSL_FALSESTART')) { + curl_setopt($curlHandle, CURLOPT_SSL_FALSESTART, true); + } + if (function_exists('curl_share_init')) { + curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle); } if (!isset($options['http']['header'])) { $options['http']['header'] = array(); } - $headers = array_diff($options['http']['header'], array('Connection: close')); + $options['http']['header'] = array_diff($options['http']['header'], array('Connection: close')); + $options['http']['header'][] = 'Connection: keep-alive'; - // TODO - $degradedMode = false; - if ($degradedMode) { - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - } else { - $headers[] = 'Connection: keep-alive'; - $version = curl_version(); - $features = $version['features']; - if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features)) { - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); - } + $version = curl_version(); + $features = $version['features']; + if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features)) { + curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); } - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - //curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); // TODO increase - curl_setopt($ch, CURLOPT_WRITEHEADER, $hd); - curl_setopt($ch, CURLOPT_FILE, $fd); - if (function_exists('curl_share_init')) { - curl_setopt($ch, CURLOPT_SHARE, $this->shareHandle); - } + $options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url); + $options = StreamContextFactory::initOptions($url, $options); foreach (self::$options as $type => $curlOptions) { foreach ($curlOptions as $name => $curlOption) { if (isset($options[$type][$name])) { - curl_setopt($ch, $curlOption, $options[$type][$name]); + curl_setopt($curlHandle, $curlOption, $options[$type][$name]); } } } - $progress = array_diff_key(curl_getinfo($ch), self::$timeInfo); + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); - $this->jobs[(int) $ch] = array( + $this->jobs[(int) $curlHandle] = array( + 'url' => $url, + 'origin' => $origin, + 'attributes' => $attributes, + 'options' => $originalOptions, 'progress' => $progress, - 'ch' => $ch, + 'curlHandle' => $curlHandle, //'callback' => $params['notification'], - 'file' => $copyTo, - 'hd' => $hd, - 'fd' => $fd, + 'filename' => $copyTo, + 'headerHandle' => $headerHandle, + 'bodyHandle' => $bodyHandle, 'resolve' => $resolve, 'reject' => $reject, ); - $this->io->write('Downloading '.$url, true, IOInterface::DEBUG); + $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : ''; + $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : ''; + $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG); - $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $ch)); + $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); //$params['notification'](STREAM_NOTIFY_RESOLVE, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false); } @@ -169,74 +213,114 @@ class CurlDownloader } while ($progress = curl_multi_info_read($this->multiHandle)) { - $h = $progress['handle']; - $i = (int) $h; + $curlHandle = $progress['handle']; + $i = (int) $curlHandle; if (!isset($this->jobs[$i])) { continue; } - $progress = array_diff_key(curl_getinfo($h), self::$timeInfo); + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); $job = $this->jobs[$i]; unset($this->jobs[$i]); - curl_multi_remove_handle($this->multiHandle, $h); - $error = curl_error($h); - $errno = curl_errno($h); - curl_close($h); + curl_multi_remove_handle($this->multiHandle, $curlHandle); + $error = curl_error($curlHandle); + $errno = curl_errno($curlHandle); + curl_close($curlHandle); + $headers = null; + $statusCode = null; + $response = null; try { - //$this->onProgress($h, $job['callback'], $progress, $job['progress']); - if ('' !== $error) { - throw new TransportException(curl_error($h)); + //$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']); + if (CURLE_OK !== $errno) { + throw new TransportException($error); } - if ($job['file']) { - if (CURLE_OK === $errno) { - fclose($job['fd']); - rename($job['file'].'~', $job['file']); - call_user_func($job['resolve'], true); - } - // TODO otherwise show error? + $statusCode = $progress['http_code']; + rewind($job['headerHandle']); + $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); + fclose($job['headerHandle']); + + // prepare response object + if ($job['filename']) { + fclose($job['bodyHandle']); + $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~'); } else { - rewind($job['hd']); - $headers = explode("\r\n", rtrim(stream_get_contents($job['hd']))); - fclose($job['hd']); - rewind($job['fd']); - $contents = stream_get_contents($job['fd']); - fclose($job['fd']); - $this->io->writeError('['.$progress['http_code'].'] '.$progress['url'], true, IOInterface::DEBUG); - call_user_func($job['resolve'], new Response(array('url' => $progress['url']), $progress['http_code'], $headers, $contents)); + rewind($job['bodyHandle']); + $contents = stream_get_contents($job['bodyHandle']); + fclose($job['bodyHandle']); + $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); + $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } - } catch (TransportException $e) { - fclose($job['hd']); - fclose($job['fd']); - if ($job['file']) { - @unlink($job['file'].'~'); + + $response = $this->retryIfAuthNeeded($job, $response); + + // handle 3xx redirects, 304 Not Modified is excluded + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['redirects'] < $this->maxRedirects) { + // TODO + $response = $this->handleRedirect($job, $response); + } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode >= 400 && $statusCode <= 599) { + throw $this->failResponse($job, $response, $response->getStatusMessage()); +// $this->io->overwriteError("Downloading (failed)", false); + } + + if ($job['attributes']['storeAuth']) { + $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); + } + + // resolve promise + if ($job['filename']) { + rename($job['filename'].'~', $job['filename']); + call_user_func($job['resolve'], true); + } else { + call_user_func($job['resolve'], $response); + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $headers) { + $e->setHeaders($headers); + $e->setStatusCode($statusCode); + } + if ($e instanceof TransportException && $response) { + $e->setResponse($response->getBody()); + } + + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if ($job['filename']) { + @unlink($job['filename'].'~'); } call_user_func($job['reject'], $e); } } - foreach ($this->jobs as $i => $h) { + foreach ($this->jobs as $i => $curlHandle) { if (!isset($this->jobs[$i])) { continue; } - $h = $this->jobs[$i]['ch']; - $progress = array_diff_key(curl_getinfo($h), self::$timeInfo); + $curlHandle = $this->jobs[$i]['curlHandle']; + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); if ($this->jobs[$i]['progress'] !== $progress) { $previousProgress = $this->jobs[$i]['progress']; $this->jobs[$i]['progress'] = $progress; try { - //$this->onProgress($h, $this->jobs[$i]['callback'], $progress, $previousProgress); + //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); } catch (TransportException $e) { var_dump('Caught '.$e->getMessage());die; unset($this->jobs[$i]); - curl_multi_remove_handle($this->multiHandle, $h); - curl_close($h); + curl_multi_remove_handle($this->multiHandle, $curlHandle); + curl_close($curlHandle); - fclose($job['hd']); - fclose($job['fd']); - if ($job['file']) { - @unlink($job['file'].'~'); + fclose($job['headerHandle']); + fclose($job['bodyHandle']); + if ($job['filename']) { + @unlink($job['filename'].'~'); } call_user_func($job['reject'], $e); } @@ -245,22 +329,77 @@ class CurlDownloader } catch (\Exception $e) { var_dump('Caught2', get_class($e), $e->getMessage(), $e);die; } - -// TODO finalize / resolve -// if ($copyTo && !isset($this->exceptions[(int) $ch])) { -// $fd = fopen($copyTo, 'rb'); -// } -// } - private function onProgress($ch, callable $notify, array $progress, array $previousProgress) + private function retryIfAuthNeeded(array $job, Response $response) + { + if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) { + $warning = null; + if ($response->getHeader('content-type') === 'application/json') { + $data = json_decode($response->getBody(), true); + if (!empty($data['warning'])) { + $warning = $data['warning']; + } + } + + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $warning, $response->getHeaders()); + + if ($result['retry']) { + // TODO retry somehow using $result['storeAuth'] in the attributes + } + } + + $locationHeader = $response->getHeader('location'); + $needsAuthRetry = false; + + // check for bitbucket login page asking to authenticate + if ( + $job['origin'] === 'bitbucket.org' + && !$this->authHelper->isPublicBitBucketDownload($job['url']) + && substr($job['url'], -4) === '.zip' + && (!$locationHeader || substr($locationHeader, -4) !== '.zip') + && preg_match('{^text/html\b}i', $response->getHeader('content-type')) + ) { + $needsAuthRetry = 'Bitbucket requires authentication and it was not provided'; + } + + // check for gitlab 404 when downloading archives + if ( + $response->getStatusCode() === 404 + && $this->config && in_array($job['origin'], $this->config->get('gitlab-domains'), true) + && false !== strpos($job['url'], 'archive.zip') + ) { + $needsAuthRetry = 'GitLab requires authentication and it was not provided'; + } + + if ($needsAuthRetry) { + if ($job['attributes']['retryAuthFailure']) { + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401); + if ($result['retry']) { + // TODO ... + // TODO return early here to abort failResponse + } + } + + throw $this->failResponse($job, $response, $needsAuthRetry); + } + + return $response; + } + + private function failResponse(array $job, Response $response, $errorMessage) + { + return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')', $response->getStatusCode()); + } + + private function onProgress($curlHandle, callable $notify, array $progress, array $previousProgress) { if (300 <= $progress['http_code'] && $progress['http_code'] < 400) { return; } if (!$previousProgress['http_code'] && $progress['http_code'] && $progress['http_code'] < 200 || 400 <= $progress['http_code']) { $code = 403 === $progress['http_code'] ? STREAM_NOTIFY_AUTH_RESULT : STREAM_NOTIFY_FAILURE; - $notify($code, STREAM_NOTIFY_SEVERITY_ERR, curl_error($ch), $progress['http_code'], 0, 0, false); + $notify($code, STREAM_NOTIFY_SEVERITY_ERR, curl_error($curlHandle), $progress['http_code'], 0, 0, false); } if ($previousProgress['download_content_length'] < $progress['download_content_length']) { $notify(STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['download_content_length'], false); diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index f76057f3e..d2774c938 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -27,7 +27,7 @@ class Response throw new \LogicException('url key missing from request array'); } $this->request = $request; - $this->code = $code; + $this->code = (int) $code; $this->headers = $headers; $this->body = $body; } @@ -37,6 +37,23 @@ class Response return $this->code; } + /** + * @return string|null + */ + public function getStatusMessage() + { + $value = null; + foreach ($this->headers as $header) { + if (preg_match('{^HTTP/\S+ \d+}i', $header)) { + // In case of redirects, headers contain the headers of all responses + // so we can not return directly and need to keep iterating + $value = $header; + } + } + + return $value; + } + public function getHeaders() { return $this->headers; @@ -51,7 +68,7 @@ class Response } elseif (preg_match('{^HTTP/}i', $header)) { // TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary // - // In case of redirects, http_response_headers contains the headers of all responses + // In case of redirects, headers contains the headers of all responses // so we reset the flag when a new response is being parsed as we are only interested in the last response $value = null; } @@ -60,7 +77,6 @@ class Response return $value; } - public function getBody() { return $this->body; diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index d07823ec0..9cdc0c919 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -66,6 +66,7 @@ class HttpDownloader $this->options = array_replace_recursive($this->options, $options); $this->config = $config; + // TODO enable curl only on 5.6+ if older versions cause any problem if (extension_loaded('curl')) { $this->curl = new Http\CurlDownloader($io, $config, $options, $disableTls); } @@ -125,6 +126,11 @@ class HttpDownloader private function addJob($request, $sync = false) { + // capture username/password from URL if there is one + if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { + $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2])); + } + $job = array( 'id' => $this->idGen++, 'status' => self::STATUS_QUEUED, @@ -138,7 +144,6 @@ class HttpDownloader $origin = $this->getOrigin($job['request']['url']); - // TODO experiment with allowing file:// through curl too if ($curl && preg_match('{^https?://}i', $job['request']['url'])) { $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { // start job @@ -183,10 +188,10 @@ class HttpDownloader $job['response'] = $response; // TODO look for more jobs to start once we throttle to max X jobs }, function ($e) use ($io, &$job) { - var_dump(__CLASS__ . __LINE__); - var_dump(gettype($e)); - var_dump($e->getMessage()); - die; + // var_dump(__CLASS__ . __LINE__); + // var_dump(get_class($e)); + // var_dump($e->getMessage()); + // die; $job['status'] = HttpDownloader::STATUS_FAILED; $job['exception'] = $e; }); @@ -248,9 +253,13 @@ class HttpDownloader private function getOrigin($url) { + if (0 === strpos($url, 'file://')) { + return $url; + } + $origin = parse_url($url, PHP_URL_HOST); - if ($origin === 'api.github.com') { + if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { return 'github.com'; } @@ -258,6 +267,20 @@ class HttpDownloader return 'packagist.org'; } + // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl + // is the host without the path, so we look for the registered gitlab-domains matching the host here + if ( + is_array($this->config->get('gitlab-domains')) + && false === strpos($origin, '/') + && !in_array($origin, $this->config->get('gitlab-domains')) + ) { + foreach ($this->config->get('gitlab-domains') as $gitlabDomain) { + if (0 === strpos($gitlabDomain, $origin)) { + return $gitlabDomain; + } + } + } + return $origin ?: $url; } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 00fe35294..2709f7006 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -41,6 +41,7 @@ class RemoteFilesystem private $retryAuthFailure; private $lastHeaders; private $storeAuth; + private $authHelper; private $degradedMode = false; private $redirects; private $maxRedirects = 20; @@ -53,7 +54,7 @@ class RemoteFilesystem * @param array $options The options * @param bool $disableTls */ - public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false) + public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) { $this->io = $io; @@ -69,6 +70,7 @@ class RemoteFilesystem // handle the other externally set options normally. $this->options = array_replace_recursive($this->options, $options); $this->config = $config; + $this->authHelper = new AuthHelper($io, $config); } /** @@ -215,27 +217,6 @@ class RemoteFilesystem */ protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true) { - if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) { - $originUrl = 'github.com'; - } - - // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl - // is the host without the path, so we look for the registered gitlab-domains matching the host here - if ( - $this->config - && is_array($this->config->get('gitlab-domains')) - && false === strpos($originUrl, '/') - && !in_array($originUrl, $this->config->get('gitlab-domains')) - ) { - foreach ($this->config->get('gitlab-domains') as $gitlabDomain) { - if (0 === strpos($gitlabDomain, $originUrl)) { - $originUrl = $gitlabDomain; - break; - } - } - unset($gitlabDomain); - } - $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; @@ -247,11 +228,6 @@ class RemoteFilesystem $this->lastHeaders = array(); $this->redirects = 1; // The first request counts. - // capture username/password from URL if there is one - if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) { - $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2])); - } - $tempAdditionalOptions = $additionalOptions; if (isset($tempAdditionalOptions['retry-auth-failure'])) { $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; @@ -272,14 +248,6 @@ class RemoteFilesystem $origFileUrl = $fileUrl; - if (isset($options['github-token'])) { - // only add the access_token if it is actually a github URL (in case we were redirected to S3) - if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) { - $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token']; - } - unset($options['github-token']); - } - if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; unset($options['gitlab-token']); @@ -400,7 +368,7 @@ class RemoteFilesystem // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' - && !$this->isPublicBitBucketDownload($fileUrl) + && !$this->authHelper->isPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' && (!$locationHeader || substr($locationHeader, -4) !== '.zip') && $contentType && preg_match('{^text/html\b}i', $contentType) @@ -544,8 +512,7 @@ class RemoteFilesystem $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); if ($this->storeAuth && $this->config) { - $authHelper = new AuthHelper($this->io, $this->config); - $authHelper->storeAuth($this->originUrl, $this->storeAuth); + $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = false; } @@ -650,111 +617,14 @@ class RemoteFilesystem protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array()) { - if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { - $gitHubUtil = new GitHub($this->io, $this->config, null); - $message = "\n"; + $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $warning, $headers); - $rateLimited = $gitHubUtil->isRateLimited($headers); - if ($rateLimited) { - $rateLimit = $gitHubUtil->getRateLimit($headers); - if ($this->io->hasAuthentication($this->originUrl)) { - $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; - } else { - $message = 'Create a GitHub OAuth token to go over the API rate limit.'; - } + $this->storeAuth = $result['storeAuth']; + $this->retry = $result['retry']; - $message = sprintf( - 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.', - $rateLimit['limit'], - $rateLimit['reset'] - )."\n"; - } else { - $message .= 'Could not fetch '.$this->fileUrl.', please '; - if ($this->io->hasAuthentication($this->originUrl)) { - $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; - } else { - $message .= 'create a GitHub OAuth token to access private repos'; - } - } - - if (!$gitHubUtil->authorizeOAuth($this->originUrl) - && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) - ) { - throw new TransportException('Could not authenticate against '.$this->originUrl, 401); - } - } elseif ($this->config && in_array($this->originUrl, $this->config->get('gitlab-domains'), true)) { - $message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit'); - $gitLabUtil = new GitLab($this->io, $this->config, null); - - if ($this->io->hasAuthentication($this->originUrl) && ($auth = $this->io->getAuthentication($this->originUrl)) && $auth['password'] === 'private-token') { - throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); - } - - if (!$gitLabUtil->authorizeOAuth($this->originUrl) - && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message)) - ) { - throw new TransportException('Could not authenticate against '.$this->originUrl, 401); - } - } elseif ($this->config && $this->originUrl === 'bitbucket.org') { - $askForOAuthToken = true; - if ($this->io->hasAuthentication($this->originUrl)) { - $auth = $this->io->getAuthentication($this->originUrl); - if ($auth['username'] !== 'x-token-auth') { - $bitbucketUtil = new Bitbucket($this->io, $this->config); - $accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); - if (!empty($accessToken)) { - $this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken); - $askForOAuthToken = false; - } - } else { - throw new TransportException('Could not authenticate against ' . $this->originUrl, 401); - } - } - - if ($askForOAuthToken) { - $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit'); - $bitBucketUtil = new Bitbucket($this->io, $this->config); - if (! $bitBucketUtil->authorizeOAuth($this->originUrl) - && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message)) - ) { - throw new TransportException('Could not authenticate against ' . $this->originUrl, 401); - } - } - } else { - // 404s are only handled for github - if ($httpStatus === 404) { - return; - } - - // fail if the console is not interactive - if (!$this->io->isInteractive()) { - if ($httpStatus === 401) { - $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console to authenticate"; - } - if ($httpStatus === 403) { - $message = "The '" . $this->fileUrl . "' URL could not be accessed: " . $reason; - } - - throw new TransportException($message, $httpStatus); - } - // fail if we already have auth - if ($this->io->hasAuthentication($this->originUrl)) { - throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); - } - - $this->io->overwriteError(''); - if ($warning) { - $this->io->writeError(' '.$warning.''); - } - $this->io->writeError(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); - $username = $this->io->ask(' Username: '); - $password = $this->io->askAndHideAnswer(' Password: '); - $this->io->setAuthentication($this->originUrl, $username, $password); - $this->storeAuth = $this->config->get('store-auths'); + if ($this->retry) { + throw new TransportException('RETRY'); } - - $this->retry = true; - throw new TransportException('RETRY'); } protected function getOptionsForUrl($originUrl, $additionalOptions) @@ -814,27 +684,7 @@ class RemoteFilesystem $headers[] = 'Connection: close'; } - if ($this->io->hasAuthentication($originUrl)) { - $auth = $this->io->getAuthentication($originUrl); - if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) { - $options['github-token'] = $auth['username']; - } elseif ($this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)) { - if ($auth['password'] === 'oauth2') { - $headers[] = 'Authorization: Bearer '.$auth['username']; - } elseif ($auth['password'] === 'private-token') { - $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; - } - } elseif ('bitbucket.org' === $originUrl - && $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username'] - ) { - if (!$this->isPublicBitBucketDownload($this->fileUrl)) { - $headers[] = 'Authorization: Bearer ' . $auth['password']; - } - } else { - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $headers[] = 'Authorization: Basic '.$authStr; - } - } + $headers = $this->authHelper->addAuthenticationHeader($headers, $originUrl, $this->fileUrl); $options['http']['follow_location'] = 0; @@ -961,29 +811,4 @@ class RemoteFilesystem return parse_url($url, PHP_URL_HOST).':'.$port; } - - /** - * @link https://github.com/composer/composer/issues/5584 - * - * @param string $urlToBitBucketFile URL to a file at bitbucket.org. - * - * @return bool Whether the given URL is a public BitBucket download which requires no authentication. - */ - private function isPublicBitBucketDownload($urlToBitBucketFile) - { - $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); - if (strpos($domain, 'bitbucket.org') === false) { - // Bitbucket downloads are hosted on amazonaws. - // We do not need to authenticate there at all - return true; - } - - $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); - - // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} - // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/} - $pathParts = explode('/', $path); - - return count($pathParts) >= 4 && $pathParts[3] == 'downloads'; - } } diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index b25b307a1..a87bc6d8b 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -41,6 +41,32 @@ final class StreamContextFactory 'max_redirects' => 20, )); + $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions)); + unset($defaultOptions['http']['header']); + $options = array_replace_recursive($options, $defaultOptions); + + if (isset($options['http']['header'])) { + $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); + } + + return stream_context_create($options, $defaultParams); + } + + /** + * @param string $url + * @param array $options + * @return array ['http' => ['header' => [...], 'proxy' => '..', 'request_fulluri' => bool]] formatted as a stream context array + */ + public static function initOptions($url, array $options) + { + // Make sure the headers are in an array form + if (!isset($options['http']['header'])) { + $options['http']['header'] = array(); + } + if (is_string($options['http']['header'])) { + $options['http']['header'] = explode("\r\n", $options['http']['header']); + } + // Handle HTTP_PROXY/http_proxy on CLI only for security reasons if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); @@ -117,42 +143,36 @@ final class StreamContextFactory } $auth = base64_encode($auth); - // Preserve headers if already set in default options - if (isset($defaultOptions['http']['header'])) { - if (is_string($defaultOptions['http']['header'])) { - $defaultOptions['http']['header'] = array($defaultOptions['http']['header']); - } - $defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}"; - } else { - $options['http']['header'] = array("Proxy-Authorization: Basic {$auth}"); - } + $options['http']['header'][] = "Proxy-Authorization: Basic {$auth}"; } } - $options = array_replace_recursive($options, $defaultOptions); - - if (isset($options['http']['header'])) { - $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); - } - if (defined('HHVM_VERSION')) { $phpVersion = 'HHVM ' . HHVM_VERSION; } else { $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; } + if (extension_loaded('curl')) { + $curl = curl_version(); + $httpVersion = 'curl '.$curl['version']; + } else { + $httpVersion = 'streams'; + } + if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) { $options['http']['header'][] = sprintf( - 'User-Agent: Composer/%s (%s; %s; %s%s)', - Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION, + 'User-Agent: Composer/%s (%s; %s; %s; %s%s)', + Composer::VERSION === '@package_version@' ? Composer::SOURCE_VERSION : Composer::VERSION, function_exists('php_uname') ? php_uname('s') : 'Unknown', function_exists('php_uname') ? php_uname('r') : 'Unknown', $phpVersion, + $httpVersion, getenv('CI') ? '; CI' : '' ); } - return stream_context_create($options, $defaultParams); + return $options; } /** diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 7da88bc8a..8d1bf3194 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -17,6 +17,20 @@ use PHPUnit\Framework\TestCase; class RemoteFilesystemTest extends TestCase { + private function getConfigMock() + { + $config = $this->getMockBuilder('Composer\Config')->getMock(); + $config->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($key) { + if ($key === 'github-domains' || $key === 'gitlab-domains') { + return array(); + } + })); + + return $config; + } + public function testGetOptionsForUrl() { $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); @@ -101,7 +115,7 @@ class RemoteFilesystemTest extends TestCase public function testCallbackGetFileSize() { - $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getConfigMock()); $this->callCallbackGet($fs, STREAM_NOTIFY_FILE_SIZE_IS, 0, '', 0, 0, 20); $this->assertAttributeEquals(20, 'bytesMax', $fs); } @@ -114,7 +128,7 @@ class RemoteFilesystemTest extends TestCase ->method('overwriteError') ; - $fs = new RemoteFilesystem($io); + $fs = new RemoteFilesystem($io, $this->getConfigMock()); $this->setAttribute($fs, 'bytesMax', 20); $this->setAttribute($fs, 'progress', true); @@ -124,7 +138,7 @@ class RemoteFilesystemTest extends TestCase public function testCallbackGetPassesThrough404() { - $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getConfigMock()); $this->assertNull($this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0)); } @@ -139,7 +153,7 @@ class RemoteFilesystemTest extends TestCase ->method('setAuthentication') ->with($this->equalTo('github.com'), $this->equalTo('user'), $this->equalTo('pass')); - $fs = new RemoteFilesystem($io); + $fs = new RemoteFilesystem($io, $this->getConfigMock()); try { $fs->getContents('github.com', 'https://user:pass@github.com/composer/composer/404'); } catch (\Exception $e) { @@ -150,14 +164,14 @@ class RemoteFilesystemTest extends TestCase public function testGetContents() { - $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getConfigMock()); $this->assertContains('testGetContents', $fs->getContents('http://example.org', 'file://'.__FILE__)); } public function testCopy() { - $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock()); + $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getConfigMock()); $file = tempnam(sys_get_temp_dir(), 'c'); $this->assertTrue($fs->copy('http://example.org', 'file://'.__FILE__, $file)); @@ -218,7 +232,7 @@ class RemoteFilesystemTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $rfs = new RemoteFilesystem($io); + $rfs = new RemoteFilesystem($io, $this->getConfigMock()); $hostname = parse_url($url, PHP_URL_HOST); $result = $rfs->getContents($hostname, $url, false); @@ -240,14 +254,6 @@ class RemoteFilesystemTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $config = $this - ->getMockBuilder('Composer\Config') - ->getMock(); - $config - ->method('get') - ->withAnyParameters() - ->willReturn(array()); - $domains = array(); $io ->expects($this->any()) @@ -267,7 +273,7 @@ class RemoteFilesystemTest extends TestCase 'password' => '1A0yeK5Po3ZEeiiRiMWLivS0jirLdoGuaSGq9NvESFx1Fsdn493wUDXC8rz_1iKVRTl1GINHEUCsDxGh5lZ=', )); - $rfs = new RemoteFilesystem($io, $config); + $rfs = new RemoteFilesystem($io, $this->getConfigMock()); $hostname = parse_url($url, PHP_URL_HOST); $result = $rfs->getContents($hostname, $url, false); @@ -278,7 +284,7 @@ class RemoteFilesystemTest extends TestCase protected function callGetOptionsForUrl($io, array $args = array(), array $options = array(), $fileUrl = '') { - $fs = new RemoteFilesystem($io, null, $options); + $fs = new RemoteFilesystem($io, $this->getConfigMock(), $options); $ref = new \ReflectionMethod($fs, 'getOptionsForUrl'); $prop = new \ReflectionProperty($fs, 'fileUrl'); $ref->setAccessible(true); From 9986b797fb1b9acca9add379337717fe16a27ea0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 16 Nov 2018 14:28:00 +0100 Subject: [PATCH 362/580] Add support for redirects/retries in curl downloader --- src/Composer/Util/Http/CurlDownloader.php | 72 +++++++++++++++++++---- src/Composer/Util/HttpDownloader.php | 35 +---------- src/Composer/Util/Url.php | 34 +++++++++++ 3 files changed, 96 insertions(+), 45 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 2accb7a0c..83f07c44a 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -19,6 +19,7 @@ use Composer\CaBundle\CaBundle; use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; +use Composer\Util\Url; use Psr\Log\LoggerInterface; use React\Promise\Promise; @@ -132,11 +133,10 @@ class CurlDownloader } curl_setopt($curlHandle, CURLOPT_URL, $url); - curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($curlHandle, CURLOPT_MAXREDIRS, 20); + curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false); //curl_setopt($curlHandle, CURLOPT_DNS_USE_GLOBAL_CACHE, false); curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($curlHandle, CURLOPT_TIMEOUT, 10); // TODO increase + curl_setopt($curlHandle, CURLOPT_TIMEOUT, 60); curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle); curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle); curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); @@ -181,7 +181,6 @@ class CurlDownloader 'options' => $originalOptions, 'progress' => $progress, 'curlHandle' => $curlHandle, - //'callback' => $params['notification'], 'filename' => $copyTo, 'headerHandle' => $headerHandle, 'bodyHandle' => $bodyHandle, @@ -252,12 +251,24 @@ class CurlDownloader $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } - $response = $this->retryIfAuthNeeded($job, $response); + $result = $this->isAuthenticatedRetryNeeded($job, $response); + if ($result['retry']) { + if ($job['filename']) { + @unlink($job['filename'].'~'); + } + + $this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth'])); + continue; + } // handle 3xx redirects, 304 Not Modified is excluded if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['redirects'] < $this->maxRedirects) { // TODO - $response = $this->handleRedirect($job, $response); + $location = $this->handleRedirect($job, $response); + if ($location) { + $this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1)); + continue; + } } // fail 4xx and 5xx responses and capture the response @@ -331,7 +342,39 @@ class CurlDownloader } } - private function retryIfAuthNeeded(array $job, Response $response) + private function handleRedirect(array $job, Response $response) + { + if ($locationHeader = $response->getHeader('location')) { + if (parse_url($locationHeader, PHP_URL_SCHEME)) { + // Absolute URL; e.g. https://example.com/composer + $targetUrl = $locationHeader; + } elseif (parse_url($locationHeader, PHP_URL_HOST)) { + // Scheme relative; e.g. //example.com/foo + $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader; + } elseif ('/' === $locationHeader[0]) { + // Absolute path; e.g. /foo + $urlHost = parse_url($job['url'], PHP_URL_HOST); + + // Replace path using hostname as an anchor. + $targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']); + } else { + // Relative path; e.g. foo + // This actually differs from PHP which seems to add duplicate slashes. + $targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']); + } + } + + if (!empty($targetUrl)) { + $this->io->writeError('', true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Following redirect (%u) %s', $job['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG); + + return $targetUrl; + } + + throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')'); + } + + private function isAuthenticatedRetryNeeded(array $job, Response $response) { if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) { $warning = null; @@ -345,7 +388,7 @@ class CurlDownloader $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $warning, $response->getHeaders()); if ($result['retry']) { - // TODO retry somehow using $result['storeAuth'] in the attributes + return $result; } } @@ -376,15 +419,22 @@ class CurlDownloader if ($job['attributes']['retryAuthFailure']) { $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401); if ($result['retry']) { - // TODO ... - // TODO return early here to abort failResponse + return $result; } } throw $this->failResponse($job, $response, $needsAuthRetry); } - return $response; + return array('retry' => false, 'storeAuth' => false); + } + + private function restartJob(array $job, $url, array $attributes = array()) + { + $attributes = array_merge($job['attributes'], $attributes); + $origin = Url::getOrigin($this->config, $url); + + $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['originalOptions'], $job['filename'], $attributes); } private function failResponse(array $job, Response $response, $errorMessage) diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 9cdc0c919..da3906728 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -142,7 +142,7 @@ class HttpDownloader $rfs = $this->rfs; $io = $this->io; - $origin = $this->getOrigin($job['request']['url']); + $origin = Url::getOrigin($this->config, $job['request']['url']); if ($curl && preg_match('{^https?://}i', $job['request']['url'])) { $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { @@ -250,37 +250,4 @@ class HttpDownloader return $resp; } - - private function getOrigin($url) - { - if (0 === strpos($url, 'file://')) { - return $url; - } - - $origin = parse_url($url, PHP_URL_HOST); - - if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { - return 'github.com'; - } - - if ($origin === 'repo.packagist.org') { - return 'packagist.org'; - } - - // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl - // is the host without the path, so we look for the registered gitlab-domains matching the host here - if ( - is_array($this->config->get('gitlab-domains')) - && false === strpos($origin, '/') - && !in_array($origin, $this->config->get('gitlab-domains')) - ) { - foreach ($this->config->get('gitlab-domains') as $gitlabDomain) { - if (0 === strpos($gitlabDomain, $origin)) { - return $gitlabDomain; - } - } - } - - return $origin ?: $url; - } } diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 4a5d5f90c..3266c4220 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -52,4 +52,38 @@ class Url return $url; } + + public static function getOrigin(Config $config, $url) + { + if (0 === strpos($url, 'file://')) { + return $url; + } + + $origin = parse_url($url, PHP_URL_HOST); + + if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { + return 'github.com'; + } + + if ($origin === 'repo.packagist.org') { + return 'packagist.org'; + } + + // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl + // is the host without the path, so we look for the registered gitlab-domains matching the host here + if ( + is_array($config->get('gitlab-domains')) + && false === strpos($origin, '/') + && !in_array($origin, $config->get('gitlab-domains')) + ) { + foreach ($config->get('gitlab-domains') as $gitlabDomain) { + if (0 === strpos($gitlabDomain, $origin)) { + return $gitlabDomain; + } + } + } + + return $origin ?: $url; + } + } From 64384f8b15314e618f1a6821dd71325e8b8178ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 16 Nov 2018 14:43:25 +0100 Subject: [PATCH 363/580] Fix tests --- src/Composer/Util/HttpDownloader.php | 14 ++--- src/Composer/Util/Url.php | 8 ++- .../Composer/Test/Util/HttpDownloaderTest.php | 51 +++++++++++++++++++ .../Test/Util/RemoteFilesystemTest.php | 19 ------- 4 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 tests/Composer/Test/Util/HttpDownloaderTest.php diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index da3906728..b259ef3e5 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -126,11 +126,6 @@ class HttpDownloader private function addJob($request, $sync = false) { - // capture username/password from URL if there is one - if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { - $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2])); - } - $job = array( 'id' => $this->idGen++, 'status' => self::STATUS_QUEUED, @@ -138,12 +133,17 @@ class HttpDownloader 'sync' => $sync, ); + $origin = Url::getOrigin($this->config, $job['request']['url']); + + // capture username/password from URL if there is one + if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { + $this->io->setAuthentication($origin, rawurldecode($match[1]), rawurldecode($match[2])); + } + $curl = $this->curl; $rfs = $this->rfs; $io = $this->io; - $origin = Url::getOrigin($this->config, $job['request']['url']); - if ($curl && preg_match('{^https?://}i', $job['request']['url'])) { $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { // start job diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 3266c4220..2874524ed 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -59,7 +59,7 @@ class Url return $url; } - $origin = parse_url($url, PHP_URL_HOST); + $origin = (string) parse_url($url, PHP_URL_HOST); if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { return 'github.com'; @@ -69,6 +69,10 @@ class Url return 'packagist.org'; } + if ($origin === '') { + $origin = $url; + } + // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl // is the host without the path, so we look for the registered gitlab-domains matching the host here if ( @@ -83,7 +87,7 @@ class Url } } - return $origin ?: $url; + return $origin; } } diff --git a/tests/Composer/Test/Util/HttpDownloaderTest.php b/tests/Composer/Test/Util/HttpDownloaderTest.php new file mode 100644 index 000000000..b65aa760a --- /dev/null +++ b/tests/Composer/Test/Util/HttpDownloaderTest.php @@ -0,0 +1,51 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\HttpDownloader; +use PHPUnit\Framework\TestCase; + +class HttpDownloaderTest extends TestCase +{ + private function getConfigMock() + { + $config = $this->getMockBuilder('Composer\Config')->getMock(); + $config->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($key) { + if ($key === 'github-domains' || $key === 'gitlab-domains') { + return array(); + } + })); + + return $config; + } + + /** + * @group slow + */ + public function testCaptureAuthenticationParamsFromUrl() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $io->expects($this->once()) + ->method('setAuthentication') + ->with($this->equalTo('github.com'), $this->equalTo('user'), $this->equalTo('pass')); + + $fs = new HttpDownloader($io, $this->getConfigMock()); + try { + $fs->get('https://user:pass@github.com/composer/composer/404'); + } catch (\Composer\Downloader\TransportException $e) { + $this->assertNotEquals(200, $e->getCode()); + } + } +} diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 8d1bf3194..2c7f3112a 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -143,25 +143,6 @@ class RemoteFilesystemTest extends TestCase $this->assertNull($this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0)); } - /** - * @group slow - */ - public function testCaptureAuthenticationParamsFromUrl() - { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $io->expects($this->once()) - ->method('setAuthentication') - ->with($this->equalTo('github.com'), $this->equalTo('user'), $this->equalTo('pass')); - - $fs = new RemoteFilesystem($io, $this->getConfigMock()); - try { - $fs->getContents('github.com', 'https://user:pass@github.com/composer/composer/404'); - } catch (\Exception $e) { - $this->assertInstanceOf('Composer\Downloader\TransportException', $e); - $this->assertNotEquals(200, $e->getCode()); - } - } - public function testGetContents() { $fs = new RemoteFilesystem($this->getMockBuilder('Composer\IO\IOInterface')->getMock(), $this->getConfigMock()); From 5d2b3276ebf6b98114eb73a5bd315e59f0f08c0a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 16 Nov 2018 15:38:01 +0100 Subject: [PATCH 364/580] Avoid starting all jobs immediately --- src/Composer/Util/Http/CurlDownloader.php | 36 +++---- src/Composer/Util/HttpDownloader.php | 110 +++++++++++++++------- 2 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 83f07c44a..4015b0bac 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -95,12 +95,17 @@ class CurlDownloader public function download($resolve, $reject, $origin, $url, $options, $copyTo = null) { - return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo); + $attributes = array(); + if (isset($options['retry-auth-failure'])) { + $attributes['retryAuthFailure'] = $options['retry-auth-failure']; + unset($options['retry-auth-failure']); + } + + return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes); } private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array()) { - // TODO allow setting attributes somehow $attributes = array_merge(array( 'retryAuthFailure' => true, 'redirects' => 1, @@ -193,12 +198,12 @@ class CurlDownloader $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG); $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); +// TODO progress //$params['notification'](STREAM_NOTIFY_RESOLVE, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false); } public function tick() { - // TODO check we have active handles before doing this if (!$this->jobs) { return; } @@ -229,6 +234,7 @@ class CurlDownloader $statusCode = null; $response = null; try { +// TODO progress //$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']); if (CURLE_OK !== $errno) { throw new TransportException($error); @@ -263,7 +269,6 @@ class CurlDownloader // handle 3xx redirects, 304 Not Modified is excluded if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['redirects'] < $this->maxRedirects) { - // TODO $location = $this->handleRedirect($job, $response); if ($location) { $this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1)); @@ -274,6 +279,7 @@ class CurlDownloader // fail 4xx and 5xx responses and capture the response if ($statusCode >= 400 && $statusCode <= 599) { throw $this->failResponse($job, $response, $response->getStatusMessage()); +// TODO progress // $this->io->overwriteError("Downloading (failed)", false); } @@ -320,24 +326,13 @@ class CurlDownloader if ($this->jobs[$i]['progress'] !== $progress) { $previousProgress = $this->jobs[$i]['progress']; $this->jobs[$i]['progress'] = $progress; - try { - //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); - } catch (TransportException $e) { - var_dump('Caught '.$e->getMessage());die; - unset($this->jobs[$i]); - curl_multi_remove_handle($this->multiHandle, $curlHandle); - curl_close($curlHandle); - fclose($job['headerHandle']); - fclose($job['bodyHandle']); - if ($job['filename']) { - @unlink($job['filename'].'~'); - } - call_user_func($job['reject'], $e); - } + // TODO + //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); } } } catch (\Exception $e) { + // TODO var_dump('Caught2', get_class($e), $e->getMessage(), $e);die; } } @@ -444,13 +439,10 @@ class CurlDownloader private function onProgress($curlHandle, callable $notify, array $progress, array $previousProgress) { + // TODO add support for progress if (300 <= $progress['http_code'] && $progress['http_code'] < 400) { return; } - if (!$previousProgress['http_code'] && $progress['http_code'] && $progress['http_code'] < 200 || 400 <= $progress['http_code']) { - $code = 403 === $progress['http_code'] ? STREAM_NOTIFY_AUTH_RESULT : STREAM_NOTIFY_FAILURE; - $notify($code, STREAM_NOTIFY_SEVERITY_ERR, curl_error($curlHandle), $progress['http_code'], 0, 0, false); - } if ($previousProgress['download_content_length'] < $progress['download_content_length']) { $notify(STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['download_content_length'], false); } diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index b259ef3e5..0e882c4a3 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -33,8 +33,8 @@ class HttpDownloader private $config; private $jobs = array(); private $options = array(); - private $index; - private $progress; + private $runningJobs = 0; + private $maxJobs = 10; private $lastProgress; private $disableTls = false; private $curl; @@ -42,8 +42,6 @@ class HttpDownloader private $idGen = 0; /** - * Constructor. - * * @param IOInterface $io The IO instance * @param Config $config The config * @param array $options The options @@ -131,35 +129,24 @@ class HttpDownloader 'status' => self::STATUS_QUEUED, 'request' => $request, 'sync' => $sync, + 'origin' => Url::getOrigin($this->config, $request['url']), ); - $origin = Url::getOrigin($this->config, $job['request']['url']); - // capture username/password from URL if there is one if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { - $this->io->setAuthentication($origin, rawurldecode($match[1]), rawurldecode($match[2])); + $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2])); } - $curl = $this->curl; $rfs = $this->rfs; - $io = $this->io; - if ($curl && preg_match('{^https?://}i', $job['request']['url'])) { - $resolver = function ($resolve, $reject) use (&$job, $curl, $origin) { - // start job - $url = $job['request']['url']; - $options = $job['request']['options']; - - $job['status'] = HttpDownloader::STATUS_STARTED; - - if ($job['request']['copyTo']) { - $curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); - } else { - $curl->download($resolve, $reject, $origin, $url, $options); - } + if ($this->curl && preg_match('{^https?://}i', $job['request']['url'])) { + $resolver = function ($resolve, $reject) use (&$job) { + $job['status'] = HttpDownloader::STATUS_QUEUED; + $job['resolve'] = $resolve; + $job['reject'] = $reject; }; } else { - $resolver = function ($resolve, $reject) use (&$job, $rfs, $curl, $origin) { + $resolver = function ($resolve, $reject) use (&$job, $rfs) { // start job $url = $job['request']['url']; $options = $job['request']['options']; @@ -167,11 +154,11 @@ class HttpDownloader $job['status'] = HttpDownloader::STATUS_STARTED; if ($job['request']['copyTo']) { - $result = $rfs->copy($origin, $url, $job['request']['copyTo'], false /* TODO progress */, $options); + $result = $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options); $resolve($result); } else { - $body = $rfs->getContents($origin, $url, false /* TODO progress */, $options); + $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options); $headers = $rfs->getLastHeaders(); $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); @@ -180,26 +167,85 @@ class HttpDownloader }; } + $downloader = $this; + $io = $this->io; + $canceler = function () {}; $promise = new Promise($resolver, $canceler); - $promise->then(function ($response) use (&$job) { + $promise->then(function ($response) use (&$job, $downloader) { $job['status'] = HttpDownloader::STATUS_COMPLETED; $job['response'] = $response; - // TODO look for more jobs to start once we throttle to max X jobs - }, function ($e) use ($io, &$job) { - // var_dump(__CLASS__ . __LINE__); - // var_dump(get_class($e)); - // var_dump($e->getMessage()); - // die; + + // TODO 3.0 this should be done directly on $this when PHP 5.3 is dropped + $downloader->markJobDone(); + $downloader->scheduleNextJob(); + + return $response; + }, function ($e) use ($io, &$job, $downloader) { $job['status'] = HttpDownloader::STATUS_FAILED; $job['exception'] = $e; + + $downloader->markJobDone(); + + throw $e; }); $this->jobs[$job['id']] =& $job; + if ($this->runningJobs < $this->maxJobs) { + $this->startJob($job['id']); + } + return array($job, $promise); } + private function startJob($id) + { + $job =& $this->jobs[$id]; + if ($job['status'] !== self::STATUS_QUEUED) { + return; + } + + // start job + $job['status'] = self::STATUS_STARTED; + $this->runningJobs++; + + $resolve = $job['resolve']; + $reject = $job['reject']; + $url = $job['request']['url']; + $options = $job['request']['options']; + $origin = $job['origin']; + + if ($job['request']['copyTo']) { + $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); + } else { + $this->curl->download($resolve, $reject, $origin, $url, $options); + } + } + + /** + * @private + */ + public function markJobDone() + { + $this->runningJobs--; + } + + /** + * @private + */ + public function scheduleNextJob() + { + foreach ($this->jobs as $job) { + if ($job['status'] === self::STATUS_QUEUED) { + $this->startJob($job['id']); + if ($this->runningJobs >= $this->maxJobs) { + return; + } + } + } + } + public function wait($index = null, $progress = false) { while (true) { From 788a822b240dfc51569ebe842079a8a05f274ff9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 16 Nov 2018 17:35:06 +0100 Subject: [PATCH 365/580] Add some phpdocs --- .../Repository/PlatformRepository.php | 4 ++ src/Composer/Util/AuthHelper.php | 47 +++++++++++++------ src/Composer/Util/Url.php | 11 ++++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 6d6e04d2f..f4c38f3ea 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -308,6 +308,10 @@ class PlatformRepository extends ArrayRepository $this->addPackage($ext); } + /** + * @param string $name + * @return string + */ private function buildPackageName($name) { return 'ext-' . str_replace(' ', '-', $name); diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index e80a5b0c3..3679b93da 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -30,7 +30,11 @@ class AuthHelper $this->config = $config; } - public function storeAuth($originUrl, $storeAuth) + /** + * @param string $origin + * @param string|bool $storeAuth + */ + public function storeAuth($origin, $storeAuth) { $store = false; $configSource = $this->config->getAuthConfigSource(); @@ -38,7 +42,7 @@ class AuthHelper $store = $configSource; } elseif ($storeAuth === 'prompt') { $answer = $this->io->askAndValidate( - 'Do you want to store credentials for '.$originUrl.' in '.$configSource->getName().' ? [Yn] ', + 'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ', function ($value) { $input = strtolower(substr(trim($value), 0, 1)); if (in_array($input, array('y','n'))) { @@ -56,14 +60,23 @@ class AuthHelper } if ($store) { $store->addConfigSetting( - 'http-basic.'.$originUrl, - $this->io->getAuthentication($originUrl) + 'http-basic.'.$origin, + $this->io->getAuthentication($origin) ); } } - - public function promptAuthIfNeeded($url, $origin, $httpStatus, $reason = null, $warning = null, $headers = array()) + /** + * @param string $url + * @param string $origin + * @param int $statusCode HTTP status code that triggered this call + * @param string|null $reason a message/description explaining why this was called + * @param string $warning an authentication warning returned by the server as {"warning": ".."}, if present + * @param string[] $headers + * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be + * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json + */ + public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array()) { $storeAuth = false; $retry = false; @@ -101,11 +114,11 @@ class AuthHelper throw new TransportException('Could not authenticate against '.$origin, 401); } } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { - $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit'); + $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); $gitLabUtil = new GitLab($this->io, $this->config, null); if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') { - throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $httpStatus); + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } if (!$gitLabUtil->authorizeOAuth($origin) @@ -130,7 +143,7 @@ class AuthHelper } if ($askForOAuthToken) { - $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit'); + $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit'); $bitBucketUtil = new Bitbucket($this->io, $this->config); if (! $bitBucketUtil->authorizeOAuth($origin) && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message)) @@ -140,24 +153,24 @@ class AuthHelper } } else { // 404s are only handled for github - if ($httpStatus === 404) { + if ($statusCode === 404) { return; } // fail if the console is not interactive if (!$this->io->isInteractive()) { - if ($httpStatus === 401) { + if ($statusCode === 401) { $message = "The '" . $url . "' URL required authentication.\nYou must be using the interactive console to authenticate"; } - if ($httpStatus === 403) { + if ($statusCode === 403) { $message = "The '" . $url . "' URL could not be accessed: " . $reason; } - throw new TransportException($message, $httpStatus); + throw new TransportException($message, $statusCode); } // fail if we already have auth if ($this->io->hasAuthentication($origin)) { - throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $httpStatus); + throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } $this->io->overwriteError(''); @@ -176,6 +189,12 @@ class AuthHelper return array('retry' => $retry, 'storeAuth' => $storeAuth); } + /** + * @param array $headers + * @param string $origin + * @param string $url + * @return array updated headers array + */ public function addAuthenticationHeader(array $headers, $origin, $url) { if ($this->io->hasAuthentication($origin)) { diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index 2874524ed..c01677522 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -19,6 +19,12 @@ use Composer\Config; */ class Url { + /** + * @param Config $config + * @param string $url + * @param string $ref + * @return string the updated URL + */ public static function updateDistReference(Config $config, $url, $ref) { $host = parse_url($url, PHP_URL_HOST); @@ -53,6 +59,10 @@ class Url return $url; } + /** + * @param string $url + * @return string + */ public static function getOrigin(Config $config, $url) { if (0 === strpos($url, 'file://')) { @@ -89,5 +99,4 @@ class Url return $origin; } - } From ed65625126c42354f3b27a3ed922eb75de8f57d7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 28 Nov 2018 08:59:45 +0100 Subject: [PATCH 366/580] Handle custom http options cleaner in ComposerRepo --- src/Composer/Repository/ComposerRepository.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8c92d3e4d..939fb84ba 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -102,11 +102,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); - if ($this->options) { - // TODO solve this somehow - should be sent at request time not on the instance - $httpDownloader = clone $httpDownloader; - $httpDownloader->setOptions($this->options); - } $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; @@ -263,7 +258,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl); - $search = $this->httpDownloader->get($url)->decodeJson(); + $search = $this->httpDownloader->get($url, $this->options)->decodeJson(); if (empty($search['results'])) { return array(); @@ -826,7 +821,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } - $response = $httpDownloader->get($filename); + $response = $httpDownloader->get($filename, $this->options); $json = $response->getBody(); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow @@ -917,7 +912,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } - $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); + $options = $this->options; + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime); $response = $httpDownloader->get($filename, $options); $json = $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { From e67030076a135bb9e1c1f1201441128c581b307e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 28 Nov 2018 09:36:05 +0100 Subject: [PATCH 367/580] Fix show command --- src/Composer/Command/ShowCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 1dc5876a8..9604bdc71 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -553,7 +553,7 @@ EOT $matches[$index] = $package->getId(); } - $pool = $repositorySet->createPool(); + $pool = $repositorySet->createPoolForPackage($package->getName()); // select preferred package according to policy rules if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) { From 0961e16795dffb5a1ed10eac209e28e44dfcdb2e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Nov 2018 19:31:41 +0100 Subject: [PATCH 368/580] Add support for new metadata-url repo attribute --- .../Repository/ComposerRepository.php | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 939fb84ba..101c23bcb 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -667,6 +667,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); } + // metadata-url indiates V2 repo protocol so it takes over from all the V1 types + // V2 only has lazyProviders and no ability to process anything else, plus support for async loading + if (!empty($data['metadata-url'])) { + $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); + $this->providersUrl = null; + $this->hasProviders = false; + $this->hasPartialPackages = false; + $this->allowSslDowngrade = false; + unset($data['providers-url'], $data['providers'], $data['providers-includes']); + } + if ($this->allowSslDowngrade) { $this->url = str_replace('https://', 'http://', $this->url); $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl); @@ -681,22 +692,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->hasProviders = true; } - // TODO this is for testing only, remove once packagist reports v2 protocol support - if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url)) { - $this->repoConfig['force-lazy-providers'] = true; - } - - // force values for packagist - if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) { - $this->url = 'https://repo.packagist.org'; - $this->baseUrl = 'https://repo.packagist.org'; - $this->lazyProvidersUrl = $this->canonicalizeUrl('https://repo.packagist.org/p/%package%.json'); - $this->providersUrl = null; - } elseif (!empty($this->repoConfig['force-lazy-providers'])) { - $this->lazyProvidersUrl = $this->canonicalizeUrl('/p/%package%.json'); - $this->providersUrl = null; - } - return $this->rootData = $data; } From e753bf08b1cd3c2438a7e9aca0b94f5957626704 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 09:34:32 +0100 Subject: [PATCH 369/580] Minor tweaks --- composer.lock | 2 +- src/Composer/DependencyResolver/Solver.php | 1 + src/Composer/Repository/ComposerRepository.php | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 63b8033b9..a22e11a6b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d356b92e869790db1e9d2c0f4b10935e", + "content-hash": "3243ce6f26231df34d1bceab1a148803", "packages": [ { "name": "composer/ca-bundle", diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index aa5432188..c3128c6c4 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -217,6 +217,7 @@ class Solver $this->setupInstalledMap(); + $this->io->writeError('Generating rules', true, IOInterface::DEBUG); $this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 101c23bcb..a491bec72 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -527,6 +527,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // TODO what if not, then throw? if ($this->lazyProvidersUrl) { foreach ($packageNames as $name => $constraint) { + $name = strtolower($name); + // skip platform packages, root package and composer-plugin-api if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { continue; From e8c694877047f331e042c17a4ac3b63b1a25962d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 11:20:35 +0100 Subject: [PATCH 370/580] Deduplicate link instances between versions of a given package --- src/Composer/Package/Loader/ArrayLoader.php | 155 ++++++++++++---- .../Repository/ComposerRepository.php | 170 ++++++++++-------- .../Repository/ComposerRepositoryTest.php | 17 +- 3 files changed, 230 insertions(+), 112 deletions(-) diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 49ba45aa8..ac91b18fe 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -38,6 +38,70 @@ class ArrayLoader implements LoaderInterface } public function load(array $config, $class = 'Composer\Package\CompletePackage') + { + $package = $this->createObject($config, $class); + + foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) { + if (isset($config[$type])) { + $method = 'set'.ucfirst($opts['method']); + $package->{$method}( + $this->parseLinks( + $package->getName(), + $package->getPrettyVersion(), + $opts['description'], + $config[$type] + ) + ); + } + } + + $package = $this->configureObject($package, $config); + + return $package; + } + + public function loadPackages(array $versions, $class) + { + static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); + + $packages = array(); + $linkCache = array(); + + foreach ($versions as $version) { + if (isset($version['versions'])) { + $baseVersion = $version; + foreach ($uniqKeys as $key) { + unset($baseVersion[$key.'s']); + } + + foreach ($version['versions'] as $index => $dummy) { + $unpackedVersion = $baseVersion; + foreach ($uniqKeys as $key) { + $unpackedVersion[$key] = $version[$key.'s'][$index]; + } + + $package = $this->createObject($unpackedVersion, $class); + + $this->configureCachedLinks($linkCache, $package, $unpackedVersion); + $package = $this->configureObject($package, $unpackedVersion); + + $packages[] = $package; + } + } else { + $package = $this->createObject($version, $class); + + $this->configureCachedLinks($linkCache, $package, $version); + $package = $this->configureObject($package, $version); + + $packages[] = $package; + } + + } + + return $packages; + } + + private function createObject(array $config, $class) { if (!isset($config['name'])) { throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').'); @@ -52,7 +116,12 @@ class ArrayLoader implements LoaderInterface } else { $version = $this->versionParser->normalize($config['version']); } - $package = new $class($config['name'], $version, $config['version']); + + return new $class($config['name'], $version, $config['version']); + } + + private function configureObject($package, array $config) + { $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library'); if (isset($config['target-dir'])) { @@ -109,20 +178,6 @@ class ArrayLoader implements LoaderInterface } } - foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) { - if (isset($config[$type])) { - $method = 'set'.ucfirst($opts['method']); - $package->{$method}( - $this->parseLinks( - $package->getName(), - $package->getPrettyVersion(), - $opts['description'], - $config[$type] - ) - ); - } - } - if (isset($config['suggest']) && is_array($config['suggest'])) { foreach ($config['suggest'] as $target => $reason) { if ('self.version' === trim($reason)) { @@ -202,21 +257,50 @@ class ArrayLoader implements LoaderInterface } } - if ($aliasNormalized = $this->getBranchAlias($config)) { - if ($package instanceof RootPackageInterface) { - $package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); - } else { - $package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); - } - } - if ($this->loadOptions && isset($config['transport-options'])) { $package->setTransportOptions($config['transport-options']); } + if ($aliasNormalized = $this->getBranchAlias($config)) { + if ($package instanceof RootPackageInterface) { + return new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); + } + + return new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); + } + return $package; } + private function configureCachedLinks(&$linkCache, $package, array $config) + { + $name = $package->getName(); + $prettyVersion = $package->getPrettyVersion(); + + foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) { + if (isset($config[$type])) { + $method = 'set'.ucfirst($opts['method']); + + $links = array(); + foreach ($config[$type] as $prettyTarget => $constraint) { + $target = strtolower($prettyTarget); + if ($constraint === 'self.version') { + $links[$target] = $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint); + } else { + if (!isset($linkCache[$name][$type][$target][$constraint])) { + $linkCache[$name][$type][$target][$constraint] = array($target, $this->createLink($name, $prettyVersion, $opts['description'], $target, $constraint)); + } + + list($target, $link) = $linkCache[$name][$type][$target][$constraint]; + $links[$target] = $link; + } + } + + $package->{$method}($links); + } + } + } + /** * @param string $source source package name * @param string $sourceVersion source package version (pretty version ideally) @@ -228,21 +312,26 @@ class ArrayLoader implements LoaderInterface { $res = array(); foreach ($links as $target => $constraint) { - if (!is_string($constraint)) { - throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')'); - } - if ('self.version' === $constraint) { - $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); - } else { - $parsedConstraint = $this->versionParser->parseConstraints($constraint); - } - - $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); + $res[strtolower($target)] = $this->createLink($source, $sourceVersion, $description, $target, $constraint); } return $res; } + private function createLink($source, $sourceVersion, $description, $target, $prettyConstraint) + { + if (!is_string($prettyConstraint)) { + throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')'); + } + if ('self.version' === $prettyConstraint) { + $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); + } else { + $parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint); + } + + return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint); + } + /** * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists * diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index a491bec72..06c984bd5 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -61,7 +61,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private $rootData; private $hasPartialPackages; private $partialPackagesByName; - private $versionParser; + /** + * TODO v3 should make this private once we can drop PHP 5.3 support + * @private + */ + public $versionParser; public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) { @@ -414,6 +418,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->providers[$name] = array(); foreach ($packages['packages'] as $versions) { + $versionsToLoad = array(); foreach ($versions as $version) { if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) { continue; @@ -440,40 +445,44 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito continue; } - // load acceptable packages in the providers - $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); - $package->setRepository($this); + $versionsToLoad[] = $version; + } + } - if ($package instanceof AliasPackage) { - $aliased = $package->getAliasOf(); - $aliased->setRepository($this); + // load acceptable packages in the providers + $loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); + foreach ($loadedPackages as $package) { + $package->setRepository($this); - $this->providers[$name][$version['uid']] = $aliased; - $this->providers[$name][$version['uid'].'-alias'] = $package; + if ($package instanceof AliasPackage) { + $aliased = $package->getAliasOf(); + $aliased->setRepository($this); - // override provider with its alias so it can be expanded in the if block above - $this->providersByUid[$version['uid']] = $package; - } else { - $this->providers[$name][$version['uid']] = $package; - $this->providersByUid[$version['uid']] = $package; - } + $this->providers[$name][$version['uid']] = $aliased; + $this->providers[$name][$version['uid'].'-alias'] = $package; - // handle root package aliases - unset($rootAliasData); + // override provider with its alias so it can be expanded in the if block above + $this->providersByUid[$version['uid']] = $package; + } else { + $this->providers[$name][$version['uid']] = $package; + $this->providersByUid[$version['uid']] = $package; + } - if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { - $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()]; - } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) { - $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()]; - } + // handle root package aliases + unset($rootAliasData); - if (isset($rootAliasData)) { - $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']); - $alias->setRepository($this); + if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { + $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()]; + } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) { + $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()]; + } - $this->providers[$name][$version['uid'].'-root'] = $alias; - $this->providersByUid[$version['uid'].'-root'] = $alias; - } + if (isset($rootAliasData)) { + $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']); + $alias->setRepository($this); + + $this->providers[$name][$version['uid'].'-root'] = $alias; + $this->providersByUid[$version['uid'].'-root'] = $alias; } } } @@ -501,8 +510,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $repoData = $this->loadDataFromServer(); - foreach ($repoData as $package) { - $this->addPackage($this->createPackage($package, 'Composer\Package\CompletePackage')); + foreach ($this->createPackages($repoData, 'Composer\Package\CompletePackage') as $package) { + $this->addPackage($package); } } @@ -545,6 +554,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->asyncFetchFile($url, $cacheKey, $lastModified) ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { + static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); + if (true === $response) { $response = $contents; } @@ -553,24 +564,37 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return; } - $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); + $versionsToLoad = array(); foreach ($response['packages'][$name] as $version) { - if (isset($version['versions'])) { - $baseVersion = $version; - foreach ($uniqKeys as $key) { - unset($baseVersion[$key.'s']); - } - - foreach ($version['versions'] as $index => $dummy) { - $unpackedVersion = $baseVersion; - foreach ($uniqKeys as $key) { - $unpackedVersion[$key] = $version[$key.'s'][$index]; + if (isset($version['version_normalizeds'])) { + foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { + if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) { + foreach ($uniqKeys as $key) { + unset($version[$key.'s'][$index]); + } } - - $repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $unpackedVersion, $constraint); + } + if (count($version['version_normalizeds'])) { + $versionsToLoad[] = $version; } } else { - $repo->createPackageIfAcceptable($packages, $isPackageAcceptableCallable, $version, $constraint); + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $repo->versionParser->normalize($version['version']); + } + + if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) { + $versionsToLoad[] = $version; + } + } + } + + $loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); + foreach ($loadedPackages as $package) { + $package->setRepository($this); + + $packages[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } }, function ($e) { @@ -592,27 +616,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito * * @private */ - public function createPackageIfAcceptable(&$packages, $isPackageAcceptableCallable, $version, $constraint) + public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionNormalized) { - if (!call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { - return; + if (!call_user_func($isPackageAcceptableCallable, strtolower($name), VersionParser::parseStability($versionNormalized))) { + return false; } - if (isset($version['version_normalized']) && $constraint && !$constraint->matches(new Constraint('==', $version['version_normalized']))) { - return; + if ($constraint && !$constraint->matches(new Constraint('==', $versionNormalized))) { + return false; } - // load acceptable packages in the providers - $package = $this->createPackage($version, 'Composer\Package\CompletePackage'); - $package->setRepository($this); - - // if there was no version_normalized, then we need to check now for the constraint - if (!$constraint || isset($version['version_normalized']) || $constraint->matches(new Constraint('==', $package->getVersion()))) { - $packages[spl_object_hash($package)] = $package; - if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { - $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); - } - } + return true; } protected function loadRootServerFile() @@ -775,23 +789,37 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $packages; } - protected function createPackage(array $data, $class = 'Composer\Package\CompletePackage') + /** + * TODO v3 should make this private once we can drop PHP 5.3 support + * + * @private + */ + public function createPackages(array $packages, $class = 'Composer\Package\CompletePackage') { + if (!$packages) { + return; + } + try { - if (!isset($data['notification-url'])) { - $data['notification-url'] = $this->notifyUrl; + foreach ($packages as &$data) { + if (!isset($data['notification-url'])) { + $data['notification-url'] = $this->notifyUrl; + } } - $package = $this->loader->load($data, $class); - if (isset($this->sourceMirrors[$package->getSourceType()])) { - $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); - } - $package->setDistMirrors($this->distMirrors); - $this->configurePackageTransportOptions($package); + $packages = $this->loader->loadPackages($packages, $class); - return $package; + foreach ($packages as $package) { + if (isset($this->sourceMirrors[$package->getSourceType()])) { + $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); + } + $package->setDistMirrors($this->distMirrors); + $this->configurePackageTransportOptions($package); + } + + return $packages; } catch (\Exception $e) { - throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e); + throw new \RuntimeException('Could not load packages '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e); } } diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 0ffd70751..3594de101 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -32,7 +32,7 @@ class ComposerRepositoryTest extends TestCase ); $repository = $this->getMockBuilder('Composer\Repository\ComposerRepository') - ->setMethods(array('loadRootServerFile', 'createPackage')) + ->setMethods(array('loadRootServerFile', 'createPackages')) ->setConstructorArgs(array( $repoConfig, new NullIO, @@ -47,16 +47,17 @@ class ComposerRepositoryTest extends TestCase ->method('loadRootServerFile') ->will($this->returnValue($repoPackages)); + $stubs = array(); foreach ($expected as $at => $arg) { - $stubPackage = $this->getPackage('stub/stub', '1.0.0'); - - $repository - ->expects($this->at($at + 2)) - ->method('createPackage') - ->with($this->identicalTo($arg), $this->equalTo('Composer\Package\CompletePackage')) - ->will($this->returnValue($stubPackage)); + $stubs[] = $this->getPackage('stub/stub', '1.0.0'); } + $repository + ->expects($this->at(2)) + ->method('createPackages') + ->with($this->identicalTo($expected), $this->equalTo('Composer\Package\CompletePackage')) + ->will($this->returnValue($stubs)); + // Triggers initialization $packages = $repository->getPackages(); From fc03ab9bbae520a38bdc71f2d5620edd90967893 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 11:36:15 +0100 Subject: [PATCH 371/580] Add COMPOSER_DISABLE_NETWORK env var for debugging --- doc/03-cli.md | 5 +++++ src/Composer/Repository/ComposerRepository.php | 7 ++++++- src/Composer/Util/HttpDownloader.php | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 74374ec6b..f0b3dca35 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -928,4 +928,9 @@ repository options. Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the composer home, cache, and data directories. +### COMPOSER_DISABLE_NETWORK + +If set to `1`, disables network access (best effort). This can be used for debugging or +to run Composer on a plane or a starship with poor connectivity. + ← [Libraries](02-libraries.md) | [Schema](04-schema.md) → diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 06c984bd5..624621d79 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -1044,7 +1044,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return false; } - if (--$retries) { + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + $retries = 0; + } + + if (--$retries > 0) { usleep(100000); return $httpDownloader->add($filename, $options)->then($accept, $reject); diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 0e882c4a3..f2308d75a 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; +use Composer\Util\Http\Response; use Psr\Log\LoggerInterface; use React\Promise\Promise; @@ -40,6 +41,7 @@ class HttpDownloader private $curl; private $rfs; private $idGen = 0; + private $disabled; /** * @param IOInterface $io The IO instance @@ -51,6 +53,8 @@ class HttpDownloader { $this->io = $io; + $this->disabled = (bool) getenv('COMPOSER_DISABLE_NETWORK'); + // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { @@ -216,6 +220,17 @@ class HttpDownloader $options = $job['request']['options']; $origin = $job['origin']; + if ($this->disabled) { + if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { + $resolve(new Response(array('url' => $url), 304, array(), '')); + } else { + $e = new TransportException('Network disabled', 499); + $e->setStatusCode(499); + $reject($e); + } + return; + } + if ($job['request']['copyTo']) { $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); } else { From 00de0f58541b09d863e025d0b881256ef1d63ae8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 11:38:26 +0100 Subject: [PATCH 372/580] Fix 5.3 support --- src/Composer/Repository/ComposerRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 624621d79..89b7c78f6 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -588,9 +588,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - $loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); + $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); foreach ($loadedPackages as $package) { - $package->setRepository($this); + $package->setRepository($repo); $packages[spl_object_hash($package)] = $package; if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { From 14d6bcedda172844cda2dcc2ccb15c86a653d61b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 15:17:52 +0100 Subject: [PATCH 373/580] Fix redirect handling and some output tweaks --- src/Composer/Util/Http/CurlDownloader.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 4015b0bac..2528b9e3f 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -108,7 +108,7 @@ class CurlDownloader { $attributes = array_merge(array( 'retryAuthFailure' => true, - 'redirects' => 1, + 'redirects' => 0, 'storeAuth' => false, ), $attributes); @@ -195,7 +195,9 @@ class CurlDownloader $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : ''; $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : ''; - $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG); + if ($attributes['redirects'] === 0) { + $this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG); + } $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); // TODO progress @@ -249,6 +251,7 @@ class CurlDownloader if ($job['filename']) { fclose($job['bodyHandle']); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~'); + $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } else { rewind($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']); @@ -268,7 +271,7 @@ class CurlDownloader } // handle 3xx redirects, 304 Not Modified is excluded - if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['redirects'] < $this->maxRedirects) { + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { $location = $this->handleRedirect($job, $response); if ($location) { $this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1)); @@ -360,8 +363,7 @@ class CurlDownloader } if (!empty($targetUrl)) { - $this->io->writeError('', true, IOInterface::DEBUG); - $this->io->writeError(sprintf('Following redirect (%u) %s', $job['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG); + $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG); return $targetUrl; } @@ -429,7 +431,7 @@ class CurlDownloader $attributes = array_merge($job['attributes'], $attributes); $origin = Url::getOrigin($this->config, $url); - $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['originalOptions'], $job['filename'], $attributes); + $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); } private function failResponse(array $job, Response $response, $errorMessage) From b47330adf10738e2533b774e39f75fcec40365c2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 17:03:56 +0100 Subject: [PATCH 374/580] Refactor ComposerRepository to work with combined repos having lazy providers and partial packages --- src/Composer/Command/ShowCommand.php | 4 +- .../Repository/ComposerRepository.php | 338 ++++++++++-------- src/Composer/Util/Http/CurlDownloader.php | 217 ++++++----- .../Repository/ComposerRepositoryTest.php | 4 +- 4 files changed, 296 insertions(+), 267 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 9604bdc71..5cb3fa860 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -317,8 +317,8 @@ EOT } else { $type = 'available'; } - if ($repo instanceof ComposerRepository && $repo->hasProviders()) { - foreach ($repo->getProviderNames() as $name) { + if ($repo instanceof ComposerRepository) { + foreach ($repo->getPackageNames() as $name) { if (!$packageFilter || preg_match($packageFilter, $name)) { $packages[$type][$name] = $name; } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 89b7c78f6..ff2895564 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -35,13 +35,13 @@ use Composer\Semver\Constraint\EmptyConstraint; */ class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface { - protected $config; - protected $repoConfig; - protected $options; - protected $url; - protected $baseUrl; - protected $io; - protected $httpDownloader; + private $config; + private $repoConfig; + private $options; + private $url; + private $baseUrl; + private $io; + private $httpDownloader; protected $cache; protected $notifyUrl; protected $searchUrl; @@ -49,14 +49,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected $providersUrl; protected $lazyProvidersUrl; protected $providerListing; - protected $providers = array(); - protected $providersByUid = array(); + private $providers = array(); + private $providersByUid = array(); protected $loader; - protected $rootAliases; - protected $allowSslDowngrade = false; - protected $eventDispatcher; - protected $sourceMirrors; - protected $distMirrors; + private $rootAliases; + private $allowSslDowngrade = false; + private $eventDispatcher; + private $sourceMirrors; + private $distMirrors; private $degradedMode = false; private $rootData; private $hasPartialPackages; @@ -134,28 +134,23 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $constraint = $this->versionParser->parseConstraints($constraint); } - // TODO we need a new way for the repo to report this v2 protocol somehow if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $name, $constraint, true); + } + return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { return true; }); } + if (!$hasProviders) { return parent::findPackage($name, $constraint); } foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $packages = $this->whatProvides($providerName); - foreach ($packages as $package) { - if ($name === $package->getName()) { - $pkgConstraint = new Constraint('==', $package->getVersion()); - if ($constraint->matches($pkgConstraint)) { - return $package; - } - } - } - break; + return $this->filterPackages($this->whatProvides($providerName), $name, $constraint, true); } } } @@ -168,40 +163,58 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); - // TODO we need a new way for the repo to report this v2 protocol somehow - if ($this->lazyProvidersUrl) { - return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { - return true; - }); - } - if (!$hasProviders) { - return parent::findPackages($name, $constraint); - } - - // normalize name $name = strtolower($name); - if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } - $packages = array(); + if ($this->lazyProvidersUrl) { + if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { + return $this->filterPackages($this->whatProvides($name), $name, $constraint); + } + + return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { + return true; + }); + } + + if (!$hasProviders) { + return parent::findPackages($name, $constraint); + } foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - $candidates = $this->whatProvides($providerName); - foreach ($candidates as $package) { - if ($name === $package->getName()) { - $pkgConstraint = new Constraint('==', $package->getVersion()); - if (null === $constraint || $constraint->matches($pkgConstraint)) { - $packages[] = $package; - } - } - } - break; + return $this->filterPackages($this->whatProvides($providerName), $name, $constraint); } } + return array(); + } + + private function filterPackages(array $packages, $name, $constraint = null, $returnFirstMatch = false) + { + $packages = array(); + + foreach ($packages as $package) { + // TODO this check can be removed once providers are only what really has that name anyway + if ($name !== $package->getName()) { + continue; + } + + $pkgConstraint = new Constraint('==', $package->getVersion()); + if (null === $constraint || $constraint->matches($pkgConstraint)) { + if ($returnFirstMatch) { + return $package; + } + + $packages[] = $package; + } + } + + if ($returnFirstMatch) { + return null; + } + return $packages; } @@ -214,39 +227,63 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return parent::getPackages(); } + public function getPackageNames() + { + if ($this->hasProviders()) { + return $this->getProviderNames(); + } + + // TODO implement new list API endpoint for repos somehow? + // TODO add getPackageNames to the RepositoryInterface perhaps? With filtering capability embedded? + + return $this->getPackages(); + } + public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) { // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); - // TODO we need a new way for the repo to report this v2 protocol somehow - if ($this->lazyProvidersUrl) { - return $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable); - } - if (!$hasProviders) { + if (!$hasProviders && !$this->hasPartialPackages() && !$this->lazyProvidersUrl) { return parent::loadPackages($packageNameMap, $isPackageAcceptableCallable); } $packages = array(); - foreach ($packageNameMap as $name => $constraint) { - $matches = array(); - $candidates = $this->whatProvides($name, false, $isPackageAcceptableCallable); - foreach ($candidates as $candidate) { - if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) { - $matches[spl_object_hash($candidate)] = $candidate; - if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { - $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); - } + + if ($hasProviders || $this->hasPartialPackages()) { + foreach ($packageNameMap as $name => $constraint) { + $matches = array(); + + if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { + continue; } - } - foreach ($candidates as $candidate) { - if ($candidate instanceof AliasPackage) { - if (isset($result[spl_object_hash($candidate->getAliasOf())])) { + + $candidates = $this->whatProvides($name, $isPackageAcceptableCallable); + foreach ($candidates as $candidate) { + if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) { $matches[spl_object_hash($candidate)] = $candidate; + if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); + } } } + foreach ($candidates as $candidate) { + if ($candidate instanceof AliasPackage) { + if (isset($result[spl_object_hash($candidate->getAliasOf())])) { + $matches[spl_object_hash($candidate)] = $candidate; + } + } + } + $packages = array_merge($packages, $matches); + + unset($packageNameMap[$name]); } - $packages = array_merge($packages, $matches); + + return $packages; + } + + if ($this->lazyProvidersUrl) { + $packages = array_merge($packages, $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable)); } return $packages; @@ -295,7 +332,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return parent::search($query, $mode); } - public function getProviderNames() + private function getProviderNames() { $this->loadRootServerFile(); @@ -315,7 +352,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return array(); } - protected function configurePackageTransportOptions(PackageInterface $package) + private function configurePackageTransportOptions(PackageInterface $package) { foreach ($package->getDistUrls() as $url) { if (strpos($url, $this->baseUrl) === 0) { @@ -326,7 +363,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - public function hasProviders() + private function hasProviders() { $this->loadRootServerFile(); @@ -335,21 +372,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito /** * @param string $name package name - * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache * @param callable $isPackageAcceptableCallable * @return array|mixed */ - public function whatProvides($name, $bypassFilters = false, $isPackageAcceptableCallable = null) + private function whatProvides($name, $isPackageAcceptableCallable = null) { - if (isset($this->providers[$name]) && !$bypassFilters) { + if (isset($this->providers[$name])) { return $this->providers[$name]; } - if ($this->hasPartialPackages && null === $this->partialPackagesByName) { - $this->initializePartialPackages(); - } - - if (!$this->hasPartialPackages || !isset($this->partialPackagesByName[$name])) { + if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { return array(); @@ -420,7 +452,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($packages['packages'] as $versions) { $versionsToLoad = array(); foreach ($versions as $version) { - if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) { + if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$version['name']])) { continue; } @@ -441,7 +473,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } else { - if (!$bypassFilters && $isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { + if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { continue; } @@ -489,15 +521,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $result = $this->providers[$name]; - // clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache - // which is likely not an issue but might cause hard to track behaviors depending on how the repo is used - if ($bypassFilters) { - foreach ($this->providers[$name] as $uid => $provider) { - unset($this->providersByUid[$uid]); - } - unset($this->providers[$name]); - } - return $result; } @@ -533,76 +556,76 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $packages = array(); $repo = $this; - // TODO what if not, then throw? - if ($this->lazyProvidersUrl) { - foreach ($packageNames as $name => $constraint) { - $name = strtolower($name); + if (!$this->lazyProvidersUrl) { + throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); + } - // skip platform packages, root package and composer-plugin-api - if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { - continue; - } + foreach ($packageNames as $name => $constraint) { + $name = strtolower($name); - $url = str_replace('%package%', $name, $this->lazyProvidersUrl); - $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + // skip platform packages, root package and composer-plugin-api + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { + continue; + } - $lastModified = null; - if ($contents = $this->cache->read($cacheKey)) { - $contents = json_decode($contents, true); - $lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null; - } + $url = str_replace('%package%', $name, $this->lazyProvidersUrl); + $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; - $this->asyncFetchFile($url, $cacheKey, $lastModified) - ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { - static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); + $lastModified = null; + if ($contents = $this->cache->read($cacheKey)) { + $contents = json_decode($contents, true); + $lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null; + } - if (true === $response) { - $response = $contents; - } + $this->asyncFetchFile($url, $cacheKey, $lastModified) + ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { + static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); - if (!isset($response['packages'][$name])) { - return; - } + if (true === $response) { + $response = $contents; + } - $versionsToLoad = array(); - foreach ($response['packages'][$name] as $version) { - if (isset($version['version_normalizeds'])) { - foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { - if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) { - foreach ($uniqKeys as $key) { - unset($version[$key.'s'][$index]); - } + if (!isset($response['packages'][$name])) { + return; + } + + $versionsToLoad = array(); + foreach ($response['packages'][$name] as $version) { + if (isset($version['version_normalizeds'])) { + foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { + if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) { + foreach ($uniqKeys as $key) { + unset($version[$key.'s'][$index]); } } - if (count($version['version_normalizeds'])) { - $versionsToLoad[] = $version; - } - } else { - if (!isset($version['version_normalized'])) { - $version['version_normalized'] = $repo->versionParser->normalize($version['version']); - } + } + if (count($version['version_normalizeds'])) { + $versionsToLoad[] = $version; + } + } else { + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $repo->versionParser->normalize($version['version']); + } - if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) { - $versionsToLoad[] = $version; - } + if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) { + $versionsToLoad[] = $version; } } + } - $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); - foreach ($loadedPackages as $package) { - $package->setRepository($repo); + $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); + foreach ($loadedPackages as $package) { + $package->setRepository($repo); - $packages[spl_object_hash($package)] = $package; - if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { - $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); - } + $packages[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } - }, function ($e) { - // TODO use ->done() above instead with react/promise 2.0 - var_dump('Uncaught Ex', $e->getMessage()); - throw $e; - }); - } + } + }, function ($e) { + // TODO use ->done() above instead with react/promise 2.0 + throw $e; + }); } $this->httpDownloader->wait(); @@ -684,12 +707,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } // metadata-url indiates V2 repo protocol so it takes over from all the V1 types - // V2 only has lazyProviders and no ability to process anything else, plus support for async loading + // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else, + // V2 also supports async loading if (!empty($data['metadata-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); $this->providersUrl = null; $this->hasProviders = false; - $this->hasPartialPackages = false; + $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); $this->allowSslDowngrade = false; unset($data['providers-url'], $data['providers'], $data['providers-includes']); } @@ -711,7 +735,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $this->rootData = $data; } - protected function canonicalizeUrl($url) + private function canonicalizeUrl($url) { if ('/' === $url[0]) { return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url); @@ -720,14 +744,23 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $url; } - protected function loadDataFromServer() + private function loadDataFromServer() { $data = $this->loadRootServerFile(); return $this->loadIncludes($data); } - protected function loadProviderListings($data) + private function hasPartialPackages() + { + if ($this->hasPartialPackages && null === $this->partialPackagesByName) { + $this->initializePartialPackages(); + } + + return $this->hasPartialPackages; + } + + private function loadProviderListings($data) { if (isset($data['providers'])) { if (!is_array($this->providerListing)) { @@ -752,7 +785,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - protected function loadIncludes($data) + private function loadIncludes($data) { $packages = array(); @@ -924,7 +957,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $data; } - protected function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime) + private function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime) { $retries = 3; while ($retries--) { @@ -990,7 +1023,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - protected function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null) + private function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null) { $retries = 3; $httpDownloader = $this->httpDownloader; @@ -1039,7 +1072,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito }; $reject = function ($e) use (&$retries, $httpDownloader, $filename, $options, &$reject, $accept, $io, $url, $cache, &$degradedMode) { - var_dump('Caught8', $e->getMessage()); if ($e instanceof TransportException && $e->getStatusCode() === 404) { return false; } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 2528b9e3f..1ff8393c9 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -211,132 +211,127 @@ class CurlDownloader } $active = true; - try { - $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); - if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { - // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select - usleep(150); + $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); + if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { + // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select + usleep(150); + } + + while ($progress = curl_multi_info_read($this->multiHandle)) { + $curlHandle = $progress['handle']; + $i = (int) $curlHandle; + if (!isset($this->jobs[$i])) { + continue; } + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + $job = $this->jobs[$i]; + unset($this->jobs[$i]); + curl_multi_remove_handle($this->multiHandle, $curlHandle); + $error = curl_error($curlHandle); + $errno = curl_errno($curlHandle); + curl_close($curlHandle); - while ($progress = curl_multi_info_read($this->multiHandle)) { - $curlHandle = $progress['handle']; - $i = (int) $curlHandle; - if (!isset($this->jobs[$i])) { - continue; + $headers = null; + $statusCode = null; + $response = null; + try { +// TODO progress + //$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']); + if (CURLE_OK !== $errno) { + throw new TransportException($error); } - $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); - $job = $this->jobs[$i]; - unset($this->jobs[$i]); - curl_multi_remove_handle($this->multiHandle, $curlHandle); - $error = curl_error($curlHandle); - $errno = curl_errno($curlHandle); - curl_close($curlHandle); - $headers = null; - $statusCode = null; - $response = null; - try { -// TODO progress - //$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']); - if (CURLE_OK !== $errno) { - throw new TransportException($error); - } + $statusCode = $progress['http_code']; + rewind($job['headerHandle']); + $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); + fclose($job['headerHandle']); - $statusCode = $progress['http_code']; - rewind($job['headerHandle']); - $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); - fclose($job['headerHandle']); + // prepare response object + if ($job['filename']) { + fclose($job['bodyHandle']); + $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~'); + $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); + } else { + rewind($job['bodyHandle']); + $contents = stream_get_contents($job['bodyHandle']); + fclose($job['bodyHandle']); + $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); + $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); + } - // prepare response object - if ($job['filename']) { - fclose($job['bodyHandle']); - $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~'); - $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); - } else { - rewind($job['bodyHandle']); - $contents = stream_get_contents($job['bodyHandle']); - fclose($job['bodyHandle']); - $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); - $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); - } - - $result = $this->isAuthenticatedRetryNeeded($job, $response); - if ($result['retry']) { - if ($job['filename']) { - @unlink($job['filename'].'~'); - } - - $this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth'])); - continue; - } - - // handle 3xx redirects, 304 Not Modified is excluded - if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { - $location = $this->handleRedirect($job, $response); - if ($location) { - $this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1)); - continue; - } - } - - // fail 4xx and 5xx responses and capture the response - if ($statusCode >= 400 && $statusCode <= 599) { - throw $this->failResponse($job, $response, $response->getStatusMessage()); -// TODO progress -// $this->io->overwriteError("Downloading (failed)", false); - } - - if ($job['attributes']['storeAuth']) { - $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); - } - - // resolve promise - if ($job['filename']) { - rename($job['filename'].'~', $job['filename']); - call_user_func($job['resolve'], true); - } else { - call_user_func($job['resolve'], $response); - } - } catch (\Exception $e) { - if ($e instanceof TransportException && $headers) { - $e->setHeaders($headers); - $e->setStatusCode($statusCode); - } - if ($e instanceof TransportException && $response) { - $e->setResponse($response->getBody()); - } - - if (is_resource($job['headerHandle'])) { - fclose($job['headerHandle']); - } - if (is_resource($job['bodyHandle'])) { - fclose($job['bodyHandle']); - } + $result = $this->isAuthenticatedRetryNeeded($job, $response); + if ($result['retry']) { if ($job['filename']) { @unlink($job['filename'].'~'); } - call_user_func($job['reject'], $e); - } - } - foreach ($this->jobs as $i => $curlHandle) { - if (!isset($this->jobs[$i])) { + $this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth'])); continue; } - $curlHandle = $this->jobs[$i]['curlHandle']; - $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); - if ($this->jobs[$i]['progress'] !== $progress) { - $previousProgress = $this->jobs[$i]['progress']; - $this->jobs[$i]['progress'] = $progress; - - // TODO - //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); + // handle 3xx redirects, 304 Not Modified is excluded + if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { + $location = $this->handleRedirect($job, $response); + if ($location) { + $this->restartJob($job, $location, array('redirects' => $job['attributes']['redirects'] + 1)); + continue; + } } + + // fail 4xx and 5xx responses and capture the response + if ($statusCode >= 400 && $statusCode <= 599) { + throw $this->failResponse($job, $response, $response->getStatusMessage()); +// TODO progress +// $this->io->overwriteError("Downloading (failed)", false); + } + + if ($job['attributes']['storeAuth']) { + $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); + } + + // resolve promise + if ($job['filename']) { + rename($job['filename'].'~', $job['filename']); + call_user_func($job['resolve'], true); + } else { + call_user_func($job['resolve'], $response); + } + } catch (\Exception $e) { + if ($e instanceof TransportException && $headers) { + $e->setHeaders($headers); + $e->setStatusCode($statusCode); + } + if ($e instanceof TransportException && $response) { + $e->setResponse($response->getBody()); + } + + if (is_resource($job['headerHandle'])) { + fclose($job['headerHandle']); + } + if (is_resource($job['bodyHandle'])) { + fclose($job['bodyHandle']); + } + if ($job['filename']) { + @unlink($job['filename'].'~'); + } + call_user_func($job['reject'], $e); + } + } + + foreach ($this->jobs as $i => $curlHandle) { + if (!isset($this->jobs[$i])) { + continue; + } + $curlHandle = $this->jobs[$i]['curlHandle']; + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); + + if ($this->jobs[$i]['progress'] !== $progress) { + $previousProgress = $this->jobs[$i]['progress']; + $this->jobs[$i]['progress'] = $progress; + + // TODO + //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); } - } catch (\Exception $e) { - // TODO - var_dump('Caught2', get_class($e), $e->getMessage(), $e);die; } } diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 3594de101..552c767a5 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -153,7 +153,9 @@ class ComposerRepositoryTest extends TestCase ), )); - $packages = $repo->whatProvides('a', false, array($this, 'isPackageAcceptableReturnTrue')); + $reflMethod = new \ReflectionMethod($repo, 'whatProvides'); + $reflMethod->setAccessible(true); + $packages = $reflMethod->invoke($repo, 'a', array($this, 'isPackageAcceptableReturnTrue')); $this->assertCount(7, $packages); $this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages)); From 137c32e72e45ec2c9cbdfb3dc7226897fe1ea92f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 17:27:23 +0100 Subject: [PATCH 375/580] Do not prohibit http for old provider URLs on .org in case they are used --- src/Composer/Util/Http/CurlDownloader.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 1ff8393c9..45b1d21de 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -114,8 +114,10 @@ class CurlDownloader $originalOptions = $options; - // check URL can be accessed (i.e. is not insecure) - $this->config->prohibitUrlByConfig($url, $this->io); + // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 + if (!preg_match('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) { + $this->config->prohibitUrlByConfig($url, $this->io); + } $curlHandle = curl_init(); $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b'); From a5d5270a7e8db39baf141bccdfc4c4875ed0f1db Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 4 Dec 2018 17:37:39 +0100 Subject: [PATCH 376/580] Make sure other providers of a name are never loaded --- .../Repository/ComposerRepository.php | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index ff2895564..420bde535 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -136,7 +136,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { - return $this->filterPackages($this->whatProvides($name), $name, $constraint, true); + return $this->filterPackages($this->whatProvides($name), $constraint, true); } return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { @@ -150,7 +150,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - return $this->filterPackages($this->whatProvides($providerName), $name, $constraint, true); + return $this->filterPackages($this->whatProvides($providerName), $constraint, true); } } } @@ -170,7 +170,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { - return $this->filterPackages($this->whatProvides($name), $name, $constraint); + return $this->filterPackages($this->whatProvides($name), $constraint); } return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { @@ -184,22 +184,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { - return $this->filterPackages($this->whatProvides($providerName), $name, $constraint); + return $this->filterPackages($this->whatProvides($providerName), $constraint); } } return array(); } - private function filterPackages(array $packages, $name, $constraint = null, $returnFirstMatch = false) + private function filterPackages(array $packages, $constraint = null, $returnFirstMatch = false) { $packages = array(); foreach ($packages as $package) { - // TODO this check can be removed once providers are only what really has that name anyway - if ($name !== $package->getName()) { - continue; - } $pkgConstraint = new Constraint('==', $package->getVersion()); if (null === $constraint || $constraint->matches($pkgConstraint)) { @@ -452,7 +448,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($packages['packages'] as $versions) { $versionsToLoad = array(); foreach ($versions as $version) { - if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$version['name']])) { + $normalizedName = strtolower($version['name']); + + // only load the actual named package, not other packages that might find themselves in the same file + if ($normalizedName !== $name) { + continue; + } + + if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) { continue; } @@ -473,7 +476,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } else { - if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, strtolower($version['name']), VersionParser::parseStability($version['version']))) { + if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $normalizedName, VersionParser::parseStability($version['version']))) { continue; } @@ -830,7 +833,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito public function createPackages(array $packages, $class = 'Composer\Package\CompletePackage') { if (!$packages) { - return; + return array(); } try { @@ -1110,19 +1113,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->partialPackagesByName = array(); foreach ($rootData['packages'] as $package => $versions) { - $package = strtolower($package); foreach ($versions as $version) { - $this->partialPackagesByName[$package][] = $version; - if (!empty($version['provide']) && is_array($version['provide'])) { - foreach ($version['provide'] as $provided => $providedVersion) { - $this->partialPackagesByName[strtolower($provided)][] = $version; - } - } - if (!empty($version['replace']) && is_array($version['replace'])) { - foreach ($version['replace'] as $provided => $providedVersion) { - $this->partialPackagesByName[strtolower($provided)][] = $version; - } - } + $this->partialPackagesByName[strtolower($version['name'])][] = $version; } } From 4b7658a2a86ee9f834dd022de1e25487e4a140c4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 5 Dec 2018 13:28:37 +0100 Subject: [PATCH 377/580] Small tweaks and make sure composer fails properly in a plane --- src/Composer/Repository/ComposerRepository.php | 9 +++++++-- src/Composer/Util/Http/CurlDownloader.php | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 420bde535..43087ee7f 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -250,13 +250,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($packageNameMap as $name => $constraint) { $matches = array(); + // if a repo has no providers but only partial packages and the partial packages are missing + // then we don't want to call whatProvides as it would try to load from the providers and fail if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { continue; } $candidates = $this->whatProvides($name, $isPackageAcceptableCallable); foreach ($candidates as $candidate) { - if ($candidate->getName() === $name && (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion())))) { + if ($candidate->getName() !== $name) { + throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); + } + if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[spl_object_hash($candidate)] = $candidate; if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); @@ -278,7 +283,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $packages; } - if ($this->lazyProvidersUrl) { + if ($this->lazyProvidersUrl && count($packageNameMap)) { $packages = array_merge($packages, $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable)); } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 45b1d21de..3dc6710b6 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -225,6 +225,7 @@ class CurlDownloader if (!isset($this->jobs[$i])) { continue; } + $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); $job = $this->jobs[$i]; unset($this->jobs[$i]); @@ -239,7 +240,7 @@ class CurlDownloader try { // TODO progress //$this->onProgress($curlHandle, $job['callback'], $progress, $job['progress']); - if (CURLE_OK !== $errno) { + if (CURLE_OK !== $errno || $error) { throw new TransportException($error); } From fd5c5ff6bc4b7662d342efebace527295f5f4160 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 14 Jan 2019 15:30:55 +0100 Subject: [PATCH 378/580] Fix implementation of whatProvides for older provider-only repos --- src/Composer/Package/Loader/ArrayLoader.php | 1 - .../Repository/ComposerRepository.php | 92 +++++-------------- .../Repository/ComposerRepositoryTest.php | 13 +-- 3 files changed, 24 insertions(+), 82 deletions(-) diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index ac91b18fe..9fed111e0 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -95,7 +95,6 @@ class ArrayLoader implements LoaderInterface $packages[] = $package; } - } return $packages; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 43087ee7f..829883b58 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -49,10 +49,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected $providersUrl; protected $lazyProvidersUrl; protected $providerListing; - private $providers = array(); - private $providersByUid = array(); protected $loader; - private $rootAliases; private $allowSslDowngrade = false; private $eventDispatcher; private $sourceMirrors; @@ -116,11 +113,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $this->repoConfig; } - public function setRootAliases(array $rootAliases) - { - $this->rootAliases = $rootAliases; - } - /** * {@inheritDoc} */ @@ -378,10 +370,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito */ private function whatProvides($name, $isPackageAcceptableCallable = null) { - if (isset($this->providers[$name])) { - return $this->providers[$name]; - } - if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { @@ -449,9 +437,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $loadingPartialPackage = true; } - $this->providers[$name] = array(); + $result = array(); + $versionsToLoad = array(); foreach ($packages['packages'] as $versions) { - $versionsToLoad = array(); foreach ($versions as $version) { $normalizedName = strtolower($version['name']); @@ -464,70 +452,34 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito continue; } - // avoid loading the same objects twice - if (isset($this->providersByUid[$version['uid']])) { - // skip if already assigned - if (!isset($this->providers[$name][$version['uid']])) { - // expand alias in two packages - if ($this->providersByUid[$version['uid']] instanceof AliasPackage) { - $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf(); - $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']]; - } else { - $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]; - } - // check for root aliases - if (isset($this->providersByUid[$version['uid'].'-root'])) { - $this->providers[$name][$version['uid'].'-root'] = $this->providersByUid[$version['uid'].'-root']; - } - } - } else { + if (!isset($versionsToLoad[$version['uid']])) { if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $normalizedName, VersionParser::parseStability($version['version']))) { continue; } - $versionsToLoad[] = $version; - } - } - - // load acceptable packages in the providers - $loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); - foreach ($loadedPackages as $package) { - $package->setRepository($this); - - if ($package instanceof AliasPackage) { - $aliased = $package->getAliasOf(); - $aliased->setRepository($this); - - $this->providers[$name][$version['uid']] = $aliased; - $this->providers[$name][$version['uid'].'-alias'] = $package; - - // override provider with its alias so it can be expanded in the if block above - $this->providersByUid[$version['uid']] = $package; - } else { - $this->providers[$name][$version['uid']] = $package; - $this->providersByUid[$version['uid']] = $package; - } - - // handle root package aliases - unset($rootAliasData); - - if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { - $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()]; - } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) { - $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()]; - } - - if (isset($rootAliasData)) { - $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']); - $alias->setRepository($this); - - $this->providers[$name][$version['uid'].'-root'] = $alias; - $this->providersByUid[$version['uid'].'-root'] = $alias; + $versionsToLoad[$version['uid']] = $version; } } } - $result = $this->providers[$name]; + // load acceptable packages in the providers + $loadedPackages = $this->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); + $uids = array_keys($versionsToLoad); + + foreach ($loadedPackages as $index => $package) { + $package->setRepository($this); + $uid = $uids[$index]; + + if ($package instanceof AliasPackage) { + $aliased = $package->getAliasOf(); + $aliased->setRepository($this); + + $result[$uid] = $aliased; + $result[$uid.'-alias'] = $package; + } else { + $result[$uid] = $package; + } + } return $result; } diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 552c767a5..55ca6bf09 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -146,21 +146,12 @@ class ComposerRepositoryTest extends TestCase ))); $versionParser = new VersionParser(); - $repo->setRootAliases(array( - 'a' => array( - $versionParser->normalize('0.6') => array('alias' => 'dev-feature', 'alias_normalized' => $versionParser->normalize('dev-feature')), - $versionParser->normalize('1.1.x-dev') => array('alias' => '1.0', 'alias_normalized' => $versionParser->normalize('1.0')), - ), - )); - $reflMethod = new \ReflectionMethod($repo, 'whatProvides'); $reflMethod->setAccessible(true); $packages = $reflMethod->invoke($repo, 'a', array($this, 'isPackageAcceptableReturnTrue')); - $this->assertCount(7, $packages); - $this->assertEquals(array('1', '1-alias', '2', '2-alias', '2-root', '3', '3-root'), array_keys($packages)); - $this->assertInstanceOf('Composer\Package\AliasPackage', $packages['2-root']); - $this->assertSame($packages['2'], $packages['2-root']->getAliasOf()); + $this->assertCount(5, $packages); + $this->assertEquals(array('1', '1-alias', '2', '2-alias', '3'), array_keys($packages)); $this->assertSame($packages['2'], $packages['2-alias']->getAliasOf()); } From c97b7a9be5701ea606ef3eecb01016903f9d25ac Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 14 Jan 2019 16:37:33 +0100 Subject: [PATCH 379/580] Fix implementation of filterPackages --- src/Composer/Repository/ComposerRepository.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 829883b58..6e3994380 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -185,17 +185,25 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private function filterPackages(array $packages, $constraint = null, $returnFirstMatch = false) { - $packages = array(); + if (null === $constraint) { + if ($returnFirstMatch) { + return reset($packages); + } + + return $packages; + } + + $filteredPackages = array(); foreach ($packages as $package) { - $pkgConstraint = new Constraint('==', $package->getVersion()); - if (null === $constraint || $constraint->matches($pkgConstraint)) { + + if ($constraint->matches($pkgConstraint)) { if ($returnFirstMatch) { return $package; } - $packages[] = $package; + $filteredPackages[] = $package; } } @@ -203,7 +211,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return null; } - return $packages; + return $filteredPackages; } public function getPackages() From 0f2f950cb6aa583b60c7b63845734dce185874a7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 15 Jan 2019 11:40:49 +0100 Subject: [PATCH 380/580] Add available-packages key for new repo format, and many consistency tweaks/fixes across various repo formats --- .../Repository/ComposerRepository.php | 108 ++++++++++++++---- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 6e3994380..8fe6a04ed 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -47,6 +47,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected $searchUrl; protected $hasProviders = false; protected $providersUrl; + protected $availablePackages; protected $lazyProvidersUrl; protected $providerListing; protected $loader; @@ -131,20 +132,28 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $this->filterPackages($this->whatProvides($name), $constraint, true); } - return $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { + if (is_array($this->availablePackages) && !isset($this->availablePackages[$name])) { + return; + } + + $packages = $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { return true; }); + + return reset($packages); } - if (!$hasProviders) { - return parent::findPackage($name, $constraint); - } - - foreach ($this->getProviderNames() as $providerName) { - if ($name === $providerName) { - return $this->filterPackages($this->whatProvides($providerName), $constraint, true); + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint, true); + } } + + return; } + + return parent::findPackage($name, $constraint); } /** @@ -165,22 +174,26 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $this->filterPackages($this->whatProvides($name), $constraint); } + if (is_array($this->availablePackages) && !isset($this->availablePackages[$name])) { + return array(); + } + return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { return true; }); } - if (!$hasProviders) { - return parent::findPackages($name, $constraint); - } - - foreach ($this->getProviderNames() as $providerName) { - if ($name === $providerName) { - return $this->filterPackages($this->whatProvides($providerName), $constraint); + if ($hasProviders) { + foreach ($this->getProviderNames() as $providerName) { + if ($name === $providerName) { + return $this->filterPackages($this->whatProvides($providerName), $constraint); + } } + + return array(); } - return array(); + return parent::findPackages($name, $constraint); } private function filterPackages(array $packages, $constraint = null, $returnFirstMatch = false) @@ -216,7 +229,22 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito public function getPackages() { - if ($this->hasProviders()) { + $hasProviders = $this->hasProviders(); + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages)) { + $packageMap = array(); + foreach ($this->availablePackages as $name) { + $packageMap[$name] = new EmptyConstraint(); + } + + return array_values($this->loadAsyncPackages($packageMap, function ($name, $stability) { return true; })); + } + + throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getProviderNames instead.'); + } + + if ($hasProviders) { throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getProviderNames instead.'); } @@ -225,14 +253,28 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito public function getPackageNames() { - if ($this->hasProviders()) { + // TODO add getPackageNames to the RepositoryInterface perhaps? With filtering capability embedded? + $hasProviders = $this->hasProviders(); + + if ($this->lazyProvidersUrl) { + if (is_array($this->availablePackages)) { + return array_keys($this->availablePackages); + } + + // TODO implement new list API endpoint for those repos somehow? + return array(); + } + + if ($hasProviders) { return $this->getProviderNames(); } - // TODO implement new list API endpoint for repos somehow? - // TODO add getPackageNames to the RepositoryInterface perhaps? With filtering capability embedded? + $names = array(); + foreach ($this->getPackages() as $package) { + $names[] = $package->getPrettyName(); + } - return $this->getPackages(); + return $names; } public function loadPackages(array $packageNameMap, $isPackageAcceptableCallable) @@ -279,11 +321,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito unset($packageNameMap[$name]); } - - return $packages; } if ($this->lazyProvidersUrl && count($packageNameMap)) { + if (is_array($this->availablePackages)) { + $availPackages = $this->availablePackages; + $packageNameMap = array_filter($packageNameMap, function ($name) use ($availPackages) { + return isset($availPackages[strtolower($name)]); + }, ARRAY_FILTER_USE_KEY); + } + $packages = array_merge($packages, $this->loadAsyncPackages($packageNameMap, $isPackageAcceptableCallable)); } @@ -317,11 +364,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $results; } - if ($this->hasProviders()) { + if ($this->hasProviders() || $this->lazyProvidersUrl) { $results = array(); $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i'; - foreach ($this->getProviderNames() as $name) { + foreach ($this->getPackageNames() as $name) { if (preg_match($regex, $name)) { $results[] = array('name' => $name); } @@ -683,6 +730,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->hasProviders = false; $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); $this->allowSslDowngrade = false; + + // provides a list of package names that are available in this repo + // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo + // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it + if (!empty($data['available-packages'])) { + $availPackages = array_map('strtolower', $data['available-packages']); + $this->availablePackages = array_combine($availPackages, $availPackages); + } + + // Remove legacy keys as most repos need to be compatible with Composer v1 + // as well but we are not interested in the old format anymore at this point unset($data['providers-url'], $data['providers'], $data['providers-includes']); } From 59360983c604bb44e8ada7a281c81046f8cd4ff5 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Thu, 17 Jan 2019 12:46:57 +0100 Subject: [PATCH 381/580] Archive: cleanup temp dir on download error --- src/Composer/Package/Archiver/ArchiveManager.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 22f8eeafe..6f8fa8a01 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -147,8 +147,13 @@ class ArchiveManager $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); $filesystem->ensureDirectoryExists($sourcePath); - // Download sources - $this->downloadManager->download($package, $sourcePath); + try { + // Download sources + $this->downloadManager->download($package, $sourcePath); + } catch (\Exception $e) { + $filesystem->removeDirectory($sourcePath); + throw $e; + } // Check exclude from downloaded composer.json if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { From 3dfcae99a901c8743272e93a0698b4838439041c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 17 Jan 2019 17:12:33 +0100 Subject: [PATCH 382/580] Add parallel download capability to FileDownloader and derivatives --- src/Composer/Command/ArchiveCommand.php | 6 +- src/Composer/Command/CreateProjectCommand.php | 22 +- src/Composer/Command/StatusCommand.php | 2 +- src/Composer/Downloader/ArchiveDownloader.php | 106 +++---- src/Composer/Downloader/DownloadManager.php | 224 ++++++++++----- .../Downloader/DownloaderInterface.php | 10 +- src/Composer/Downloader/FileDownloader.php | 221 ++++++++------ src/Composer/Downloader/FossilDownloader.php | 2 +- src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Downloader/GzipDownloader.php | 13 +- src/Composer/Downloader/HgDownloader.php | 2 +- src/Composer/Downloader/PathDownloader.php | 9 + .../Downloader/PerforceDownloader.php | 2 +- src/Composer/Downloader/PharDownloader.php | 4 +- src/Composer/Downloader/RarDownloader.php | 3 +- src/Composer/Downloader/SvnDownloader.php | 2 +- src/Composer/Downloader/TarDownloader.php | 4 +- src/Composer/Downloader/VcsDownloader.php | 12 +- src/Composer/Downloader/XzDownloader.php | 10 +- src/Composer/Downloader/ZipDownloader.php | 2 +- src/Composer/Factory.php | 14 +- .../Installer/InstallationManager.php | 27 +- src/Composer/Installer/InstallerInterface.php | 10 + src/Composer/Installer/LibraryInstaller.php | 10 +- .../Installer/MetapackageInstaller.php | 8 + src/Composer/Installer/NoopInstaller.php | 7 + src/Composer/Installer/PluginInstaller.php | 10 +- src/Composer/Installer/ProjectInstaller.php | 13 +- .../Package/Archiver/ArchiveManager.php | 9 +- .../Repository/ComposerRepository.php | 13 +- src/Composer/Util/Http/CurlDownloader.php | 2 +- src/Composer/Util/HttpDownloader.php | 6 +- src/Composer/Util/Loop.php | 47 +++ tests/Composer/Test/ComposerTest.php | 2 +- .../Test/Downloader/ArchiveDownloaderTest.php | 2 +- .../Test/Downloader/DownloadManagerTest.php | 271 ++++++++---------- .../Test/Downloader/FileDownloaderTest.php | 39 ++- .../Test/Downloader/FossilDownloaderTest.php | 4 +- .../Test/Downloader/GitDownloaderTest.php | 12 +- .../Test/Downloader/HgDownloaderTest.php | 4 +- .../Downloader/PerforceDownloaderTest.php | 4 +- .../Test/Downloader/XzDownloaderTest.php | 9 +- .../Test/Downloader/ZipDownloaderTest.php | 43 +-- .../EventDispatcher/EventDispatcherTest.php | 2 +- .../Installer/InstallationManagerTest.php | 41 ++- .../Test/Installer/LibraryInstallerTest.php | 2 +- tests/Composer/Test/Mock/FactoryMock.php | 5 +- .../Test/Mock/InstallationManagerMock.php | 13 + .../Package/Archiver/ArchiveManagerTest.php | 6 +- .../Test/Plugin/PluginInstallerTest.php | 2 +- 50 files changed, 803 insertions(+), 492 deletions(-) create mode 100644 src/Composer/Util/Loop.php diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index f893ed679..e26aa59a1 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -22,6 +22,7 @@ use Composer\Script\ScriptEvents; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\Filesystem; +use Composer\Util\Loop; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -104,8 +105,9 @@ EOT $archiveManager = $composer->getArchiveManager(); } else { $factory = new Factory; - $downloadManager = $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config)); - $archiveManager = $factory->createArchiveManager($config, $downloadManager); + $httpDownloader = $factory->createHttpDownloader($io, $config); + $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader); + $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader)); } if ($packageName) { diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index a1c364539..e165649a6 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder; use Composer\Json\JsonFile; use Composer\Config\JsonConfigSource; use Composer\Util\Filesystem; +use Composer\Util\Loop; use Composer\Package\Version\VersionParser; /** @@ -345,15 +346,18 @@ EOT $package = $package->getAliasOf(); } - $dm = $this->createDownloadManager($io, $config); + $factory = new Factory(); + + $httpDownloader = $factory->createHttpDownloader($io, $config); + $dm = $factory->createDownloadManager($io, $config, $httpDownloader); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setOutputProgress(!$noProgress); $projectInstaller = new ProjectInstaller($directory, $dm); - $im = $this->createInstallationManager(); + $im = $factory->createInstallationManager(new Loop($httpDownloader)); $im->addInstaller($projectInstaller); - $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); + $im->execute(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); $im->notifyInstalls($io); // collect suggestions @@ -369,16 +373,4 @@ EOT return $installedFromVcs; } - - protected function createDownloadManager(IOInterface $io, Config $config) - { - $factory = new Factory(); - - return $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config)); - } - - protected function createInstallationManager() - { - return new InstallationManager(); - } } diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 3e46b7fa0..06fc7638b 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -89,7 +89,7 @@ EOT // list packages foreach ($installedRepo->getCanonicalPackages() as $package) { - $downloader = $dm->getDownloaderForInstalledPackage($package); + $downloader = $dm->getDownloaderForPackage($package); $targetDir = $im->getInstallPath($package); if ($downloader instanceof ChangeReportInterface) { diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index d041a7f88..3c53a086e 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -30,33 +30,50 @@ abstract class ArchiveDownloader extends FileDownloader * @throws \RuntimeException * @throws \UnexpectedValueException */ - public function download(PackageInterface $package, $path, $output = true) + public function install(PackageInterface $package, $path, $output = true) { - $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); - $retries = 3; - while ($retries--) { - $fileName = parent::download($package, $path, $output); + if ($output) { + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + } - if ($output) { - $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); + $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); + $fileName = $this->getFileName($package, $path); + + if ($output) { + $this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE); + } + + try { + $this->filesystem->ensureDirectoryExists($temporaryDir); + try { + $this->extract($package, $fileName, $temporaryDir); + } catch (\Exception $e) { + // remove cache if the file was corrupted + parent::clearLastCacheWrite($package); + throw $e; } - try { - $this->filesystem->ensureDirectoryExists($temporaryDir); - try { - $this->extract($fileName, $temporaryDir); - } catch (\Exception $e) { - // remove cache if the file was corrupted - parent::clearLastCacheWrite($package); - throw $e; + $this->filesystem->unlink($fileName); + + $renameAsOne = false; + if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) { + $renameAsOne = true; + } + + $contentDir = $this->getFolderContent($temporaryDir); + $singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir)); + + if ($renameAsOne) { + // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents + if ($singleDirAtTopLevel) { + $extractedDir = (string) reset($contentDir); + } else { + $extractedDir = $temporaryDir; } - - $this->filesystem->unlink($fileName); - - $contentDir = $this->getFolderContent($temporaryDir); - + $this->filesystem->rename($extractedDir, $path); + } else { // only one dir in the archive, extract its contents out of it - if (1 === count($contentDir) && is_dir(reset($contentDir))) { + if ($singleDirAtTopLevel) { $contentDir = $this->getFolderContent((string) reset($contentDir)); } @@ -65,35 +82,24 @@ abstract class ArchiveDownloader extends FileDownloader $file = (string) $file; $this->filesystem->rename($file, $path . '/' . basename($file)); } - - $this->filesystem->removeDirectory($temporaryDir); - if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) { - $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/'); - } - if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) { - $this->filesystem->removeDirectory($this->config->get('vendor-dir')); - } - } catch (\Exception $e) { - // clean up - $this->filesystem->removeDirectory($path); - $this->filesystem->removeDirectory($temporaryDir); - - // retry downloading if we have an invalid zip file - if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) { - $this->io->writeError(''); - if ($this->io->isDebug()) { - $this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...'); - } else { - $this->io->writeError(' Invalid zip file, retrying...'); - } - usleep(500000); - continue; - } - - throw $e; } - break; + $this->filesystem->removeDirectory($temporaryDir); + if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) { + $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/'); + } + if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) { + $this->filesystem->removeDirectory($this->config->get('vendor-dir')); + } + } catch (\Exception $e) { + // clean up + $this->filesystem->removeDirectory($path); + $this->filesystem->removeDirectory($temporaryDir); + if (file_exists($fileName)) { + $this->filesystem->unlink($fileName); + } + + throw $e; } } @@ -102,7 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader */ protected function getFileName(PackageInterface $package, $path) { - return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); + return rtrim($path.'_'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); } /** @@ -113,7 +119,7 @@ abstract class ArchiveDownloader extends FileDownloader * * @throws \UnexpectedValueException If can not extract downloaded file to path */ - abstract protected function extract($file, $path); + abstract protected function extract(PackageInterface $package, $file, $path); /** * Returns the folder content, excluding dotfiles diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 15c00a6e6..4bc865827 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -15,6 +15,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; use Composer\IO\IOInterface; use Composer\Util\Filesystem; +use React\Promise\PromiseInterface; /** * Downloaders manager. @@ -24,6 +25,7 @@ use Composer\Util\Filesystem; class DownloadManager { private $io; + private $httpDownloader; private $preferDist = false; private $preferSource = false; private $packagePreferences = array(); @@ -33,9 +35,9 @@ class DownloadManager /** * Initializes download manager. * - * @param IOInterface $io The Input Output Interface - * @param bool $preferSource prefer downloading from source - * @param Filesystem|null $filesystem custom Filesystem object + * @param IOInterface $io The Input Output Interface + * @param bool $preferSource prefer downloading from source + * @param Filesystem|null $filesystem custom Filesystem object */ public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null) { @@ -140,7 +142,7 @@ class DownloadManager * wrong type * @return DownloaderInterface|null */ - public function getDownloaderForInstalledPackage(PackageInterface $package) + public function getDownloaderForPackage(PackageInterface $package) { $installationSource = $package->getInstallationSource(); @@ -154,7 +156,7 @@ class DownloadManager $downloader = $this->getDownloader($package->getSourceType()); } else { throw new \InvalidArgumentException( - 'Package '.$package.' seems not been installed properly' + 'Package '.$package.' does not have an installation source set' ); } @@ -171,63 +173,95 @@ class DownloadManager return $downloader; } + public function getDownloaderType(DownloaderInterface $downloader) + { + return array_search($downloader, $this->downloaders); + } + /** * Downloads package into target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir - * @param bool $preferSource prefer installation from source + * @param PackageInterface $prevPackage previous package instance in case of updates + * + * @return PromiseInterface + * @throws \InvalidArgumentException if package have no urls to download from + * @throws \RuntimeException + */ + public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null) + { + $this->filesystem->ensureDirectoryExists(dirname($targetDir)); + + $sources = $this->getAvailableSources($package, $prevPackage); + + $io = $this->io; + $self = $this; + + $download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) { + $source = array_shift($sources); + if ($retry) { + $io->writeError(' Now trying to download from ' . $source . ''); + } + $package->setInstallationSource($source); + + $downloader = $self->getDownloaderForPackage($package); + if (!$downloader) { + return \React\Promise\resolve(); + } + + $handleError = function ($e) use ($sources, $source, $package, $io, $download) { + if ($e instanceof \RuntimeException) { + if (!$sources) { + throw $e; + } + + $io->writeError( + ' Failed to download '. + $package->getPrettyName(). + ' from ' . $source . ': '. + $e->getMessage().'' + ); + + return $download(true); + } + + throw $e; + }; + + try { + $result = $downloader->download($package, $targetDir); + } catch (\Exception $e) { + return $handleError($e); + } + if (!$result instanceof PromiseInterface) { + return \React\Promise\resolve($result); + } + + $res = $result->then(function ($res) { + return $res; + }, $handleError); + + return $res; + }; + + return $download(); + } + + /** + * Installs package into target dir. + * + * @param PackageInterface $package package instance + * @param string $targetDir target dir * * @throws \InvalidArgumentException if package have no urls to download from * @throws \RuntimeException */ - public function download(PackageInterface $package, $targetDir, $preferSource = null) + public function install(PackageInterface $package, $targetDir) { - $preferSource = null !== $preferSource ? $preferSource : $this->preferSource; - $sourceType = $package->getSourceType(); - $distType = $package->getDistType(); - - $sources = array(); - if ($sourceType) { - $sources[] = 'source'; - } - if ($distType) { - $sources[] = 'dist'; - } - - if (empty($sources)) { - throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); - } - - if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { - $sources = array_reverse($sources); - } - - $this->filesystem->ensureDirectoryExists($targetDir); - - foreach ($sources as $i => $source) { - if (isset($e)) { - $this->io->writeError(' Now trying to download from ' . $source . ''); - } - $package->setInstallationSource($source); - try { - $downloader = $this->getDownloaderForInstalledPackage($package); - if ($downloader) { - $downloader->download($package, $targetDir); - } - break; - } catch (\RuntimeException $e) { - if ($i === count($sources) - 1) { - throw $e; - } - - $this->io->writeError( - ' Failed to download '. - $package->getPrettyName(). - ' from ' . $source . ': '. - $e->getMessage().'' - ); - } + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + $downloader->install($package, $targetDir); } } @@ -242,31 +276,23 @@ class DownloadManager */ public function update(PackageInterface $initial, PackageInterface $target, $targetDir) { - $downloader = $this->getDownloaderForInstalledPackage($initial); + $downloader = $this->getDownloaderForPackage($target); + $initialDownloader = $this->getDownloaderForPackage($initial); + + // no downloaders present means update from metapackage to metapackage, nothing to do + if (!$initialDownloader && !$downloader) { + return; + } + + // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed if (!$downloader) { + $initialDownloader->remove($initial, $targetDir); return; } - $installationSource = $initial->getInstallationSource(); - - if ('dist' === $installationSource) { - $initialType = $initial->getDistType(); - $targetType = $target->getDistType(); - } else { - $initialType = $initial->getSourceType(); - $targetType = $target->getSourceType(); - } - - // upgrading from a dist stable package to a dev package, force source reinstall - if ($target->isDev() && 'dist' === $installationSource) { - $downloader->remove($initial, $targetDir); - $this->download($target, $targetDir); - - return; - } - + $initialType = $this->getDownloaderType($initialDownloader); + $targetType = $this->getDownloaderType($downloader); if ($initialType === $targetType) { - $target->setInstallationSource($installationSource); try { $downloader->update($initial, $target, $targetDir); @@ -282,8 +308,12 @@ class DownloadManager } } - $downloader->remove($initial, $targetDir); - $this->download($target, $targetDir, 'source' === $installationSource); + // if downloader type changed, or update failed and user asks for reinstall, + // we wipe the dir and do a new install instead of updating it + if ($initialDownloader) { + $initialDownloader->remove($initial, $targetDir); + } + $this->install($target, $targetDir); } /** @@ -294,7 +324,7 @@ class DownloadManager */ public function remove(PackageInterface $package, $targetDir) { - $downloader = $this->getDownloaderForInstalledPackage($package); + $downloader = $this->getDownloaderForPackage($package); if ($downloader) { $downloader->remove($package, $targetDir); } @@ -322,4 +352,48 @@ class DownloadManager return $package->isDev() ? 'source' : 'dist'; } + + /** + * @return string[] + */ + private function getAvailableSources(PackageInterface $package, PackageInterface $prevPackage = null) + { + $sourceType = $package->getSourceType(); + $distType = $package->getDistType(); + + // add source before dist by default + $sources = array(); + if ($sourceType) { + $sources[] = 'source'; + } + if ($distType) { + $sources[] = 'dist'; + } + + if (empty($sources)) { + throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); + } + + if ( + $prevPackage + // if we are updating, we want to keep the same source as the previously installed package (if available in the new one) + && in_array($prevPackage->getInstallationSource(), $sources, true) + // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over + && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev()) + ) { + $prevSource = $prevPackage->getInstallationSource(); + usort($sources, function ($a, $b) use ($prevSource) { + return $a === $prevSource ? -1 : 1; + }); + + return $sources; + } + + // reverse sources in case dist is the preferred source for this package + if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { + $sources = array_reverse($sources); + } + + return $sources; + } } diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index 713bf36dc..ac56583c4 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -13,6 +13,7 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; /** * Downloader interface. @@ -29,13 +30,20 @@ interface DownloaderInterface */ public function getInstallationSource(); + /** + * This should do any network-related tasks to prepare for install/update + * + * @return PromiseInterface|null + */ + public function download(PackageInterface $package, $path); + /** * Downloads specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path */ - public function download(PackageInterface $package, $path); + public function install(PackageInterface $package, $path); /** * Updates specific package in specific folder from initial to target version. diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8b196e60c..3418eef84 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -26,6 +26,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; use Composer\Util\HttpDownloader; use Composer\Util\Url as UrlUtil; +use Composer\Downloader\TransportException; /** * Base downloader for files @@ -43,7 +44,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface protected $filesystem; protected $cache; protected $outputProgress = true; - private $lastCacheWrites = array(); + /** + * @private this is only public for php 5.3 support in closures + */ + public $lastCacheWrites = array(); private $eventDispatcher; /** @@ -87,108 +91,149 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface throw new \InvalidArgumentException('The given package is missing url information'); } - if ($output) { - $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): ", false); - } - + $retries = 3; $urls = $package->getDistUrls(); - while ($url = array_shift($urls)) { - try { - $fileName = $this->doDownload($package, $path, $url); - break; - } catch (\Exception $e) { - if ($this->io->isDebug()) { - $this->io->writeError(''); - $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); - } elseif (count($urls)) { - $this->io->writeError(''); - $this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false); - } - - if (!count($urls)) { - throw $e; - } - } + foreach ($urls as $index => $url) { + $processedUrl = $this->processUrl($package, $url); + $urls[$index] = array( + 'base' => $url, + 'processed' => $processedUrl, + 'cacheKey' => $this->getCacheKey($package, $processedUrl) + ); } - if ($output) { - $this->io->writeError(''); - } - - return $fileName; - } - - protected function doDownload(PackageInterface $package, $path, $url) - { $this->filesystem->emptyDirectory($path); - $fileName = $this->getFileName($package, $path); - $processedUrl = $this->processUrl($package, $url); + $io = $this->io; + $cache = $this->cache; + $originalHttpDownloader = $this->httpDownloader; + $eventDispatcher = $this->eventDispatcher; + $filesystem = $this->filesystem; + $self = $this; - $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $processedUrl); - if ($this->eventDispatcher) { - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - } - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + $accept = null; + $reject = null; + $download = function () use ($io, $output, $originalHttpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) { + $url = reset($urls); + + $httpDownloader = $originalHttpDownloader; + if ($eventDispatcher) { + $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']); + $eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); + $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + } - try { $checksum = $package->getDistSha1Checksum(); - $cacheKey = $this->getCacheKey($package, $processedUrl); + $cacheKey = $url['cacheKey']; // use from cache if it is present and has a valid checksum or we have no checksum to check against - if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { - $this->io->writeError('Loading from cache', false); + if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) { + if ($output) { + $io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache"); + } + $result = \React\Promise\resolve($fileName); } else { - // download if cache restore failed - if (!$this->outputProgress) { - $this->io->writeError('Downloading', false); + if ($output) { + $io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } - // try to download 3 times then fail hard - $retries = 3; - while ($retries--) { - try { - // TODO handle this->outputProgress - $httpDownloader->copy($processedUrl, $fileName, $package->getTransportOptions()); - break; - } catch (TransportException $e) { - // if we got an http response with a proper code, then requesting again will probably not help, abort - if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { - throw $e; - } - $this->io->writeError(''); - $this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE); - usleep(500000); - } - } - - if (!$this->outputProgress) { - $this->io->writeError(' (100%)', false); - } - - if ($this->cache) { - $this->lastCacheWrites[$package->getName()] = $cacheKey; - $this->cache->copyFrom($cacheKey, $fileName); - } + $result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) + ->then($accept, $reject); } - if (!file_exists($fileName)) { - throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' - .' directory is writable and you have internet connectivity'); + return $result->then(function ($result) use ($fileName, $checksum, $url) { + // in case of retry, the first call's Promise chain finally calls this twice at the end, + // once with $result being the returned $fileName from $accept, and then once for every + // failed request with a null result, which can be skipped. + if (null === $result) { + return $fileName; + } + + if (!file_exists($fileName)) { + throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the' + .' directory is writable and you have internet connectivity'); + } + + if ($checksum && hash_file('sha1', $fileName) !== $checksum) { + throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')'); + } + + return $fileName; + }); + }; + + $accept = function ($response) use ($io, $cache, $package, $fileName, $path, $self, &$urls) { + $url = reset($urls); + $cacheKey = $url['cacheKey']; + + if ($cache) { + $self->lastCacheWrites[$package->getName()] = $cacheKey; + $cache->copyFrom($cacheKey, $fileName); } - if ($checksum && hash_file('sha1', $fileName) !== $checksum) { - throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')'); - } - } catch (\Exception $e) { + $response->collect(); + + return $fileName; + }; + + $reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) { // clean up - $this->filesystem->removeDirectory($path); - $this->clearLastCacheWrite($package); + $filesystem->removeDirectory($path); + $self->clearLastCacheWrite($package); + + if ($e instanceof TransportException) { + // if we got an http response with a proper code, then requesting again will probably not help, abort + if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { + $retries = 0; + } + } + + // special error code returned when network is being artificially disabled + if ($e instanceof TransportException && $e->getStatusCode() === 499) { + $retries = 0; + $urls = array(); + } + + if ($retries) { + usleep(500000); + $retries--; + + return $download(); + } + + array_shift($urls); + if ($urls) { + if ($io->isDebug()) { + $io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); + $io->writeError(' Trying the next URL for '.$package->getName()); + } elseif (count($urls)) { + $io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); + } + + $retries = 3; + usleep(100000); + + return $download(); + } + throw $e; + }; + + return $download(); + } + + /** + * {@inheritDoc} + */ + public function install(PackageInterface $package, $path, $output = true) + { + if ($output) { + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } - return $fileName; + $this->filesystem->ensureDirectoryExists($path); + $this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); } /** @@ -201,7 +246,11 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface return $this; } - protected function clearLastCacheWrite(PackageInterface $package) + /** + * TODO mark private in v3 + * @protected This is public due to PHP 5.3 + */ + public function clearLastCacheWrite(PackageInterface $package) { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { $this->cache->remove($this->lastCacheWrites[$package->getName()]); @@ -222,7 +271,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); $this->remove($initial, $path, false); - $this->download($target, $path, false); + $this->install($target, $path, false); $this->io->writeError(''); } @@ -249,7 +298,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface */ protected function getFileName(PackageInterface $package, $path) { - return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); + return $path.'_'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); } /** @@ -299,7 +348,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $e = null; try { - $this->download($package, $targetDir.'_compare', false); + $res = $this->download($package, $targetDir.'_compare', false); + $this->httpDownloader->wait(); + $res = $this->install($package, $targetDir.'_compare', false); $comparer = new Comparer(); $comparer->setSource($targetDir.'_compare'); diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index 135e973e0..a814f89b7 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -23,7 +23,7 @@ class FossilDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doDownload(PackageInterface $package, $path, $url) + public function doInstall(PackageInterface $package, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 869d5330b..ff398f300 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -38,7 +38,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface /** * {@inheritDoc} */ - public function doDownload(PackageInterface $package, $path, $url) + public function doInstall(PackageInterface $package, $path, $url) { GitUtil::cleanEnv(); $path = $this->normalizePath($path); diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index f65fcf27d..9748b91ac 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -36,9 +36,10 @@ class GzipDownloader extends ArchiveDownloader parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } - protected function extract($file, $path) + protected function extract(PackageInterface $package, $file, $path) { - $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); + $filename = pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME); + $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename; // Try to use gunzip on *nix if (!Platform::isWindows()) { @@ -63,14 +64,6 @@ class GzipDownloader extends ArchiveDownloader $this->extractUsingExt($file, $targetFilepath); } - /** - * {@inheritdoc} - */ - protected function getFileName(PackageInterface $package, $path) - { - return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); - } - private function extractUsingExt($file, $targetFilepath) { $archiveFile = gzopen($file, 'rb'); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 2921cc4b7..add381a75 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -24,7 +24,7 @@ class HgDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doDownload(PackageInterface $package, $path, $url) + public function doInstall(PackageInterface $package, $path, $url) { $hgUtils = new HgUtils($this->io, $this->config, $this->process); diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index e7084bd97..ea583cfc0 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -61,6 +61,15 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $realUrl )); } + } + + /** + * {@inheritdoc} + */ + public function install(PackageInterface $package, $path, $output = true) + { + $url = $package->getDistUrl(); + $realUrl = realpath($url); // Get the transport options with default values $transportOptions = $package->getTransportOptions() + array('symlink' => null); diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index a472b84c6..df270417f 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -27,7 +27,7 @@ class PerforceDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doDownload(PackageInterface $package, $path, $url) + public function doInstall(PackageInterface $package, $path, $url) { $ref = $package->getSourceReference(); $label = $this->getLabelFromSourceReference($ref); diff --git a/src/Composer/Downloader/PharDownloader.php b/src/Composer/Downloader/PharDownloader.php index 13fec244b..62741ee0e 100644 --- a/src/Composer/Downloader/PharDownloader.php +++ b/src/Composer/Downloader/PharDownloader.php @@ -12,6 +12,8 @@ namespace Composer\Downloader; +use Composer\Package\PackageInterface; + /** * Downloader for phar files * @@ -22,7 +24,7 @@ class PharDownloader extends ArchiveDownloader /** * {@inheritDoc} */ - protected function extract($file, $path) + protected function extract(PackageInterface $package, $file, $path) { // Can throw an UnexpectedValueException $archive = new \Phar($file); diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index 6fe4cf27c..2ebc3bf18 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -20,6 +20,7 @@ use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; use RarArchive; /** @@ -39,7 +40,7 @@ class RarDownloader extends ArchiveDownloader parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } - protected function extract($file, $path) + protected function extract(PackageInterface $package, $file, $path) { $processError = null; diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index e23958164..0aae163c6 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -28,7 +28,7 @@ class SvnDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doDownload(PackageInterface $package, $path, $url) + public function doInstall(PackageInterface $package, $path, $url) { SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); diff --git a/src/Composer/Downloader/TarDownloader.php b/src/Composer/Downloader/TarDownloader.php index 34c43da5f..e48407230 100644 --- a/src/Composer/Downloader/TarDownloader.php +++ b/src/Composer/Downloader/TarDownloader.php @@ -12,6 +12,8 @@ namespace Composer\Downloader; +use Composer\Package\PackageInterface; + /** * Downloader for tar files: tar, tar.gz or tar.bz2 * @@ -22,7 +24,7 @@ class TarDownloader extends ArchiveDownloader /** * {@inheritDoc} */ - protected function extract($file, $path) + protected function extract(PackageInterface $package, $file, $path) { // Can throw an UnexpectedValueException $archive = new \PharData($file); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index aa666058e..237d7e49d 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -55,6 +55,14 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa * {@inheritDoc} */ public function download(PackageInterface $package, $path) + { + // noop for now, ideally we would do a git fetch already here, or make sure the cached git repo is synced, etc. + } + + /** + * {@inheritDoc} + */ + public function install(PackageInterface $package, $path) { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); @@ -87,7 +95,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $url = $needle . $url; } } - $this->doDownload($package, $path, $url); + $this->doInstall($package, $path, $url); break; } catch (\Exception $e) { // rethrow phpunit exceptions to avoid hard to debug bug failures @@ -260,7 +268,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa * @param string $path download path * @param string $url package url */ - abstract protected function doDownload(PackageInterface $package, $path, $url); + abstract protected function doInstall(PackageInterface $package, $path, $url); /** * Updates specific package in specific folder from initial to target version. diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index bd7b028e2..19e51c321 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -37,7 +37,7 @@ class XzDownloader extends ArchiveDownloader parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); } - protected function extract($file, $path) + protected function extract(PackageInterface $package, $file, $path) { $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path); @@ -49,12 +49,4 @@ class XzDownloader extends ArchiveDownloader throw new \RuntimeException($processError); } - - /** - * {@inheritdoc} - */ - protected function getFileName(PackageInterface $package, $path) - { - return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); - } } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 9eceab250..efa9fc994 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -185,7 +185,7 @@ class ZipDownloader extends ArchiveDownloader * @param string $file File to extract * @param string $path Path where to extract file */ - public function extract($file, $path) + public function extract(PackageInterface $package, $file, $path) { // Each extract calls its alternative if not available or fails if (self::$isWindows) { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index c96fd2188..e66e64ed1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -24,6 +24,7 @@ use Composer\Util\Filesystem; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; +use Composer\Util\Loop; use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; @@ -326,6 +327,7 @@ class Factory } $httpDownloader = self::createHttpDownloader($io, $config); + $loop = new Loop($httpDownloader); // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); @@ -352,7 +354,7 @@ class Factory $composer->setPackage($package); // initialize installation manager - $im = $this->createInstallationManager(); + $im = $this->createInstallationManager($loop); $composer->setInstallationManager($im); if ($fullLoad) { @@ -365,7 +367,7 @@ class Factory $composer->setAutoloadGenerator($generator); // initialize archive manager - $am = $this->createArchiveManager($config, $dm); + $am = $this->createArchiveManager($config, $dm, $loop); $composer->setArchiveManager($am); } @@ -501,9 +503,9 @@ class Factory * @param Downloader\DownloadManager $dm Manager use to download sources * @return Archiver\ArchiveManager */ - public function createArchiveManager(Config $config, Downloader\DownloadManager $dm) + public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop) { - $am = new Archiver\ArchiveManager($dm); + $am = new Archiver\ArchiveManager($dm, $loop); $am->addArchiver(new Archiver\ZipArchiver); $am->addArchiver(new Archiver\PharArchiver); @@ -525,9 +527,9 @@ class Factory /** * @return Installer\InstallationManager */ - protected function createInstallationManager() + public function createInstallationManager(Loop $loop) { - return new Installer\InstallationManager(); + return new Installer\InstallationManager($loop); } /** diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 9f50b5980..ce10dc4da 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -24,6 +24,7 @@ use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\Util\StreamContextFactory; +use Composer\Util\Loop; /** * Package operation manager. @@ -37,6 +38,12 @@ class InstallationManager private $installers = array(); private $cache = array(); private $notifiablePackages = array(); + private $loop; + + public function __construct(Loop $loop) + { + $this->loop = $loop; + } public function reset() { @@ -156,7 +163,24 @@ class InstallationManager */ public function execute(RepositoryInterface $repo, OperationInterface $operation) { + // TODO this should take all operations in one go $method = $operation->getJobType(); + + if ($method === 'install') { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + $promise = $installer->download($package); + } elseif ($method === 'update') { + $target = $operation->getTargetPackage(); + $targetType = $target->getType(); + $installer = $this->getInstaller($targetType); + $promise = $installer->download($target, $operation->getInitialPackage()); + } + + if (isset($promise)) { + $this->loop->wait(array($promise)); + } + $this->$method($repo, $operation); } @@ -194,7 +218,8 @@ class InstallationManager $this->markForNotification($target); } else { $this->getInstaller($initialType)->uninstall($repo, $initial); - $this->getInstaller($targetType)->install($repo, $target); + $installer = $this->getInstaller($targetType); + $installer->install($repo, $target); } } diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index e64dfadd2..e00877ed9 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -15,6 +15,7 @@ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use InvalidArgumentException; +use React\Promise\PromiseInterface; /** * Interface for the package installation manager. @@ -42,6 +43,15 @@ interface InstallerInterface */ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); + /** + * Downloads the files needed to later install the given package. + * + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface + */ + public function download(PackageInterface $package, PackageInterface $prevPackage = null); + /** * Installs specific package. * diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 34fbbbee4..4c2f45601 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -85,6 +85,14 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath); } + public function download(PackageInterface $package, PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->downloadManager->download($package, $downloadPath, $prevPackage); + } + /** * {@inheritDoc} */ @@ -194,7 +202,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); - $this->downloadManager->download($package, $downloadPath); + $this->downloadManager->install($package, $downloadPath); } protected function updateCode(PackageInterface $initial, PackageInterface $target) diff --git a/src/Composer/Installer/MetapackageInstaller.php b/src/Composer/Installer/MetapackageInstaller.php index 3f99ec03c..7dbf4af67 100644 --- a/src/Composer/Installer/MetapackageInstaller.php +++ b/src/Composer/Installer/MetapackageInstaller.php @@ -38,6 +38,14 @@ class MetapackageInstaller implements InstallerInterface return $repo->hasPackage($package); } + /** + * {@inheritDoc} + */ + public function download(PackageInterface $package, PackageInterface $prevPackage = null) + { + // noop + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/NoopInstaller.php b/src/Composer/Installer/NoopInstaller.php index 72cf17d22..51df3c305 100644 --- a/src/Composer/Installer/NoopInstaller.php +++ b/src/Composer/Installer/NoopInstaller.php @@ -40,6 +40,13 @@ class NoopInstaller implements InstallerInterface return $repo->hasPackage($package); } + /** + * {@inheritDoc} + */ + public function download(PackageInterface $package, PackageInterface $prevPackage = null) + { + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index c400ca4a6..62a16fc62 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -50,13 +50,21 @@ class PluginInstaller extends LibraryInstaller /** * {@inheritDoc} */ - public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + public function download(PackageInterface $package, PackageInterface $prevPackage = null) { $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } + return parent::download($package, $prevPackage); + } + + /** + * {@inheritDoc} + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { parent::install($repo, $package); try { $this->composer->getPluginManager()->registerPackage($package, true); diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index c79238b36..350b220f5 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + public function download(PackageInterface $package, PackageInterface $prevPackage = null) { $installPath = $this->installPath; if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { @@ -67,7 +67,16 @@ class ProjectInstaller implements InstallerInterface if (!is_dir($installPath)) { mkdir($installPath, 0777, true); } - $this->downloadManager->download($package, $installPath); + + return $this->downloadManager->download($package, $installPath, $prevPackage); + } + + /** + * {@inheritDoc} + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->downloadManager->install($package, $this->installPath); } /** diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 22f8eeafe..359d6b053 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Util\Filesystem; +use Composer\Util\Loop; use Composer\Json\JsonFile; /** @@ -25,6 +26,7 @@ use Composer\Json\JsonFile; class ArchiveManager { protected $downloadManager; + protected $loop; protected $archivers = array(); @@ -36,9 +38,10 @@ class ArchiveManager /** * @param DownloadManager $downloadManager A manager used to download package sources */ - public function __construct(DownloadManager $downloadManager) + public function __construct(DownloadManager $downloadManager, Loop $loop) { $this->downloadManager = $downloadManager; + $this->loop = $loop; } /** @@ -148,7 +151,9 @@ class ArchiveManager $filesystem->ensureDirectoryExists($sourcePath); // Download sources - $this->downloadManager->download($package, $sourcePath); + $promise = $this->downloadManager->download($package, $sourcePath); + $this->loop->wait(array($promise)); + $this->downloadManager->install($package, $sourcePath); // Check exclude from downloaded composer.json if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8fe6a04ed..af627df73 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -22,6 +22,7 @@ use Composer\Config; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Util\HttpDownloader; +use Composer\Util\Loop; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; @@ -42,6 +43,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private $baseUrl; private $io; private $httpDownloader; + private $loop; protected $cache; protected $notifyUrl; protected $searchUrl; @@ -107,6 +109,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; + $this->loop = new Loop($this->httpDownloader); } public function getRepoConfig() @@ -569,6 +572,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->loadRootServerFile(); $packages = array(); + $promises = array(); $repo = $this; if (!$this->lazyProvidersUrl) { @@ -592,7 +596,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null; } - $this->asyncFetchFile($url, $cacheKey, $lastModified) + $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); @@ -637,13 +641,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } - }, function ($e) { - // TODO use ->done() above instead with react/promise 2.0 - throw $e; }); } - $this->httpDownloader->wait(); + $this->loop->wait($promises); return $packages; // RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed @@ -1119,7 +1120,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $degradedMode = true; - return true; + throw $e; }; return $httpDownloader->add($filename, $options)->then($accept, $reject); diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 3dc6710b6..ff31bf695 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -295,7 +295,7 @@ class CurlDownloader // resolve promise if ($job['filename']) { rename($job['filename'].'~', $job['filename']); - call_user_func($job['resolve'], true); + call_user_func($job['resolve'], $response); } else { call_user_func($job['resolve'], $response); } diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index f2308d75a..172ea875a 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -160,7 +160,10 @@ class HttpDownloader if ($job['request']['copyTo']) { $result = $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options); - $resolve($result); + $headers = $rfs->getLastHeaders(); + $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~'); + + $resolve($response); } else { $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options); $headers = $rfs->getLastHeaders(); @@ -191,6 +194,7 @@ class HttpDownloader $job['exception'] = $e; $downloader->markJobDone(); + $downloader->scheduleNextJob(); throw $e; }); diff --git a/src/Composer/Util/Loop.php b/src/Composer/Util/Loop.php new file mode 100644 index 000000000..1be7d478b --- /dev/null +++ b/src/Composer/Util/Loop.php @@ -0,0 +1,47 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Util\HttpDownloader; +use React\Promise\Promise; + +/** + * @author Jordi Boggiano + */ +class Loop +{ + private $io; + + public function __construct(HttpDownloader $httpDownloader) + { + $this->httpDownloader = $httpDownloader; + } + + public function wait(array $promises) + { + $uncaught = null; + + \React\Promise\all($promises)->then( + function () { }, + function ($e) use (&$uncaught) { + $uncaught = $e; + } + ); + + $this->httpDownloader->wait(); + + if ($uncaught) { + throw $uncaught; + } + } +} diff --git a/tests/Composer/Test/ComposerTest.php b/tests/Composer/Test/ComposerTest.php index c2c425e76..87270baae 100644 --- a/tests/Composer/Test/ComposerTest.php +++ b/tests/Composer/Test/ComposerTest.php @@ -57,7 +57,7 @@ class ComposerTest extends TestCase public function testSetGetInstallationManager() { $composer = new Composer(); - $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); + $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock(); $composer->setInstallationManager($manager); $this->assertSame($manager, $composer->getInstallationManager()); diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index ddf21c64b..d887ba103 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -29,7 +29,7 @@ class ArchiveDownloaderTest extends TestCase $method->setAccessible(true); $first = $method->invoke($downloader, $packageMock, '/path'); - $this->assertRegExp('#/path/[a-z0-9]+\.js#', $first); + $this->assertRegExp('#/path_[a-z0-9]+\.js#', $first); $this->assertSame($first, $method->invoke($downloader, $packageMock, '/path')); } diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php index 222e541d7..307b2294f 100644 --- a/tests/Composer/Test/Downloader/DownloadManagerTest.php +++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php @@ -50,7 +50,7 @@ class DownloadManagerTest extends TestCase $this->setExpectedException('InvalidArgumentException'); - $manager->getDownloaderForInstalledPackage($package); + $manager->getDownloaderForPackage($package); } public function testGetDownloaderForCorrectlyInstalledDistPackage() @@ -82,7 +82,7 @@ class DownloadManagerTest extends TestCase ->with('pear') ->will($this->returnValue($downloader)); - $this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package)); + $this->assertSame($downloader, $manager->getDownloaderForPackage($package)); } public function testGetDownloaderForIncorrectlyInstalledDistPackage() @@ -116,7 +116,7 @@ class DownloadManagerTest extends TestCase $this->setExpectedException('LogicException'); - $manager->getDownloaderForInstalledPackage($package); + $manager->getDownloaderForPackage($package); } public function testGetDownloaderForCorrectlyInstalledSourcePackage() @@ -148,7 +148,7 @@ class DownloadManagerTest extends TestCase ->with('git') ->will($this->returnValue($downloader)); - $this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package)); + $this->assertSame($downloader, $manager->getDownloaderForPackage($package)); } public function testGetDownloaderForIncorrectlyInstalledSourcePackage() @@ -182,7 +182,7 @@ class DownloadManagerTest extends TestCase $this->setExpectedException('LogicException'); - $manager->getDownloaderForInstalledPackage($package); + $manager->getDownloaderForPackage($package); } public function testGetDownloaderForMetapackage() @@ -195,7 +195,7 @@ class DownloadManagerTest extends TestCase $manager = new DownloadManager($this->io, false, $this->filesystem); - $this->assertNull($manager->getDownloaderForInstalledPackage($package)); + $this->assertNull($manager->getDownloaderForPackage($package)); } public function testFullPackageDownload() @@ -223,11 +223,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -274,16 +274,16 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->at(0)) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloaderFail)); $manager ->expects($this->at(1)) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloaderSuccess)); @@ -333,11 +333,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -369,11 +369,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -399,11 +399,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue(null)); // There is no downloader for Metapackages. @@ -435,11 +435,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -472,11 +472,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -509,11 +509,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -550,33 +550,30 @@ class DownloadManagerTest extends TestCase $initial ->expects($this->once()) ->method('getDistType') - ->will($this->returnValue('pear')); + ->will($this->returnValue('zip')); $target = $this->createPackageMock(); $target ->expects($this->once()) - ->method('getDistType') - ->will($this->returnValue('pear')); + ->method('getInstallationSource') + ->will($this->returnValue('dist')); $target ->expects($this->once()) - ->method('setInstallationSource') - ->with('dist'); + ->method('getDistType') + ->will($this->returnValue('zip')); - $pearDownloader = $this->createDownloaderMock(); - $pearDownloader + $zipDownloader = $this->createDownloaderMock(); + $zipDownloader ->expects($this->once()) ->method('update') ->with($initial, $target, 'vendor/bundles/FOS/UserBundle'); + $zipDownloader + ->expects($this->any()) + ->method('getInstallationSource') + ->will($this->returnValue('dist')); - $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') - ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) - ->getMock(); - $manager - ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') - ->with($initial) - ->will($this->returnValue($pearDownloader)); + $manager = new DownloadManager($this->io, false, $this->filesystem); + $manager->setDownloader('zip', $zipDownloader); $manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle'); } @@ -591,113 +588,89 @@ class DownloadManagerTest extends TestCase $initial ->expects($this->once()) ->method('getDistType') - ->will($this->returnValue('pear')); + ->will($this->returnValue('xz')); $target = $this->createPackageMock(); $target - ->expects($this->once()) + ->expects($this->any()) + ->method('getInstallationSource') + ->will($this->returnValue('dist')); + $target + ->expects($this->any()) ->method('getDistType') - ->will($this->returnValue('composer')); + ->will($this->returnValue('zip')); - $pearDownloader = $this->createDownloaderMock(); - $pearDownloader + $xzDownloader = $this->createDownloaderMock(); + $xzDownloader ->expects($this->once()) ->method('remove') ->with($initial, 'vendor/bundles/FOS/UserBundle'); + $xzDownloader + ->expects($this->any()) + ->method('getInstallationSource') + ->will($this->returnValue('dist')); - $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') - ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage', 'download')) - ->getMock(); - $manager + $zipDownloader = $this->createDownloaderMock(); + $zipDownloader ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') - ->with($initial) - ->will($this->returnValue($pearDownloader)); - $manager - ->expects($this->once()) - ->method('download') - ->with($target, 'vendor/bundles/FOS/UserBundle', false); + ->method('install') + ->with($target, 'vendor/bundles/FOS/UserBundle'); + $zipDownloader + ->expects($this->any()) + ->method('getInstallationSource') + ->will($this->returnValue('dist')); + + $manager = new DownloadManager($this->io, false, $this->filesystem); + $manager->setDownloader('xz', $xzDownloader); + $manager->setDownloader('zip', $zipDownloader); $manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle'); } - public function testUpdateSourceWithEqualTypes() + /** + * @dataProvider updatesProvider + */ + public function testGetAvailableSourcesUpdateSticksToSameSource($prevPkgSource, $prevPkgIsDev, $targetAvailable, $targetIsDev, $expected) { - $initial = $this->createPackageMock(); - $initial - ->expects($this->once()) - ->method('getInstallationSource') - ->will($this->returnValue('source')); - $initial - ->expects($this->once()) - ->method('getSourceType') - ->will($this->returnValue('svn')); + $initial = null; + if ($prevPkgSource) { + $initial = $this->prophesize('Composer\Package\PackageInterface'); + $initial->getInstallationSource()->willReturn($prevPkgSource); + $initial->isDev()->willReturn($prevPkgIsDev); + } - $target = $this->createPackageMock(); - $target - ->expects($this->once()) - ->method('getSourceType') - ->will($this->returnValue('svn')); + $target = $this->prophesize('Composer\Package\PackageInterface'); + $target->getSourceType()->willReturn(in_array('source', $targetAvailable, true) ? 'git' : null); + $target->getDistType()->willReturn(in_array('dist', $targetAvailable, true) ? 'zip' : null); + $target->isDev()->willReturn($targetIsDev); - $svnDownloader = $this->createDownloaderMock(); - $svnDownloader - ->expects($this->once()) - ->method('update') - ->with($initial, $target, 'vendor/pkg'); - - $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') - ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage', 'download')) - ->getMock(); - $manager - ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') - ->with($initial) - ->will($this->returnValue($svnDownloader)); - - $manager->update($initial, $target, 'vendor/pkg'); + $manager = new DownloadManager($this->io, false, $this->filesystem); + $method = new \ReflectionMethod($manager, 'getAvailableSources'); + $method->setAccessible(true); + $this->assertEquals($expected, $method->invoke($manager, $target->reveal(), $initial ? $initial->reveal() : null)); } - public function testUpdateSourceWithNotEqualTypes() + public static function updatesProvider() { - $initial = $this->createPackageMock(); - $initial - ->expects($this->once()) - ->method('getInstallationSource') - ->will($this->returnValue('source')); - $initial - ->expects($this->once()) - ->method('getSourceType') - ->will($this->returnValue('svn')); - - $target = $this->createPackageMock(); - $target - ->expects($this->once()) - ->method('getSourceType') - ->will($this->returnValue('git')); - - $svnDownloader = $this->createDownloaderMock(); - $svnDownloader - ->expects($this->once()) - ->method('remove') - ->with($initial, 'vendor/pkg'); - - $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') - ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage', 'download')) - ->getMock(); - $manager - ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') - ->with($initial) - ->will($this->returnValue($svnDownloader)); - $manager - ->expects($this->once()) - ->method('download') - ->with($target, 'vendor/pkg', true); - - $manager->update($initial, $target, 'vendor/pkg'); + return array( + // prevPkg source, prevPkg isDev, pkg available, pkg isDev, expected + // updates keep previous source as preference + array('source', false, array('source', 'dist'), false, array('source', 'dist')), + array('dist', false, array('source', 'dist'), false, array('dist', 'source')), + // updates do not keep previous source if target package does not have it + array('source', false, array('dist'), false, array('dist')), + array('dist', false, array('source'), false, array('source')), + // updates do not keep previous source if target is dev and prev wasn't dev and installed from dist + array('source', false, array('source', 'dist'), true, array('source', 'dist')), + array('dist', false, array('source', 'dist'), true, array('source', 'dist')), + // install picks the right default + array(null, null, array('source', 'dist'), true, array('source', 'dist')), + array(null, null, array('dist'), true, array('dist')), + array(null, null, array('source'), true, array('source')), + array(null, null, array('source', 'dist'), false, array('dist', 'source')), + array(null, null, array('dist'), false, array('dist')), + array(null, null, array('source'), false, array('source')), + ); } public function testUpdateMetapackage() @@ -707,11 +680,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager - ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->expects($this->exactly(2)) + ->method('getDownloaderForPackage') ->with($initial) ->will($this->returnValue(null)); // There is no downloader for metapackages. @@ -730,11 +703,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($pearDownloader)); @@ -747,11 +720,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue(null)); // There is no downloader for metapackages. @@ -790,11 +763,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -833,11 +806,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); @@ -879,11 +852,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'source')); @@ -926,11 +899,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'source')); @@ -973,11 +946,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'auto')); @@ -1020,11 +993,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'auto')); @@ -1063,11 +1036,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'source')); @@ -1106,11 +1079,11 @@ class DownloadManagerTest extends TestCase $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->setConstructorArgs(array($this->io, false, $this->filesystem)) - ->setMethods(array('getDownloaderForInstalledPackage')) + ->setMethods(array('getDownloaderForPackage')) ->getMock(); $manager ->expects($this->once()) - ->method('getDownloaderForInstalledPackage') + ->method('getDownloaderForPackage') ->with($package) ->will($this->returnValue($downloader)); $manager->setPreferences(array('foo/*' => 'dist')); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 12edfe19d..9ce536474 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -15,6 +15,8 @@ namespace Composer\Test\Downloader; use Composer\Downloader\FileDownloader; use Composer\Test\TestCase; use Composer\Util\Filesystem; +use Composer\Util\Http\Response; +use Composer\Util\Loop; class FileDownloaderTest extends TestCase { @@ -23,6 +25,11 @@ class FileDownloaderTest extends TestCase $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); $httpDownloader = $httpDownloader ?: $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); + $httpDownloader + ->expects($this->any()) + ->method('addCopy') + ->will($this->returnValue(\React\Promise\resolve(new Response(array('url' => 'http://example.org/'), 200, array(), 'file~')))); + $this->httpDownloader = $httpDownloader; return new FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $filesystem); } @@ -84,7 +91,7 @@ class FileDownloaderTest extends TestCase $method = new \ReflectionMethod($downloader, 'getFileName'); $method->setAccessible(true); - $this->assertEquals('/path/script.js', $method->invoke($downloader, $packageMock, '/path')); + $this->assertEquals('/path_script.js', $method->invoke($downloader, $packageMock, '/path')); } public function testDownloadButFileIsUnsaved() @@ -118,8 +125,11 @@ class FileDownloaderTest extends TestCase $downloader = $this->getDownloader($ioMock); try { - $downloader->download($packageMock, $path); - $this->fail(); + $promise = $downloader->download($packageMock, $path); + $loop = new Loop($this->httpDownloader); + $loop->wait(array($promise)); + + $this->fail('Download was expected to throw'); } catch (\Exception $e) { if (is_dir($path)) { $fs = new Filesystem(); @@ -128,7 +138,7 @@ class FileDownloaderTest extends TestCase unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e); + $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); $this->assertContains('could not be saved to', $e->getMessage()); } } @@ -188,11 +198,14 @@ class FileDownloaderTest extends TestCase $path = $this->getUniqueTmpDirectory(); $downloader = $this->getDownloader(null, null, null, null, null, $filesystem); // make sure the file expected to be downloaded is on disk already - touch($path.'/script.js'); + touch($path.'_script.js'); try { - $downloader->download($packageMock, $path); - $this->fail(); + $promise = $downloader->download($packageMock, $path); + $loop = new Loop($this->httpDownloader); + $loop->wait(array($promise)); + + $this->fail('Download was expected to throw'); } catch (\Exception $e) { if (is_dir($path)) { $fs = new Filesystem(); @@ -201,7 +214,7 @@ class FileDownloaderTest extends TestCase unlink($path); } - $this->assertInstanceOf('UnexpectedValueException', $e); + $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); $this->assertContains('checksum verification', $e->getMessage()); } } @@ -232,17 +245,25 @@ class FileDownloaderTest extends TestCase $ioMock = $this->getMock('Composer\IO\IOInterface'); $ioMock->expects($this->at(0)) + ->method('writeError') + ->with($this->stringContains('Downloading')); + + $ioMock->expects($this->at(1)) ->method('writeError') ->with($this->stringContains('Downgrading')); $path = $this->getUniqueTmpDirectory(); - touch($path.'/script.js'); + touch($path.'_script.js'); $filesystem = $this->getMock('Composer\Util\Filesystem'); $filesystem->expects($this->once()) ->method('removeDirectory') ->will($this->returnValue(true)); $downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem); + $promise = $downloader->download($newPackage, $path, $oldPackage); + $loop = new Loop($this->httpDownloader); + $loop->wait(array($promise)); + $downloader->update($oldPackage, $newPackage, $path); } } diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 623f7dec2..9ab7b6b84 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -56,7 +56,7 @@ class FossilDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); - $downloader->download($packageMock, '/path'); + $downloader->install($packageMock, '/path'); } public function testDownload() @@ -89,7 +89,7 @@ class FossilDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); - $downloader->download($packageMock, 'repo'); + $downloader->install($packageMock, 'repo'); } /** diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index c3cd31a4a..b5d0054de 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -79,7 +79,7 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); - $downloader->download($packageMock, '/path'); + $downloader->install($packageMock, '/path'); } public function testDownload() @@ -130,7 +130,7 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); } public function testDownloadWithCache() @@ -195,7 +195,7 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, $config, $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); @rmdir($cachePath); } @@ -265,7 +265,7 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); } public function pushUrlProvider() @@ -329,7 +329,7 @@ class GitDownloaderTest extends TestCase $config->merge(array('config' => array('github-protocols' => $protocols))); $downloader = $this->getDownloaderMock(null, $config, $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); } /** @@ -360,7 +360,7 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(1)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); } /** diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index c71d463cb..a4219d143 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -56,7 +56,7 @@ class HgDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); - $downloader->download($packageMock, '/path'); + $downloader->install($packageMock, '/path'); } public function testDownload() @@ -83,7 +83,7 @@ class HgDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); - $downloader->download($packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); } /** diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 1b5041d9f..d2b8fb753 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -138,7 +138,7 @@ class PerforceDownloaderTest extends TestCase $perforce->expects($this->at(5))->method('syncCodeBase')->with($label); $perforce->expects($this->at(6))->method('cleanupClientSpec'); $this->downloader->setPerforce($perforce); - $this->downloader->doDownload($this->package, $this->testPath, 'url'); + $this->downloader->doInstall($this->package, $this->testPath, 'url'); } /** @@ -161,6 +161,6 @@ class PerforceDownloaderTest extends TestCase $perforce->expects($this->at(5))->method('syncCodeBase')->with($label); $perforce->expects($this->at(6))->method('cleanupClientSpec'); $this->downloader->setPerforce($perforce); - $this->downloader->doDownload($this->package, $this->testPath, 'url'); + $this->downloader->doInstall($this->package, $this->testPath, 'url'); } } diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index 451592d37..4c2fdb2af 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -16,6 +16,7 @@ use Composer\Downloader\XzDownloader; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; +use Composer\Util\Loop; use Composer\Util\HttpDownloader; class XzDownloaderTest extends TestCase @@ -66,10 +67,14 @@ class XzDownloaderTest extends TestCase ->method('get') ->with('vendor-dir') ->will($this->returnValue($this->testDir)); - $downloader = new XzDownloader($io, $config, new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null); + $downloader = new XzDownloader($io, $config, $httpDownloader = new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null); try { - $downloader->download($packageMock, $this->getUniqueTmpDirectory()); + $promise = $downloader->download($packageMock, $this->testDir); + $loop = new Loop($httpDownloader); + $loop->wait(array($promise)); + $downloader->install($packageMock, $this->testDir); + $this->fail('Download of invalid tarball should throw an exception'); } catch (\RuntimeException $e) { $this->assertRegexp('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage()); diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 0c1311427..b754af607 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -17,6 +17,7 @@ use Composer\Package\PackageInterface; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\HttpDownloader; +use Composer\Util\Loop; class ZipDownloaderTest extends TestCase { @@ -27,6 +28,7 @@ class ZipDownloaderTest extends TestCase private $prophet; private $io; private $config; + private $package; public function setUp() { @@ -35,6 +37,7 @@ class ZipDownloaderTest extends TestCase $this->config = $this->getMockBuilder('Composer\Config')->getMock(); $dlConfig = $this->getMockBuilder('Composer\Config')->getMock(); $this->httpDownloader = new HttpDownloader($this->io, $dlConfig); + $this->package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); } public function tearDown() @@ -71,16 +74,15 @@ class ZipDownloaderTest extends TestCase ->with('vendor-dir') ->will($this->returnValue($this->testDir)); - $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); - $packageMock->expects($this->any()) + $this->package->expects($this->any()) ->method('getDistUrl') ->will($this->returnValue($distUrl = 'file://'.__FILE__)) ; - $packageMock->expects($this->any()) + $this->package->expects($this->any()) ->method('getDistUrls') ->will($this->returnValue(array($distUrl))) ; - $packageMock->expects($this->atLeastOnce()) + $this->package->expects($this->atLeastOnce()) ->method('getTransportOptions') ->will($this->returnValue(array())) ; @@ -90,7 +92,11 @@ class ZipDownloaderTest extends TestCase $this->setPrivateProperty('hasSystemUnzip', false); try { - $downloader->download($packageMock, sys_get_temp_dir().'/composer-zip-test'); + $promise = $downloader->download($this->package, $path = sys_get_temp_dir().'/composer-zip-test'); + $loop = new Loop($this->httpDownloader); + $loop->wait(array($promise)); + $downloader->install($this->package, $path); + $this->fail('Download of invalid zip files should throw an exception'); } catch (\Exception $e) { $this->assertContains('is not a zip archive', $e->getMessage()); @@ -119,7 +125,7 @@ class ZipDownloaderTest extends TestCase ->will($this->returnValue(false)); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } /** @@ -144,7 +150,7 @@ class ZipDownloaderTest extends TestCase ->will($this->throwException(new \ErrorException('Not a directory'))); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } /** @@ -168,7 +174,7 @@ class ZipDownloaderTest extends TestCase ->will($this->returnValue(true)); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } /** @@ -189,7 +195,7 @@ class ZipDownloaderTest extends TestCase ->will($this->returnValue(1)); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } public function testSystemUnzipOnlyGood() @@ -206,7 +212,7 @@ class ZipDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } public function testNonWindowsFallbackGood() @@ -234,7 +240,7 @@ class ZipDownloaderTest extends TestCase $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } /** @@ -266,7 +272,7 @@ class ZipDownloaderTest extends TestCase $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } public function testWindowsFallbackGood() @@ -294,7 +300,7 @@ class ZipDownloaderTest extends TestCase $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } /** @@ -326,7 +332,7 @@ class ZipDownloaderTest extends TestCase $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); - $downloader->extract('testfile.zip', 'vendor/dir'); + $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } } @@ -337,8 +343,13 @@ class MockedZipDownloader extends ZipDownloader return; } - public function extract($file, $path) + public function install(PackageInterface $package, $path, $output = true) { - parent::extract($file, $path); + return; + } + + public function extract(PackageInterface $package, $file, $path) + { + parent::extract($package, $file, $path); } } diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 7786e7807..6d812e20a 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -101,7 +101,7 @@ class EventDispatcherTest extends TestCase $composer->setPackage($package); $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest()); - $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock()); + $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock()); $dispatcher = new EventDispatcher( $composer, diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index 86e860bc2..407a5b10f 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Installer; use Composer\Installer\InstallationManager; +use Composer\Installer\NoopInstaller; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; @@ -21,9 +22,11 @@ use PHPUnit\Framework\TestCase; class InstallationManagerTest extends TestCase { protected $repository; + protected $loop; public function setUp() { + $this->loop = $this->getMockBuilder('Composer\Util\Loop')->disableOriginalConstructor()->getMock(); $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); } @@ -38,7 +41,7 @@ class InstallationManagerTest extends TestCase return $arg === 'vendor'; })); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $this->assertSame($installer, $manager->getInstaller('vendor')); @@ -67,7 +70,7 @@ class InstallationManagerTest extends TestCase return $arg === 'vendor'; })); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $this->assertSame($installer, $manager->getInstaller('vendor')); @@ -80,16 +83,21 @@ class InstallationManagerTest extends TestCase public function testExecute() { $manager = $this->getMockBuilder('Composer\Installer\InstallationManager') + ->setConstructorArgs(array($this->loop)) ->setMethods(array('install', 'update', 'uninstall')) ->getMock(); - $installOperation = new InstallOperation($this->createPackageMock()); - $removeOperation = new UninstallOperation($this->createPackageMock()); + $installOperation = new InstallOperation($package = $this->createPackageMock()); + $removeOperation = new UninstallOperation($package); $updateOperation = new UpdateOperation( - $this->createPackageMock(), - $this->createPackageMock() + $package, + $package ); + $package->expects($this->any()) + ->method('getType') + ->will($this->returnValue('library')); + $manager ->expects($this->once()) ->method('install') @@ -103,6 +111,7 @@ class InstallationManagerTest extends TestCase ->method('update') ->with($this->repository, $updateOperation); + $manager->addInstaller(new NoopInstaller()); $manager->execute($this->repository, $installOperation); $manager->execute($this->repository, $removeOperation); $manager->execute($this->repository, $updateOperation); @@ -111,7 +120,7 @@ class InstallationManagerTest extends TestCase public function testInstall() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $package = $this->createPackageMock(); @@ -139,7 +148,7 @@ class InstallationManagerTest extends TestCase public function testUpdateWithEqualTypes() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $initial = $this->createPackageMock(); @@ -173,18 +182,17 @@ class InstallationManagerTest extends TestCase { $libInstaller = $this->createInstallerMock(); $bundleInstaller = $this->createInstallerMock(); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($libInstaller); $manager->addInstaller($bundleInstaller); $initial = $this->createPackageMock(); - $target = $this->createPackageMock(); - $operation = new UpdateOperation($initial, $target, 'test'); - $initial ->expects($this->once()) ->method('getType') ->will($this->returnValue('library')); + + $target = $this->createPackageMock(); $target ->expects($this->once()) ->method('getType') @@ -213,13 +221,14 @@ class InstallationManagerTest extends TestCase ->method('install') ->with($this->repository, $target); + $operation = new UpdateOperation($initial, $target, 'test'); $manager->update($this->repository, $operation); } public function testUninstall() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $package = $this->createPackageMock(); @@ -249,7 +258,7 @@ class InstallationManagerTest extends TestCase $installer = $this->getMockBuilder('Composer\Installer\LibraryInstaller') ->disableOriginalConstructor() ->getMock(); - $manager = new InstallationManager(); + $manager = new InstallationManager($this->loop); $manager->addInstaller($installer); $package = $this->createPackageMock(); @@ -281,7 +290,9 @@ class InstallationManagerTest extends TestCase private function createPackageMock() { - return $this->getMockBuilder('Composer\Package\PackageInterface') + $mock = $this->getMockBuilder('Composer\Package\PackageInterface') ->getMock(); + + return $mock; } } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 772bb05c8..672f8eb0a 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -113,7 +113,7 @@ class LibraryInstallerTest extends TestCase $this->dm ->expects($this->once()) - ->method('download') + ->method('install') ->with($package, $this->vendorDir.'/some/package'); $this->repository diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index 47683afcd..fcb93d2cc 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -20,6 +20,7 @@ use Composer\Repository\WritableRepositoryInterface; use Composer\Installer; use Composer\IO\IOInterface; use Composer\Test\TestCase; +use Composer\Util\Loop; class FactoryMock extends Factory { @@ -39,9 +40,9 @@ class FactoryMock extends Factory { } - protected function createInstallationManager() + public function createInstallationManager(Loop $loop) { - return new InstallationManagerMock; + return new InstallationManagerMock(); } protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) diff --git a/tests/Composer/Test/Mock/InstallationManagerMock.php b/tests/Composer/Test/Mock/InstallationManagerMock.php index de1de514b..21e717224 100644 --- a/tests/Composer/Test/Mock/InstallationManagerMock.php +++ b/tests/Composer/Test/Mock/InstallationManagerMock.php @@ -17,6 +17,7 @@ use Composer\Repository\RepositoryInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; @@ -29,6 +30,18 @@ class InstallationManagerMock extends InstallationManager private $uninstalled = array(); private $trace = array(); + public function __construct() + { + + } + + public function execute(RepositoryInterface $repo, OperationInterface $operation) + { + $method = $operation->getJobType(); + // skipping download() step here for tests + $this->$method($repo, $operation); + } + public function getInstallPath(PackageInterface $package) { return ''; diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index b9f08e693..714c9b923 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -16,6 +16,7 @@ use Composer\IO\NullIO; use Composer\Factory; use Composer\Package\Archiver\ArchiveManager; use Composer\Package\PackageInterface; +use Composer\Util\Loop; use Composer\Test\Mock\FactoryMock; class ArchiveManagerTest extends ArchiverTest @@ -35,9 +36,10 @@ class ArchiveManagerTest extends ArchiverTest $dm = $factory->createDownloadManager( $io = new NullIO, $config = FactoryMock::createConfig(), - $factory->createHttpDownloader($io, $config) + $httpDownloader = $factory->createHttpDownloader($io, $config) ); - $this->manager = $factory->createArchiveManager($factory->createConfig(), $dm); + $loop = new Loop($httpDownloader); + $this->manager = $factory->createArchiveManager($factory->createConfig(), $dm, $loop); $this->targetDir = $this->testDir.'/composer_archiver_tests'; } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 01832f94d..633c5ab18 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -89,7 +89,7 @@ class PluginInstallerTest extends TestCase ->method('getLocalRepository') ->will($this->returnValue($this->repository)); - $im = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); + $im = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock(); $im->expects($this->any()) ->method('getInstallPath') ->will($this->returnCallback(function ($package) { From ea978ee7f8e4897667c4b490c839145582a33cd1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Jan 2019 08:46:46 +0100 Subject: [PATCH 383/580] Update react/promise requirement --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1b75131bc..03839772e 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", "symfony/process": "^2.7 || ^3.0 || ^4.0", - "react/promise": "^1.2" + "react/promise": "^1.2 || ^2.7" }, "conflict": { "symfony/console": "2.8.38" diff --git a/composer.lock b/composer.lock index a22e11a6b..d2a448608 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3243ce6f26231df34d1bceab1a148803", + "content-hash": "b078b12b2912d599e0c6904f64def484", "packages": [ { "name": "composer/ca-bundle", From 5b78ea529a90f273684b338132e6b84f46b5209f Mon Sep 17 00:00:00 2001 From: Den Girnyk Date: Thu, 17 Jan 2019 18:11:32 +0200 Subject: [PATCH 384/580] Fix: Keep replaced packages for autoload dumping with --no-dev --- src/Composer/Autoload/AutoloadGenerator.php | 8 +++-- .../Test/Autoload/AutoloadGeneratorTest.php | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 0db4015c3..7ea1a3444 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -940,9 +940,13 @@ INITIALIZER; $packageMap, function ($item) use ($include) { $package = $item[0]; - $name = $package->getName(); + foreach ($package->getNames() as $name) { + if (isset($include[$name])) { + return true; + } + } - return isset($include[$name]); + return false; } ); } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 4d672084e..615a613f9 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Autoload; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; use Composer\Package\AliasPackage; use Composer\Package\Package; @@ -419,6 +420,39 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testNonDevAutoloadShouldIncludeReplacedPackages() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array(new Link('a', 'a/a'))); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + + $a->setRequires(array(new Link('a/a', 'b/c'))); + + $b->setAutoload(array('psr-4' => array('B\\' => 'src/'))); + $b->setReplaces( + array(new Link('b/b', 'b/c', new Constraint('==', '1.0'), 'replaces')) + ); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src/C'); + file_put_contents($this->vendorDir.'/b/b/src/C/C.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_5'); + + $this->assertEquals( + array( + 'B\\C\\C' => $this->vendorDir.'/b/b/src/C/C.php', + ), + include $this->vendorDir.'/composer/autoload_classmap.php' + ); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); From 549ccd8f794c0c1c51b957e864cc914482e82a85 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Jan 2019 11:48:51 +0100 Subject: [PATCH 385/580] Remote outputProgress concept from downloaders as it does not make sense when things happen in parallel, refs #7901 --- src/Composer/Command/CreateProjectCommand.php | 4 +--- src/Composer/Command/InstallCommand.php | 1 - src/Composer/Command/RemoveCommand.php | 1 - src/Composer/Command/RequireCommand.php | 1 - src/Composer/Command/UpdateCommand.php | 2 -- src/Composer/Downloader/DownloadManager.php | 16 ---------------- src/Composer/Downloader/DownloaderInterface.php | 8 -------- src/Composer/Downloader/FileDownloader.php | 14 -------------- src/Composer/Downloader/VcsDownloader.php | 9 --------- 9 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index e165649a6..c11a0595e 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -162,7 +162,6 @@ EOT } $composer = Factory::create($io, null, $disablePlugins); - $composer->getDownloadManager()->setOutputProgress(!$noProgress); $fs = new Filesystem(); @@ -351,8 +350,7 @@ EOT $httpDownloader = $factory->createHttpDownloader($io, $config); $dm = $factory->createDownloadManager($io, $config, $httpDownloader); $dm->setPreferSource($preferSource) - ->setPreferDist($preferDist) - ->setOutputProgress(!$noProgress); + ->setPreferDist($preferDist); $projectInstaller = new ProjectInstaller($directory, $dm); $im = $factory->createInstallationManager(new Loop($httpDownloader)); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index cc590d8c9..951d20289 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -85,7 +85,6 @@ EOT } $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 27be1a0ca..ea412ec66 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -126,7 +126,6 @@ EOT // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 1f29751b9..f15308bad 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -167,7 +167,6 @@ EOT // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 34420b747..06f998d63 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -120,8 +120,6 @@ EOT } } - $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); - $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 4bc865827..0b1ddb5a6 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -85,22 +85,6 @@ class DownloadManager return $this; } - /** - * Sets whether to output download progress information for all registered - * downloaders - * - * @param bool $outputProgress - * @return DownloadManager - */ - public function setOutputProgress($outputProgress) - { - foreach ($this->downloaders as $downloader) { - $downloader->setOutputProgress($outputProgress); - } - - return $this; - } - /** * Sets installer downloader for a specific installation type. * diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index ac56583c4..2074b16da 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -61,12 +61,4 @@ interface DownloaderInterface * @param string $path download path */ public function remove(PackageInterface $package, $path); - - /** - * Sets whether to output download progress information or not - * - * @param bool $outputProgress - * @return DownloaderInterface - */ - public function setOutputProgress($outputProgress); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 3418eef84..54acae710 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -43,7 +43,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface protected $httpDownloader; protected $filesystem; protected $cache; - protected $outputProgress = true; /** * @private this is only public for php 5.3 support in closures */ @@ -236,16 +235,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); } - /** - * {@inheritDoc} - */ - public function setOutputProgress($outputProgress) - { - $this->outputProgress = $outputProgress; - - return $this; - } - /** * TODO mark private in v3 * @protected This is public due to PHP 5.3 @@ -340,11 +329,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface public function getLocalChanges(PackageInterface $package, $targetDir) { $prevIO = $this->io; - $prevProgress = $this->outputProgress; $this->io = new NullIO; $this->io->loadConfiguration($this->config); - $this->outputProgress = false; $e = null; try { @@ -362,7 +349,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface } $this->io = $prevIO; - $this->outputProgress = $prevProgress; if ($e) { throw $e; diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 237d7e49d..b87f6433a 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -210,15 +210,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } } - /** - * Download progress information is not available for all VCS downloaders. - * {@inheritDoc} - */ - public function setOutputProgress($outputProgress) - { - return $this; - } - /** * {@inheritDoc} */ From bb2f64c7bc37bbc01de6eb4efa49719c5ff80a77 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Jan 2019 12:14:37 +0100 Subject: [PATCH 386/580] Remove ability to override the entire HttpDownloader instance in PRE_FILE_DOWNLOAD events --- src/Composer/Downloader/FileDownloader.php | 6 ++---- src/Composer/Plugin/PreFileDownloadEvent.php | 18 +++++------------- src/Composer/Repository/ComposerRepository.php | 13 +++---------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 54acae710..4f64a9501 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -106,21 +106,19 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $io = $this->io; $cache = $this->cache; - $originalHttpDownloader = $this->httpDownloader; + $httpDownloader = $this->httpDownloader; $eventDispatcher = $this->eventDispatcher; $filesystem = $this->filesystem; $self = $this; $accept = null; $reject = null; - $download = function () use ($io, $output, $originalHttpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) { + $download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) { $url = reset($urls); - $httpDownloader = $originalHttpDownloader; if ($eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']); $eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } $checksum = $package->getDistSha1Checksum(); diff --git a/src/Composer/Plugin/PreFileDownloadEvent.php b/src/Composer/Plugin/PreFileDownloadEvent.php index 076449484..c2751da02 100644 --- a/src/Composer/Plugin/PreFileDownloadEvent.php +++ b/src/Composer/Plugin/PreFileDownloadEvent.php @@ -25,7 +25,7 @@ class PreFileDownloadEvent extends Event /** * @var HttpDownloader */ - private $rfs; + private $httpDownloader; /** * @var string @@ -36,13 +36,13 @@ class PreFileDownloadEvent extends Event * Constructor. * * @param string $name The event name - * @param HttpDownloader $rfs + * @param HttpDownloader $httpDownloader * @param string $processedUrl */ - public function __construct($name, HttpDownloader $rfs, $processedUrl) + public function __construct($name, HttpDownloader $httpDownloader, $processedUrl) { parent::__construct($name); - $this->rfs = $rfs; + $this->httpDownloader = $httpDownloader; $this->processedUrl = $processedUrl; } @@ -51,15 +51,7 @@ class PreFileDownloadEvent extends Event */ public function getHttpDownloader() { - return $this->rfs; - } - - /** - * @param HttpDownloader $rfs - */ - public function setHttpDownloader(HttpDownloader $rfs) - { - $this->rfs = $rfs; + return $this->httpDownloader; } /** diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index af627df73..e905f2b22 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -898,15 +898,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $httpDownloader = $this->httpDownloader; - if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } - $response = $httpDownloader->get($filename, $this->options); + $response = $this->httpDownloader->get($filename, $this->options); $json = $response->getBody(); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow @@ -989,12 +986,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; while ($retries--) { try { - $httpDownloader = $this->httpDownloader; - if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } $options = $this->options; @@ -1002,7 +996,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime); - $response = $httpDownloader->get($filename, $options); + $response = $this->httpDownloader->get($filename, $options); $json = $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { return true; @@ -1053,12 +1047,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private function asyncFetchFile($filename, $cacheKey, $lastModifiedTime = null) { $retries = 3; - $httpDownloader = $this->httpDownloader; + $httpDownloader = $this->httpDownloader; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); } $options = $lastModifiedTime ? array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))) : array(); From f54237159da2437aea09a0b70a8214c7766527a0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Jan 2019 12:14:47 +0100 Subject: [PATCH 387/580] Cleanups --- tests/Composer/Test/Repository/Pear/ChannelReaderTest.php | 8 ++++---- .../Test/Repository/Pear/ChannelRest10ReaderTest.php | 4 ++-- .../Test/Repository/Pear/ChannelRest11ReaderTest.php | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index 7ad30825d..95e59e906 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -28,13 +28,13 @@ class ChannelReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new HttpDownloaderMock(array( + $httpDownloader = new HttpDownloaderMock(array( 'http://pear.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), )); - $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + $reader = new \Composer\Repository\Pear\ChannelReader($httpDownloader); $channelInfo = $reader->read('http://pear.net/'); $packages = $channelInfo->getPackages(); @@ -50,7 +50,7 @@ class ChannelReaderTest extends TestCase public function testShouldSelectCorrectReader() { - $rfs = new HttpDownloaderMock(array( + $httpDownloader = new HttpDownloaderMock(array( 'http://pear.1.0.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.0.xml'), 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), @@ -64,7 +64,7 @@ class ChannelReaderTest extends TestCase 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), )); - $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + $reader = new \Composer\Repository\Pear\ChannelReader($httpDownloader); $reader->read('http://pear.1.0.net/'); $reader->read('http://pear.1.1.net/'); diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php index 5a40915e1..3960c7858 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php @@ -19,7 +19,7 @@ class ChannelRest10ReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new HttpDownloaderMock(array( + $httpDownloader = new HttpDownloaderMock(array( 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), 'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'), @@ -29,7 +29,7 @@ class ChannelRest10ReaderTest extends TestCase 'http://test.loc/rest10/r/http_request/deps.1.4.0.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_deps.1.4.0.txt'), )); - $reader = new \Composer\Repository\Pear\ChannelRest10Reader($rfs); + $reader = new \Composer\Repository\Pear\ChannelRest10Reader($httpDownloader); /** @var \Composer\Package\PackageInterface[] $packages */ $packages = $reader->read('http://test.loc/rest10'); diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php index 08c3a2998..684c59155 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php @@ -19,13 +19,13 @@ class ChannelRest11ReaderTest extends TestCase { public function testShouldBuildPackagesFromPearSchema() { - $rfs = new HttpDownloaderMock(array( + $httpDownloader = new HttpDownloaderMock(array( 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), )); - $reader = new \Composer\Repository\Pear\ChannelRest11Reader($rfs); + $reader = new \Composer\Repository\Pear\ChannelRest11Reader($httpDownloader); /** @var \Composer\Package\PackageInterface[] $packages */ $packages = $reader->read('http://test.loc/rest11'); From 37550ce44b5c7d8f42c3528389a354af4b975d59 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 18 Jan 2019 18:49:45 +0100 Subject: [PATCH 388/580] Add support for new minified format --- .../Repository/ComposerRepository.php | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index e905f2b22..bb613497f 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -598,8 +598,6 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { - static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); - if (true === $response) { $response = $contents; } @@ -608,8 +606,38 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return; } + $versions = $response['packages'][$name]; + + if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { + // TODO extract in other method + $expanded = array(); + $expandedVersion = null; + foreach ($versions as $versionData) { + if (!$expandedVersion) { + $expandedVersion = $versionData; + $expanded[] = $expandedVersion; + continue; + } + + // add any changes from the previous version to the expanded one + foreach ($versionData as $key => $val) { + if ($val === '__unset') { + unset($expandedVersion[$key]); + } else { + $expandedVersion[$key] = $val; + } + } + + $expanded[] = $expandedVersion; + } + + $versions = $expanded; + unset($expanded, $expandedVersion, $versionData); + } + + static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); $versionsToLoad = array(); - foreach ($response['packages'][$name] as $version) { + foreach ($versions as $version) { if (isset($version['version_normalizeds'])) { foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) { From bcff704bc52efc22be7f74a8a62145ea924250e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Xu=C3=A2n=20Qu=E1=BB=B3nh?= Date: Sun, 20 Jan 2019 12:46:13 +0700 Subject: [PATCH 389/580] Add alias of run-script command --- src/Composer/Command/RunScriptCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index ea3b5c892..7997dfb37 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -48,6 +48,7 @@ class RunScriptCommand extends BaseCommand { $this ->setName('run-script') + ->setAliases(array('run')) ->setDescription('Runs the scripts defined in composer.json.') ->setDefinition(array( new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), From 3d1e0e79ccc79af687beaf807123496fa74053d3 Mon Sep 17 00:00:00 2001 From: Kath Young Date: Tue, 22 Jan 2019 11:18:35 +1030 Subject: [PATCH 390/580] Allow for no-api for Github to be a composer configuration as well as repo specific --- src/Composer/Config.php | 5 +++-- src/Composer/Repository/Vcs/GitHubDriver.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 7b4220724..c34d8f38f 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -61,6 +61,7 @@ class Config 'archive-format' => 'tar', 'archive-dir' => '.', 'htaccess-protect' => true, + 'no-api' => false, // valid keys without defaults (auth config stuff): // bitbucket-oauth // github-oauth @@ -317,10 +318,10 @@ class Config case 'disable-tls': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - case 'secure-http': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - + case 'no-api': + return $this->config[$key] !== 'false' && (bool) $this->config[$key]; default: if (!isset($this->config[$key])) { return null; diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index d0b721af9..14e51699f 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -56,7 +56,7 @@ class GitHubDriver extends VcsDriver } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); - if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) { + if ( $this->config->get('no-api') === true || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){ $this->setupGitDriver($this->url); return; From 8b1f8a46293669ac9b5b0737bd4715108b3245c6 Mon Sep 17 00:00:00 2001 From: Kath Young Date: Tue, 22 Jan 2019 11:22:55 +1030 Subject: [PATCH 391/580] Add no-api in the config as an acceptable config --- src/Composer/Command/ConfigCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index b002fd3a7..3ed62e0c5 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -302,6 +302,7 @@ EOT $uniqueConfigValues = array( 'process-timeout' => array('is_numeric', 'intval'), 'use-include-path' => array($booleanValidator, $booleanNormalizer), + 'no-api' => array($booleanValidator, $booleanNormalizer), 'preferred-install' => array( function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); From c5cc178375cad478efabf74270e8583e217afa4a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 14:31:16 +0100 Subject: [PATCH 392/580] Update to latest CA bundle --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 957382bc6..fa0ed41fd 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "time": "2019-01-28T09:30:10+00:00" }, { "name": "composer/semver", From a9aaa25d4c705219a4c14f2f3bb58395aac6cf24 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 14:38:32 +0100 Subject: [PATCH 393/580] Fix compat with Symfony Process 4.2, fixes #7923 --- src/Composer/Command/InitCommand.php | 7 ++++++- src/Composer/Util/Perforce.php | 8 +++++++- src/Composer/Util/ProcessExecutor.php | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 02efbf686..abcea73b2 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -557,7 +557,12 @@ EOT $finder = new ExecutableFinder(); $gitBin = $finder->find('git'); - $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); + // TODO in v3 always call with an array + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $cmd = new Process(array($gitBin, 'config', '-l')); + } else { + $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); + } $cmd->run(); if ($cmd->isSuccessful()) { diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index b064feec4..31ddeffec 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -370,7 +370,13 @@ class Perforce public function windowsLogin($password) { $command = $this->generateP4Command(' login -a'); - $process = new Process($command, null, null, $password); + + // TODO in v3 generate command as an array + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $process = Process::fromShellCommandline($command, null, null, $password); + } else { + $process = new Process($command, null, null, $password); + } return $process->run(); } diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 2a0742778..f5e1ef610 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -62,7 +62,13 @@ class ProcessExecutor $this->captureOutput = func_num_args() > 1; $this->errorOutput = null; - $process = new Process($command, $cwd, null, null, static::getTimeout()); + + // TODO in v3, commands should be passed in as arrays of cmd + args + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); + } else { + $process = new Process($command, $cwd, null, null, static::getTimeout()); + } $callback = is_callable($output) ? $output : array($this, 'outputHandler'); $process->run($callback); From 02ceb74151eb027cf74c17059dfb80a1af1a088d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 15:29:37 +0100 Subject: [PATCH 394/580] Tweak --no-cache option to be available globally and to not break VCS drivers relying on it, refs #7880, refs #6650 --- doc/03-cli.md | 2 ++ src/Composer/Cache.php | 7 ++++++- src/Composer/Command/InstallCommand.php | 14 -------------- src/Composer/Command/UpdateCommand.php | 14 -------------- src/Composer/Console/Application.php | 6 ++++++ src/Composer/Downloader/GitDownloader.php | 3 ++- src/Composer/Repository/Vcs/FossilDriver.php | 5 +++++ src/Composer/Repository/Vcs/GitDriver.php | 4 ++++ src/Composer/Repository/Vcs/HgDriver.php | 5 +++++ src/Composer/Repository/Vcs/PerforceDriver.php | 5 +++++ 10 files changed, 35 insertions(+), 30 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 300d4837f..6070b8509 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -22,6 +22,8 @@ The following options are available with every command: * **--quiet (-q):** Do not output any message. * **--no-interaction (-n):** Do not ask any interactive question. * **--no-plugins:** Disables plugins. +* **--no-cache:** Disables the use of the cache directory. Same as setting the COMPOSER_CACHE_DIR + env var to /dev/null (or NUL on Windows). * **--working-dir (-d):** If specified, use the given directory as working directory. * **--profile:** Display timing and memory usage information * **--ansi:** Force ANSI output. diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 44395c3a2..3f2861797 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -44,7 +44,7 @@ class Cache $this->whitelist = $whitelist; $this->filesystem = $filesystem ?: new Filesystem(); - if (preg_match('{(^|[\\\\/])(\$null|NUL|/dev/null)([\\\\/]|$)}', $cacheDir)) { + if (!self::isUsable($cacheDir)) { $this->enabled = false; return; @@ -59,6 +59,11 @@ class Cache } } + public static function isUsable($path) + { + return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); + } + public function isEnabled() { return $this->enabled; diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index a2e55b6c8..cc590d8c9 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -15,7 +15,6 @@ namespace Composer\Command; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; -use Composer\Util\Platform; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -41,7 +40,6 @@ class InstallCommand extends BaseCommand new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), - new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), @@ -95,18 +93,6 @@ EOT $install = Installer::create($io, $composer); $config = $composer->getConfig(); - - if ($input->getOption('no-cache')) { - $io->write('Skipping cache directory'); - $config->merge( - array( - 'config' => array( - 'cache-dir' => Platform::isWindows() ? 'nul' : '/dev/null', - ) - ) - ); - } - list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index a912cc373..34420b747 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -17,7 +17,6 @@ use Composer\Installer; use Composer\IO\IOInterface; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; -use Composer\Util\Platform; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -45,7 +44,6 @@ class UpdateCommand extends BaseCommand new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), - new InputOption('no-cache', null, InputOption::VALUE_NONE, 'Do not use the cache directory'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), @@ -130,18 +128,6 @@ EOT $install = Installer::create($io, $composer); $config = $composer->getConfig(); - - if ($input->getOption('no-cache')) { - $io->write('Skipping cache directory'); - $config->merge( - array( - 'config' => array( - 'cache-dir' => Platform::isWindows() ? 'nul' : '/dev/null', - ) - ) - ); - } - list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index c25ee3fe0..96c3ff821 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -118,6 +118,11 @@ class Application extends BaseApplication ))); ErrorHandler::register($io); + if ($input->hasParameterOption('--no-cache')) { + $io->writeError('Disabling cache usage', true, IOInterface::DEBUG); + putenv('COMPOSER_CACHE_DIR='.(Platform::isWindows() ? 'nul' : '/dev/null')); + } + // switch working dir if ($newWorkDir = $this->getNewWorkingDir($input)) { $oldWorkingDir = getcwd(); @@ -451,6 +456,7 @@ class Application extends BaseApplication $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); + $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); return $definition; } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 869d5330b..f1657c6c4 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -19,6 +19,7 @@ use Composer\Util\Filesystem; use Composer\Util\Git as GitUtil; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; +use Composer\Cache; /** * @author Jordi Boggiano @@ -51,7 +52,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface $msg = "Cloning ".$this->getShortHash($ref); $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer'; - if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=')) { + if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); try { diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index cc872474e..491fafa86 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -12,6 +12,7 @@ namespace Composer\Repository\Vcs; +use Composer\Cache; use Composer\Config; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; @@ -45,6 +46,10 @@ class FossilDriver extends VcsDriver if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) { $this->checkoutDir = $this->url; } else { + if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $localName = preg_replace('{[^a-z0-9]}i', '-', $this->url); $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 0269f4721..4a14974fb 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -41,6 +41,10 @@ class GitDriver extends VcsDriver $this->repoDir = $this->url; $cacheUrl = realpath($this->url); } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; GitUtil::cleanEnv(); diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 45f13d5fe..373b24af1 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Config; +use Composer\Cache; use Composer\Util\Hg as HgUtils; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; @@ -37,6 +38,10 @@ class HgDriver extends VcsDriver if (Filesystem::isLocalPath($this->url)) { $this->repoDir = $this->url; } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $cacheDir = $this->config->get('cache-vcs-dir'); $this->repoDir = $cacheDir . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; diff --git a/src/Composer/Repository/Vcs/PerforceDriver.php b/src/Composer/Repository/Vcs/PerforceDriver.php index 667f914df..09b5d4b16 100644 --- a/src/Composer/Repository/Vcs/PerforceDriver.php +++ b/src/Composer/Repository/Vcs/PerforceDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Config; +use Composer\Cache; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; use Composer\Util\Perforce; @@ -54,6 +55,10 @@ class PerforceDriver extends VcsDriver return; } + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } From b7d1f8784841e8969340721f98168e4a138a775b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 15:44:21 +0100 Subject: [PATCH 395/580] Fix tests --- tests/Composer/Test/ApplicationTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 5f491440b..8739a5004 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -31,6 +31,11 @@ class ApplicationTest extends TestCase ->with($this->equalTo('--no-plugins')) ->will($this->returnValue(true)); + $inputMock->expects($this->at($index++)) + ->method('hasParameterOption') + ->with($this->equalTo('--no-cache')) + ->will($this->returnValue(false)); + $inputMock->expects($this->at($index++)) ->method('getParameterOption') ->with($this->equalTo(array('--working-dir', '-d'))) @@ -84,6 +89,11 @@ class ApplicationTest extends TestCase ->with($this->equalTo('--no-plugins')) ->will($this->returnValue(true)); + $inputMock->expects($this->at($index++)) + ->method('hasParameterOption') + ->with($this->equalTo('--no-cache')) + ->will($this->returnValue(false)); + $inputMock->expects($this->at($index++)) ->method('getParameterOption') ->with($this->equalTo(array('--working-dir', '-d'))) From abcde190224398da456ae1bc352091f9bce38231 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 16:17:18 +0100 Subject: [PATCH 396/580] Document --no-check-all better, fixes #7889 --- doc/03-cli.md | 2 +- src/Composer/Command/ValidateCommand.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 74374ec6b..c3faffe34 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -491,7 +491,7 @@ php composer.phar validate ### Options -* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound version constraints. +* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound or overly strict version constraints. * **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. * **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid. * **--with-dependencies:** Also validate the composer.json of all installed dependencies. diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index b86fd92ea..52023e528 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -39,7 +39,7 @@ class ValidateCommand extends BaseCommand ->setName('validate') ->setDescription('Validates a composer.json and composer.lock.') ->setDefinition(array( - new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), + new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), From ea333aa134ad36e104e2cb45c79093df4df67b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Mon, 28 Jan 2019 16:46:58 +0100 Subject: [PATCH 397/580] Fix: Remove empty node --- src/Composer/Config/JsonConfigSource.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index 128ebf8ec..15d40d200 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -193,6 +193,10 @@ class JsonConfigSource implements ConfigSourceInterface { $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) { unset($config[$type][$name]); + + if (0 === count($config[$type])) { + unset($config[$type]); + } }); } From 3b6b63784fe5e11e5eaf469aebd6fa40e723403a Mon Sep 17 00:00:00 2001 From: Den Girnyk Date: Thu, 17 Jan 2019 18:11:32 +0200 Subject: [PATCH 398/580] Fix: Keep replaced packages for autoload dumping with --no-dev --- src/Composer/Autoload/AutoloadGenerator.php | 8 +++-- .../Test/Autoload/AutoloadGeneratorTest.php | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 0db4015c3..7ea1a3444 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -940,9 +940,13 @@ INITIALIZER; $packageMap, function ($item) use ($include) { $package = $item[0]; - $name = $package->getName(); + foreach ($package->getNames() as $name) { + if (isset($include[$name])) { + return true; + } + } - return isset($include[$name]); + return false; } ); } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 4d672084e..615a613f9 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Autoload; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; use Composer\Package\AliasPackage; use Composer\Package\Package; @@ -419,6 +420,39 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testNonDevAutoloadShouldIncludeReplacedPackages() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array(new Link('a', 'a/a'))); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + + $a->setRequires(array(new Link('a/a', 'b/c'))); + + $b->setAutoload(array('psr-4' => array('B\\' => 'src/'))); + $b->setReplaces( + array(new Link('b/b', 'b/c', new Constraint('==', '1.0'), 'replaces')) + ); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src/C'); + file_put_contents($this->vendorDir.'/b/b/src/C/C.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_5'); + + $this->assertEquals( + array( + 'B\\C\\C' => $this->vendorDir.'/b/b/src/C/C.php', + ), + include $this->vendorDir.'/composer/autoload_classmap.php' + ); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); From 386382503dfb009f378f8318c2b9b973c3e6aee7 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Mon, 20 Aug 2018 22:06:46 +0200 Subject: [PATCH 399/580] Add a test for autoloading if a package is only required via replacing name --- .../Test/Autoload/AutoloadGeneratorTest.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 615a613f9..c1605bf97 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -453,6 +453,39 @@ class AutoloadGeneratorTest extends TestCase ); } + public function testNonDevAutoloadExclusionWithRecursionReplace() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); + $a->setRequires(array( + new Link('a/a', 'c/c'), + )); + $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); + $b->setReplaces(array( + new Link('b/b', 'c/c'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); From 50cb5fe3daa8e29b1de19cc24dddd278422b8ddd Mon Sep 17 00:00:00 2001 From: Sascha Egerer Date: Wed, 12 Dec 2018 16:21:15 +0100 Subject: [PATCH 400/580] Update all whitelist matching root dependencies The update command can receive a pattern like `vendor/prefix-*` to update all matching packages. This has not worked if multiple packages, depending on each other, where matched to the given pattern. No package has been updated in this case as only the first package matching the pattern was added to the whitelist. --- src/Composer/Installer.php | 2 +- ...elist-patterns-with-root-dependencies.test | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index bd0d22e3c..12f071c51 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1332,8 +1332,8 @@ class Installer $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { + $depPackages = array_merge($pool->whatProvides($rootRequiredPackageName)); $nameMatchesRequiredPackage = true; - break; } } } diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test new file mode 100644 index 000000000..360ef49f5 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test @@ -0,0 +1,45 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0", "require": { "whitelisted-component2": "1.1.0" } }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.1.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) From dc59af555aadcf52ff74735de571f5f5e3318560 Mon Sep 17 00:00:00 2001 From: Sascha Egerer Date: Wed, 12 Dec 2018 17:27:26 +0100 Subject: [PATCH 401/580] Fix invalid call to array_merge --- src/Composer/Installer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 12f071c51..2046dd1da 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1323,7 +1323,7 @@ class Installer foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; - $depPackages = $pool->whatProvides($packageName); + $depPackages = [$pool->whatProvides($packageName)]; $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); @@ -1332,12 +1332,14 @@ class Installer $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { - $depPackages = array_merge($pool->whatProvides($rootRequiredPackageName)); + $depPackages[] = $pool->whatProvides($rootRequiredPackageName); $nameMatchesRequiredPackage = true; } } } + $depPackages = array_merge(...$depPackages); + if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } From 1845adcfbdeeff9884ff4561287a32ffaab25749 Mon Sep 17 00:00:00 2001 From: Sascha Egerer Date: Thu, 13 Dec 2018 13:54:22 +0100 Subject: [PATCH 402/580] Fix update whitelist pattern resolving and add more tests --- src/Composer/Installer.php | 25 +++++----- src/Composer/Package/BasePackage.php | 5 +- ...telist-patterns-with-all-dependencies.test | 46 +++++++++++++++++ ...-whitelist-patterns-with-dependencies.test | 49 +++++++++++++++++++ ...elist-patterns-with-root-dependencies.test | 12 ++++- ...itelist-patterns-without-dependencies.test | 44 +++++++++++++++++ 6 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test create mode 100644 tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test create mode 100644 tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 2046dd1da..88b4ea1d5 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1301,11 +1301,6 @@ class Installer $rootRequires = array_merge($rootRequires, $rootDevRequires); - $requiredPackageNames = array(); - foreach ($rootRequires as $require) { - $requiredPackageNames[] = $require->getTarget(); - } - $skipPackages = array(); if (!$this->whitelistAllDependencies) { foreach ($rootRequires as $require) { @@ -1323,22 +1318,26 @@ class Installer foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; - $depPackages = [$pool->whatProvides($packageName)]; - - $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); - + $depPackages = $pool->whatProvides($packageName); + $matchesByPattern = []; // check if the name is a glob pattern that did not match directly - if (!$nameMatchesRequiredPackage) { + if (empty($depPackages)) { + $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); + foreach ($localOrLockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { + $matchesByPattern[] = $pool->whatProvides($installedPackage['name']); + } $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { - $depPackages[] = $pool->whatProvides($rootRequiredPackageName); $nameMatchesRequiredPackage = true; + break; } } } - $depPackages = array_merge(...$depPackages); + if (!empty($matchesByPattern)) { + $depPackages = array_merge($depPackages, array_merge(...$matchesByPattern)); + } if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); @@ -1371,7 +1370,7 @@ class Installer continue; } - if (isset($skipPackages[$requirePackage->getName()])) { + if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) { $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); continue; } diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 65ea6860f..f2f5be707 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -239,12 +239,13 @@ abstract class BasePackage implements PackageInterface * Build a regexp from a package name, expanding * globs as required * * @param string $whiteListedPattern + * @param bool $wrap Wrap the cleaned string by the given string * @return string */ - public static function packageNameToRegexp($whiteListedPattern) + public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i') { $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); - return "{^" . $cleanedWhiteListedPattern . "$}i"; + return sprintf($wrap, $cleanedWhiteListedPattern); } } diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test new file mode 100644 index 000000000..8ea177cad --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test @@ -0,0 +1,46 @@ +--TEST-- +Update with a package whitelist pattern and all-dependencies flag updates packages and their dependencies, even if defined as root dependency, matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-all-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test new file mode 100644 index 000000000..c685f14ce --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test @@ -0,0 +1,49 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern but no dependencies defined as roo package +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "root-dependency", "version": "1.1.0" }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "root-dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test index 360ef49f5..a24bafb91 100644 --- a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test @@ -10,8 +10,14 @@ Update with a package whitelist only updates those packages and their dependenci { "name": "fixed", "version": "1.0.0" }, { "name": "whitelisted-component1", "version": "1.1.0", "require": { "whitelisted-component2": "1.1.0" } }, { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, - { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.1.0" } }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.1.0", "whitelisted-component5": "1.0.0" } }, { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.1.0", "require": { "whitelisted-component4": "1.1.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.1.0" }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.1.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, { "name": "dependency", "version": "1.1.0" }, { "name": "dependency", "version": "1.0.0" }, { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, @@ -25,6 +31,7 @@ Update with a package whitelist only updates those packages and their dependenci "fixed": "1.*", "whitelisted-component1": "1.*", "whitelisted-component2": "1.*", + "whitelisted-component3": "1.0.0", "unrelated": "1.*" } } @@ -33,6 +40,9 @@ Update with a package whitelist only updates those packages and their dependenci { "name": "fixed", "version": "1.0.0" }, { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, { "name": "dependency", "version": "1.0.0" }, { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, { "name": "unrelated-dependency", "version": "1.0.0" } diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test new file mode 100644 index 000000000..e5551b43f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test @@ -0,0 +1,44 @@ +--TEST-- +Update with a package whitelist only updates those packages matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) From 82ecf95a3cf6e020a97cbbf1206771925c2cde35 Mon Sep 17 00:00:00 2001 From: Sascha Egerer Date: Mon, 17 Dec 2018 15:21:03 +0100 Subject: [PATCH 403/580] Add PHP 5.3 compatibility --- src/Composer/Installer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 88b4ea1d5..b4885e76c 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1319,7 +1319,7 @@ class Installer $packageQueue = new \SplQueue; $depPackages = $pool->whatProvides($packageName); - $matchesByPattern = []; + $matchesByPattern = array(); // check if the name is a glob pattern that did not match directly if (empty($depPackages)) { $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); @@ -1336,7 +1336,7 @@ class Installer } if (!empty($matchesByPattern)) { - $depPackages = array_merge($depPackages, array_merge(...$matchesByPattern)); + $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); } if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { From 1f97ffdcd700df530376f7a5e013c9a78ee0d93b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 28 Jan 2019 17:54:32 +0100 Subject: [PATCH 404/580] Add some docs --- src/Composer/Installer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b4885e76c..f38086611 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1322,10 +1322,13 @@ class Installer $matchesByPattern = array(); // check if the name is a glob pattern that did not match directly if (empty($depPackages)) { + // add any installed package matching the whitelisted name/pattern $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); foreach ($localOrLockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { $matchesByPattern[] = $pool->whatProvides($installedPackage['name']); } + + // add root requirements which match the whitelisted name/pattern $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { From 4765a8f21b38b7ee65968569a00fa96d00f9d2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4fer?= Date: Fri, 21 Dec 2018 21:57:19 +0100 Subject: [PATCH 405/580] MB to MiB I did not study computer science, so correct me if I'm wrong. But I think you are calculating mebibyte (MiB) not megabyte (MB). Megabyte would be: ... round($valueInByte / 1000 / 1000, 2).'MB ... Or is there some specific standard you follow? According to https://en.wikipedia.org/wiki/Binary_prefix both calculations (yours and mine) are correct in a way but I find yours to be not completely clear. --- src/Composer/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index e6ff7da9d..ccf83c943 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -262,7 +262,7 @@ class Application extends BaseApplication } if (isset($startTime)) { - $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MB), time: '.round(microtime(true) - $startTime, 2).'s'); + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } restore_error_handler(); From e0c44f2a25dfca01394dd497ee302ce7fbe1d595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4fer?= Date: Fri, 28 Dec 2018 15:07:21 +0100 Subject: [PATCH 406/580] Another MB to MiB --- src/Composer/IO/ConsoleIO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index c0f235659..8b29177d5 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -153,7 +153,7 @@ class ConsoleIO extends BaseIO $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) { - return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } From 909d1c430eb439a91cab7fc6373be97f9396cba1 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 28 Jan 2019 20:52:02 +0000 Subject: [PATCH 407/580] Update xdebug-handler, fixes #7921 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index fa0ed41fd..05df052f8 100644 --- a/composer.lock +++ b/composer.lock @@ -187,16 +187,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "dc523135366eb68f22268d069ea7749486458562" + "reference": "d17708133b6c276d6e42ef887a877866b909d892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", - "reference": "dc523135366eb68f22268d069ea7749486458562", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", + "reference": "d17708133b6c276d6e42ef887a877866b909d892", "shasum": "" }, "require": { @@ -227,7 +227,7 @@ "Xdebug", "performance" ], - "time": "2018-11-29T10:59:02+00:00" + "time": "2019-01-28T20:25:53+00:00" }, { "name": "justinrainbow/json-schema", From fbb9d20c33597b2aeb1d985e2d44ff8cb00e9c62 Mon Sep 17 00:00:00 2001 From: Kath Young Date: Tue, 29 Jan 2019 07:23:24 +1030 Subject: [PATCH 408/580] Adjusted config name to be more descriptive, added documentation --- doc/06-config.md | 8 ++++++++ res/composer-schema.json | 4 ++++ src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Config.php | 4 ++-- src/Composer/Repository/Vcs/GitHubDriver.php | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/06-config.md b/doc/06-config.md index 87ffb02a0..6fc743f13 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -234,6 +234,14 @@ github API will have a date instead of the machine hostname. Defaults to `["gitlab.com"]`. A list of domains of GitLab servers. This is used if you use the `gitlab` repository type. +## use-github-api + +Defaults to `true`. Similar to the `no-api` key on a specific repository, setting `use-github-api` to `false` will define the global behavior for all GitHub repositories to clone the +repository as it would with any other git repository instead of using the +GitHub API. But unlike using the `git` driver directly, Composer will still +attempt to use github's zip files. + + ## notify-on-install Defaults to `true`. Composer allows repositories to define a notification URL, diff --git a/res/composer-schema.json b/res/composer-schema.json index ce80c209b..cb3594f7b 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -271,6 +271,10 @@ "type": "string" } }, + "use-github-api": { + "type": "boolean", + "description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository." + }, "archive-format": { "type": "string", "description": "The default archiving format when not provided on cli, defaults to \"tar\"." diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 3ed62e0c5..d6fe2844f 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -302,7 +302,7 @@ EOT $uniqueConfigValues = array( 'process-timeout' => array('is_numeric', 'intval'), 'use-include-path' => array($booleanValidator, $booleanNormalizer), - 'no-api' => array($booleanValidator, $booleanNormalizer), + 'use-github-api' => array($booleanValidator, $booleanNormalizer), 'preferred-install' => array( function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); diff --git a/src/Composer/Config.php b/src/Composer/Config.php index c34d8f38f..941505252 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -61,7 +61,7 @@ class Config 'archive-format' => 'tar', 'archive-dir' => '.', 'htaccess-protect' => true, - 'no-api' => false, + 'use-github-api' => true, // valid keys without defaults (auth config stuff): // bitbucket-oauth // github-oauth @@ -320,7 +320,7 @@ class Config return $this->config[$key] !== 'false' && (bool) $this->config[$key]; case 'secure-http': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - case 'no-api': + case 'use-github-api': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; default: if (!isset($this->config[$key])) { diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 14e51699f..42b30222e 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -56,7 +56,7 @@ class GitHubDriver extends VcsDriver } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); - if ( $this->config->get('no-api') === true || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){ + if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){ $this->setupGitDriver($this->url); return; From dec2b5cd5008a5e69a2f47ea32310ac39ac71052 Mon Sep 17 00:00:00 2001 From: Andrew Gillis Date: Mon, 28 Jan 2019 13:36:28 -0500 Subject: [PATCH 409/580] add gitlab token auth for git clone --- src/Composer/Util/Git.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 37410eecd..74e5c286f 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -153,6 +153,28 @@ class Git return; } } + } elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) { + if (!$this->io->hasAuthentication($match[2])) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process); + $message = 'Cloning failed, enter your GitLab credentials to access private repos'; + + if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) { + $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message); + } + } + + if ($this->io->hasAuthentication($match[2])) { + $auth = $this->io->getAuthentication($match[2]); + if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') { + $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password + } else { + $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3]; + } + $command = call_user_func($commandCallable, $authUrl); + if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { + return; + } + } } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate if (strpos($match[2], '@')) { list($authParts, $match[2]) = explode('@', $match[2], 2); @@ -304,6 +326,11 @@ class Git return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')'; } + public static function getGitLabDomainsRegex(Config $config) + { + return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; + } + public static function sanitizeUrl($message) { return preg_replace_callback('{://(?P[^@]+?):(?P.+?)@}', function ($m) { From 2d7a8c67e88281af1f35fc5766486bbbeb288ca6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 10:55:05 +0100 Subject: [PATCH 410/580] Doc formatting fixes --- doc/06-config.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/06-config.md b/doc/06-config.md index 6fc743f13..87d73f8a1 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -236,11 +236,11 @@ This is used if you use the `gitlab` repository type. ## use-github-api -Defaults to `true`. Similar to the `no-api` key on a specific repository, setting `use-github-api` to `false` will define the global behavior for all GitHub repositories to clone the -repository as it would with any other git repository instead of using the -GitHub API. But unlike using the `git` driver directly, Composer will still -attempt to use github's zip files. - +Defaults to `true`. Similar to the `no-api` key on a specific repository, +setting `use-github-api` to `false` will define the global behavior for all +GitHub repositories to clone the repository as it would with any other git +repository instead of using the GitHub API. But unlike using the `git` +driver directly, Composer will still attempt to use GitHub's zip files. ## notify-on-install From 98a15bc93cd4bbe1b11778f9a8a424b5d0f6b043 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 11:14:22 +0100 Subject: [PATCH 411/580] Add output for metapackage installs/updates/.. fixes #7586 --- src/Composer/Downloader/FileDownloader.php | 4 ++-- src/Composer/Factory.php | 2 +- .../Installer/MetapackageInstaller.php | 19 +++++++++++++++++++ .../Test/Downloader/FileDownloaderTest.php | 4 ++-- .../Installer/MetapackageInstallerTest.php | 8 +++++++- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 6596d9c8b..e63df021a 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -215,8 +215,8 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface public function update(PackageInterface $initial, PackageInterface $target, $path) { $name = $target->getName(); - $from = $initial->getPrettyVersion(); - $to = $target->getPrettyVersion(); + $from = $initial->getFullPrettyVersion(); + $to = $target->getFullPrettyVersion(); $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading'; $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 6df73ceb3..7e9fd1bd1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -546,7 +546,7 @@ class Factory $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library')); $im->addInstaller(new Installer\PluginInstaller($io, $composer)); - $im->addInstaller(new Installer\MetapackageInstaller()); + $im->addInstaller(new Installer\MetapackageInstaller($io)); } /** diff --git a/src/Composer/Installer/MetapackageInstaller.php b/src/Composer/Installer/MetapackageInstaller.php index 3f99ec03c..e1f31c1bf 100644 --- a/src/Composer/Installer/MetapackageInstaller.php +++ b/src/Composer/Installer/MetapackageInstaller.php @@ -14,6 +14,8 @@ namespace Composer\Installer; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Package\Version\VersionParser; +use Composer\IO\IOInterface; /** * Metapackage installation manager. @@ -22,6 +24,13 @@ use Composer\Package\PackageInterface; */ class MetapackageInstaller implements InstallerInterface { + private $io; + + public function __construct(IOInterface $io) + { + $this->io = $io; + } + /** * {@inheritDoc} */ @@ -43,6 +52,8 @@ class MetapackageInstaller implements InstallerInterface */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + $repo->addPackage(clone $package); } @@ -55,6 +66,12 @@ class MetapackageInstaller implements InstallerInterface throw new \InvalidArgumentException('Package is not installed: '.$initial); } + $name = $target->getName(); + $from = $initial->getFullPrettyVersion(); + $to = $target->getFullPrettyVersion(); + $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading'; + $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . ")"); + $repo->removePackage($initial); $repo->addPackage(clone $target); } @@ -68,6 +85,8 @@ class MetapackageInstaller implements InstallerInterface throw new \InvalidArgumentException('Package is not installed: '.$package); } + $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + $repo->removePackage($package); } diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 476b9a8f7..b09065f85 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -210,7 +210,7 @@ class FileDownloaderTest extends TestCase { $oldPackage = $this->getMock('Composer\Package\PackageInterface'); $oldPackage->expects($this->once()) - ->method('getPrettyVersion') + ->method('getFullPrettyVersion') ->will($this->returnValue('1.2.0')); $oldPackage->expects($this->once()) ->method('getVersion') @@ -218,7 +218,7 @@ class FileDownloaderTest extends TestCase $newPackage = $this->getMock('Composer\Package\PackageInterface'); $newPackage->expects($this->once()) - ->method('getPrettyVersion') + ->method('getFullPrettyVersion') ->will($this->returnValue('1.0.0')); $newPackage->expects($this->once()) ->method('getVersion') diff --git a/tests/Composer/Test/Installer/MetapackageInstallerTest.php b/tests/Composer/Test/Installer/MetapackageInstallerTest.php index 1a6b7a264..171773579 100644 --- a/tests/Composer/Test/Installer/MetapackageInstallerTest.php +++ b/tests/Composer/Test/Installer/MetapackageInstallerTest.php @@ -27,7 +27,7 @@ class MetapackageInstallerTest extends TestCase $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $this->installer = new MetapackageInstaller(); + $this->installer = new MetapackageInstaller($this->io); } public function testInstall() @@ -45,7 +45,13 @@ class MetapackageInstallerTest extends TestCase public function testUpdate() { $initial = $this->createPackageMock(); + $initial->expects($this->once()) + ->method('getVersion') + ->will($this->returnValue('1.0.0')); $target = $this->createPackageMock(); + $target->expects($this->once()) + ->method('getVersion') + ->will($this->returnValue('1.0.1')); $this->repository ->expects($this->exactly(2)) From acea4a4d4de0f9cf8523cc0fb81c654d0190d483 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 13:27:52 +0100 Subject: [PATCH 412/580] Warn on invalid package name or require/provide/.., fixes #7874 --- src/Composer/Factory.php | 2 +- .../Package/Loader/RootPackageLoader.php | 25 ++++++++++++++- .../Package/Loader/ValidatingArrayLoader.php | 32 +++++++++++++++++++ .../Repository/PlatformRepository.php | 2 +- .../installer/github-issues-4319.test | 8 ++--- .../installer/github-issues-4795-2.test | 22 ++++++------- .../installer/github-issues-4795.test | 20 ++++++------ .../Fixtures/installer/solver-problems.test | 30 ++++++++--------- .../update-with-all-dependencies.test | 22 ++++++------- 9 files changed, 109 insertions(+), 54 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 7e9fd1bd1..8a0ff1e2d 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -347,7 +347,7 @@ class Factory // load package $parser = new VersionParser; $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser); - $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser); + $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index f917eb838..84e99a857 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -15,6 +15,7 @@ namespace Composer\Package\Loader; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Config; +use Composer\IO\IOInterface; use Composer\Package\RootPackageInterface; use Composer\Repository\RepositoryFactory; use Composer\Package\Version\VersionGuesser; @@ -46,13 +47,19 @@ class RootPackageLoader extends ArrayLoader */ private $versionGuesser; - public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null) + /** + * @var IOInterface + */ + private $io; + + public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null, IOInterface $io = null) { parent::__construct($parser); $this->manager = $manager; $this->config = $config; $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser); + $this->io = $io; } /** @@ -65,6 +72,10 @@ class RootPackageLoader extends ArrayLoader { if (!isset($config['name'])) { $config['name'] = '__root__'; + } elseif ($this->io) { + if ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) { + $this->io->writeError('Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.'); + } } $autoVersioned = false; if (!isset($config['version'])) { @@ -131,6 +142,18 @@ class RootPackageLoader extends ArrayLoader } } + if ($this->io) { + foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { + if (isset($config[$linkType])) { + foreach ($config[$linkType] as $linkName => $constraint) { + if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) { + $this->io->writeError('Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.'); + } + } + } + } + } + if (isset($links[$config['name']])) { throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . 'Did you accidentally name your root package after an external package?', $config['name'])); diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index f4753025b..405e567f0 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -336,6 +336,38 @@ class ValidatingArrayLoader implements LoaderInterface return $this->errors; } + public static function hasPackageNamingError($name, $isLink = false) + { + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { + return; + } + + if (!preg_match('{^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$}iD', $name)) { + return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*".'; + } + + $reservedNames = array('nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'); + $bits = explode('/', strtolower($name)); + if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) { + return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.'; + } + + if (preg_match('{\.json$}', $name)) { + return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.'; + } + + if (preg_match('{[A-Z]}', $name)) { + if ($isLink) { + return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.'; + } + + $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); + $suggestName = strtolower($suggestName); + + return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.'; + } + } + private function validateRegex($property, $regex, $mandatory = false) { if (!$this->validateString($property, $mandatory)) { diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 6d6e04d2f..4d74d8ed2 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -24,7 +24,7 @@ use Composer\XdebugHandler\XdebugHandler; */ class PlatformRepository extends ArrayRepository { - const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/ ]+)$}i'; + const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:-?[a-z0-9]+)*)$}iD'; private $versionParser; diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test index d97aefc8d..ee221dab0 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test @@ -13,12 +13,12 @@ Present a clear error message when config.platform.php version results in a conf { "type": "package", "package": [ - { "name": "a", "version": "1.0.0", "require": { "php": "5.5" } } + { "name": "a/a", "version": "1.0.0", "require": { "php": "5.5" } } ] } ], "require": { - "a": "~1.0" + "a/a": "~1.0" }, "config": { "platform": { @@ -36,8 +36,8 @@ Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - - Installation request for a ~1.0 -> satisfiable by a[1.0.0]. - - a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement. + - Installation request for a/a ~1.0 -> satisfiable by a/a[1.0.0]. + - a/a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test index d807c6df8..877ac3653 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test @@ -11,27 +11,27 @@ that are also a root package, when that root package is also explicitly whitelis { "type": "package", "package": [ - { "name": "a", "version": "1.0.0" }, - { "name": "a", "version": "1.1.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }, - { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "a/a", "version": "1.1.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }, + { "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } } ] } ], "require": { - "a": "~1.0", - "b": "~1.0" + "a/a": "~1.0", + "b/b": "~1.0" } } --INSTALLED-- [ - { "name": "a", "version": "1.0.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } } ] --RUN-- -update a b --with-dependencies +update a/a b/b --with-dependencies --EXPECT-OUTPUT-- Loading composer repositories with package information @@ -41,5 +41,5 @@ Writing lock file Generating autoload files --EXPECT-- -Updating a (1.0.0) to a (1.1.0) -Updating b (1.0.0) to b (1.1.0) +Updating a/a (1.0.0) to a/a (1.1.0) +Updating b/b (1.0.0) to b/b (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test index 5a1158f26..1f4b1af27 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795.test @@ -11,30 +11,30 @@ dependency of one the requirements that is whitelisted for update. { "type": "package", "package": [ - { "name": "a", "version": "1.0.0" }, - { "name": "a", "version": "1.1.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }, - { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "a/a", "version": "1.1.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }, + { "name": "b/b", "version": "1.1.0", "require": { "a/b": "~1.1" } } ] } ], "require": { - "a": "~1.0", - "b": "~1.0" + "a/a": "~1.0", + "b/b": "~1.0" } } --INSTALLED-- [ - { "name": "a", "version": "1.0.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } } ] --RUN-- -update b --with-dependencies +update b/b --with-dependencies --EXPECT-OUTPUT-- -Dependency "a" is also a root requirement, but is not explicitly whitelisted. Ignoring. +Dependency "a/a" is also a root requirement, but is not explicitly whitelisted. Ignoring. Loading composer repositories with package information Updating dependencies (including require-dev) Nothing to install or update diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test index e0359a151..cab45f9dc 100644 --- a/tests/Composer/Test/Fixtures/installer/solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/solver-problems.test @@ -8,30 +8,30 @@ Test the error output of solver problems. "package": [ { "name": "unstable/package", "version": "2.0.0-alpha" }, { "name": "unstable/package", "version": "1.0.0" }, - { "name": "requirer", "version": "1.0.0", "require": {"dependency": "1.0.0" } }, - { "name": "dependency", "version": "2.0.0" }, - { "name": "dependency", "version": "1.0.0" }, - { "name": "stable-requiree-excluded", "version": "1.0.1" }, - { "name": "stable-requiree-excluded", "version": "1.0.0" } + { "name": "requirer/pkg", "version": "1.0.0", "require": {"dependency/pkg": "1.0.0" } }, + { "name": "dependency/pkg", "version": "2.0.0" }, + { "name": "dependency/pkg", "version": "1.0.0" }, + { "name": "stable-requiree-excluded/pkg", "version": "1.0.1" }, + { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" } ] } ], "require": { "unstable/package": "2.*", - "bogus": "1.*", - "requirer": "1.*", - "dependency": "2.*", - "stable-requiree-excluded": "1.0.1" + "bogus/pkg": "1.*", + "requirer/pkg": "1.*", + "dependency/pkg": "2.*", + "stable-requiree-excluded/pkg": "1.0.1" } } --INSTALLED-- [ - { "name": "stable-requiree-excluded", "version": "1.0.0" } + { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" } ] --RUN-- -update unstable/package requirer dependency +update unstable/package requirer/pkg dependency/pkg --EXPECT-EXIT-CODE-- 2 @@ -44,12 +44,12 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - The requested package unstable/package 2.* exists as unstable/package[1.0.0] but these are rejected by your constraint. Problem 2 - - The requested package bogus could not be found in any version, there may be a typo in the package name. + - The requested package bogus/pkg could not be found in any version, there may be a typo in the package name. Problem 3 - - The requested package stable-requiree-excluded (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded[1.0.0] but these conflict with your requirements or minimum-stability. + - The requested package stable-requiree-excluded/pkg (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded/pkg[1.0.0] but these conflict with your requirements or minimum-stability. Problem 4 - - Installation request for requirer 1.* -> satisfiable by requirer[1.0.0]. - - requirer 1.0.0 requires dependency 1.0.0 -> satisfiable by dependency[1.0.0] but these conflict with your requirements or minimum-stability. + - Installation request for requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0]. + - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> satisfiable by dependency/pkg[1.0.0] but these conflict with your requirements or minimum-stability. Potential causes: - A typo in the package name diff --git a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test index f4fbfce9b..c0019e6ca 100644 --- a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test @@ -10,27 +10,27 @@ When `--with-all-dependencies` is used, Composer\Installer::whitelistUpdateDepen { "type": "package", "package": [ - { "name": "a", "version": "1.0.0" }, - { "name": "a", "version": "1.1.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }, - { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "a/a", "version": "1.1.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }, + { "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } } ] } ], "require": { - "a": "~1.0", - "b": "~1.0" + "a/a": "~1.0", + "b/b": "~1.0" } } --INSTALLED-- [ - { "name": "a", "version": "1.0.0" }, - { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } } + { "name": "a/a", "version": "1.0.0" }, + { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } } ] --RUN-- -update b --with-all-dependencies +update b/b --with-all-dependencies --EXPECT-OUTPUT-- Loading composer repositories with package information @@ -40,5 +40,5 @@ Writing lock file Generating autoload files --EXPECT-- -Updating a (1.0.0) to a (1.1.0) -Updating b (1.0.0) to b (1.1.0) \ No newline at end of file +Updating a/a (1.0.0) to a/a (1.1.0) +Updating b/b (1.0.0) to b/b (1.1.0) From 9566b97e6e87aa03cd4993e110445de6f21e42a9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 13:57:47 +0100 Subject: [PATCH 413/580] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf06a0fd4..4b022d2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### [1.8.1] 2019-01-29 + + * Deprecated support for non-standard package names (anything with uppercase, or no / in it). Make sure to follow the warnings if you see any to avoid problems in 2.0. + * Fixed some packages missing from the autoloader config when installing with --no-dev + * Fixed support for cloning GitLab repos using OAuth tokens instead of SSH keys + * Fixed metapackage installs/updates missing from output + * Fixed --with-dependencies / --with-all-dependencies not updating some packages in some edge cases + * Fixed compatibility with Symfony 4.2 deprecations + * Fixed temp dir not being cleaned up on download error while archiving packages + * Updated to latest ca-bundle + ### [1.8.0] 2018-12-03 * Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before From 71193132a3d7de69bb42e01d4a1524f6069d4a55 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 14:22:20 +0100 Subject: [PATCH 414/580] Fix test --- tests/Composer/Test/Downloader/FileDownloaderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index f3f5dc6dd..10ea401a8 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -222,7 +222,7 @@ class FileDownloaderTest extends TestCase public function testDowngradeShowsAppropriateMessage() { $oldPackage = $this->getMock('Composer\Package\PackageInterface'); - $oldPackage->expects($this->once()) + $oldPackage->expects($this->any()) ->method('getFullPrettyVersion') ->will($this->returnValue('1.2.0')); $oldPackage->expects($this->once()) @@ -230,7 +230,7 @@ class FileDownloaderTest extends TestCase ->will($this->returnValue('1.2.0.0')); $newPackage = $this->getMock('Composer\Package\PackageInterface'); - $newPackage->expects($this->once()) + $newPackage->expects($this->any()) ->method('getFullPrettyVersion') ->will($this->returnValue('1.0.0')); $newPackage->expects($this->once()) From 585535a01d04afae84e00e7f46905a3291575da4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 14:58:37 +0100 Subject: [PATCH 415/580] Fix platform package regex --- src/Composer/Repository/PlatformRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 4d74d8ed2..221f2eb97 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -24,7 +24,7 @@ use Composer\XdebugHandler\XdebugHandler; */ class PlatformRepository extends ArrayRepository { - const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:-?[a-z0-9]+)*)$}iD'; + const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*)$}iD'; private $versionParser; From 5e35464044ac7eb623028f13276c1ff4caf58b30 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 29 Jan 2019 15:00:48 +0100 Subject: [PATCH 416/580] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b022d2bc..27eaacc67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### [1.8.2] 2019-01-29 + + * Fixed invalid deprecation warning for ext-pdo_mysql and similar + * Updated to latest xdebug-handler + ### [1.8.1] 2019-01-29 * Deprecated support for non-standard package names (anything with uppercase, or no / in it). Make sure to follow the warnings if you see any to avoid problems in 2.0. From 85617aa74061000bb474b44f629bf8c3be337ee4 Mon Sep 17 00:00:00 2001 From: Hans-Christian Otto Date: Tue, 29 Jan 2019 17:18:58 +0100 Subject: [PATCH 417/580] Installer: Initialize $nameMatchesRequiredPackage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We had a case where we got `Undefined variable: nameMatchesRequiredPackage` — I think it should be initialized with false, right? --- src/Composer/Installer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f38086611..8573f3695 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1317,6 +1317,7 @@ class Installer foreach ($this->updateWhitelist as $packageName => $void) { $packageQueue = new \SplQueue; + $nameMatchesRequiredPackage = false; $depPackages = $pool->whatProvides($packageName); $matchesByPattern = array(); From 1b196720bf451774f65a55e35b2298b9ef5482a4 Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Tue, 22 Jan 2019 13:45:25 -0800 Subject: [PATCH 418/580] Support identifying the HHVM version when not running with HHVM hhvm-nightly (and the next release) are no longer able to execute Composer. Support executing Composer with PHP to install dependencies for hack projects. The goal is for this to be temporary, until Hack identifies a new package manager, given that Composer does not aim to be a multi-language package manager. fixes #7734 --- .../Repository/PlatformRepository.php | 29 ++++++-- .../Repository/PlatformRepositoryTest.php | 69 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/Composer/Test/Repository/PlatformRepositoryTest.php diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 221f2eb97..56dc6ced5 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -16,8 +16,11 @@ use Composer\Package\CompletePackage; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginInterface; +use Composer\Util\ProcessExecutor; use Composer\Util\Silencer; +use Composer\Util\Platform; use Composer\XdebugHandler\XdebugHandler; +use Symfony\Component\Process\ExecutableFinder; /** * @author Jordi Boggiano @@ -37,8 +40,11 @@ class PlatformRepository extends ArrayRepository */ private $overrides = array(); - public function __construct(array $packages = array(), array $overrides = array()) + private $process; + + public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null) { + $this->process = $process === null ? (new ProcessExecutor()) : $process; foreach ($overrides as $name => $version) { $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version); } @@ -220,12 +226,27 @@ class PlatformRepository extends ArrayRepository $this->addPackage($lib); } - if (defined('HHVM_VERSION')) { + $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if ($hhvmVersion === null && !Platform::isWindows()) { + $finder = new ExecutableFinder(); + $hhvm = $finder->find('hhvm'); + if ($hhvm !== null) { + $exitCode = $this->process->execute( + ProcessExecutor::escape($hhvm). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + $hhvmVersion + ); + if ($exitCode !== 0) { + $hhvmVersion = null; + } + } + } + if ($hhvmVersion) { try { - $prettyVersion = HHVM_VERSION; + $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { - $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', HHVM_VERSION); + $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', $hhvmVersion); $version = $this->versionParser->normalize($prettyVersion); } diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php new file mode 100644 index 000000000..90674d4a3 --- /dev/null +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Repository; + +use Composer\Repository\PlatformRepository; +use Composer\Test\TestCase; +use Composer\Util\Platform; +use Symfony\Component\Process\ExecutableFinder; + +class PlatformRepositoryTest extends TestCase { + public function testHHVMVersionWhenExecutingInHHVM() { + if (!defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('Not running with HHVM'); + return; + } + $repository = new PlatformRepository(); + $package = $repository->findPackage('hhvm', '*'); + $this->assertNotNull($package, 'failed to find HHVM package'); + $this->assertSame( + sprintf('%d.%d.%d', + HHVM_VERSION_ID / 10000, + (HHVM_VERSION_ID / 100) % 100, + HHVM_VERSION_ID % 100 + ), + $package->getPrettyVersion() + ); + } + + public function testHHVMVersionWhenExecutingInPHP() { + if (defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('Running with HHVM'); + return; + } + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('Test only works on PHP 5.4+'); + return; + } + if (Platform::isWindows()) { + $this->markTestSkipped('Test does not run on Windows'); + return; + } + $hhvm = (new ExecutableFinder())->find('hhvm'); + if ($hhvm === null) { + $this->markTestSkipped('HHVM is not installed'); + } + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $process->expects($this->once())->method('execute')->will($this->returnCallback( + function($command, &$out) { + $this->assertContains('HHVM_VERSION', $command); + $out = '4.0.1-dev'; + return 0; + } + )); + $repository = new PlatformRepository(array(), array(), $process); + $package = $repository->findPackage('hhvm', '*'); + $this->assertNotNull($package, 'failed to find HHVM package'); + $this->assertSame('4.0.1.0-dev', $package->getVersion()); + } +} From 6d58b13ee4aef653529d455f2e0f416f72261c28 Mon Sep 17 00:00:00 2001 From: Will Otterburn Date: Tue, 29 Jan 2019 18:03:03 +0000 Subject: [PATCH 419/580] Update installer commit to fix SHA384 bug Installer linked is affected by #7669 --- doc/faqs/how-to-install-composer-programmatically.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faqs/how-to-install-composer-programmatically.md b/doc/faqs/how-to-install-composer-programmatically.md index 8a35e34d7..ba6536e54 100644 --- a/doc/faqs/how-to-install-composer-programmatically.md +++ b/doc/faqs/how-to-install-composer-programmatically.md @@ -35,7 +35,7 @@ give it uniqueness and authenticity as long as you can trust the GitHub servers. For example: ```bash -wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet +wget https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer -O - -q | php -- --quiet ``` You may replace the commit hash by whatever the last commit hash is on From b3182b0f7dd84f36e7d8e57dc6316bb8a937871c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 30 Jan 2019 08:31:28 +0100 Subject: [PATCH 420/580] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27eaacc67..db9890bda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### [1.8.3] 2019-01-30 + + * Fixed regression when executing partial updates + ### [1.8.2] 2019-01-29 * Fixed invalid deprecation warning for ext-pdo_mysql and similar From 19ba2edd5c9e86a5786245e6349e979e5ca380d1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 30 Jan 2019 08:58:38 +0100 Subject: [PATCH 421/580] Add warning/info msg when tweaking disable-tls setting to avoid confusion, fixes #7935 --- src/Composer/Command/ConfigCommand.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index d6fe2844f..49cb138d8 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -456,6 +456,10 @@ EOT ); if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { + if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } + return $this->configSource->removeConfigSetting($settingKey); } if (isset($uniqueConfigValues[$settingKey])) { @@ -640,7 +644,17 @@ EOT )); } - return call_user_func(array($this->configSource, $method), $key, $normalizer($values[0])); + $normalizedValue = $normalizer($values[0]); + + if ($key === 'disable-tls') { + if (!$normalizedValue && $this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); + } elseif ($normalizedValue && !$this->config->get('disable-tls')) { + $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); + } + } + + return call_user_func(array($this->configSource, $method), $key, $normalizedValue); } protected function handleMultiValue($key, array $callbacks, array $values, $method) From e085a72f6450a83d173f0eaa7399158a7f5fd686 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Thu, 31 Jan 2019 11:20:17 +0000 Subject: [PATCH 422/580] Fix mode bitmask when detecting a Windows junction --- src/Composer/Util/Filesystem.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index ebb7dfbd3..3a8f523fc 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -648,6 +648,10 @@ class Filesystem /** * Returns whether the target directory is a Windows NTFS Junction. * + * We test if the path is a directory and not an ordinary link, then check + * that the mode value returned from lstat (which gives the status of the + * link itself) is not a directory. + * * @param string $junction Path to check. * @return bool */ @@ -659,22 +663,14 @@ class Filesystem if (!is_dir($junction) || is_link($junction)) { return false; } - /** - * According to MSDN at https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx we can detect a junction now - * using the 'mode' value from stat: "The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit - * is set if path specifies an ordinary file or a device." We have just tested for a directory above, so if - * we have a directory that isn't one according to lstat(...) we must have a junction. - * - * #define _S_IFDIR 0x4000 - * #define _S_IFREG 0x8000 - * - * Stat cache should be cleared before to avoid accidentally reading wrong information from previous installs. - */ + + // Important to clear cache first clearstatcache(true, $junction); clearstatcache(false); $stat = lstat($junction); - return !($stat['mode'] & 0xC000); + // S_IFDIR is 0x4000, S_IFMT is the 0xF000 bitmask + return $stat ? 0x4000 !== ($stat['mode'] & 0xF000) : false; } /** From d1ce9f6246f797b4c65f47e4c156fdb064b9f8b0 Mon Sep 17 00:00:00 2001 From: Arnout Boks Date: Wed, 30 Jan 2019 14:18:06 +0100 Subject: [PATCH 423/580] Fix defaultRepos fallback does not use auth config When a full 'composer' cannot be constructed (because there is no local composer.json and no global composer.json), some commands (e.g. `show -a`) fall back to the default repositories from the `$COMPOSER_HOME/config.json` file. Without this fix, any auth configuration from `$COMPOSER_HOME/auth.json` is not used for these repositories in such a fallback scenario. Steps to reproduce: * Configure a password-protected composer repository in `$COMPOSER_HOME/config.json`. * Configure valid credentials for that repository in `$COMPOSER_HOME/auth.json`. * Make sure there is no file `$COMPOSER_HOME/composer.json`. * Ensure the current working directory has no `composer.json`. * Run `composer show -a some/package`. Expected: Information about `some/package` is shown without needing to enter credentials. Actual: A prompt "Authentication required" is shown for the private repository. When running the same command in a dir that has a `composer.json`, or when `$COMPOSER_HOME/composer.json` exists, things work as expected. --- src/Composer/Repository/RepositoryFactory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index ca479a7fd..3c7e837c3 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -93,6 +93,7 @@ class RepositoryFactory { if (!$config) { $config = Factory::createConfig($io); + $io->loadConfiguration($config); } if (!$rm) { if (!$io) { From e151a6c51c08e638d271c64b5384cbbd5ecc6333 Mon Sep 17 00:00:00 2001 From: Arnout Boks Date: Thu, 31 Jan 2019 09:37:28 +0100 Subject: [PATCH 424/580] Only load configuration into IO if IO is available --- src/Composer/Repository/RepositoryFactory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 3c7e837c3..d8e917147 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -93,7 +93,9 @@ class RepositoryFactory { if (!$config) { $config = Factory::createConfig($io); - $io->loadConfiguration($config); + if ($io) { + $io->loadConfiguration($config); + } } if (!$rm) { if (!$io) { From 82b010782d1c1a3553862621d4eae83e31cdb3d1 Mon Sep 17 00:00:00 2001 From: Arnout Boks Date: Thu, 31 Jan 2019 13:38:20 +0100 Subject: [PATCH 425/580] Also load config into IO if not freshly created --- src/Composer/Repository/RepositoryFactory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index d8e917147..5d3a2a61f 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -93,9 +93,9 @@ class RepositoryFactory { if (!$config) { $config = Factory::createConfig($io); - if ($io) { - $io->loadConfiguration($config); - } + } + if ($io) { + $io->loadConfiguration($config); } if (!$rm) { if (!$io) { From bac2ef3dfdfe60cec4c1cea7371f776fa6073d0a Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Fri, 1 Feb 2019 11:20:34 -0800 Subject: [PATCH 426/580] Don't do (new Foo())->bar() - not 5.3-compatible --- tests/Composer/Test/Repository/PlatformRepositoryTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index 90674d4a3..aa51a2fc6 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -49,7 +49,8 @@ class PlatformRepositoryTest extends TestCase { $this->markTestSkipped('Test does not run on Windows'); return; } - $hhvm = (new ExecutableFinder())->find('hhvm'); + $finder = new ExecutableFinder(); + $hhvm = $finder->find('hhvm'); if ($hhvm === null) { $this->markTestSkipped('HHVM is not installed'); } From 6b2edeae56d83b84ba60957aee18c314257d98fa Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 3 Feb 2019 01:53:17 +0100 Subject: [PATCH 427/580] Fix solver problem exceptions with unexpected contradictory "Conclusions" This 5 character fix comes with a solver test as well as a functional installer test essentially verifying the same thing. The solver test is more useful when working on the solver. But the functional test is less likely to be accidentally modified incorrectly during refactoring, as every single package, version and link in the rather complex test scenario is essential, and a modified version of the test may very well still result in a successful installation but no longer verify the bug described below. Background: In commit 451bab1c2cd58e05af6e21639b829408ad023463 from May 19, 2012 I refactored literals from complex objects into pure integers to reduce memory consumption. The absolute value of an integer literal is the id of the package it refers to in the package pool. The sign indicates whether the package should be installed (positive) or removed (negative), So a major part of the refactoring was swapping this call: $literal->getPackageId() For this: abs($literal) Unintentionally in line 554/523 I incorrectly applied this change to the line: $this->literalFromId(-$literal->getPackageId()); It was converted to: -abs($literal); The function literalFromId used to create a new literal object. By using the abs() function this change essentially forces the resulting literal to be negative, while the minus sign previously inverted the literal, so positive into negative and vice versa. This particular line is in a function meant to analyze a conflicting decision during dependency resolution and to draw a conclusion from it, then revert the state of the solver to an earlier position, and attempt to solve the rest of the rules again with this new "learned" conclusion. Because of this bug these conclusions could only ever occur in the negative, e.g. "don't install package X". This is by far the most likely scenario when the solver reaches this particular line, but there are exceptions. If you experienced a solver problem description that contained a statement like "Conclusion: don't install vendor/package 1.2.3" which directly contradicted other statements listed as part of the problem, this could likely have been the cause. --- src/Composer/DependencyResolver/Decisions.php | 12 +++ src/Composer/DependencyResolver/Solver.php | 2 +- .../Test/DependencyResolver/SolverTest.php | 64 +++++++++++ ...everts-and-learning-positive-literals.test | 100 ++++++++++++++++++ 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/update-requiring-decision-reverts-and-learning-positive-literals.test diff --git a/src/Composer/DependencyResolver/Decisions.php b/src/Composer/DependencyResolver/Decisions.php index a9808e60e..86b62c3d3 100644 --- a/src/Composer/DependencyResolver/Decisions.php +++ b/src/Composer/DependencyResolver/Decisions.php @@ -196,4 +196,16 @@ class Decisions implements \Iterator, \Countable $this->decisionMap[$packageId] = -$level; } } + + public function __toString() + { + $decisionMap = $this->decisionMap; + ksort($decisionMap); + $str = '['; + foreach ($decisionMap as $packageId => $level) { + $str .= $packageId.':'.$level.','; + } + $str .= ']'; + return $str; + } } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 1ed35ad9c..c40789c1f 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -470,7 +470,7 @@ class Solver unset($seen[abs($literal)]); if ($num && 0 === --$num) { - $learnedLiterals[0] = -abs($literal); + $learnedLiterals[0] = -$literal; if (!$l1num) { break 2; diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 24147e6ad..7094f412d 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -838,6 +838,70 @@ class SolverTest extends TestCase )); } + /** + * Tests for a bug introduced in commit 451bab1c2cd58e05af6e21639b829408ad023463 Solver.php line 554/523 + * + * Every package and link in this test matters, only a combination this complex will run into the situation in which + * a negatively decided literal will need to be learned inverted as a positive assertion. + * + * In particular in this case the goal is to first have the solver decide X 2.0 should not be installed to later + * decide to learn that X 2.0 must be installed and revert decisions to retry solving with this new assumption. + */ + public function testLearnPositiveLiteral() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageC1 = $this->getPackage('C', '1.0')); + $this->repo->addPackage($packageC2 = $this->getPackage('C', '2.0')); + $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); + $this->repo->addPackage($packageE = $this->getPackage('E', '1.0')); + $this->repo->addPackage($packageF1 = $this->getPackage('F', '1.0')); + $this->repo->addPackage($packageF2 = $this->getPackage('F', '2.0')); + $this->repo->addPackage($packageG1 = $this->getPackage('G', '1.0')); + $this->repo->addPackage($packageG2 = $this->getPackage('G', '2.0')); + $this->repo->addPackage($packageG3 = $this->getPackage('G', '3.0')); + + $packageA->setRequires(array( + 'b' => new Link('A', 'B', $this->getVersionConstraint('==', '1.0'), 'requires'), + 'c' => new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'), + 'd' => new Link('A', 'D', $this->getVersionConstraint('==', '1.0'), 'requires'), + )); + + $packageB->setRequires(array( + 'e' => new Link('B', 'E', $this->getVersionConstraint('==', '1.0'), 'requires'), + )); + + $packageC1->setRequires(array( + 'f' => new Link('C', 'F', $this->getVersionConstraint('==', '1.0'), 'requires'), + )); + $packageC2->setRequires(array( + 'f' => new Link('C', 'F', $this->getVersionConstraint('==', '1.0'), 'requires'), + 'g' => new Link('C', 'G', $this->getVersionConstraint('>=', '1.0'), 'requires'), + )); + + $packageD->setRequires(array( + 'f' => new Link('D', 'F', $this->getVersionConstraint('>=', '1.0'), 'requires'), + )); + + $packageE->setRequires(array( + 'g' => new Link('E', 'G', $this->getVersionConstraint('<=', '2.0'), 'requires'), + )); + + $this->reposComplete(); + + $this->request->install('A'); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageF1), + array('job' => 'install', 'package' => $packageD), + array('job' => 'install', 'package' => $packageG2), + array('job' => 'install', 'package' => $packageC2), + array('job' => 'install', 'package' => $packageE), + array('job' => 'install', 'package' => $packageB), + array('job' => 'install', 'package' => $packageA), + )); + } + protected function reposComplete() { $this->pool->addRepository($this->repoInstalled); diff --git a/tests/Composer/Test/Fixtures/installer/update-requiring-decision-reverts-and-learning-positive-literals.test b/tests/Composer/Test/Fixtures/installer/update-requiring-decision-reverts-and-learning-positive-literals.test new file mode 100644 index 000000000..3f5667823 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-requiring-decision-reverts-and-learning-positive-literals.test @@ -0,0 +1,100 @@ +--TEST-- +Update a project which requires decision reverts and learning a positive literal to arrive at a correct solution. + +Tests for solver regression in commit 451bab1c2cd58e05af6e21639b829408ad023463. See also SolverTest testLearnPositiveLiteral +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "spryker-feature/product", + "require": { + "spryker-feature/spryker-core": "1.0.0", + "spryker-shop/product-search-widget": ">=1.0.0", + "spryker/product-category-filter-gui": "1.0.0" + }, + "version": "1.0.0" + }, + { + "name": "spryker-feature/spryker-core", + "version": "1.0.0", + "require": { + "spryker/store": "1.0.0" + } + }, + { + "name": "spryker/store", + "version": "1.0.0", + "require": { + "spryker/kernel": "<=2.0.0" + } + }, + { + "name": "spryker-shop/product-search-widget", + "version": "1.0.0", + "require": { + "spryker/catalog": "1.0.0" + } + }, + { + "name": "spryker-shop/product-search-widget", + "version": "2.0.0", + "require": { + "spryker/catalog": "1.0.0", + "spryker/kernel": ">=1.0.0" + } + }, + { + "name": "spryker/product-category-filter-gui", + "version": "1.0.0", + "require": { + "spryker/catalog": ">=1.0.0" + } + }, + { + "name": "spryker/catalog", + "version": "1.0.0", + "require": { } + }, + { + "name": "spryker/catalog", + "version": "2.0.0", + "require": { } + }, + + { + "name": "spryker/kernel", + "version": "1.0.0", + "require": { } + }, + { + "name": "spryker/kernel", + "version": "2.0.0", + "require": { + } + }, + { + "name": "spryker/kernel", + "version": "3.0.0", + "require": { } + } + ] + } + ], + "require": { + "spryker-feature/product": "1.0.0" + } +} +--RUN-- +update +--EXPECT-- +Installing spryker/catalog (1.0.0) +Installing spryker/product-category-filter-gui (1.0.0) +Installing spryker/kernel (2.0.0) +Installing spryker-shop/product-search-widget (2.0.0) +Installing spryker/store (1.0.0) +Installing spryker-feature/spryker-core (1.0.0) +Installing spryker-feature/product (1.0.0) + From 3b117da6d792ceae88b7dc894dfbe5b2f051c9b2 Mon Sep 17 00:00:00 2001 From: JakeConnors376W <46732192+JakeConnors376W@users.noreply.github.com> Date: Mon, 4 Feb 2019 15:59:50 -0800 Subject: [PATCH 428/580] Fix inconsistent casing --- doc/01-basic-usage.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index ffcb1589e..4981542e8 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -9,13 +9,13 @@ a logging library. If you have not yet installed Composer, refer to the > **Note:** for the sake of simplicity, this introduction will assume you > have performed a [local](00-intro.md#locally) install of Composer. -## `composer.json`: Project Setup +## `composer.json`: Project setup To start using Composer in your project, all you need is a `composer.json` file. This file describes the dependencies of your project and may contain other metadata as well. -### The `require` Key +### The `require` key The first (and often only) thing you specify in `composer.json` is the [`require`](04-schema.md#require) key. You are simply telling Composer which @@ -41,7 +41,7 @@ assumed that the `monolog/monolog` package is registered on Packagist. (See more about Packagist [below](#packagist), or read more about repositories [here](05-repositories.md)). -### Package Names +### Package names The package name consists of a vendor name and the project's name. Often these will be identical - the vendor name only exists to prevent naming clashes. For @@ -53,7 +53,7 @@ Read more about publishing packages and package naming [here](02-libraries.md). you to require certain versions of server software. See [platform packages](#platform-packages) below.) -### Package Version Constraints +### Package version constraints In our example, we are requesting the Monolog package with the version constraint [`1.0.*`](https://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*). @@ -84,7 +84,7 @@ versions, how versions relate to each other, and on version constraints. > versions of a package. Read more about stability flags and the `minimum-stability` > key on the [schema page](04-schema.md). -## Installing Dependencies +## Installing dependencies To install the defined dependencies for your project, run the [`install`](03-cli.md#install) command. @@ -95,7 +95,7 @@ php composer.phar install When you run this command, one of two things may happen: -### Installing Without `composer.lock` +### Installing without `composer.lock` If you have never run the command before and there is also no `composer.lock` file present, Composer simply resolves all dependencies listed in your `composer.json` file and downloads @@ -114,7 +114,7 @@ of them that it downloaded to the `composer.lock` file, locking the project to t versions. You should commit the `composer.lock` file to your project repo so that all people working on the project are locked to the same versions of dependencies (more below). -### Installing With `composer.lock` +### Installing with `composer.lock` This brings us to the second scenario. If there is already a `composer.lock` file as well as a `composer.json` file when you run `composer install`, it means either you ran the @@ -130,7 +130,7 @@ working on your project. As a result you will have all dependencies requested by the file was created). This is by design, it ensures that your project does not break because of unexpected changes in dependencies. -### Commit Your `composer.lock` File to Version Control +### Commit your `composer.lock` file to version control Committing this file to VC is important because it will cause anyone who sets up the project to use the exact same @@ -142,7 +142,7 @@ reinstalling the project you can feel confident the dependencies installed are still working even if your dependencies released many new versions since then. (See note below about using the `update` command.) -## Updating Dependencies to their Latest Versions +## Updating dependencies to their latest versions As mentioned above, the `composer.lock` file prevents you from automatically getting the latest versions of your dependencies. To update to the latest versions, use the From 17788c76f635f0f4ff43e19e672e651fc144faf4 Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Wed, 6 Feb 2019 12:51:30 -0800 Subject: [PATCH 429/580] Better error message for present but incompatible versions hhvm-nightly (and next week's release) now report 4.x, so all the 3.x constraints are now giving misleading error messages with this patch. Before: ``` - facebook/fbexpect v2.3.0 requires hhvm ^3.28 -> you are running this with PHP and not HHVM. ``` After: ``` - facebook/fbexpect v2.3.0 requires hhvm ^3.28 -> your HHVM version (4.0.0-dev) does not satisfy that requirement. ``` --- src/Composer/DependencyResolver/Rule.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 4760b8964..82c9c499c 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -175,13 +175,18 @@ abstract class Rule return $text . ' -> your HHVM version does not satisfy that requirement.'; } - if ($targetName === 'hhvm') { - return $text . ' -> you are running this with PHP and not HHVM.'; - } - $packages = $pool->whatProvides($targetName); $package = count($packages) ? current($packages) : phpversion(); + if ($targetName === 'hhvm') { + if ($package instanceof CompletePackage) { + return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.'; + } else { + return $text . ' -> you are running this with PHP and not HHVM.'; + } + } + + if (!($package instanceof CompletePackage)) { return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.'; } From 41c7f4d2bfedcd7266e854240e2ac22e582473c3 Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Wed, 6 Feb 2019 13:11:04 -0800 Subject: [PATCH 430/580] Same but for Problem.php --- src/Composer/DependencyResolver/Problem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index de24b0991..073f64e2d 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -106,7 +106,7 @@ class Problem $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; - if (defined('HHVM_VERSION')) { + if (defined('HHVM_VERSION') || count($available)) { return $msg . 'your HHVM version does not satisfy that requirement.'; } From f89f9439e4a24013cb48c20fa84e96f00ae280b2 Mon Sep 17 00:00:00 2001 From: Marc Wilhelm Date: Thu, 7 Feb 2019 17:26:17 +0100 Subject: [PATCH 431/580] Update aliases.md Add composer command for alias. --- doc/articles/aliases.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index 98a6d1335..d36eb11ff 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -89,6 +89,12 @@ Add this to your project's root `composer.json`: } ``` +Or let composer add it for you with: + +``` +php composer.phar require monolog/monolog:"dev-bugfix as 1.0.x-dev" +``` + That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub and alias it to `1.0.x-dev`. From f4b9bbbf42064548f77a77d602ff29ce06bd2f10 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 4 Feb 2019 12:02:05 +0000 Subject: [PATCH 432/580] Make unixy proxy code POSIX compatible --- src/Composer/Installer/BinaryInstaller.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 0f709f60b..0a7b97149 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -196,9 +196,13 @@ class BinaryInstaller dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd) -if [ -d /proc/cygdrive ] && [[ \$(which php) == \$(readlink -n /proc/cygdrive)/* ]]; then - # We are in Cgywin using Windows php, so the path must be translated - dir=\$(cygpath -m "\$dir"); +if [ -d /proc/cygdrive ]; then + case \$(which php) in + \$(readlink -n /proc/cygdrive)/*) + # We are in Cygwin using Windows php, so the path must be translated + dir=\$(cygpath -m "\$dir"); + ;; + esac fi "\${dir}/$binFile" "\$@" From eee98018f72903424d6d2a5d5bfd26baf3a1a13f Mon Sep 17 00:00:00 2001 From: Michael Telgmann Date: Thu, 7 Feb 2019 16:24:40 +0100 Subject: [PATCH 433/580] Soften hard exit after revert of composer file --- src/Composer/Command/RequireCommand.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 1f29751b9..b347de094 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -195,7 +195,7 @@ EOT $status = $install->run(); if ($status !== 0) { - $this->revertComposerFile(); + $this->revertComposerFile(false); } return $status; @@ -226,7 +226,7 @@ EOT return; } - public function revertComposerFile() + public function revertComposerFile($hardExit = true) { $io = $this->getIO(); @@ -238,6 +238,8 @@ EOT file_put_contents($this->json->getPath(), $this->composerBackup); } - exit(1); + if ($hardExit) { + exit(1); + } } } From 408df4b87804503dc2c8861256dd303055652089 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 10 Feb 2019 12:39:00 +0100 Subject: [PATCH 434/580] Avoid dumping null values for dist reference/shasum and source reference, fixes #7955 --- src/Composer/Package/Dumper/ArrayDumper.php | 12 +++++++++--- src/Composer/Package/Loader/ArrayLoader.php | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index 6593143d5..b1e20dbf5 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -48,7 +48,9 @@ class ArrayDumper if ($package->getSourceType()) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); - $data['source']['reference'] = $package->getSourceReference(); + if (null !== ($value = $package->getSourceReference())) { + $data['source']['reference'] = $value; + } if ($mirrors = $package->getSourceMirrors()) { $data['source']['mirrors'] = $mirrors; } @@ -57,8 +59,12 @@ class ArrayDumper if ($package->getDistType()) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); - $data['dist']['reference'] = $package->getDistReference(); - $data['dist']['shasum'] = $package->getDistSha1Checksum(); + if (null !== ($value = $package->getDistReference())) { + $data['dist']['reference'] = $value; + } + if (null !== ($value = $package->getDistSha1Checksum())) { + $data['dist']['shasum'] = $value; + } if ($mirrors = $package->getDistMirrors()) { $data['dist']['mirrors'] = $mirrors; } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 303cc3c13..c269afa22 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -85,7 +85,7 @@ class ArrayLoader implements LoaderInterface } $package->setSourceType($config['source']['type']); $package->setSourceUrl($config['source']['url']); - $package->setSourceReference($config['source']['reference']); + $package->setSourceReference(isset($config['source']['reference']) ? $config['source']['reference'] : null); if (isset($config['source']['mirrors'])) { $package->setSourceMirrors($config['source']['mirrors']); } From 4de1f021f59a4565f362712558650de4d94e363e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 10 Feb 2019 12:40:11 +0100 Subject: [PATCH 435/580] Quote wildcards to avoid issues in some shells, fixes #7960 --- doc/03-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index c3faffe34..6460e9e1d 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -138,7 +138,7 @@ php composer.phar update vendor/package vendor/package2 You can also use wildcards to update a bunch of packages at once: ```sh -php composer.phar update vendor/* +php composer.phar update "vendor/*" ``` ### Options From e1ac0c794880e269f8600d21149d8a4c5dfa0ed5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 10 Feb 2019 12:49:29 +0100 Subject: [PATCH 436/580] Recognize composer-plugin-api as a platform package, fixes #7951 --- src/Composer/Repository/PlatformRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 56dc6ced5..50cbb4649 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -27,7 +27,7 @@ use Symfony\Component\Process\ExecutableFinder; */ class PlatformRepository extends ArrayRepository { - const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*)$}iD'; + const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-plugin-api)$}iD'; private $versionParser; From 94df55425596c0137d0aa14485bba2fb05e15de6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 10 Feb 2019 12:56:43 +0100 Subject: [PATCH 437/580] Make sure config command output is also output on --quiet so that warnings can be hidden, fixes #7963 --- src/Composer/Command/ConfigCommand.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index b002fd3a7..c2347d306 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Composer\Config; use Composer\Config\JsonConfigSource; use Composer\Factory; +use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Semver\VersionParser; use Composer\Package\BasePackage; @@ -284,7 +285,7 @@ EOT $value = json_encode($value); } - $this->getIO()->write($value); + $this->getIO()->write($value, true, IOInterface::QUIET); return 0; } @@ -695,9 +696,9 @@ EOT } if (is_string($rawVal) && $rawVal != $value) { - $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); + $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')', true, IOInterface::QUIET); } else { - $io->write('[' . $k . $key . '] ' . $value . ''); + $io->write('[' . $k . $key . '] ' . $value . '', true, IOInterface::QUIET); } } } From c66bb0b1d06454cb73e6cd65b6be470417fae5ff Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 10 Feb 2019 13:07:42 +0100 Subject: [PATCH 438/580] Fix tests --- .../Fixtures/installer/install-dev-using-dist.test | 3 +-- .../Fixtures/installer/update-changes-url.test | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test b/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test index ccc9ead1a..5846d13c0 100644 --- a/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test +++ b/tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test @@ -35,8 +35,7 @@ install --prefer-dist "dist": { "type": "zip", "url": "http://www.example.com/dist.zip", - "reference": "459720ff3b74ee0c0d159277c6f2f5df89d8a4f6", - "shasum": null + "reference": "459720ff3b74ee0c0d159277c6f2f5df89d8a4f6" }, "type": "library" } diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index 70294c8e6..d774ea188 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -101,43 +101,43 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "a/a", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip", "shasum": null }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }, "type": "library" }, { "name": "b/b", "version": "2.0.3", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip", "shasum": null }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" }, "type": "library" }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/newc", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": null }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { "name": "d/d", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/newd", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": null }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { "name": "e/e", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/e/newe", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": null }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": null }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { "name": "g/g", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/g/newg", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": null }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" } ], From da0dc74414fd8e6eca65619a6d9c1dd1a04facc6 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sun, 10 Feb 2019 14:41:20 +0000 Subject: [PATCH 439/580] Update doc block, remove redundant clearstatcache --- src/Composer/Util/Filesystem.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 3a8f523fc..2ebc434b7 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -652,6 +652,11 @@ class Filesystem * that the mode value returned from lstat (which gives the status of the * link itself) is not a directory. * + * This relies on the fact that PHP does not set this value because there is + * no universal file type flag for a junction or a mount point. However a + * bug in PHP can cause a random value to be returned and this could result + * in a junction not being detected: https://bugs.php.net/bug.php?id=77552 + * * @param string $junction Path to check. * @return bool */ @@ -664,9 +669,8 @@ class Filesystem return false; } - // Important to clear cache first + // Important to clear all caches first clearstatcache(true, $junction); - clearstatcache(false); $stat = lstat($junction); // S_IFDIR is 0x4000, S_IFMT is the 0xF000 bitmask From 29ff6a40ae2e64d52b444329ce16cf6b10eaf652 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 10 Feb 2019 20:26:47 +0100 Subject: [PATCH 440/580] Follow up to #7946 test: add solver flag to assert path execution --- src/Composer/DependencyResolver/Solver.php | 6 ++++++ tests/Composer/Test/DependencyResolver/SolverTest.php | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index c40789c1f..dbfafd243 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -57,6 +57,9 @@ class Solver /** @var array */ protected $learnedWhy = array(); + /** @var bool */ + public $testFlagLearnedPositiveLiteral = false; + /** @var IOInterface */ protected $io; @@ -470,6 +473,9 @@ class Solver unset($seen[abs($literal)]); if ($num && 0 === --$num) { + if ($literal < 0) { + $this->testFlagLearnedPositiveLiteral = true; + } $learnedLiterals[0] = -$literal; if (!$l1num) { diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 7094f412d..4dadee7d8 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -891,6 +891,9 @@ class SolverTest extends TestCase $this->request->install('A'); + // check correct setup for assertion later + $this->assertFalse($this->solver->testFlagLearnedPositiveLiteral); + $this->checkSolverResult(array( array('job' => 'install', 'package' => $packageF1), array('job' => 'install', 'package' => $packageD), @@ -900,6 +903,10 @@ class SolverTest extends TestCase array('job' => 'install', 'package' => $packageB), array('job' => 'install', 'package' => $packageA), )); + + // verify that the code path leading to a negative literal resulting in a positive learned literal is actually + // executed + $this->assertTrue($this->solver->testFlagLearnedPositiveLiteral); } protected function reposComplete() From a5cd912c02b94e8f5c97a0506eda456d47286a37 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Feb 2019 10:51:32 +0100 Subject: [PATCH 441/580] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db9890bda..36ceddfc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +### [1.8.4] 2019-02-11 + + * Fixed long standing solver bug leading to odd solving issues in edge cases, see #7946 + * Fixed HHVM support for upcoming releases + * Fixed unix proxy for binaries to be POSIX compatible instead of breaking some shells + * Fixed invalid deprecation warning for composer-plugin-api + * Fixed edge case issues with Windows junctions when working with path repositories + ### [1.8.3] 2019-01-30 * Fixed regression when executing partial updates @@ -729,6 +737,10 @@ * Initial release +[1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4 +[1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3 +[1.8.2]: https://github.com/composer/composer/compare/1.8.1...1.8.2 +[1.8.1]: https://github.com/composer/composer/compare/1.8.0...1.8.1 [1.8.0]: https://github.com/composer/composer/compare/1.7.3...1.8.0 [1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3 [1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2 From c903a63f100cd6a5afbbcbdca4bb8fceae2261fd Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 28 Jan 2019 21:18:26 +0000 Subject: [PATCH 442/580] Update appveyor to PHP 7.3 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e7c20c9e6..cb0f3d33c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ clone_depth: 5 environment: # This sets the PHP version (from Chocolatey) - PHPCI_CHOCO_VERSION: 7.2.9 + PHPCI_CHOCO_VERSION: 7.3.1 PHPCI_CACHE: C:\tools\phpci PHPCI_PHP: C:\tools\phpci\php PHPCI_COMPOSER: C:\tools\phpci\composer From d1cf69fa922d6a46cad283e9d347761175ee0b0a Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sun, 10 Feb 2019 19:04:58 +0000 Subject: [PATCH 443/580] Remove junctions with PHP rather than system rmdir PHP will happily remove junctions using its `rmdir` function (tested on versions back to 5.2.17). This saves invoking system `rmdir` through cmd.exe. --- src/Composer/Util/Filesystem.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 2ebc434b7..8b0546f3a 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -692,9 +692,7 @@ class Filesystem if (!$this->isJunction($junction)) { throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); } - $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape($junction)); - clearstatcache(true, $junction); - return ($this->getProcess()->execute($cmd, $output) === 0); + return $this->rmdir($junction); } } From 6212eadcb07349ed946fc8e9a7698f49d20084b7 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 11 Feb 2019 21:30:56 +0000 Subject: [PATCH 444/580] Only use junctions if they can be safely removed --- src/Composer/Downloader/PathDownloader.php | 30 ++++++++++++++++++++++ src/Composer/Util/Filesystem.php | 17 +++++++----- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index e7084bd97..a63a3d82d 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -82,6 +82,12 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $allowedStrategies = array(self::STRATEGY_MIRROR); } + // Check we can use junctions safely if we are on Windows + if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) { + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = array(self::STRATEGY_MIRROR); + } + $fileSystem = new Filesystem(); $this->filesystem->removeDirectory($path); @@ -172,4 +178,28 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter return $packageVersion['commit']; } } + + /** + * Returns true if junctions can be safely used on Windows + * + * A PHP bug makes junction detection fragile, leading to possible data loss + * when removing a package. See https://bugs.php.net/bug.php?id=77552 + * + * For safety we require a minimum version of Windows 7, so we can call the + * system rmdir which can detect junctions and not delete target content. + * + * @return bool + */ + private function safeJunctions() + { + // Bug fixed in 7.3.3 and 7.2.16 + if (PHP_VERSION_ID >= 70303 || (PHP_VERSION_ID >= 70216 && PHP_VERSION_ID < 70300)) { + return true; + } + + // Windows 7 is version 6.1 + return function_exists('proc_open') && + (PHP_WINDOWS_VERSION_MAJOR > 6 || + (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1)); + } } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 8b0546f3a..c4e1b1b12 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -650,12 +650,17 @@ class Filesystem * * We test if the path is a directory and not an ordinary link, then check * that the mode value returned from lstat (which gives the status of the - * link itself) is not a directory. + * link itself) is not a directory, by replicating the POSIX S_ISDIR test. * - * This relies on the fact that PHP does not set this value because there is - * no universal file type flag for a junction or a mount point. However a - * bug in PHP can cause a random value to be returned and this could result - * in a junction not being detected: https://bugs.php.net/bug.php?id=77552 + * This logic works because PHP does not set the mode value for a junction, + * since there is no universal file type flag for it. Unfortunately an + * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a + * random value to be returned. See https://bugs.php.net/bug.php?id=77552 + * + * If this random value passes the S_ISDIR test, then a junction will not be + * detected and a recursive delete operation could lead to loss of data in + * the target directory. Note that Windows rmdir can handle this situation + * and will only delete the junction (from Windows 7 onwards). * * @param string $junction Path to check. * @return bool @@ -673,7 +678,7 @@ class Filesystem clearstatcache(true, $junction); $stat = lstat($junction); - // S_IFDIR is 0x4000, S_IFMT is the 0xF000 bitmask + // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) return $stat ? 0x4000 !== ($stat['mode'] & 0xF000) : false; } From ae9ed20812a2f0d6b2959dc882fcee26746bd361 Mon Sep 17 00:00:00 2001 From: John Stevenson Date: Tue, 12 Feb 2019 12:09:25 +0000 Subject: [PATCH 445/580] Update 05-repositories.md --- doc/05-repositories.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 9cd1dbf28..ba5713aed 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -666,6 +666,10 @@ Instead of default fallback strategy you can force to use symlink with mirroring can be useful when deploying or generating package from a monolithic repository. +> **Note:** On Windows, directory symlinks are implemented using NTFS junctions +> because they can be created by non-admin users. Mirroring will always be used +> on versions below Windows 7 or if `proc_open` has been disabled. + ```json { "repositories": [ From fc2c445c063cc71eafc945e6a19125215b0acb00 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 12 Feb 2019 11:54:23 +0100 Subject: [PATCH 446/580] Make sure we properly usleep() on windows rmdir/unlink usleep() returns void, therefore the previous code didn't work --- src/Composer/Util/Filesystem.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index c4e1b1b12..7c342573b 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -199,9 +199,15 @@ class Filesystem */ public function unlink($path) { - if (!@$this->unlinkImplementation($path)) { + $unlinked = @$this->unlinkImplementation($path); + if (!$unlinked) { // retry after a bit on windows since it tends to be touchy with mass removals - if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) { + if (Platform::isWindows()) { + usleep(350000); + $unlinked = @$this->unlinkImplementation($path); + } + + if (!$unlinked) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; if (Platform::isWindows()) { @@ -224,9 +230,15 @@ class Filesystem */ public function rmdir($path) { - if (!@rmdir($path)) { + $deleted = @rmdir($path); + if (!$deleted) { // retry after a bit on windows since it tends to be touchy with mass removals - if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) { + if (Platform::isWindows()) { + usleep(350000); + $deleted = !@rmdir($path); + } + + if (!$deleted) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . @$error['message']; if (Platform::isWindows()) { From 4cf069535fca7ab64fdfeb6d281e2c98e6f8e259 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Tue, 12 Feb 2019 15:05:37 +0000 Subject: [PATCH 447/580] Improve safe junction logic --- src/Composer/Downloader/PathDownloader.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index a63a3d82d..4c71fb4f4 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -180,24 +180,21 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter } /** - * Returns true if junctions can be safely used on Windows + * Returns true if junctions can be created and safely used on Windows * * A PHP bug makes junction detection fragile, leading to possible data loss * when removing a package. See https://bugs.php.net/bug.php?id=77552 * * For safety we require a minimum version of Windows 7, so we can call the - * system rmdir which can detect junctions and not delete target content. + * system rmdir which will preserve target content if given a junction. + * + * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). * * @return bool */ private function safeJunctions() { - // Bug fixed in 7.3.3 and 7.2.16 - if (PHP_VERSION_ID >= 70303 || (PHP_VERSION_ID >= 70216 && PHP_VERSION_ID < 70300)) { - return true; - } - - // Windows 7 is version 6.1 + // We need to call mklink, and rmdir on Windows 7 (version 6.1) return function_exists('proc_open') && (PHP_WINDOWS_VERSION_MAJOR > 6 || (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1)); From 0aa030f09d3fb47b708e39cffeb7470099010f0c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Feb 2019 07:26:14 +0100 Subject: [PATCH 448/580] Fixed typo introduced in recent fix --- src/Composer/Util/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 7c342573b..1903f1c8d 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -235,7 +235,7 @@ class Filesystem // retry after a bit on windows since it tends to be touchy with mass removals if (Platform::isWindows()) { usleep(350000); - $deleted = !@rmdir($path); + $deleted = @rmdir($path); } if (!$deleted) { From d9f873d00e6670f6802441e21538e0e4704128a2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 16 Feb 2019 17:39:59 +0100 Subject: [PATCH 449/580] Abort when HHVM 4.0 is detected to output a clear user message, refs #7990 --- bin/composer | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/composer b/bin/composer index 5e142662c..3585787f2 100755 --- a/bin/composer +++ b/bin/composer @@ -18,6 +18,11 @@ $xdebug = new XdebugHandler('Composer', '--ansi'); $xdebug->check(); unset($xdebug); +if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) { + echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL; + exit(1); +} + if (function_exists('ini_set')) { @ini_set('display_errors', 1); From 4d85e217c30ebcc67618e0978cd99544f7aeb634 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Sat, 16 Feb 2019 18:46:59 +0100 Subject: [PATCH 450/580] Extract the ZIP utility functions from ArtifactRepository --- .../Repository/ArtifactRepository.php | 66 +------------ src/Composer/Util/Zip.php | 96 +++++++++++++++++++ 2 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 src/Composer/Util/Zip.php diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 079d34c54..223ea4aef 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -16,6 +16,7 @@ use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\LoaderInterface; +use Composer\Util\Zip; /** * @author Serge Smertin @@ -80,73 +81,14 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito } } - /** - * Find a file by name, returning the one that has the shortest path. - * - * @param \ZipArchive $zip - * @param string $filename - * @return bool|int - */ - private function locateFile(\ZipArchive $zip, $filename) - { - $indexOfShortestMatch = false; - $lengthOfShortestMatch = -1; - - for ($i = 0; $i < $zip->numFiles; $i++) { - $stat = $zip->statIndex($i); - if (strcmp(basename($stat['name']), $filename) === 0) { - $directoryName = dirname($stat['name']); - if ($directoryName == '.') { - //if composer.json is in root directory - //it has to be the one to use. - return $i; - } - - if (strpos($directoryName, '\\') !== false || - strpos($directoryName, '/') !== false) { - //composer.json files below first directory are rejected - continue; - } - - $length = strlen($stat['name']); - if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { - //Check it's not a directory. - $contents = $zip->getFromIndex($i); - if ($contents !== false) { - $indexOfShortestMatch = $i; - $lengthOfShortestMatch = $length; - } - } - } - } - - return $indexOfShortestMatch; - } - private function getComposerInformation(\SplFileInfo $file) { - $zip = new \ZipArchive(); - if ($zip->open($file->getPathname()) !== true) { + $composerFile = Zip::findFile($file->getPathname(), 'composer.json'); + + if (null === $composerFile) { return false; } - if (0 == $zip->numFiles) { - $zip->close(); - - return false; - } - - $foundFileIndex = $this->locateFile($zip, 'composer.json'); - if (false === $foundFileIndex) { - $zip->close(); - - return false; - } - - $configurationFileName = $zip->getNameIndex($foundFileIndex); - $zip->close(); - - $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; $json = file_get_contents($composerFile); $package = JsonFile::parseJson($json, $composerFile); diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php new file mode 100644 index 000000000..1dfd99d39 --- /dev/null +++ b/src/Composer/Util/Zip.php @@ -0,0 +1,96 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Jordi Boggiano + */ +class Zip +{ + /** + * Finds the path to a file inside a ZIP archive. + * + * @param $pathToZip + * @param $filename + * + * @return string|null + */ + public static function findFile($pathToZip, $filename) + { + $zip = new \ZipArchive(); + if ($zip->open($pathToZip) !== true) { + return null; + } + + if (0 == $zip->numFiles) { + $zip->close(); + + return null; + } + + $foundFileIndex = static::locateFile($zip, $filename); + if (false === $foundFileIndex) { + $zip->close(); + + return null; + } + + $configurationFileName = $zip->getNameIndex($foundFileIndex); + $zip->close(); + + return "zip://{$pathToZip}#$configurationFileName"; + } + + /** + * Find a file by name, returning the one that has the shortest path. + * + * @param \ZipArchive $zip + * @param string $filename + * @return bool|int + */ + private static function locateFile(\ZipArchive $zip, $filename) + { + $indexOfShortestMatch = false; + $lengthOfShortestMatch = -1; + + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + if (strcmp(basename($stat['name']), $filename) === 0) { + $directoryName = dirname($stat['name']); + if ($directoryName == '.') { + //if composer.json is in root directory + //it has to be the one to use. + return $i; + } + + if (strpos($directoryName, '\\') !== false || + strpos($directoryName, '/') !== false) { + //composer.json files below first directory are rejected + continue; + } + + $length = strlen($stat['name']); + if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { + //Check it's not a directory. + $contents = $zip->getFromIndex($i); + if ($contents !== false) { + $indexOfShortestMatch = $i; + $lengthOfShortestMatch = $length; + } + } + } + } + + return $indexOfShortestMatch; + } +} From 0fd74d1cc43ce7bedfc21e473ea4b44e9b3a2503 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 12:28:21 +0100 Subject: [PATCH 451/580] Fix PerforceDownloader, fixes #7979 --- src/Composer/Downloader/PerforceDownloader.php | 2 +- tests/Composer/Test/Downloader/PerforceDownloaderTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index df270417f..a7dc013b3 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -78,7 +78,7 @@ class PerforceDownloader extends VcsDownloader */ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { - $this->doDownload($target, $path, $url); + $this->doInstall($target, $path, $url); } /** diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index d2b8fb753..77a61f9cf 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -121,7 +121,7 @@ class PerforceDownloaderTest extends TestCase * @depends testInitPerforceInstantiatesANewPerforceObject * @depends testInitPerforceDoesNothingIfPerforceAlreadySet */ - public function testDoDownloadWithTag() + public function testDoInstallWithTag() { //I really don't like this test but the logic of each Perforce method is tested in the Perforce class. Really I am just enforcing workflow. $ref = 'SOURCE_REF@123'; @@ -145,7 +145,7 @@ class PerforceDownloaderTest extends TestCase * @depends testInitPerforceInstantiatesANewPerforceObject * @depends testInitPerforceDoesNothingIfPerforceAlreadySet */ - public function testDoDownloadWithNoTag() + public function testDoInstallWithNoTag() { $ref = 'SOURCE_REF'; $label = null; From fb3d0981c04570d283485da984f4d4d96a998b78 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 12:35:24 +0100 Subject: [PATCH 452/580] Fix test suite --- tests/Composer/Test/DependencyResolver/SolverTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index b80e7c61d..e50927fe2 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -899,6 +899,8 @@ class SolverTest extends TestCase $this->request->install('A'); + $this->createSolver(); + // check correct setup for assertion later $this->assertFalse($this->solver->testFlagLearnedPositiveLiteral); From a062cd1a31f05dd5b87f761f1784dec4775f7306 Mon Sep 17 00:00:00 2001 From: CZechBoY Date: Mon, 7 Jan 2019 16:22:41 +0100 Subject: [PATCH 453/580] added phpstan on level 0 --- .travis.yml | 12 +++++- phpstan/autoload.php | 5 +++ phpstan/config.neon | 38 +++++++++++++++++++ .../DependencyResolver/PoolBuilder.php | 6 +-- src/Composer/Downloader/GzipDownloader.php | 1 + src/Composer/Downloader/RarDownloader.php | 1 + src/Composer/Downloader/XzDownloader.php | 1 + src/Composer/Downloader/ZipDownloader.php | 2 + src/Composer/Package/Locker.php | 8 ++++ .../Package/Version/VersionGuesser.php | 1 + src/Composer/Util/Bitbucket.php | 6 +++ src/Composer/Util/Filesystem.php | 4 ++ src/Composer/Util/GitHub.php | 4 ++ src/Composer/Util/GitLab.php | 4 ++ src/Composer/Util/Loop.php | 2 +- src/Composer/Util/StreamContextFactory.php | 1 + .../Test/Downloader/FileDownloaderTest.php | 2 + .../Test/Downloader/ZipDownloaderTest.php | 2 +- 18 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 phpstan/autoload.php create mode 100644 phpstan/config.neon diff --git a/.travis.yml b/.travis.yml index f02fefcb1..1140053c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,10 +23,15 @@ matrix: - php: 5.6 - php: 7.0 - php: 7.1 + env: PHPSTAN=1 - php: 7.2 + env: PHPSTAN=1 - php: 7.3 + env: PHPSTAN=1 - php: 7.3 - env: deps=high + env: + - deps=high + - PHPSTAN=1 - php: nightly fast_finish: true allow_failures: @@ -58,6 +63,11 @@ before_script: script: # run test suite directories in parallel using GNU parallel - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' + # Run PHPStan + - if [[ $PHPSTAN == "1" ]]; then + composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs && + vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php; + fi before_deploy: - php -d phar.readonly=0 bin/compile diff --git a/phpstan/autoload.php b/phpstan/autoload.php new file mode 100644 index 000000000..7d1ed7671 --- /dev/null +++ b/phpstan/autoload.php @@ -0,0 +1,5 @@ +pool = new Pool($this->filterRequires); + $pool = new Pool($this->filterRequires); $this->rootAliases = $rootAliases; // TODO do we really want the request here? kind of want a root requirements thingy instead @@ -133,13 +133,13 @@ class PoolBuilder } } - $this->pool->setPackages($this->packages, $this->priorities); + $pool->setPackages($this->packages, $this->priorities); unset($this->aliasMap); unset($this->loadedNames); unset($this->nameConstraints); - return $this->pool; + return $pool; } private function loadPackage(PackageInterface $package, $repoIndex) diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index 9748b91ac..91be4593d 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -28,6 +28,7 @@ use Composer\IO\IOInterface; */ class GzipDownloader extends ArchiveDownloader { + /** @var ProcessExecutor */ protected $process; public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index 2ebc3bf18..d0fbadcc6 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -32,6 +32,7 @@ use RarArchive; */ class RarDownloader extends ArchiveDownloader { + /** @var ProcessExecutor */ protected $process; public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index 19e51c321..371ceda1b 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -28,6 +28,7 @@ use Composer\IO\IOInterface; */ class XzDownloader extends ArchiveDownloader { + /** @var ProcessExecutor */ protected $process; public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index efa9fc994..bd8d3b499 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -33,7 +33,9 @@ class ZipDownloader extends ArchiveDownloader private static $hasZipArchive; private static $isWindows; + /** @var ProcessExecutor */ protected $process; + /** @var ZipArchive|null */ private $zipArchiveObject; public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 405d43261..68540581c 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -31,13 +31,21 @@ use Seld\JsonLint\ParsingException; */ class Locker { + /** @var JsonFile */ private $lockFile; + /** @var RepositoryManager */ private $repositoryManager; + /** @var InstallationManager */ private $installationManager; + /** @var string */ private $hash; + /** @var string */ private $contentHash; + /** @var ArrayLoader */ private $loader; + /** @var ArrayDumper */ private $dumper; + /** @var ProcessExecutor */ private $process; private $lockDataCache; diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 1c2fdf986..d655bc080 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -17,6 +17,7 @@ use Composer\Repository\Vcs\HgDriver; use Composer\IO\NullIO; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Util\Git as GitUtil; +use Composer\Util\HttpDownloader; use Composer\Util\ProcessExecutor; use Composer\Util\Svn as SvnUtil; diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index d9f569b1b..ea02dfa0b 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -22,11 +22,17 @@ use Composer\Downloader\TransportException; */ class Bitbucket { + /** @var IOInterface */ private $io; + /** @var Config */ private $config; + /** @var ProcessExecutor */ private $process; + /** @var HttpDownloader */ private $httpDownloader; + /** @var array */ private $token = array(); + /** @var int|null */ private $time; const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 1903f1c8d..805eda14b 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -23,6 +23,7 @@ use Symfony\Component\Finder\Finder; */ class Filesystem { + /** @var ProcessExecutor */ private $processExecutor; public function __construct(ProcessExecutor $executor = null) @@ -537,6 +538,9 @@ class Filesystem return $size; } + /** + * @return ProcessExecutor + */ protected function getProcess() { return $this->processExecutor; diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index c3046cb77..d6bfb62ca 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -22,9 +22,13 @@ use Composer\Downloader\TransportException; */ class GitHub { + /** @var IOInterface */ protected $io; + /** @var Config */ protected $config; + /** @var ProcessExecutor */ protected $process; + /** @var HttpDownloader */ protected $httpDownloader; /** diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 2a4867954..b2dbf2836 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -23,9 +23,13 @@ use Composer\Json\JsonFile; */ class GitLab { + /** @var IOInterface */ protected $io; + /** @var Config */ protected $config; + /** @var ProcessExecutor */ protected $process; + /** @var HttpDownloader */ protected $httpDownloader; /** diff --git a/src/Composer/Util/Loop.php b/src/Composer/Util/Loop.php index 1be7d478b..c50cf4b02 100644 --- a/src/Composer/Util/Loop.php +++ b/src/Composer/Util/Loop.php @@ -20,7 +20,7 @@ use React\Promise\Promise; */ class Loop { - private $io; + private $httpDownloader; public function __construct(HttpDownloader $httpDownloader) { diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index a87bc6d8b..f39c6dd38 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -14,6 +14,7 @@ namespace Composer\Util; use Composer\Composer; use Composer\CaBundle\CaBundle; +use Composer\Downloader\TransportException; use Psr\Log\LoggerInterface; /** diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 10ea401a8..89c538eab 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -20,6 +20,8 @@ use Composer\Util\Loop; class FileDownloaderTest extends TestCase { + private $httpDownloader; + protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $httpDownloader = null, $filesystem = null) { $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index b754af607..e69149271 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -25,7 +25,7 @@ class ZipDownloaderTest extends TestCase * @var string */ private $testDir; - private $prophet; + private $httpDownloader; private $io; private $config; private $package; From 799717f102ec64482d2aaaa5fef58bd86952e9ad Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 13:03:34 +0100 Subject: [PATCH 454/580] Tweak and fix some more phpstan reports --- phpstan/config.neon | 6 ++++-- .../Test/DependencyResolver/SolverTest.php | 2 +- .../Test/Downloader/PerforceDownloaderTest.php | 4 ++-- .../Test/Repository/Vcs/PerforceDriverTest.php | 2 +- .../Test/Repository/VcsRepositoryTest.php | 15 +++++++++------ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/phpstan/config.neon b/phpstan/config.neon index 5f77d5cd1..7c0122f9a 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -29,9 +29,11 @@ parameters: - '~^Undefined variable: \$vendorDir$~' - '~^Undefined variable: \$baseDir$~' + # variable defined in eval + - '~^Undefined variable: \$res$~' + # always checked whether the class exists - - '~^Instantiated class Symfony\\Component\\Console\\Terminal not found\.$~' - - '~^Class Symfony\\Component\\Console\\Input\\StreamableInputInterface not found\.$~' + - '~^Call to an undefined static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\).$~' # parent call in test mocks - '~^Composer\\Test\\Mock\\HttpDownloaderMock::__construct\(\) does not call parent constructor from Composer\\Util\\HttpDownloader\.$~' diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index e50927fe2..95696e610 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -40,7 +40,7 @@ class SolverTest extends TestCase $this->repo = new ArrayRepository; $this->repoInstalled = new InstalledArrayRepository; - $this->request = new Request($this->repoSet); + $this->request = new Request(); $this->policy = new DefaultPolicy; } diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 77a61f9cf..b6347789b 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -129,7 +129,7 @@ class PerforceDownloaderTest extends TestCase $this->package->expects($this->once())->method('getSourceReference')->will($this->returnValue($ref)); $this->io->expects($this->once())->method('writeError')->with($this->stringContains('Cloning '.$ref)); $perforceMethods = array('setStream', 'p4Login', 'writeP4ClientSpec', 'connectClient', 'syncCodeBase', 'cleanupClientSpec'); - $perforce = $this->getMockBuilder('Composer\Util\Perforce', $perforceMethods)->disableOriginalConstructor()->getMock(); + $perforce = $this->getMockBuilder('Composer\Util\Perforce')->disableOriginalConstructor()->getMock(); $perforce->expects($this->at(0))->method('initializePath')->with($this->equalTo($this->testPath)); $perforce->expects($this->at(1))->method('setStream')->with($this->equalTo($ref)); $perforce->expects($this->at(2))->method('p4Login'); @@ -152,7 +152,7 @@ class PerforceDownloaderTest extends TestCase $this->package->expects($this->once())->method('getSourceReference')->will($this->returnValue($ref)); $this->io->expects($this->once())->method('writeError')->with($this->stringContains('Cloning '.$ref)); $perforceMethods = array('setStream', 'p4Login', 'writeP4ClientSpec', 'connectClient', 'syncCodeBase', 'cleanupClientSpec'); - $perforce = $this->getMockBuilder('Composer\Util\Perforce', $perforceMethods)->disableOriginalConstructor()->getMock(); + $perforce = $this->getMockBuilder('Composer\Util\Perforce')->disableOriginalConstructor()->getMock(); $perforce->expects($this->at(0))->method('initializePath')->with($this->equalTo($this->testPath)); $perforce->expects($this->at(1))->method('setStream')->with($this->equalTo($ref)); $perforce->expects($this->at(2))->method('p4Login'); diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php index ff4e19121..1c44e82dd 100644 --- a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php @@ -108,7 +108,7 @@ class PerforceDriverTest extends TestCase { $methods = array('p4login', 'checkStream', 'writeP4ClientSpec', 'connectClient', 'getComposerInformation', 'cleanupClientSpec'); - return $this->getMockBuilder('Composer\Util\Perforce', $methods)->disableOriginalConstructor()->getMock(); + return $this->getMockBuilder('Composer\Util\Perforce')->disableOriginalConstructor()->getMock(); } public function testInitializeCapturesVariablesFromRepoConfig() diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index 0cab2f8bb..65bf52409 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -32,17 +32,18 @@ class VcsRepositoryTest extends TestCase protected function initialize() { - $oldCwd = getcwd(); - self::$composerHome = $this->getUniqueTmpDirectory(); - self::$gitRepo = $this->getUniqueTmpDirectory(); - $locator = new ExecutableFinder(); if (!$locator->find('git')) { $this->skipped = 'This test needs a git binary in the PATH to be able to run'; return; } - if (!@mkdir(self::$gitRepo) || !@chdir(self::$gitRepo)) { + + $oldCwd = getcwd(); + self::$composerHome = $this->getUniqueTmpDirectory(); + self::$gitRepo = $this->getUniqueTmpDirectory(); + + if (!@chdir(self::$gitRepo)) { $this->skipped = 'Could not create and move into the temp git repo '.self::$gitRepo; return; @@ -60,6 +61,7 @@ class VcsRepositoryTest extends TestCase $exec('git init'); $exec('git config user.email composertest@example.org'); $exec('git config user.name ComposerTest'); + $exec('git config commit.gpgsign false'); touch('foo'); $exec('git add foo'); $exec('git commit -m init'); @@ -150,7 +152,8 @@ class VcsRepositoryTest extends TestCase 'home' => self::$composerHome, ), )); - $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, $config); + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); + $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, $config, $httpDownloader); $packages = $repo->getPackages(); $dumper = new ArrayDumper(); From 2c520bf93b985a33ab7c5f3ca281142ea00ca256 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 13:37:30 +0100 Subject: [PATCH 455/580] Fix build --- phpstan/config.neon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpstan/config.neon b/phpstan/config.neon index 7c0122f9a..9fd155963 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -33,6 +33,8 @@ parameters: - '~^Undefined variable: \$res$~' # always checked whether the class exists + - '~^Instantiated class Symfony\\Component\\Console\\Terminal not found\.$~' + - '~^Class Symfony\\Component\\Console\\Input\\StreamableInputInterface not found\.$~' - '~^Call to an undefined static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\).$~' # parent call in test mocks From 9da40b3c2c000ce36ddb74b9afd80e8ab77333d4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 13:56:08 +0100 Subject: [PATCH 456/580] Only run phpstan once --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1140053c0..a06922904 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,15 +23,12 @@ matrix: - php: 5.6 - php: 7.0 - php: 7.1 - env: PHPSTAN=1 - php: 7.2 - env: PHPSTAN=1 - php: 7.3 env: PHPSTAN=1 - php: 7.3 env: - deps=high - - PHPSTAN=1 - php: nightly fast_finish: true allow_failures: From 0caa616e6c498e0ffb4a6363d0ee695698956068 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 14:03:23 +0100 Subject: [PATCH 457/580] Fix test warnings --- tests/Composer/Test/ApplicationTest.php | 2 +- tests/Composer/Test/CacheTest.php | 6 ++++-- .../Test/Downloader/FileDownloaderTest.php | 8 ++++---- .../Test/Downloader/GitDownloaderTest.php | 16 ++++++++-------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 8739a5004..39d462544 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -47,7 +47,7 @@ class ApplicationTest extends TestCase $index = 0; $outputMock->expects($this->at($index++)) - ->method("writeError"); + ->method("write"); if (extension_loaded('xdebug')) { $outputMock->expects($this->at($index++)) diff --git a/tests/Composer/Test/CacheTest.php b/tests/Composer/Test/CacheTest.php index 50c767752..a8d3eae4a 100644 --- a/tests/Composer/Test/CacheTest.php +++ b/tests/Composer/Test/CacheTest.php @@ -20,6 +20,7 @@ class CacheTest extends TestCase private $files; private $root; private $finder; + private $filesystem; private $cache; public function setUp() @@ -34,11 +35,12 @@ class CacheTest extends TestCase } $this->finder = $this->getMockBuilder('Symfony\Component\Finder\Finder')->disableOriginalConstructor()->getMock(); + $this->filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->cache = $this->getMockBuilder('Composer\Cache') ->setMethods(array('getFinder')) - ->setConstructorArgs(array($io, $this->root)) + ->setConstructorArgs(array($io, $this->root, 'a-z0-9.', $this->filesystem)) ->getMock(); $this->cache ->expects($this->any()) @@ -105,7 +107,7 @@ class CacheTest extends TestCase public function testClearCache() { - $this->finder + $this->filesystem ->method('removeDirectory') ->with($this->root) ->willReturn(true); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 89c538eab..7c4259ca3 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -223,7 +223,7 @@ class FileDownloaderTest extends TestCase public function testDowngradeShowsAppropriateMessage() { - $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $oldPackage->expects($this->any()) ->method('getFullPrettyVersion') ->will($this->returnValue('1.2.0')); @@ -231,7 +231,7 @@ class FileDownloaderTest extends TestCase ->method('getVersion') ->will($this->returnValue('1.2.0.0')); - $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $newPackage->expects($this->any()) ->method('getFullPrettyVersion') ->will($this->returnValue('1.0.0')); @@ -245,7 +245,7 @@ class FileDownloaderTest extends TestCase ->method('getDistUrls') ->will($this->returnValue(array($distUrl))); - $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('Downloading')); @@ -256,7 +256,7 @@ class FileDownloaderTest extends TestCase $path = $this->getUniqueTmpDirectory(); touch($path.'_script.js'); - $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock(); $filesystem->expects($this->once()) ->method('removeDirectory') ->will($this->returnValue(true)); diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index b5d0054de..b9a85a666 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -604,7 +604,7 @@ composer https://github.com/old/url (push) public function testDowngradeShowsAppropriateMessage() { - $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $oldPackage->expects($this->any()) ->method('getVersion') ->will($this->returnValue('1.2.0.0')); @@ -618,7 +618,7 @@ composer https://github.com/old/url (push) ->method('getSourceUrls') ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); - $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $newPackage->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -632,12 +632,12 @@ composer https://github.com/old/url (push) ->method('getFullPrettyVersion') ->will($this->returnValue('1.0.0')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->any()) ->method('execute') ->will($this->returnValue(0)); - $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('Downgrading')); @@ -649,7 +649,7 @@ composer https://github.com/old/url (push) public function testNotUsingDowngradingWithReferences() { - $oldPackage = $this->getMock('Composer\Package\PackageInterface'); + $oldPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $oldPackage->expects($this->any()) ->method('getVersion') ->will($this->returnValue('dev-ref')); @@ -660,7 +660,7 @@ composer https://github.com/old/url (push) ->method('getSourceUrls') ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); - $newPackage = $this->getMock('Composer\Package\PackageInterface'); + $newPackage = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $newPackage->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); @@ -671,12 +671,12 @@ composer https://github.com/old/url (push) ->method('getVersion') ->will($this->returnValue('dev-ref2')); - $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->any()) ->method('execute') ->will($this->returnValue(0)); - $ioMock = $this->getMock('Composer\IO\IOInterface'); + $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->at(0)) ->method('writeError') ->with($this->stringContains('updating')); From 936933fa66e70795e8d4ddb0372e1720fdd9c90a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 14:12:01 +0100 Subject: [PATCH 458/580] Fix cache tests --- tests/Composer/Test/CacheTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Composer/Test/CacheTest.php b/tests/Composer/Test/CacheTest.php index a8d3eae4a..1c4c67c98 100644 --- a/tests/Composer/Test/CacheTest.php +++ b/tests/Composer/Test/CacheTest.php @@ -13,6 +13,7 @@ namespace Composer\Test; use Composer\Test\TestCase; +use Composer\Cache; use Composer\Util\Filesystem; class CacheTest extends TestCase @@ -40,7 +41,7 @@ class CacheTest extends TestCase $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $this->cache = $this->getMockBuilder('Composer\Cache') ->setMethods(array('getFinder')) - ->setConstructorArgs(array($io, $this->root, 'a-z0-9.', $this->filesystem)) + ->setConstructorArgs(array($io, $this->root)) ->getMock(); $this->cache ->expects($this->any()) @@ -109,13 +110,12 @@ class CacheTest extends TestCase { $this->filesystem ->method('removeDirectory') - ->with($this->root) + ->with($this->root.'/') ->willReturn(true); - $this->assertTrue($this->cache->clear()); + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->cache = new Cache($io, $this->root, 'a-z0-9.', $this->filesystem); - for ($i = 0; $i < 3; $i++) { - $this->assertFileNotExists("{$this->root}/cached.file{$i}.zip"); - } + $this->assertTrue($this->cache->clear()); } } From d381b3a781b70e92152e752df3c704e700e6270e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 16:59:09 +0100 Subject: [PATCH 459/580] Fix variable name --- src/Composer/Repository/ComposerRepository.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index bb613497f..d75d02bac 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -313,9 +313,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } + + // add aliases of matched packages even if they did not match the constraint foreach ($candidates as $candidate) { if ($candidate instanceof AliasPackage) { - if (isset($result[spl_object_hash($candidate->getAliasOf())])) { + if (isset($matches[spl_object_hash($candidate->getAliasOf())])) { $matches[spl_object_hash($candidate)] = $candidate; } } From 169fb2347ac63cf3a105bd65589fc982577d07e2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 17:00:57 +0100 Subject: [PATCH 460/580] Avoid issues with git signatures when running tests --- .../Test/Package/Archiver/ArchivableFilesFinderTest.php | 1 + tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index f6afe10f1..cea4088b1 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -189,6 +189,7 @@ class ArchivableFilesFinderTest extends TestCase 'git init && '. 'git config user.email "you@example.com" && '. 'git config user.name "Your Name" && '. + 'git config commit.gpgsign false && '. 'git add .git* && '. 'git commit -m "ignore rules" && '. 'git add . && '. diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index 714c9b923..45a635437 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -125,6 +125,12 @@ class ArchiveManagerTest extends ArchiverTest throw new \RuntimeException('Could not config: '.$this->process->getErrorOutput()); } + $result = $this->process->execute('git config commit.gpgsign false', $output, $this->testDir); + if ($result > 0) { + chdir($currentWorkDir); + throw new \RuntimeException('Could not config: '.$this->process->getErrorOutput()); + } + $result = $this->process->execute('git config user.name "Your Name"', $output, $this->testDir); if ($result > 0) { chdir($currentWorkDir); From 1b7e957cc1b62ad815dfdd4fab912bd8916e428f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 18:12:38 +0100 Subject: [PATCH 461/580] Add EventDispatcher::removeListener to allow deregistration of listeners --- .../EventDispatcher/EventDispatcher.php | 21 +++++++- .../EventDispatcher/EventDispatcherTest.php | 48 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index c37d7cf45..c24660659 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -46,7 +46,7 @@ class EventDispatcher protected $io; protected $loader; protected $process; - protected $listeners; + protected $listeners = array(); private $eventStack; /** @@ -172,6 +172,9 @@ class EventDispatcher throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); } + if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1] ), true, IOInterface::VERBOSE); + } $event = $this->checkListenerExpectedEvent($callable, $event); $return = false === call_user_func($callable, $event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { @@ -364,6 +367,22 @@ class EventDispatcher $this->listeners[$eventName][$priority][] = $listener; } + /** + * @param callable|object $listener A callable or an object instance for which all listeners should be removed + */ + public function removeListener($listener) + { + foreach ($this->listeners as $eventName => $priorities) { + foreach ($priorities as $priority => $listeners) { + foreach ($listeners as $index => $candidate) { + if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) { + unset($this->listeners[$eventName][$priority][$index]); + } + } + } + } + } + /** * Adds object methods as listeners for the events in getSubscribedEvents * diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 6d812e20a..a41d745ff 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -171,6 +171,49 @@ class EventDispatcherTest extends TestCase return $rm; } + public function testDispatcherRemoveListener() + { + $composer = $this->createComposerInstance(); + + $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest()); + $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock()); + + $dispatcher = new EventDispatcher( + $composer, + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE), + $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock() + ); + + $listener = array($this, 'someMethod'); + $listener2 = array($this, 'someMethod2'); + $listener3 = 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod'; + + $dispatcher->addListener('ev1', $listener, 0); + $dispatcher->addListener('ev1', $listener, 1); + $dispatcher->addListener('ev1', $listener2, 1); + $dispatcher->addListener('ev1', $listener3); + $dispatcher->addListener('ev2', $listener3); + $dispatcher->addListener('ev2', $listener); + $dispatcher->dispatch('ev1'); + $dispatcher->dispatch('ev2'); + + $expected = '> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL + .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod2'.PHP_EOL + .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL + .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL + .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL + .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL; + $this->assertEquals($expected, $io->getOutput()); + + $dispatcher->removeListener($this); + $dispatcher->dispatch('ev1'); + $dispatcher->dispatch('ev2'); + + $expected .= '> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL + .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL; + $this->assertEquals($expected, $io->getOutput()); + } + public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack() { $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); @@ -446,6 +489,11 @@ class EventDispatcherTest extends TestCase return true; } + public static function someMethod2() + { + return true; + } + private function createComposerInstance() { $composer = new Composer; From 3fc9ede24b85cbca0914060e6043abdc2cb1e406 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 18:14:46 +0100 Subject: [PATCH 462/580] Add plugin callbacks for deactivation and uninstall, fixes #3000 --- src/Composer/Installer/PluginInstaller.php | 24 ++-- src/Composer/Plugin/PluginInterface.php | 18 +++ src/Composer/Plugin/PluginManager.php | 108 +++++++++++++++++- .../Fixtures/plugin-v1/Installer/Plugin.php | 11 ++ .../Fixtures/plugin-v2/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v3/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v4/Installer/Plugin1.php | 11 ++ .../Fixtures/plugin-v4/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v5/Installer/Plugin5.php | 11 ++ .../Fixtures/plugin-v6/Installer/Plugin6.php | 11 ++ .../Fixtures/plugin-v7/Installer/Plugin7.php | 11 ++ .../Fixtures/plugin-v8/Installer/Plugin8.php | 11 ++ .../Fixtures/plugin-v9/Installer/Plugin.php | 11 ++ .../Test/Plugin/PluginInstallerTest.php | 32 +++++- 14 files changed, 282 insertions(+), 10 deletions(-) diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 62a16fc62..a52e1937e 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -70,7 +70,7 @@ class PluginInstaller extends LibraryInstaller $this->composer->getPluginManager()->registerPackage($package, true); } catch (\Exception $e) { // Rollback installation - $this->io->writeError('Plugin installation failed, rolling back'); + $this->io->writeError('Plugin initialization failed, uninstalling plugin'); parent::uninstall($repo, $package); throw $e; } @@ -81,12 +81,22 @@ class PluginInstaller extends LibraryInstaller */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { - $extra = $target->getExtra(); - if (empty($extra['class'])) { - throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); - } - parent::update($repo, $initial, $target); - $this->composer->getPluginManager()->registerPackage($target, true); + + try { + $this->composer->getPluginManager()->deactivatePackage($initial, true); + $this->composer->getPluginManager()->registerPackage($target, true); + } catch (\Exception $e) { + // Rollback installation + $this->io->writeError('Plugin initialization failed, uninstalling plugin'); + parent::uninstall($repo, $target); + throw $e; + } + } + + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->composer->getPluginManager()->uninstallPackage($package, true); + parent::uninstall($repo, $package); } } diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 5158b66f6..27b8c9754 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -36,4 +36,22 @@ interface PluginInterface * @param IOInterface $io */ public function activate(Composer $composer, IOInterface $io); + + /** + * Remove any hooks from Composer + * + * @param Composer $composer + * @param IOInterface $io + */ + public function deactivate(Composer $composer, IOInterface $io); + + /** + * Prepare the plugin to be uninstalled + * + * This will be called after deactivate + * + * @param Composer $composer + * @param IOInterface $io + */ + public function uninstall(Composer $composer, IOInterface $io); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 786d846c5..7408359bd 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -144,7 +144,7 @@ class PluginManager $oldInstallerPlugin = ($package->getType() === 'composer-installer'); - if (in_array($package->getName(), $this->registeredPlugins)) { + if (isset($this->registeredPlugins[$package->getName()])) { return; } @@ -200,16 +200,82 @@ class PluginManager if ($oldInstallerPlugin) { $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); + $this->registeredPlugins[$package->getName()] = $installer; } elseif (class_exists($class)) { $plugin = new $class(); $this->addPlugin($plugin); - $this->registeredPlugins[] = $package->getName(); + $this->registeredPlugins[$package->getName()] = $plugin; } elseif ($failOnMissingClasses) { throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); } } } + /** + * Deactivates a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @param PackageInterface $package + * + * @throws \UnexpectedValueException + */ + public function deactivatePackage(PackageInterface $package) + { + if ($this->disablePlugins) { + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + if ($oldInstallerPlugin) { + $installer = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->composer->getInstallationManager()->removeInstaller($installer); + } else { + $plugin = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->removePlugin($plugin); + } + } + + /** + * Uninstall a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @param PackageInterface $package + * + * @throws \UnexpectedValueException + */ + public function uninstallPackage(PackageInterface $package) + { + if ($this->disablePlugins) { + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + if ($oldInstallerPlugin) { + $this->deactivatePackage($package); + } else { + $plugin = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->removePlugin($plugin); + $this->uninstallPlugin($plugin); + } + } + /** * Returns the version of the internal composer-plugin-api package. * @@ -240,6 +306,44 @@ class PluginManager } } + /** + * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance + * + * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function removePlugin(PluginInterface $plugin) + { + $index = array_search($plugin, $this->plugins, true); + if ($index === false) { + return; + } + + $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG); + unset($this->plugins[$index]); + $plugin->deactivate($this->composer, $this->io); + + $this->composer->getEventDispatcher()->removeListener($plugin); + } + + /** + * Notifies a plugin it is being uninstalled and should clean up + * + * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function uninstallPlugin(PluginInterface $plugin) + { + $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG); + $plugin->uninstall($this->composer, $this->io); + } + /** * Load all plugins and installers from a repository * diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php index f80acd325..c757d4b09 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php @@ -12,5 +12,16 @@ class Plugin implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v1'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v1'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v1'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php index db5a4462e..32090b66d 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php @@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v2'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v2'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v2'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php index 861c1679b..034388162 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php @@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v3'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v3'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v3'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php index 93bcabc98..2eaee6a3f 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php @@ -13,5 +13,16 @@ class Plugin1 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v4-plugin1'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v4-plugin1'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v4-plugin1'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php index d946deb89..3c5311a82 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php @@ -13,5 +13,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v4-plugin2'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v4-plugin2'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v4-plugin2'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php index a2ac37bc5..fb9f08a6d 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php @@ -10,5 +10,16 @@ class Plugin5 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v5'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v5'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v5'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php index e46c0fcb0..acce1f972 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php @@ -10,5 +10,16 @@ class Plugin6 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v6'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v6'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v6'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php index 5560a6047..84734ce3b 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php @@ -10,5 +10,16 @@ class Plugin7 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v7'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v7'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v7'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php index 7e9a0aab1..4534e13ef 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php @@ -13,6 +13,17 @@ class Plugin8 implements PluginInterface, Capable public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v8'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v8'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v8'); } public function getCapabilities() diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php index 74e1beb8b..870f11cd1 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php @@ -14,5 +14,16 @@ class Plugin implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v9'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v9'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v9'); } } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 633c5ab18..bd83ce16f 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -19,6 +19,9 @@ use Composer\Package\CompletePackage; use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Plugin\PluginManager; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\IO\BufferIO; +use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Test\TestCase; use Composer\Util\Filesystem; @@ -96,7 +99,7 @@ class PluginInstallerTest extends TestCase return __DIR__.'/Fixtures/'.$package->getPrettyName(); })); - $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->io = new BufferIO(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $this->autoloadGenerator = new AutoloadGenerator($dispatcher); @@ -108,6 +111,7 @@ class PluginInstallerTest extends TestCase $this->composer->setRepositoryManager($rm); $this->composer->setInstallationManager($im); $this->composer->setAutoloadGenerator($this->autoloadGenerator); + $this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io)); $this->pm = new PluginManager($this->io, $this->composer); $this->composer->setPluginManager($this->pm); @@ -140,6 +144,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertEquals('installer-v1', $plugins[0]->version); + $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } public function testInstallMultiplePlugins() @@ -158,6 +163,7 @@ class PluginInstallerTest extends TestCase $this->assertEquals('installer-v4', $plugins[0]->version); $this->assertEquals('plugin2', $plugins[1]->name); $this->assertEquals('installer-v4', $plugins[1]->version); + $this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithNewClassName() @@ -176,7 +182,29 @@ class PluginInstallerTest extends TestCase $installer->update($this->repository, $this->packages[0], $this->packages[1]); $plugins = $this->pm->getPlugins(); + $this->assertCount(1, $plugins); $this->assertEquals('installer-v2', $plugins[1]->version); + $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput()); + } + + public function testUninstall() + { + $this->repository + ->expects($this->once()) + ->method('getPackages') + ->will($this->returnValue(array($this->packages[0]))); + $this->repository + ->expects($this->exactly(1)) + ->method('hasPackage') + ->will($this->onConsecutiveCalls(true, false)); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); + + $installer->uninstall($this->repository, $this->packages[0]); + + $plugins = $this->pm->getPlugins(); + $this->assertCount(0, $plugins); + $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithSameClassName() @@ -196,6 +224,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertEquals('installer-v3', $plugins[1]->version); + $this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput()); } public function testRegisterPluginOnlyOneTime() @@ -213,6 +242,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertCount(1, $plugins); $this->assertEquals('installer-v1', $plugins[0]->version); + $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } /** From 6c782599f1e2e4a07a102aa28d5f70d39300bf95 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Feb 2019 10:54:42 +0100 Subject: [PATCH 463/580] Make IOInterface implement psr-4 LoggerInterface, fixes #5180 --- src/Composer/IO/BaseIO.php | 3 +-- src/Composer/IO/IOInterface.php | 3 ++- src/Composer/Util/Http/CurlDownloader.php | 1 - src/Composer/Util/HttpDownloader.php | 4 +--- src/Composer/Util/RemoteFilesystem.php | 4 +--- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index b327f1bbf..b63b59484 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -14,10 +14,9 @@ namespace Composer\IO; use Composer\Config; use Composer\Util\ProcessExecutor; -use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; -abstract class BaseIO implements IOInterface, LoggerInterface +abstract class BaseIO implements IOInterface { protected $authentications = array(); diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 5766ba479..46302088f 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -13,13 +13,14 @@ namespace Composer\IO; use Composer\Config; +use Psr\Log\LoggerInterface; /** * The Input/Output helper interface. * * @author François Pluchino */ -interface IOInterface +interface IOInterface extends LoggerInterface { const QUIET = 1; const NORMAL = 2; diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index ff31bf695..ab0dae91e 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -20,7 +20,6 @@ use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; use Composer\Util\Url; -use Psr\Log\LoggerInterface; use React\Promise\Promise; /** diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 172ea875a..68e11a4a4 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -17,7 +17,6 @@ use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Composer\Util\Http\Response; -use Psr\Log\LoggerInterface; use React\Promise\Promise; /** @@ -58,8 +57,7 @@ class HttpDownloader // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { - $logger = $io instanceof LoggerInterface ? $io : null; - $this->options = StreamContextFactory::getTlsDefaults($options, $logger); + $this->options = StreamContextFactory::getTlsDefaults($options, $io); } else { $this->disableTls = true; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 2709f7006..e2c50472c 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -16,7 +16,6 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; -use Psr\Log\LoggerInterface; /** * @author François Pluchino @@ -61,8 +60,7 @@ class RemoteFilesystem // Setup TLS options // The cafile option can be set via config.json if ($disableTls === false) { - $logger = $io instanceof LoggerInterface ? $io : null; - $this->options = StreamContextFactory::getTlsDefaults($options, $logger); + $this->options = StreamContextFactory::getTlsDefaults($options, $io); } else { $this->disableTls = true; } From ab945a6ec1899d5f5f5f844d39d5e921ddcebccd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Feb 2019 11:11:35 +0100 Subject: [PATCH 464/580] Clean up RepositoryInterface, fixes #5464 --- src/Composer/Repository/RepositoryInterface.php | 3 ++- src/Composer/Repository/RepositoryManager.php | 8 +------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 567455163..e5f2c5159 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -78,8 +78,9 @@ interface RepositoryInterface extends \Countable * * @param string $query search query * @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only + * @param string $type The type of package to search for. Defaults to all types of packages * * @return array[] an array of array('name' => '...', 'description' => '...') */ - public function search($query, $mode = 0); + public function search($query, $mode = 0, $type = null); } diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index c3ce0c24a..2dca57099 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -125,13 +125,7 @@ class RepositoryManager $class = $this->repositoryClasses[$type]; - $reflMethod = new \ReflectionMethod($class, '__construct'); - $params = $reflMethod->getParameters(); - if (isset($params[3]) && $params[3]->getClass() && $params[3]->getClass()->getName() === 'Composer\Util\HttpDownloader') { - return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher); - } - - return new $class($config, $this->io, $this->config, $this->eventDispatcher); + return new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher); } /** From 65903aacfdc19acb52c441dc5567d45052cfa169 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 19 Feb 2019 09:35:48 -0500 Subject: [PATCH 465/580] Fix type issues (#7996) * Fix type issues found by Psalm --- src/Composer/Command/BaseCommand.php | 6 ++++-- src/Composer/Command/ConfigCommand.php | 2 +- src/Composer/Command/DiagnoseCommand.php | 16 +--------------- src/Composer/Command/ShowCommand.php | 12 ++++++------ src/Composer/Command/SuggestsCommand.php | 5 ++++- src/Composer/Config/ConfigSourceInterface.php | 2 +- src/Composer/Config/JsonConfigSource.php | 2 +- src/Composer/Console/Application.php | 2 +- src/Composer/DependencyResolver/GenericRule.php | 8 ++++---- src/Composer/DependencyResolver/PoolBuilder.php | 6 +++--- src/Composer/DependencyResolver/Problem.php | 2 +- src/Composer/DependencyResolver/Solver.php | 5 +++-- src/Composer/Downloader/DownloadManager.php | 4 +--- src/Composer/Downloader/GitDownloader.php | 6 +++--- src/Composer/Downloader/PerforceDownloader.php | 2 +- src/Composer/EventDispatcher/EventDispatcher.php | 3 ++- src/Composer/Factory.php | 2 +- src/Composer/IO/IOInterface.php | 6 +++--- src/Composer/Installer.php | 2 +- src/Composer/Installer/InstallerInterface.php | 2 +- src/Composer/Installer/LibraryInstaller.php | 2 +- src/Composer/Package/BasePackage.php | 2 +- src/Composer/Package/PackageInterface.php | 2 +- src/Composer/Package/Version/VersionSelector.php | 2 +- .../Repository/Pear/BaseChannelReader.php | 2 +- .../Repository/Pear/ChannelRest10Reader.php | 2 +- src/Composer/Repository/PearRepository.php | 2 +- .../Repository/Vcs/VcsDriverInterface.php | 6 +++--- src/Composer/Util/AuthHelper.php | 2 +- src/Composer/Util/Filesystem.php | 1 + src/Composer/Util/HttpDownloader.php | 2 +- src/Composer/Util/Loop.php | 1 + src/Composer/Util/RemoteFilesystem.php | 2 +- src/Composer/Util/Svn.php | 2 +- 34 files changed, 59 insertions(+), 66 deletions(-) diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index 888b2a7f2..56ee9f7f4 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -27,6 +27,8 @@ use Symfony\Component\Console\Command\Command; /** * Base class for Composer commands * + * @method Application getApplication() + * * @author Ryan Weaver * @author Konstantin Kudryashov */ @@ -46,7 +48,7 @@ abstract class BaseCommand extends Command * @param bool $required * @param bool|null $disablePlugins * @throws \RuntimeException - * @return Composer + * @return Composer|null */ public function getComposer($required = true, $disablePlugins = null) { @@ -173,7 +175,7 @@ abstract class BaseCommand extends Command if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); - $preferDist = $input->getOption('prefer-dist'); + $preferDist = (bool) $input->getOption('prefer-dist'); } return array($preferSource, $preferDist); diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index 89ba495cf..3970d915d 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -226,7 +226,7 @@ EOT } $settingKey = $input->getArgument('setting-key'); - if (!$settingKey) { + if (!$settingKey || !is_string($settingKey)) { return 0; } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 481d58060..c123e7003 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -602,20 +602,6 @@ EOT $text .= "Install either of them or recompile php without --disable-iconv"; break; - case 'unicode': - $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL; - $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; - $text .= " detect_unicode = Off"; - $displayIniMessage = true; - break; - - case 'suhosin': - $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL; - $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL; - $text .= " suhosin.executor.include.whitelist = phar ".$current; - $displayIniMessage = true; - break; - case 'php': $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher."; break; @@ -713,7 +699,7 @@ EOT /** * Check if allow_url_fopen is ON * - * @return bool|string + * @return true|string */ private function checkConnectivity() { diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 5cb3fa860..a5a2cd780 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -823,10 +823,10 @@ EOT /** * Display a package tree * - * @param PackageInterface|string $package - * @param array $packagesInTree - * @param string $previousTreeBar - * @param int $level + * @param array|string $package + * @param array $packagesInTree + * @param string $previousTreeBar + * @param int $level */ protected function displayTree( $package, @@ -835,7 +835,7 @@ EOT $level = 1 ) { $previousTreeBar = str_replace('├', '│', $previousTreeBar); - if (isset($package['requires'])) { + if (is_array($package) && isset($package['requires'])) { $requires = $package['requires']; $treeBar = $previousTreeBar . ' ├'; $i = 0; @@ -968,7 +968,7 @@ EOT * @param string $phpVersion * @param bool $minorOnly * - * @return PackageInterface|null + * @return PackageInterface|false */ private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false) { diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php index 225725e12..1c1f23f93 100644 --- a/src/Composer/Command/SuggestsCommand.php +++ b/src/Composer/Command/SuggestsCommand.php @@ -43,6 +43,9 @@ EOT ; } + /** + * {@inheritDoc} + */ protected function execute(InputInterface $input, OutputInterface $output) { $lock = $this->getComposer()->getLocker()->getLockData(); @@ -117,7 +120,7 @@ EOT $io->write(sprintf('%s', $suggestion)); } - return; + return null; } // Grouped by package diff --git a/src/Composer/Config/ConfigSourceInterface.php b/src/Composer/Config/ConfigSourceInterface.php index 0d56fc0ed..a00e989bb 100644 --- a/src/Composer/Config/ConfigSourceInterface.php +++ b/src/Composer/Config/ConfigSourceInterface.php @@ -39,7 +39,7 @@ interface ConfigSourceInterface * Add a config setting * * @param string $name Name - * @param string $value Value + * @param string|array $value Value */ public function addConfigSetting($name, $value); diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index 15d40d200..e22bbb1e5 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -259,7 +259,7 @@ class JsonConfigSource implements ConfigSourceInterface * * @param array $array * @param mixed $value - * @return array + * @return int */ private function arrayUnshiftRef(&$array, &$value) { diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 0bfee80f3..64a32b46f 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -284,7 +284,7 @@ class Application extends BaseApplication return $result; } catch (ScriptExecutionException $e) { - return $e->getCode(); + return (int) $e->getCode(); } catch (\Exception $e) { $this->hintCommonErrors($e); restore_error_handler(); diff --git a/src/Composer/DependencyResolver/GenericRule.php b/src/Composer/DependencyResolver/GenericRule.php index df8a2a003..eb753067e 100644 --- a/src/Composer/DependencyResolver/GenericRule.php +++ b/src/Composer/DependencyResolver/GenericRule.php @@ -23,10 +23,10 @@ class GenericRule extends Rule protected $literals; /** - * @param array $literals - * @param int $reason A RULE_* constant describing the reason for generating this rule - * @param Link|PackageInterface $reasonData - * @param array $job The job this rule was created from + * @param array $literals + * @param int|null $reason A RULE_* constant describing the reason for generating this rule + * @param Link|PackageInterface|int|null $reasonData + * @param array $job The job this rule was created from */ public function __construct(array $literals, $reason, $reasonData, $job = null) { diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 9eb3dc0d0..42444cc91 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -176,12 +176,12 @@ class PoolBuilder if (!isset($this->loadedNames[$require])) { $loadNames[$require] = null; } - if ($link->getConstraint()) { + if ($linkConstraint = $link->getConstraint()) { if (!array_key_exists($require, $this->nameConstraints)) { - $this->nameConstraints[$require] = new MultiConstraint(array($link->getConstraint()), false); + $this->nameConstraints[$require] = new MultiConstraint(array($linkConstraint), false); } elseif ($this->nameConstraints[$require]) { // TODO addConstraint function? - $this->nameConstraints[$require] = new MultiConstraint(array_merge(array($link->getConstraint()), $this->nameConstraints[$require]->getConstraints()), false); + $this->nameConstraints[$require] = new MultiConstraint(array_merge(array($linkConstraint), $this->nameConstraints[$require]->getConstraints()), false); } } else { $this->nameConstraints[$require] = null; diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 271c7261f..98b07405a 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -180,7 +180,7 @@ class Problem * Store a reason descriptor but ignore duplicates * * @param string $id A canonical identifier for the reason - * @param string $reason The reason descriptor + * @param string|array $reason The reason descriptor */ protected function addReason($id, $reason) { diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 2188d99ce..821928382 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -13,6 +13,7 @@ namespace Composer\DependencyResolver; use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; use Composer\Repository\RepositoryInterface; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositorySet; @@ -44,7 +45,7 @@ class Solver protected $watchGraph; /** @var Decisions */ protected $decisions; - /** @var int[] */ + /** @var PackageInterface[] */ protected $installedMap; /** @var int */ @@ -691,7 +692,7 @@ class Solver /** * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code? */ - $disableRules = array(); + $disableRules = false; $level = 1; $systemLevel = $level + 1; diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 0b1ddb5a6..a23c167b5 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -294,9 +294,7 @@ class DownloadManager // if downloader type changed, or update failed and user asks for reinstall, // we wipe the dir and do a new install instead of updating it - if ($initialDownloader) { - $initialDownloader->remove($initial, $targetDir); - } + $initialDownloader->remove($initial, $targetDir); $this->install($target, $targetDir); } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 96e47cb22..f698981fe 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -362,7 +362,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface ) { $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference)); if (0 === $this->process->execute($command, $output, $path)) { - return; + return null; } } @@ -380,14 +380,14 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface ) { $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); if (0 === $this->process->execute($command, $output, $path)) { - return; + return null; } } } $command = sprintf($template, ProcessExecutor::escape($gitRef)); if (0 === $this->process->execute($command, $output, $path)) { - return; + return null; } // reference was not found (prints "fatal: reference is not a tree: $ref") diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index a7dc013b3..0427ec8c8 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -88,7 +88,7 @@ class PerforceDownloader extends VcsDownloader { $this->io->writeError('Perforce driver does not check for local changes before overriding', true); - return; + return null; } /** diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index c24660659..05e34dda1 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -199,6 +199,7 @@ class EventDispatcher } try { + /** @var InstallerEvent $event */ $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); } catch (ScriptExecutionException $e) { $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); @@ -499,7 +500,7 @@ class EventDispatcher * * @param Event $event * @throws \RuntimeException - * @return number + * @return int */ protected function pushEvent(Event $event) { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index b2fd4efab..383337aa6 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -413,7 +413,7 @@ class Factory /** * @param IOInterface $io IO instance * @param bool $disablePlugins Whether plugins should not be loaded - * @return Composer + * @return Composer|null */ public static function createGlobal(IOInterface $io, $disablePlugins = false) { diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 46302088f..95f891c57 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -108,7 +108,7 @@ interface IOInterface extends LoggerInterface * @param string $default The default answer if none is given by the user * * @throws \RuntimeException If there is no data to read in the input stream - * @return string The user answer + * @return string|null The user answer */ public function ask($question, $default = null); @@ -146,7 +146,7 @@ interface IOInterface extends LoggerInterface * * @param string $question The question to ask * - * @return string The answer + * @return string|null The answer */ public function askAndHideAnswer($question); @@ -161,7 +161,7 @@ interface IOInterface extends LoggerInterface * @param bool $multiselect Select more than one value separated by comma * * @throws \InvalidArgumentException - * @return int|string|array The selected value or values (the key of the choices array) + * @return int|string|array|bool The selected value or values (the key of the choices array) */ public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 19c0015d6..fc877f18e 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -959,7 +959,7 @@ class Installer * @param RepositoryInterface $lockedRepository * @param string $task * @param array|null $operations - * @return array + * @return array|null */ private function processDevPackages($localRepo, Pool $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null) { diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index e00877ed9..310c5fcfc 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -48,7 +48,7 @@ interface InstallerInterface * * @param PackageInterface $package package instance * @param PackageInterface $prevPackage previous package instance in case of an update - * @return PromiseInterface + * @return PromiseInterface|null */ public function download(PackageInterface $package, PackageInterface $prevPackage = null); diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 4c2f45601..a89553b1b 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -43,7 +43,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface * * @param IOInterface $io * @param Composer $composer - * @param string $type + * @param string|null $type * @param Filesystem $filesystem * @param BinaryInstaller $binaryInstaller */ diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index f2f5be707..9630e7ef0 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -239,7 +239,7 @@ abstract class BasePackage implements PackageInterface * Build a regexp from a package name, expanding * globs as required * * @param string $whiteListedPattern - * @param bool $wrap Wrap the cleaned string by the given string + * @param string $wrap Wrap the cleaned string by the given string * @return string */ public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i') diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 73d2ade41..30488e89f 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -76,7 +76,7 @@ interface PackageInterface /** * Returns the package targetDir property * - * @return string The package targetDir + * @return string|null The package targetDir */ public function getTargetDir(); diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index d99780ab1..a8b4ae17b 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -45,7 +45,7 @@ class VersionSelector * @param string $targetPackageVersion * @param string $targetPhpVersion * @param string $preferredStability - * @return PackageInterface|bool + * @return PackageInterface|false */ public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable') { diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php index b778bf08b..9b9acf2f2 100644 --- a/src/Composer/Repository/Pear/BaseChannelReader.php +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -47,7 +47,7 @@ abstract class BaseChannelReader * @param string $origin server * @param string $path relative path to content * @throws \UnexpectedValueException - * @return \SimpleXMLElement + * @return string */ protected function requestContent($origin, $path) { diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php index 93969043a..9d14b71ea 100644 --- a/src/Composer/Repository/Pear/ChannelRest10Reader.php +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -150,7 +150,7 @@ class ChannelRest10Reader extends BaseChannelReader * @param string $baseUrl * @param string $packageName * @param string $version - * @return DependencyInfo[] + * @return DependencyInfo */ private function readPackageReleaseDependencies($baseUrl, $packageName, $version) { diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 1bb22c0ed..5cffb6233 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -97,7 +97,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn * * @param ChannelInfo $channelInfo * @param SemverVersionParser $versionParser - * @return CompletePackage + * @return CompletePackage[] */ private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser) { diff --git a/src/Composer/Repository/Vcs/VcsDriverInterface.php b/src/Composer/Repository/Vcs/VcsDriverInterface.php index 5e3bcec68..e59bcf647 100644 --- a/src/Composer/Repository/Vcs/VcsDriverInterface.php +++ b/src/Composer/Repository/Vcs/VcsDriverInterface.php @@ -38,7 +38,7 @@ interface VcsDriverInterface * * @param string $file * @param string $identifier - * @return string + * @return string|null */ public function getFileContent($file, $identifier); @@ -46,7 +46,7 @@ interface VcsDriverInterface * Get the changedate for $identifier. * * @param string $identifier - * @return \DateTime + * @return \DateTime|null */ public function getChangeDate($identifier); @@ -73,7 +73,7 @@ interface VcsDriverInterface /** * @param string $identifier Any identifier to a specific branch/tag/commit - * @return array With type, url reference and shasum keys. + * @return array|null With type, url reference and shasum keys. */ public function getDist($identifier); diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index 3679b93da..2868d3346 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -73,7 +73,7 @@ class AuthHelper * @param string|null $reason a message/description explaining why this was called * @param string $warning an authentication warning returned by the server as {"warning": ".."}, if present * @param string[] $headers - * @return array containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be + * @return array|null containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json */ public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array()) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 805eda14b..2d73016c6 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -292,6 +292,7 @@ class Filesystem $this->ensureDirectoryExists($target); $result = true; + /** @var RecursiveDirectoryIterator $ri */ foreach ($ri as $file) { $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName(); if ($file->isDir()) { diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 68e11a4a4..5a5ec14b2 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -117,7 +117,7 @@ class HttpDownloader /** * Merges new options * - * @return array $options + * @return void */ public function setOptions(array $options) { diff --git a/src/Composer/Util/Loop.php b/src/Composer/Util/Loop.php index c50cf4b02..dfaa2ac53 100644 --- a/src/Composer/Util/Loop.php +++ b/src/Composer/Util/Loop.php @@ -29,6 +29,7 @@ class Loop public function wait(array $promises) { + /** @var \Exception|null */ $uncaught = null; \React\Promise\all($promises)->then( diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e2c50472c..07eccc791 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -398,7 +398,7 @@ class RemoteFilesystem // fail 4xx and 5xx responses and capture the response if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { if (!$this->retry) { - if ($this->progress && !$this->retry && !$isRedirect) { + if ($this->progress && !$isRedirect) { $this->io->overwriteError("Downloading (failed)", false); } diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 58114ac93..be1a81c91 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -304,7 +304,7 @@ class Svn $this->createAuthFromUrl(); } - return $this->hasAuth; + return (bool) $this->hasAuth; } /** From 61cd8664e56cfd71ed88537edabf616262518a9a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 08:43:33 +0100 Subject: [PATCH 466/580] Avoid creating empty bitbucket files if there was no composer.json present in the original branch/tag --- .../Repository/Vcs/BitbucketDriver.php | 76 ++++++++++--------- src/Composer/Repository/Vcs/GitHubDriver.php | 2 +- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 24a4af4dd..556ca5012 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -124,50 +124,52 @@ abstract class BitbucketDriver extends VcsDriver $composer = $this->getBaseComposerInformation($identifier); - // specials for bitbucket - if (!isset($composer['support']['source'])) { - $label = array_search( - $identifier, - $this->getTags() - ) ?: array_search( - $identifier, - $this->getBranches() - ) ?: $identifier; + if ($composer) { + // specials for bitbucket + if (!isset($composer['support']['source'])) { + $label = array_search( + $identifier, + $this->getTags() + ) ?: array_search( + $identifier, + $this->getBranches() + ) ?: $identifier; - if (array_key_exists($label, $tags = $this->getTags())) { - $hash = $tags[$label]; - } elseif (array_key_exists($label, $branches = $this->getBranches())) { - $hash = $branches[$label]; + if (array_key_exists($label, $tags = $this->getTags())) { + $hash = $tags[$label]; + } elseif (array_key_exists($label, $branches = $this->getBranches())) { + $hash = $branches[$label]; + } + + if (! isset($hash)) { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src', + $this->originUrl, + $this->owner, + $this->repository + ); + } else { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src/%s/?at=%s', + $this->originUrl, + $this->owner, + $this->repository, + $hash, + $label + ); + } } - - if (! isset($hash)) { - $composer['support']['source'] = sprintf( - 'https://%s/%s/%s/src', + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf( + 'https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository ); - } else { - $composer['support']['source'] = sprintf( - 'https://%s/%s/%s/src/%s/?at=%s', - $this->originUrl, - $this->owner, - $this->repository, - $hash, - $label - ); } - } - if (!isset($composer['support']['issues']) && $this->hasIssues) { - $composer['support']['issues'] = sprintf( - 'https://%s/%s/%s/issues', - $this->originUrl, - $this->owner, - $this->repository - ); - } - if (!isset($composer['homepage'])) { - $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; + if (!isset($composer['homepage'])) { + $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; + } } $this->infoCache[$identifier] = $composer; diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index d0b721af9..40a75b4dd 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -152,8 +152,8 @@ class GitHubDriver extends VcsDriver } $composer = $this->getBaseComposerInformation($identifier); - if ($composer) { + if ($composer) { // specials for github if (!isset($composer['support']['source'])) { $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; From ff82334124633d35f23343db17017e0d5abab1c2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 08:42:49 +0100 Subject: [PATCH 467/580] Load ~dev files as well as main provider files for new v2 protocol, fixes #6415 --- src/Composer/Repository/ComposerRepository.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index d75d02bac..92d39adfd 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -581,6 +581,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); } + // load ~dev variants as well if present + // TODO ideally there should be a flag set from the repositoryset/poolbuilder to know which packages should have the dev packages loaded + // so we can optimize away some requests entirely + foreach ($packageNames as $name => $constraint) { + $packageNames[$name.'~dev'] = $constraint; + } + foreach ($packageNames as $name => $constraint) { $name = strtolower($name); From 177f21ec5c975df8771517d53192da7aa2b46651 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 10:51:07 +0100 Subject: [PATCH 468/580] Fix loading of dev providers, refs #6415 --- src/Composer/Repository/ComposerRepository.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 92d39adfd..8d0d8c3a4 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -591,8 +591,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($packageNames as $name => $constraint) { $name = strtolower($name); + $realName = preg_replace('{~dev$}', '', $name); // skip platform packages, root package and composer-plugin-api - if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $name) { + if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $realName) || '__root__' === $realName || 'composer-plugin-api' === $realName) { continue; } @@ -606,16 +607,16 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) - ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { + ->then(function ($response) use (&$packages, $contents, $realName, $constraint, $repo, $isPackageAcceptableCallable) { if (true === $response) { $response = $contents; } - if (!isset($response['packages'][$name])) { + if (!isset($response['packages'][$realName])) { return; } - $versions = $response['packages'][$name]; + $versions = $response['packages'][$realName]; if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { // TODO extract in other method @@ -649,7 +650,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito foreach ($versions as $version) { if (isset($version['version_normalizeds'])) { foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { - if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $normalizedVersion)) { + if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $normalizedVersion)) { foreach ($uniqKeys as $key) { unset($version[$key.'s'][$index]); } @@ -663,7 +664,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $version['version_normalized'] = $repo->versionParser->normalize($version['version']); } - if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $version['version_normalized'])) { + if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version['version_normalized'])) { $versionsToLoad[] = $version; } } From 2e204b016187c1ce4a50021fc127ff19ea5c650e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 11:10:44 +0100 Subject: [PATCH 469/580] Remove support for the first version of the compression algo (#7906) --- src/Composer/Package/Loader/ArrayLoader.php | 31 +++---------------- .../Repository/ComposerRepository.php | 24 +++----------- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index c5edd0d68..511f6c7ca 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -62,39 +62,16 @@ class ArrayLoader implements LoaderInterface public function loadPackages(array $versions, $class) { - static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); - $packages = array(); $linkCache = array(); foreach ($versions as $version) { - if (isset($version['versions'])) { - $baseVersion = $version; - foreach ($uniqKeys as $key) { - unset($baseVersion[$key.'s']); - } + $package = $this->createObject($version, $class); - foreach ($version['versions'] as $index => $dummy) { - $unpackedVersion = $baseVersion; - foreach ($uniqKeys as $key) { - $unpackedVersion[$key] = $version[$key.'s'][$index]; - } + $this->configureCachedLinks($linkCache, $package, $version); + $package = $this->configureObject($package, $version); - $package = $this->createObject($unpackedVersion, $class); - - $this->configureCachedLinks($linkCache, $package, $unpackedVersion); - $package = $this->configureObject($package, $unpackedVersion); - - $packages[] = $package; - } - } else { - $package = $this->createObject($version, $class); - - $this->configureCachedLinks($linkCache, $package, $version); - $package = $this->configureObject($package, $version); - - $packages[] = $package; - } + $packages[] = $package; } return $packages; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8d0d8c3a4..ccb14f622 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -645,28 +645,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito unset($expanded, $expandedVersion, $versionData); } - static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); $versionsToLoad = array(); foreach ($versions as $version) { - if (isset($version['version_normalizeds'])) { - foreach ($version['version_normalizeds'] as $index => $normalizedVersion) { - if (!$repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $normalizedVersion)) { - foreach ($uniqKeys as $key) { - unset($version[$key.'s'][$index]); - } - } - } - if (count($version['version_normalizeds'])) { - $versionsToLoad[] = $version; - } - } else { - if (!isset($version['version_normalized'])) { - $version['version_normalized'] = $repo->versionParser->normalize($version['version']); - } + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $repo->versionParser->normalize($version['version']); + } - if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version['version_normalized'])) { - $versionsToLoad[] = $version; - } + if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version['version_normalized'])) { + $versionsToLoad[] = $version; } } From bdf1f7f82b6c7c876278fdcd2c85fa13b318a6e8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 13:24:44 +0100 Subject: [PATCH 470/580] Fix loading of aliased packages in ComposerRepository when filtering by callback --- src/Composer/Repository/BaseRepository.php | 8 ++- .../Repository/ComposerRepository.php | 55 ++++++++++++------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index d835d55fd..fb10fb678 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -32,9 +32,11 @@ abstract class BaseRepository implements RepositoryInterface $result = array(); foreach ($packages as $package) { - if (array_key_exists($package->getName(), $packageMap) && - (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) && - call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability())) { + if ( + array_key_exists($package->getName(), $packageMap) + && (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) + && call_user_func($isPackageAcceptableCallable, $package->getNames(), $package->getStability()) + ) { $result[spl_object_hash($package)] = $package; if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) { $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index ccb14f622..f12c85b85 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -103,7 +103,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->io = $io; - $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$'); + $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~'); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); $this->httpDownloader = $httpDownloader; @@ -139,9 +139,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return; } - $packages = $this->loadAsyncPackages(array($name => $constraint), function ($name, $stability) { - return true; - }); + $packages = $this->loadAsyncPackages(array($name => $constraint)); return reset($packages); } @@ -181,9 +179,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return array(); } - return $this->loadAsyncPackages(array($name => $constraint ?: new EmptyConstraint()), function ($name, $stability) { - return true; - }); + return $this->loadAsyncPackages(array($name => $constraint)); } if ($hasProviders) { @@ -241,7 +237,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $packageMap[$name] = new EmptyConstraint(); } - return array_values($this->loadAsyncPackages($packageMap, function ($name, $stability) { return true; })); + return array_values($this->loadAsyncPackages($packageMap)); } throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getProviderNames instead.'); @@ -513,11 +509,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } if (!isset($versionsToLoad[$version['uid']])) { - if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $normalizedName, VersionParser::parseStability($version['version']))) { - continue; + if (!isset($version['version_normalized'])) { + $version['version_normalized'] = $this->versionParser->normalize($version['version']); } - $versionsToLoad[$version['uid']] = $version; + if ($this->isVersionAcceptable($isPackageAcceptableCallable, null, $normalizedName, $version)) { + $versionsToLoad[$version['uid']] = $version; + } } } } @@ -569,7 +567,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->configurePackageTransportOptions($package); } - private function loadAsyncPackages(array $packageNames, $isPackageAcceptableCallable) + /** + * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded + */ + private function loadAsyncPackages(array $packageNames, $isPackageAcceptableCallable = null) { $this->loadRootServerFile(); @@ -598,7 +599,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $url = str_replace('%package%', $name, $this->lazyProvidersUrl); - $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; + $cacheKey = 'provider-'.strtr($name, '/', '~').'.json'; $lastModified = null; if ($contents = $this->cache->read($cacheKey)) { @@ -651,7 +652,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $version['version_normalized'] = $repo->versionParser->normalize($version['version']); } - if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version['version_normalized'])) { + if ($repo->isVersionAcceptable($isPackageAcceptableCallable, $constraint, $realName, $version)) { $versionsToLoad[] = $version; } } @@ -659,9 +660,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $loadedPackages = $repo->createPackages($versionsToLoad, 'Composer\Package\CompletePackage'); foreach ($loadedPackages as $package) { $package->setRepository($repo); - $packages[spl_object_hash($package)] = $package; + if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { + $package->getAliasOf()->setRepository($repo); $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } @@ -677,19 +679,30 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito /** * TODO v3 should make this private once we can drop PHP 5.3 support * + * @param string $name package name (must be lowercased already) * @private */ - public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionNormalized) + public function isVersionAcceptable($isPackageAcceptableCallable, $constraint, $name, $versionData) { - if (!call_user_func($isPackageAcceptableCallable, strtolower($name), VersionParser::parseStability($versionNormalized))) { - return false; + $versions = array($versionData['version_normalized']); + + if ($alias = $this->loader->getBranchAlias($versionData)) { + $versions[] = $alias; } - if ($constraint && !$constraint->matches(new Constraint('==', $versionNormalized))) { - return false; + foreach ($versions as $version) { + if ($isPackageAcceptableCallable && !call_user_func($isPackageAcceptableCallable, $name, VersionParser::parseStability($version))) { + continue; + } + + if ($constraint && !$constraint->matches(new Constraint('==', $version))) { + continue; + } + + return true; } - return true; + return false; } protected function loadRootServerFile() From 8fe2b9ec69b8da2e4a0ca9629f20559c568bff6a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Feb 2019 13:41:43 +0100 Subject: [PATCH 471/580] Fix test --- tests/Composer/Test/Repository/ComposerRepositoryTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 55ca6bf09..c8af9418c 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -99,7 +99,13 @@ class ComposerRepositoryTest extends TestCase public function testWhatProvides() { $repo = $this->getMockBuilder('Composer\Repository\ComposerRepository') - ->disableOriginalConstructor() + ->setConstructorArgs(array( + array('url' => 'https://dummy.test.link'), + new NullIO, + FactoryMock::createConfig(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() + )) ->setMethods(array('fetchFile')) ->getMock(); From 60df8925174dfb385368efbbfd2d19c7f372c2cd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 12:27:02 +0100 Subject: [PATCH 472/580] Store dev mode in installed.json, fixes #3008 --- .travis.yml | 2 +- src/Composer/Installer.php | 4 ++-- src/Composer/Repository/FilesystemRepository.php | 15 ++++++++++----- .../Repository/WritableArrayRepository.php | 2 +- .../Repository/WritableRepositoryInterface.php | 4 +++- .../Mock/InstalledFilesystemRepositoryMock.php | 2 +- .../Test/Repository/FilesystemRepositoryTest.php | 5 +++-- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index a06922904..c732f9421 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,7 +62,7 @@ script: - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' # Run PHPStan - if [[ $PHPSTAN == "1" ]]; then - composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs && + bin/composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs && vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php; fi diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index fc877f18e..4dddd9505 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -616,7 +616,7 @@ class Installer } if ($this->executeOperations || $this->writeLock) { - $localRepo->write(); + $localRepo->write($this->devMode); } $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); @@ -628,7 +628,7 @@ class Installer if ($this->executeOperations) { // force source/dist urls to be updated for all packages $this->processPackageUrls($pool, $policy, $localRepo, $repositories); - $localRepo->write(); + $localRepo->write($this->devMode); } return array(0, $devPackages); diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index bde55aad3..9dbac5f76 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -49,7 +49,12 @@ class FilesystemRepository extends WritableArrayRepository } try { - $packages = $this->file->read(); + $data = $this->file->read(); + if (isset($data['packages'])) { + $packages = $data['packages']; + } else { + $packages = $data; + } if (!is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); @@ -74,16 +79,16 @@ class FilesystemRepository extends WritableArrayRepository /** * Writes writable repository. */ - public function write() + public function write($devMode) { - $data = array(); + $data = array('packages' => array(), 'dev' => $devMode); $dumper = new ArrayDumper(); foreach ($this->getCanonicalPackages() as $package) { - $data[] = $dumper->dump($package); + $data['packages'][] = $dumper->dump($package); } - usort($data, function ($a, $b) { + usort($data['packages'], function ($a, $b) { return strcmp($a['name'], $b['name']); }); diff --git a/src/Composer/Repository/WritableArrayRepository.php b/src/Composer/Repository/WritableArrayRepository.php index 041e40562..284f9bcb0 100644 --- a/src/Composer/Repository/WritableArrayRepository.php +++ b/src/Composer/Repository/WritableArrayRepository.php @@ -24,7 +24,7 @@ class WritableArrayRepository extends ArrayRepository implements WritableReposit /** * {@inheritDoc} */ - public function write() + public function write($devMode) { } diff --git a/src/Composer/Repository/WritableRepositoryInterface.php b/src/Composer/Repository/WritableRepositoryInterface.php index 4500005d9..4fb3d0c66 100644 --- a/src/Composer/Repository/WritableRepositoryInterface.php +++ b/src/Composer/Repository/WritableRepositoryInterface.php @@ -23,8 +23,10 @@ interface WritableRepositoryInterface extends RepositoryInterface { /** * Writes repository (f.e. to the disc). + * + * @param bool $devMode Whether dev requirements were included or not in this installation */ - public function write(); + public function write($devMode); /** * Adds package to the repository. diff --git a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php index 9c11dc307..8c8c280e8 100644 --- a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php +++ b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php @@ -20,7 +20,7 @@ class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository { } - public function write() + public function write($devMode) { } } diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index be8b0d0a9..4d8d7c103 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -95,11 +95,12 @@ class FilesystemRepositoryTest extends TestCase ->expects($this->once()) ->method('write') ->with(array( - array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0'), + 'packages' => array(array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0')), + 'dev' => true, )); $repository->addPackage($this->getPackage('mypkg', '0.1.10')); - $repository->write(); + $repository->write(true); } private function createJsonFileMock() From ba346ef04d7cc6fdbf9423b06f51e48485d20b77 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 12:57:27 +0100 Subject: [PATCH 473/580] Add forward compatibility for upcoming v2 installed.json format, refs #7999 --- src/Composer/Repository/FilesystemRepository.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index bde55aad3..204aa095d 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -51,6 +51,11 @@ class FilesystemRepository extends WritableArrayRepository try { $packages = $this->file->read(); + // forward compatibility for composer v2 installed.json + if (isset($packages['packages'])) { + $packages = $packages['packages']; + } + if (!is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); } From 427116749558f99e4c04ffa3fe358ebfd74d2fa5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 13:39:12 +0100 Subject: [PATCH 474/580] Improve version reporting --- src/Composer/Compiler.php | 1 + src/Composer/Composer.php | 37 ++++++++++++++++++++++ src/Composer/Console/Application.php | 6 ++-- src/Composer/Util/StreamContextFactory.php | 2 +- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 27b1f4816..2c763d053 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -193,6 +193,7 @@ class Compiler $content = str_replace('@package_version@', $this->version, $content); $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content); $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content); + $content = preg_replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content); } $phar->addFromString($path, $content); diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index a3972f44f..7b60b28af 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -29,9 +29,46 @@ use Composer\Package\Archiver\ArchiveManager; */ class Composer { + /* + * Examples of the following constants in the various configurations they can be in + * + * releases (phar): + * const VERSION = '1.8.2'; + * const BRANCH_ALIAS_VERSION = ''; + * const RELEASE_DATE = '2019-01-29 15:00:53'; + * const SOURCE_VERSION = ''; + * + * snapshot builds (phar): + * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7'; + * const BRANCH_ALIAS_VERSION = '1.9-dev'; + * const RELEASE_DATE = '2019-02-20 07:43:56'; + * const SOURCE_VERSION = ''; + * + * source (git clone): + * const VERSION = '@package_version@'; + * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; + * const RELEASE_DATE = '@release_date@'; + * const SOURCE_VERSION = '1.8-dev+source'; + */ const VERSION = '@package_version@'; const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; const RELEASE_DATE = '@release_date@'; + const SOURCE_VERSION = '1.8-dev+source'; + + public static function getVersion() + { + // no replacement done, this must be a source checkout + if (self::VERSION === '@package_version'.'@') { + return self::SOURCE_VERSION; + } + + // we have a branch alias and version is a commit id, this must be a snapshot build + if (self::BRANCH_ALIAS_VERSION !== '' && preg_match('{^[a-f0-9]{40}$}', self::VERSION)) { + return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION; + } + + return self::VERSION; + } /** * @var Package\RootPackageInterface diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index ccf83c943..a829ac38a 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -89,7 +89,7 @@ class Application extends BaseApplication $this->io = new NullIO(); - parent::__construct('Composer', Composer::VERSION); + parent::__construct('Composer', Composer::getVersion()); } /** @@ -181,7 +181,7 @@ class Application extends BaseApplication if (!$isProxyCommand) { $io->writeError(sprintf( 'Running %s (%s) with %s on %s', - Composer::VERSION, + Composer::getVersion(), Composer::RELEASE_DATE, defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' @@ -425,7 +425,7 @@ class Application extends BaseApplication */ public function getLongVersion() { - if (Composer::BRANCH_ALIAS_VERSION) { + if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') { return sprintf( '%s version %s (%s) %s', $this->getName(), diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 8dfd6624a..da3e578bd 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -142,7 +142,7 @@ final class StreamContextFactory if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) { $options['http']['header'][] = sprintf( 'User-Agent: Composer/%s (%s; %s; %s%s)', - Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION, + Composer::getVersion(), function_exists('php_uname') ? php_uname('s') : 'Unknown', function_exists('php_uname') ? php_uname('r') : 'Unknown', $phpVersion, From 0f36a42d614deb4638f5e00cd3a86309df78db72 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 14:05:56 +0100 Subject: [PATCH 475/580] Allow filtering of warning/info msgs by composer version --- .../Repository/ComposerRepository.php | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8a5da2b23..b9de0d7ea 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -20,6 +20,7 @@ use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Cache; use Composer\Config; +use Composer\Composer; use Composer\Factory; use Composer\IO\IOInterface; use Composer\Util\RemoteFilesystem; @@ -699,12 +700,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = JsonFile::parseJson($json, $filename); - if (!empty($data['warning'])) { - $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); - } - if (!empty($data['info'])) { - $this->io->writeError('Info from '.$this->url.': '.$data['info'].''); - } + $this->outputWarnings($data); if ($cacheKey) { if ($storeLastModifiedTime) { @@ -769,12 +765,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = JsonFile::parseJson($json, $filename); - if (!empty($data['warning'])) { - $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); - } - if (!empty($data['info'])) { - $this->io->writeError('Info from '.$this->url.': '.$data['info'].''); - } + $this->outputWarnings($data); $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); if ($lastModifiedDate) { @@ -835,4 +826,24 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // wipe rootData as it is fully consumed at this point and this saves some memory $this->rootData = true; } + + private function outputWarnings($data) + { + foreach (array('warning', 'info') as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $this->io->writeError('<'.$type.'>'.ucfirst($type).' from '.$this->url.': '.$data[$type].''); + } + } } From 60f198c17d99728b0c1adb5b17e581fe274e34e8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 14:06:41 +0100 Subject: [PATCH 476/580] Update target version --- src/Composer/Composer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 7b60b28af..0d5faceb2 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -53,7 +53,7 @@ class Composer const VERSION = '@package_version@'; const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; const RELEASE_DATE = '@release_date@'; - const SOURCE_VERSION = '1.8-dev+source'; + const SOURCE_VERSION = '1.9-dev+source'; public static function getVersion() { From 3f5a986170867e1c06517f7cc82c6db9cf99f1d7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 14:49:06 +0100 Subject: [PATCH 477/580] Show warning in all 400/500 responses if available, fixes #7814 --- .../Repository/ComposerRepository.php | 26 +++---------------- src/Composer/Util/AuthHelper.php | 7 +---- src/Composer/Util/Http/CurlDownloader.php | 15 +++++------ src/Composer/Util/HttpDownloader.php | 22 ++++++++++++++++ src/Composer/Util/RemoteFilesystem.php | 18 ++++++------- 5 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index e835e221b..0cdb74bd6 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -962,7 +962,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = $response->decodeJson(); - $this->outputWarnings($data); + HttpDownloader::outputWarnings($this->io, $this->url, $data); if ($cacheKey) { if ($storeLastModifiedTime) { @@ -1036,7 +1036,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = $response->decodeJson(); - $this->outputWarnings($data); + HttpDownloader::outputWarnings($this->io, $this->url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); @@ -1101,7 +1101,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = $response->decodeJson(); - $this->outputWarnings($data); + HttpDownloader::outputWarnings($io, $url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); @@ -1161,24 +1161,4 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // wipe rootData as it is fully consumed at this point and this saves some memory $this->rootData = true; } - - private function outputWarnings($data) - { - foreach (array('warning', 'info') as $type) { - if (empty($data[$type])) { - continue; - } - - if (!empty($data[$type . '-versions'])) { - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($data[$type . '-versions']); - $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); - if (!$constraint->matches($composer)) { - continue; - } - } - - $this->io->writeError('<'.$type.'>'.ucfirst($type).' from '.$this->url.': '.$data[$type].''); - } - } } diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index 2868d3346..7085f2561 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -71,12 +71,11 @@ class AuthHelper * @param string $origin * @param int $statusCode HTTP status code that triggered this call * @param string|null $reason a message/description explaining why this was called - * @param string $warning an authentication warning returned by the server as {"warning": ".."}, if present * @param string[] $headers * @return array|null containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json */ - public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $warning = null, $headers = array()) + public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array()) { $storeAuth = false; $retry = false; @@ -173,10 +172,6 @@ class AuthHelper throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); } - $this->io->overwriteError(''); - if ($warning) { - $this->io->writeError(' '.$warning.''); - } $this->io->writeError(' Authentication required ('.parse_url($url, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index ab0dae91e..1ebc64242 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -20,6 +20,7 @@ use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; use Composer\Util\Url; +use Composer\Util\HttpDownloader; use React\Promise\Promise; /** @@ -261,6 +262,10 @@ class CurlDownloader $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } + if ($response->getStatusCode() >= 400 && $response->getHeader('content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true)); + } + $result = $this->isAuthenticatedRetryNeeded($job, $response); if ($result['retry']) { if ($job['filename']) { @@ -371,15 +376,7 @@ class CurlDownloader private function isAuthenticatedRetryNeeded(array $job, Response $response) { if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) { - $warning = null; - if ($response->getHeader('content-type') === 'application/json') { - $data = json_decode($response->getBody(), true); - if (!empty($data['warning'])) { - $warning = $data['warning']; - } - } - - $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $warning, $response->getHeaders()); + $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders()); if ($result['retry']) { return $result; diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 5a5ec14b2..94e7ec0db 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -17,6 +17,8 @@ use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Composer\Util\Http\Response; +use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; use React\Promise\Promise; /** @@ -313,4 +315,24 @@ class HttpDownloader return $resp; } + + public static function outputWarnings(IOInterface $io, $url, $data) + { + foreach (array('warning', 'info') as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].''); + } + } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 07eccc791..c6ba4085c 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -16,6 +16,7 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; +use Composer\Util\HttpDownloader; /** * @author François Pluchino @@ -291,15 +292,12 @@ class RemoteFilesystem if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); + if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { + HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true)); + } + if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) { - $warning = null; - if ($this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { - $data = json_decode($result, true); - if (!empty($data['warning'])) { - $warning = $data['warning']; - } - } - $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning, $http_response_header); + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header); } } @@ -613,9 +611,9 @@ class RemoteFilesystem } } - protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array()) + protected function promptAuthAndRetry($httpStatus, $reason = null, $headers = array()) { - $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $warning, $headers); + $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers); $this->storeAuth = $result['storeAuth']; $this->retry = $result['retry']; From d37642d9f2719316e99ab186ab971e6ccf255bb8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 14:59:28 +0100 Subject: [PATCH 478/580] Add missing use --- src/Composer/Util/HttpDownloader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 94e7ec0db..93026ecbe 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -17,6 +17,7 @@ use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Composer\Util\Http\Response; +use Composer\Composer; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\Constraint; use React\Promise\Promise; From f77285916a8c0481a57554ad8615a049660e6b9b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 15:28:50 +0100 Subject: [PATCH 479/580] Clean up temp file on curl request failure and make sure the response body is avaiable on 3xx/4xx/5xx responses --- src/Composer/Util/Http/CurlDownloader.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 1ebc64242..989e63d12 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -251,16 +251,20 @@ class CurlDownloader // prepare response object if ($job['filename']) { - fclose($job['bodyHandle']); - $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $job['filename'].'~'); + $contents = $job['filename'].'~'; + if ($statusCode >= 300) { + rewind($job['bodyHandle']); + $contents = stream_get_contents($job['bodyHandle']); + } + $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } else { rewind($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']); - fclose($job['bodyHandle']); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); } + fclose($job['bodyHandle']); if ($response->getStatusCode() >= 400 && $response->getHeader('content-type') === 'application/json') { HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true)); @@ -268,10 +272,6 @@ class CurlDownloader $result = $this->isAuthenticatedRetryNeeded($job, $response); if ($result['retry']) { - if ($job['filename']) { - @unlink($job['filename'].'~'); - } - $this->restartJob($job, $job['url'], array('storeAuth' => $result['storeAuth'])); continue; } @@ -422,6 +422,10 @@ class CurlDownloader private function restartJob(array $job, $url, array $attributes = array()) { + if ($job['filename']) { + @unlink($job['filename'].'~'); + } + $attributes = array_merge($job['attributes'], $attributes); $origin = Url::getOrigin($this->config, $url); @@ -430,6 +434,10 @@ class CurlDownloader private function failResponse(array $job, Response $response, $errorMessage) { + if ($job['filename']) { + @unlink($job['filename'].'~'); + } + return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')', $response->getStatusCode()); } From 9de07bed1b3f4b96e0da90f2caab525073cf5048 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 25 Feb 2019 08:01:38 +0100 Subject: [PATCH 480/580] Fixed docblocks --- src/Composer/Util/Zip.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 1dfd99d39..a14eea924 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -20,8 +20,8 @@ class Zip /** * Finds the path to a file inside a ZIP archive. * - * @param $pathToZip - * @param $filename + * @param string $pathToZip + * @param string $filename * * @return string|null */ @@ -55,7 +55,8 @@ class Zip * Find a file by name, returning the one that has the shortest path. * * @param \ZipArchive $zip - * @param string $filename + * @param string $filename + * * @return bool|int */ private static function locateFile(\ZipArchive $zip, $filename) From 05d6b2178542857131011e3e46a8165414abc8aa Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 25 Feb 2019 08:02:04 +0100 Subject: [PATCH 481/580] Use self:: for private method --- src/Composer/Util/Zip.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index a14eea924..2e0333ac0 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -38,7 +38,7 @@ class Zip return null; } - $foundFileIndex = static::locateFile($zip, $filename); + $foundFileIndex = self::locateFile($zip, $filename); if (false === $foundFileIndex) { $zip->close(); From 6473dd91850460b9825fa8300b71e7712618bafe Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 27 Feb 2019 15:03:25 +0100 Subject: [PATCH 482/580] Minor improvements to VersionCacheInterface --- src/Composer/Repository/VcsRepository.php | 28 +++++++++++++++++++ .../Repository/VersionCacheInterface.php | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index d6fb1bbee..9a9ba5833 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -43,6 +43,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt private $driver; /** @var VersionCacheInterface */ private $versionCache; + private $emptyReferences = array(); public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null) { @@ -117,6 +118,11 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt return $this->branchErrorOccurred; } + public function getEmptyReferences() + { + return $this->emptyReferences; + } + protected function initialize() { parent::initialize(); @@ -159,6 +165,10 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($cachedPackage) { $this->addPackage($cachedPackage); + continue; + } elseif ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + continue; } @@ -174,6 +184,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', no composer file'); } + $this->emptyReferences[] = $identifier; continue; } @@ -212,6 +223,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { + if ($e instanceof TransportException) { + $this->emptyReferences[] = $identifier; + } if ($verbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); } @@ -258,6 +272,10 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($cachedPackage) { $this->addPackage($cachedPackage); + continue; + } elseif ($cachedPackage === false) { + $this->emptyReferences[] = $identifier; + continue; } @@ -266,6 +284,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file'); } + $this->emptyReferences[] = $identifier; continue; } @@ -284,6 +303,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } $this->addPackage($package); } catch (TransportException $e) { + $this->emptyReferences[] = $identifier; if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } @@ -352,6 +372,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); + if ($cachedPackage === false) { + if ($verbose) { + $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); + } + + return false; + } + if ($cachedPackage) { $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; if ($verbose) { diff --git a/src/Composer/Repository/VersionCacheInterface.php b/src/Composer/Repository/VersionCacheInterface.php index db5934b59..41d485c64 100644 --- a/src/Composer/Repository/VersionCacheInterface.php +++ b/src/Composer/Repository/VersionCacheInterface.php @@ -17,7 +17,7 @@ interface VersionCacheInterface /** * @param string $version * @param string $identifier - * @return array Package version data + * @return array|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier */ public function getVersionPackage($version, $identifier); } From 0d0cb53f314eb7422f16b479e2efd145c730362f Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Fri, 1 Mar 2019 11:06:03 +0100 Subject: [PATCH 483/580] Adjust Zip Util to only find the root composer.json --- src/Composer/Repository/ArtifactRepository.php | 2 +- src/Composer/Util/Zip.php | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 223ea4aef..2358f9205 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -83,7 +83,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { - $composerFile = Zip::findFile($file->getPathname(), 'composer.json'); + $composerFile = Zip::findComposerJson($file->getPathname()); if (null === $composerFile) { return false; diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 2e0333ac0..fcad76604 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -18,15 +18,19 @@ namespace Composer\Util; class Zip { /** - * Finds the path to a file inside a ZIP archive. + * Finds the path to the root composer.json inside a ZIP archive. * * @param string $pathToZip * @param string $filename * * @return string|null */ - public static function findFile($pathToZip, $filename) + public static function findComposerJson($pathToZip) { + if (!extension_loaded('zip')) { + throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); + } + $zip = new \ZipArchive(); if ($zip->open($pathToZip) !== true) { return null; @@ -38,7 +42,7 @@ class Zip return null; } - $foundFileIndex = self::locateFile($zip, $filename); + $foundFileIndex = self::locateFile($zip, 'composer.json'); if (false === $foundFileIndex) { $zip->close(); @@ -68,7 +72,7 @@ class Zip $stat = $zip->statIndex($i); if (strcmp(basename($stat['name']), $filename) === 0) { $directoryName = dirname($stat['name']); - if ($directoryName == '.') { + if ($directoryName === '.') { //if composer.json is in root directory //it has to be the one to use. return $i; From 627a832cc15f9321cbf97eb5692e515800b30f6f Mon Sep 17 00:00:00 2001 From: Patrick Reimers Date: Fri, 1 Mar 2019 19:39:07 +0100 Subject: [PATCH 484/580] Return non zero exit code on deprecation --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 405e567f0..a8aa65331 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -195,7 +195,9 @@ class ValidatingArrayLoader implements LoaderInterface foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { - if (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) { + if ($err = self::hasPackageNamingError($package, true)) { + $this->warnings[] = 'Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.'; + } elseif (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) { $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; } if (!is_string($constraint)) { From 5d14a95543d12436f3962b32357bafb6ae4bac8b Mon Sep 17 00:00:00 2001 From: Patrick Reimers Date: Fri, 1 Mar 2019 20:11:20 +0100 Subject: [PATCH 485/580] Add test for warning on deprecated naming --- .../Package/Loader/ValidatingArrayLoaderTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index ebe6871fe..6de6d45fa 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -337,6 +337,18 @@ class ValidatingArrayLoaderTest extends TestCase ), false, ), + array( + array( + 'name' => 'foo/bar', + 'require' => array( + 'Foo/Baz' => '^1.0', + ), + ), + array( + 'Deprecation warning: require.Foo/Baz is invalid, it should not contain uppercase characters. Please use foo/baz instead. Make sure you fix this as Composer 2.0 will error.', + ), + false, + ), array( array( 'name' => 'foo/bar', From 6bce9da8c87450c9a08c217e1a4e6142087b4cf5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2019 08:53:18 +0100 Subject: [PATCH 486/580] Only keep track of empty references that returned a 404 --- src/Composer/Repository/VcsRepository.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 9a9ba5833..edd0dabf8 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -223,7 +223,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { - if ($e instanceof TransportException) { + if ($e instanceof TransportException && $e->getCode() === 404) { $this->emptyReferences[] = $identifier; } if ($verbose) { @@ -303,7 +303,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } $this->addPackage($package); } catch (TransportException $e) { - $this->emptyReferences[] = $identifier; + if ($e->getCode() === 404) { + $this->emptyReferences[] = $identifier; + } if ($verbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } From a91fd20673843e40e7d692f5f40fe3132912b030 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 4 Mar 2019 09:54:35 +0100 Subject: [PATCH 487/580] Return the composer.json content instead of a zip:// path --- src/Composer/Repository/ArtifactRepository.php | 8 +++----- src/Composer/Util/Zip.php | 13 ++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 2358f9205..aff80e4cd 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -83,15 +83,13 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { - $composerFile = Zip::findComposerJson($file->getPathname()); + $json = Zip::getComposerJson($file->getPathname()); - if (null === $composerFile) { + if (null === $json) { return false; } - $json = file_get_contents($composerFile); - - $package = JsonFile::parseJson($json, $composerFile); + $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); $package['dist'] = array( 'type' => 'zip', 'url' => strtr($file->getPathname(), '\\', '/'), diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index fcad76604..6698616ff 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -18,14 +18,14 @@ namespace Composer\Util; class Zip { /** - * Finds the path to the root composer.json inside a ZIP archive. + * Gets content of the root composer.json inside a ZIP archive. * * @param string $pathToZip * @param string $filename * * @return string|null */ - public static function findComposerJson($pathToZip) + public static function getComposerJson($pathToZip) { if (!extension_loaded('zip')) { throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); @@ -49,10 +49,17 @@ class Zip return null; } + $content = null; $configurationFileName = $zip->getNameIndex($foundFileIndex); + $stream = $zip->getStream($configurationFileName); + + if (false !== $stream) { + $content = stream_get_contents($stream); + } + $zip->close(); - return "zip://{$pathToZip}#$configurationFileName"; + return $content; } /** From 0e2215dc6c40c67ca720fe4863eaff5624d8ead1 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 4 Mar 2019 11:08:59 +0100 Subject: [PATCH 488/580] Added full unit test coverage --- src/Composer/Util/Zip.php | 2 +- .../Composer/Test/Util/Fixtures/Zip/empty.zip | Bin 0 -> 22 bytes .../Test/Util/Fixtures/Zip/folder.zip | Bin 0 -> 314 bytes .../Test/Util/Fixtures/Zip/multiple.zip | Bin 0 -> 2569 bytes .../Test/Util/Fixtures/Zip/nojson.zip | Bin 0 -> 134 bytes .../Composer/Test/Util/Fixtures/Zip/root.zip | Bin 0 -> 194 bytes .../Test/Util/Fixtures/Zip/subfolder.zip | Bin 0 -> 1328 bytes tests/Composer/Test/Util/ZipTest.php | 117 ++++++++++++++++++ 8 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/empty.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/folder.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/multiple.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/nojson.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/root.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip create mode 100644 tests/Composer/Test/Util/ZipTest.php diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 6698616ff..8c79d106c 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -13,7 +13,7 @@ namespace Composer\Util; /** - * @author Jordi Boggiano + * @author Andreas Schempp */ class Zip { diff --git a/tests/Composer/Test/Util/Fixtures/Zip/empty.zip b/tests/Composer/Test/Util/Fixtures/Zip/empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/folder.zip b/tests/Composer/Test/Util/Fixtures/Zip/folder.zip new file mode 100644 index 0000000000000000000000000000000000000000..72b17b542f11eaaf47e413832e50a5538eb75e9c GIT binary patch literal 314 zcmWIWW@h1H0D+>Q6hANnO0X~pFr?+@>xV}0Fsyl76SD${zcPw21ORo2FmM22Fq#fQ zsE*|P+=Be#)FQpC;`}_A_B^Qe)z5+$n3nE2awkMpn|0}yKQ(`s98pqT7o`U@n4P0O zuXwu@&;cME;LXS+%8bi#JTSL9ymbUIAx`ChI~AfE;ZS6g1sM>!moyqdb)z{OVid^P T0p6@^AS;-FuoFnn25}ewNhC+U literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip new file mode 100644 index 0000000000000000000000000000000000000000..db8c50302d78e7512c043c4598fd62f518af151c GIT binary patch literal 2569 zcmWIWW@h1H0D9J_iOYJ9Y%ed5Eum*72cZaN; zaO1hyoCT}rHJO$_(7EzMR7prBVnNskyOY0^Ds%kJwjF24`F0_sf&IaR+?UBr$ITWk zIBoJHw=et7;;j7DKSRxJuOFOPWPgm;YD-4jPyO)b75;*!*R2!J-G1ZSj^|&WJ^lFQ zWsTOVpKe!P@t|$qy1u1*_P$Z zvM=ps&z`pNhrzl%+Hq%qa`wMc760+NN7~K(FCKsWzf*^O&(yDb<~`i2ATjaWtqr-; zcQZc7?E9#?;6#6G`i~d*(ws90894P&B}z0J}C&6u`A1#~!j)?6D_-u2nA{XGA6^h$g2eBqefhbjzQbzgzyynQ}!*S#@2M9@x2TPq(|s0i6oM2s&*) zb0=Up7d^*_qBz%3A0@y+84i(itoaTZ2)Ly8a(kQTY2Gbh$V+fN$+?4pHGIMMi;A}d zH;DuVs5Vtj`Wn_%pDE+daO1;{_y!(@gT2OflWvH$tTQ{O*dpN6QJcK3h17he(UnQoQ2wV@9g5>*eL1`PU;LD zxtoANhd-%<0s$q>LZTfJj)c>$0xl!a(lBz6QY{TbQX+byKuw8;`WPt@3CQR8j6inDU&pP;Mj(3#VGP1U$c32%iibX+8-tP#vHK0ZDC0pg@vUPu z6C{8UMIyv|pt1}(ph2b}ms*lYrYvdPie?I0I^lp9kH{g0p0ZILrp1ixFi@t2I1H4u zksSsq`m&J915p%*rJ$LDk`iFK7THngDFNBIzmAzKNGSmnjff%zJyl?jP6ZU#t-)m+ zTC}1%6k;+c^pG>h~n5SP$Mynf|UUQ-mGk({K^Z2 L*MQ+J3+4d;JZ$R> literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip b/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip new file mode 100644 index 0000000000000000000000000000000000000000..e536b956ce1f7c30410c96e53079a7a272bee0d6 GIT binary patch literal 134 zcmWIWW@h1H0D;-TDSluElwe^HU`Wf)*AI>0VYvOiCgvIte`OS52=HcP5@p7vhX-ba d!&^rX6Ji1f+=KvcRyL40BM{mFX(JGa0RSi#7c&3= literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/root.zip b/tests/Composer/Test/Util/Fixtures/Zip/root.zip new file mode 100644 index 0000000000000000000000000000000000000000..fd08f4d34dca94997e0a368500e9db6d9458ee46 GIT binary patch literal 194 zcmWIWW@Zs#-~htlpcFp_B*4ocz>u7uTaaIzTBMg%oSzpO!NV~BZB0xb5PxM9VOaev zh=FP8o+EccRJB=`ZuwL5cgYbY)pb#N3<2Kk9QAp{+ogcUfpCB~Ba9<*fOmYO^o~FaN4*AxTAik* z2{)dL%~_!BcW{<YzU)LgyQ-2i#pKZKtBI5c|F|oE_%ktS7iS*FE=|M50!NPqi(^6jtqz$w;lm-d`6t`T9;Jrwua zFSm-5p}cqtKcnZLNQ*=1`#}c0c612b^@tNyd=1r@oS$2eUz}Q`msOmf2TR3y zZy|xW`dJVI)6zXh?u4jnvo788r{?dHBTB04qV&KHt + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Zip; +use PHPUnit\Framework\TestCase; + +/** + * @author Andreas Schempp + */ +class ZipTest extends TestCase +{ + public function testThrowsExceptionIfZipExcentionIsNotLoaded() + { + if (extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is loaded.'); + } + + $this->setExpectedException('\RuntimeException', 'The Zip Util requires PHP\'s zip extension'); + + Zip::getComposerJson(''); + } + + public function testReturnsNullifTheZipIsNotFound() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipIsEmpty() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipHasNoComposerJson() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheComposerJsonIsInASubSubfolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); + + $this->assertNull($result); + } + + public function testReturnsComposerJsonInZipRoot() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsComposerJsonInFirstFolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsRootComposerJsonAndSkipsSubfolders() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } +} From d96d046209646e93259d54ca9985004341c40fe2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 4 Mar 2019 11:38:58 +0100 Subject: [PATCH 489/580] Fix require of platform packages with --ignore-platform-reqs, fixes #8012 --- src/Composer/Command/InitCommand.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index abcea73b2..ecc3d3238 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -694,15 +694,22 @@ EOT { // find the latest version allowed in this pool $versionSelector = new VersionSelector($this->getPool($input, $minimumStability)); - $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); + $ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs'); - // retry without phpVersion if platform requirements are ignored in case nothing was found - if ($input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs')) { + // ignore phpVersion if platform requirements are ignored + if ($ignorePlatformReqs) { $phpVersion = null; - $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); } + $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); + if (!$package) { + // platform packages can not be found in the pool in versions other than the local platform's has + // so if platform reqs are ignored we just take the user's word for it + if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { + return array($name, $requiredVersion ?: '*'); + } + // Check whether the PHP version was the problem if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) { throw new \InvalidArgumentException(sprintf( From c876613d5c651d1aa0f0d6621d7955bfe09520e2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 4 Mar 2019 12:55:38 +0100 Subject: [PATCH 490/580] Added "Read more at" links to all commands (#8019) --- src/Composer/Command/ArchiveCommand.php | 1 + src/Composer/Command/ClearCacheCommand.php | 2 ++ src/Composer/Command/ConfigCommand.php | 2 ++ src/Composer/Command/CreateProjectCommand.php | 1 + src/Composer/Command/DependsCommand.php | 1 + src/Composer/Command/DiagnoseCommand.php | 1 + src/Composer/Command/DumpAutoloadCommand.php | 2 ++ src/Composer/Command/ExecCommand.php | 7 +++++++ src/Composer/Command/GlobalCommand.php | 1 + src/Composer/Command/HomeCommand.php | 2 ++ src/Composer/Command/InitCommand.php | 1 + src/Composer/Command/InstallCommand.php | 1 + src/Composer/Command/LicensesCommand.php | 1 + src/Composer/Command/OutdatedCommand.php | 2 +- src/Composer/Command/ProhibitsCommand.php | 1 + src/Composer/Command/RemoveCommand.php | 1 + src/Composer/Command/RequireCommand.php | 1 + src/Composer/Command/RunScriptCommand.php | 2 ++ src/Composer/Command/ScriptAliasCommand.php | 2 ++ src/Composer/Command/SearchCommand.php | 1 + src/Composer/Command/SelfUpdateCommand.php | 1 + src/Composer/Command/ShowCommand.php | 1 + src/Composer/Command/StatusCommand.php | 1 + src/Composer/Command/SuggestsCommand.php | 1 + src/Composer/Command/UpdateCommand.php | 1 + src/Composer/Command/ValidateCommand.php | 1 + 26 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 29858c6fc..d0d9542cb 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -56,6 +56,7 @@ package in the specified version and writes it to the specified directory. php composer.phar archive [--format=zip] [--dir=/foo] [package [version]] +Read more at https://getcomposer.org/doc/03-cli.md#archive EOT ) ; diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index 2514f6330..ec51c56d3 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -32,6 +32,8 @@ class ClearCacheCommand extends BaseCommand <<clear-cache deletes all cached packages from composer's cache directory. + +Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache- EOT ) ; diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index c2347d306..b7e3b1f99 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -125,6 +125,8 @@ You can always pass more than one option. As an example, if you want to edit the global config.json file. %command.full_name% --editor --global + +Read more at https://getcomposer.org/doc/03-cli.md#config EOT ) ; diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index cca5f1871..3702c3595 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -104,6 +104,7 @@ controlled code by appending the '--prefer-source' flag. To install a package from another repository than the default one you can pass the '--repository=https://myrepository.org' flag. +Read more at https://getcomposer.org/doc/03-cli.md#create-project EOT ) ; diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index acbc89a70..d6adec083 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -37,6 +37,7 @@ Displays detailed information about where a package is referenced. php composer.phar depends composer/composer +Read more at https://getcomposer.org/doc/03-cli.md#depends-why- EOT ) ; diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index 3c4c3bb32..19ed81392 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -55,6 +55,7 @@ The diagnose command checks common errors to help debugging problem The process exit code will be 1 in case of warnings and 2 for errors. +Read more at https://getcomposer.org/doc/03-cli.md#diagnose EOT ) ; diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 55a2c5f16..3add15166 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -39,6 +39,8 @@ class DumpAutoloadCommand extends BaseCommand ->setHelp( <<php composer.phar dump-autoload + +Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload- EOT ) ; diff --git a/src/Composer/Command/ExecCommand.php b/src/Composer/Command/ExecCommand.php index f07bc9d28..c9184c707 100644 --- a/src/Composer/Command/ExecCommand.php +++ b/src/Composer/Command/ExecCommand.php @@ -36,6 +36,13 @@ class ExecCommand extends BaseCommand 'Arguments to pass to the binary. Use -- to separate from composer arguments' ), )) + ->setHelp( + <</.config/composer Note: This path may vary depending on customizations to bin-dir in composer.json or the environmental variable COMPOSER_BIN_DIR. +Read more at https://getcomposer.org/doc/03-cli.md#global EOT ) ; diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index a2f0756a1..b7d907066 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -49,6 +49,8 @@ homepage in your default browser. To open the homepage by default, use -H or --homepage. To show instead of open the repository or homepage URL, use -s or --show. + +Read more at https://getcomposer.org/doc/03-cli.md#browse-home EOT ); } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index ecc3d3238..66a56f978 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -72,6 +72,7 @@ in the current directory. php composer.phar init +Read more at https://getcomposer.org/doc/03-cli.md#init EOT ) ; diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index cc590d8c9..32fb1bdc6 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -61,6 +61,7 @@ exist it will look for composer.json and do the same. php composer.phar install +Read more at https://getcomposer.org/doc/03-cli.md#install-i EOT ) ; diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index 9dec45e1b..b3c30d63b 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -41,6 +41,7 @@ class LicensesCommand extends BaseCommand The license command displays detailed information about the licenses of the installed dependencies. +Read more at https://getcomposer.org/doc/03-cli.md#licenses EOT ) ; diff --git a/src/Composer/Command/OutdatedCommand.php b/src/Composer/Command/OutdatedCommand.php index 79409c58f..ae26a7487 100644 --- a/src/Composer/Command/OutdatedCommand.php +++ b/src/Composer/Command/OutdatedCommand.php @@ -50,7 +50,7 @@ The color coding (or signage if you have ANSI colors disabled) for dependency ve may involve work. - red (!): Dependency has a new version that is semver-compatible and you should upgrade it. - +Read more at https://getcomposer.org/doc/03-cli.md#outdated EOT ) ; diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php index edf6729ab..9e5575c74 100644 --- a/src/Composer/Command/ProhibitsCommand.php +++ b/src/Composer/Command/ProhibitsCommand.php @@ -37,6 +37,7 @@ Displays detailed information about why a package cannot be installed. php composer.phar prohibits composer/composer +Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not- EOT ) ; diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 27be1a0ca..e4407d4cb 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -56,6 +56,7 @@ list of installed packages php composer.phar remove +Read more at https://getcomposer.org/doc/03-cli.md#remove EOT ) ; diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index b347de094..4cad91023 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -73,6 +73,7 @@ If you do not specify a version constraint, composer will choose a suitable one If you do not want to install the new dependencies immediately you can call it with --no-update +Read more at https://getcomposer.org/doc/03-cli.md#require EOT ) ; diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index ea3b5c892..6d39ce6da 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -62,6 +62,8 @@ class RunScriptCommand extends BaseCommand The run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script EOT ) ; diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index 1aba0b074..455f7420c 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -48,6 +48,8 @@ class ScriptAliasCommand extends BaseCommand The run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd + +Read more at https://getcomposer.org/doc/03-cli.md#run-script EOT ) ; diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index ed180e84c..54aa4dcea 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -49,6 +49,7 @@ class SearchCommand extends BaseCommand The search command searches for packages by its name php composer.phar search symfony composer +Read more at https://getcomposer.org/doc/03-cli.md#search EOT ) ; diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 243755963..78b27460e 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -60,6 +60,7 @@ versions of composer and if found, installs the latest. php composer.phar self-update +Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate- EOT ) ; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index cc0fe0154..e9061743f 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -85,6 +85,7 @@ class ShowCommand extends BaseCommand The show command displays detailed information about a package, or lists all packages available. +Read more at https://getcomposer.org/doc/03-cli.md#show EOT ) ; diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 3e46b7fa0..cd153fc58 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -52,6 +52,7 @@ class StatusCommand extends BaseCommand The status command displays a list of dependencies that have been modified locally. +Read more at https://getcomposer.org/doc/03-cli.md#status EOT ) ; diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php index 225725e12..a200f8f69 100644 --- a/src/Composer/Command/SuggestsCommand.php +++ b/src/Composer/Command/SuggestsCommand.php @@ -38,6 +38,7 @@ The %command.name% command shows a sorted list of suggested package Enabling -v implies --by-package --by-suggestion, showing both lists. +Read more at https://getcomposer.org/doc/03-cli.md#suggests EOT ) ; diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 34420b747..e68c265c0 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -81,6 +81,7 @@ from a specific vendor: To select packages names interactively with auto-completion use -i. +Read more at https://getcomposer.org/doc/03-cli.md#update-u EOT ) ; diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 52023e528..5aba74adf 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -55,6 +55,7 @@ Exit codes in case of errors are: 2 validation error(s) 3 file unreadable or missing +Read more at https://getcomposer.org/doc/03-cli.md#validate EOT ); } From 8ae8d131d5c8635a6bd34063adfd340e29e3a158 Mon Sep 17 00:00:00 2001 From: Patrick Reimers Date: Tue, 5 Mar 2019 10:44:55 +0100 Subject: [PATCH 491/580] Add deprecation warning for name attribute --- src/Composer/Package/Loader/ValidatingArrayLoader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index a8aa65331..43f23236b 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -49,6 +49,10 @@ class ValidatingArrayLoader implements LoaderInterface $this->warnings = array(); $this->config = $config; + if ($err = self::hasPackageNamingError($config['name'])) { + $this->warnings[] = 'Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.'; + } + if ($this->strictName) { $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true); } else { From dd1e80a38f7bb1ce759c127ec405a65f8d97c388 Mon Sep 17 00:00:00 2001 From: Patrick Reimers Date: Tue, 5 Mar 2019 10:53:43 +0100 Subject: [PATCH 492/580] Add tests for wrong package name. --- .../Loader/ValidatingArrayLoaderTest.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 6de6d45fa..2fc059f3c 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -298,6 +298,30 @@ class ValidatingArrayLoaderTest extends TestCase 'homepage : invalid value (foo:bar), must be an http/https URL', ), ), + array( + array( + 'name' => 'foo/bar.json', + ), + array( + 'Deprecation warning: Your package name foo/bar.json is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead. Make sure you fix this as Composer 2.0 will error.', + ), + ), + array( + array( + 'name' => 'com1/foo', + ), + array( + 'Deprecation warning: Your package name com1/foo is reserved, package and vendor names can not match any of: nul, con, prn, aux, com1, com2, com3, com4, com5, com6, com7, com8, com9, lpt1, lpt2, lpt3, lpt4, lpt5, lpt6, lpt7, lpt8, lpt9. Make sure you fix this as Composer 2.0 will error.', + ), + ), + array( + array( + 'name' => 'Foo/Bar', + ), + array( + 'Deprecation warning: Your package name Foo/Bar is invalid, it should not contain uppercase characters. We suggest using foo/bar instead. Make sure you fix this as Composer 2.0 will error.', + ), + ), array( array( 'name' => 'foo/bar', From 20ff8b22f29ecb7422f756a86347d7562e17b935 Mon Sep 17 00:00:00 2001 From: Mike Hatch <4390485+mikeshatch@users.noreply.github.com> Date: Sat, 9 Mar 2019 00:27:34 -0600 Subject: [PATCH 493/580] Corrected a typo and two grammar errors. --- doc/00-intro.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/00-intro.md b/doc/00-intro.md index e4af54c2c..ed7a17c7c 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -10,7 +10,7 @@ Composer is **not** a package manager in the same sense as Yum or Apt are. Yes, it deals with "packages" or libraries, but it manages them on a per-project basis, installing them in a directory (e.g. `vendor`) inside your project. By default it does not install anything globally. Thus, it is a dependency -manager. It does however support a "global" project for convenience via the +manager. It does however support a "global" project for convenience via the [global](03-cli.md#global) command. This idea is not new and Composer is strongly inspired by node's @@ -47,7 +47,7 @@ Linux and macOS. ### Downloading the Composer Executable Composer offers a convenient installer that you can execute directly from the -commandline. Feel free to [download this file](https://getcomposer.org/installer) +command line. Feel free to [download this file](https://getcomposer.org/installer) or review it on [GitHub](https://github.com/composer/getcomposer.org/blob/master/web/installer) if you wish to know more about the inner workings of the installer. The source is plain PHP. @@ -82,7 +82,7 @@ Now run `php bin/composer` in order to run Composer. #### Globally You can place the Composer PHAR anywhere you wish. If you put it in a directory -that is part of your `PATH`, you can access it globally. On unixy systems you +that is part of your `PATH`, you can access it globally. On Unix systems you can even make it executable and invoke it without directly using the `php` interpreter. From 7c64300a1b4516fe8693ef17c690edafd25b716d Mon Sep 17 00:00:00 2001 From: Christian Ego Date: Mon, 11 Mar 2019 10:24:39 +0100 Subject: [PATCH 494/580] using emptyDirectory instead of remove for clearing the cache --- src/Composer/Cache.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 3f2861797..06c6a0996 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -189,7 +189,8 @@ class Cache public function clear() { if ($this->enabled) { - return $this->filesystem->removeDirectory($this->root); + $this->filesystem->emptyDirectory($this->root); + return true; } return false; From 486b25fd3015dc21df0524178d0ad279c91c8980 Mon Sep 17 00:00:00 2001 From: Novicaine Date: Fri, 15 Mar 2019 13:15:01 -0500 Subject: [PATCH 495/580] Fix for UNC Windows paths Made isAbsolutePath recognize Windows UNC-style absolute paths starting with \\ --- src/Composer/Util/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 1903f1c8d..d9e83280b 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -440,7 +440,7 @@ class Filesystem */ public function isAbsolutePath($path) { - return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':'; + return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path,0,2) === '\\'; } /** From 077bbe33729f0a794052f1c7966e7f15211e3b41 Mon Sep 17 00:00:00 2001 From: Quynh Xuan Nguyen Date: Tue, 19 Mar 2019 11:20:21 +0700 Subject: [PATCH 496/580] Correct description grammar --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5ed969969..0878f5b65 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "composer/composer", "type": "library", - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "keywords": [ "package", "dependency", From 8944627245b66948111a33f467b7c865115c559b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Mar 2019 11:34:23 +0100 Subject: [PATCH 497/580] Fix syntax and backslash escaping --- src/Composer/Util/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index d9e83280b..c025a6b8c 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -440,7 +440,7 @@ class Filesystem */ public function isAbsolutePath($path) { - return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path,0,2) === '\\'; + return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\'; } /** From 4441be1a0536789b28bea20c3a86cbcac852a0d2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 19 Mar 2019 18:31:12 +0100 Subject: [PATCH 498/580] Update deps --- composer.lock | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/composer.lock b/composer.lock index d0f72d83c..44b30fa22 100644 --- a/composer.lock +++ b/composer.lock @@ -64,16 +64,16 @@ }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -122,7 +122,7 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", @@ -231,23 +231,23 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.7", + "version": "5.2.8", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23" + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.1", + "friendsofphp/php-cs-fixer": "~2.2.20", "json-schema/json-schema-test-suite": "1.2.0", "phpunit/phpunit": "^4.8.35" }, @@ -293,7 +293,7 @@ "json", "schema" ], - "time": "2018-02-14T22:26:30+00:00" + "time": "2019-01-14T23:55:14+00:00" }, { "name": "psr/log", @@ -437,7 +437,7 @@ }, { "name": "symfony/console", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -498,7 +498,7 @@ }, { "name": "symfony/debug", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -555,7 +555,7 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -605,7 +605,7 @@ }, { "name": "symfony/finder", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -771,7 +771,7 @@ }, { "name": "symfony/process", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -1360,6 +1360,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2015-10-02T06:51:40+00:00" }, { @@ -1736,7 +1737,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.48", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", From fb8b06edef6fee799344aac0ef342e0846ef4974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 21 Mar 2019 19:44:49 +0100 Subject: [PATCH 499/580] Remove unused local variable --- src/Composer/DependencyResolver/Problem.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 073f64e2d..0dcc938fd 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -79,7 +79,6 @@ class Problem reset($reasons); $reason = current($reasons); - $rule = $reason['rule']; $job = $reason['job']; if (isset($job['constraint'])) { From 625bcee63a58e17cd6e2cc981e76541ea526040e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 1 Apr 2019 17:56:03 +0200 Subject: [PATCH 500/580] Fix handling of warnings to incl all 4xx responses --- .../Repository/ComposerRepository.php | 24 +---------- src/Composer/Util/RemoteFilesystem.php | 40 +++++++++++++------ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index b9de0d7ea..38b865103 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -700,7 +700,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = JsonFile::parseJson($json, $filename); - $this->outputWarnings($data); + RemoteFilesystem::outputWarnings($this->io, $this->url, $data); if ($cacheKey) { if ($storeLastModifiedTime) { @@ -765,7 +765,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $data = JsonFile::parseJson($json, $filename); - $this->outputWarnings($data); + RemoteFilesystem::outputWarnings($this->io, $this->url, $data); $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified'); if ($lastModifiedDate) { @@ -826,24 +826,4 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito // wipe rootData as it is fully consumed at this point and this saves some memory $this->rootData = true; } - - private function outputWarnings($data) - { - foreach (array('warning', 'info') as $type) { - if (empty($data[$type])) { - continue; - } - - if (!empty($data[$type . '-versions'])) { - $versionParser = new VersionParser(); - $constraint = $versionParser->parseConstraints($data[$type . '-versions']); - $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); - if (!$constraint->matches($composer)) { - continue; - } - } - - $this->io->writeError('<'.$type.'>'.ucfirst($type).' from '.$this->url.': '.$data[$type].''); - } - } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index ea18a9e30..e1a93fcf9 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -13,6 +13,9 @@ namespace Composer\Util; use Composer\Config; +use Composer\Composer; +use Composer\Semver\Constraint\Constraint; +use Composer\Package\Version\VersionParser; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; @@ -324,15 +327,12 @@ class RemoteFilesystem if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); + if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { + self::outputWarnings($this->io, $originUrl, json_decode($result, true)); + } + if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) { - $warning = null; - if ($this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { - $data = json_decode($result, true); - if (!empty($data['warning'])) { - $warning = $data['warning']; - } - } - $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $warning, $http_response_header); + $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), null, $http_response_header); } } @@ -741,10 +741,6 @@ class RemoteFilesystem throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); } - $this->io->overwriteError(''); - if ($warning) { - $this->io->writeError(' '.$warning.''); - } $this->io->writeError(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); @@ -1090,4 +1086,24 @@ class RemoteFilesystem return count($pathParts) >= 4 && $pathParts[3] == 'downloads'; } + + public static function outputWarnings(IOInterface $io, $url, $data) + { + foreach (array('warning', 'info') as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].''); + } + } } From 88852fbf1adfc1ffeb91e2db7cbbe1aa7f0321f9 Mon Sep 17 00:00:00 2001 From: Jim Birch <5177009+thejimbirch@users.noreply.github.com> Date: Mon, 1 Apr 2019 09:04:32 -0400 Subject: [PATCH 501/580] Updates typo: nother-feature to another-feature --- doc/articles/versions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/versions.md b/doc/articles/versions.md index c1fb25551..e3da6c8ec 100644 --- a/doc/articles/versions.md +++ b/doc/articles/versions.md @@ -32,7 +32,7 @@ repository:* v1 v2 my-feature -nother-feature +another-feature ~/my-library$ git tag v1.0 From 971528916b5c955066a7bc677d94032fd4c99298 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 3 Apr 2019 10:33:58 +0200 Subject: [PATCH 502/580] fix regex for heredoc/nowdoc * take into account relaxed changes introduced in php 7.3 * see: https://github.com/php/php-src/commit/4887357269107ed669463c4b95bd755fbbb52490 * allow " as well as ', which was introduced in php 5.3 closes #8080 --- src/Composer/Autoload/ClassMapGenerator.php | 2 +- .../Test/Autoload/Fixtures/classmap/StripNoise.php | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 5d937433b..f3245bca6 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -162,7 +162,7 @@ class ClassMapGenerator } // strip heredocs/nowdocs - $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + $contents = preg_replace('{<<<\s*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r|\s*)\\2(?=\r\n|\n|\r|\s|;)}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php index 4c344089b..3806791e3 100644 --- a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php +++ b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php @@ -33,12 +33,18 @@ class Fail5 } ANOTHER -. <<< 'ONEMORE' +. <<< "ONEMORE" class Fail6 { } -ONEMORE; +ONEMORE +. << Date: Wed, 3 Apr 2019 11:38:06 +0200 Subject: [PATCH 503/580] expand regex and testcases --- src/Composer/Autoload/ClassMapGenerator.php | 2 +- .../Autoload/Fixtures/classmap/StripNoise.php | 91 +++++++++++++------ 2 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index f3245bca6..b7a3902df 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -162,7 +162,7 @@ class ClassMapGenerator } // strip heredocs/nowdocs - $contents = preg_replace('{<<<\s*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r|\s*)\\2(?=\r\n|\n|\r|\s|;)}s', 'null', $contents); + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\s+)\\2(?=\s+|[;,.)])}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php index 3806791e3..8944360ee 100644 --- a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php +++ b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php @@ -7,48 +7,81 @@ namespace Foo; */ class StripNoise { - public function test() + public function test_heredoc() { - return <<'; + } + + public function test_simple_string() + { + return 'class FailSimpleString {}'; } } From 4ea8e48bf8830a83defcf31923cef95c89f66391 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Thu, 4 Apr 2019 08:45:08 +0200 Subject: [PATCH 504/580] leading whitespace is optional, but newline is not --- src/Composer/Autoload/ClassMapGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index b7a3902df..1ecf96bfe 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -162,7 +162,7 @@ class ClassMapGenerator } // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\s+)\\2(?=\s+|[;,.)])}s', 'null', $contents); + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed From 043b33ed382b6232f8451a85ff1ab415c5a4e5eb Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Sat, 6 Apr 2019 08:44:23 -0700 Subject: [PATCH 505/580] Fixes #8065: Sort plugins deterministically before loading. --- src/Composer/Autoload/AutoloadGenerator.php | 3 ++- src/Composer/Plugin/PluginManager.php | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 7ea1a3444..2d4c481b1 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -28,6 +28,7 @@ use Composer\Script\ScriptEvents; */ class AutoloadGenerator { + /** * @var EventDispatcher */ @@ -959,7 +960,7 @@ INITIALIZER; * @param array $packageMap * @return array */ - protected function sortPackageMap(array $packageMap) + public function sortPackageMap(array $packageMap) { $packages = array(); $paths = array(); diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index e8f4b58c3..da9fb5c20 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -15,10 +15,10 @@ namespace Composer\Plugin; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; +use Composer\Package\CompletePackage; use Composer\Package\Package; use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryInterface; -use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; @@ -253,8 +253,13 @@ class PluginManager */ private function loadRepository(RepositoryInterface $repo) { - foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */ - if ($package instanceof AliasPackage) { + $packages = $repo->getPackages(); + $generator = $this->composer->getAutoloadGenerator(); + $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $this->composer->getPackage(), $packages); + $sortedPackageMap = array_reverse($generator->sortPackageMap($packageMap)); + foreach ($sortedPackageMap as $fullPackage) { + $package = $fullPackage[0]; /** @var PackageInterface $package */ + if (!($package instanceof CompletePackage)) { continue; } if ('composer-plugin' === $package->getType()) { From 3e6300b5e810aada05581769f25c07f30f1f5a75 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Sat, 6 Apr 2019 08:49:45 -0700 Subject: [PATCH 506/580] code style fix. --- src/Composer/Autoload/AutoloadGenerator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 2d4c481b1..272c6b920 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -28,7 +28,6 @@ use Composer\Script\ScriptEvents; */ class AutoloadGenerator { - /** * @var EventDispatcher */ From a908e22a91895fc63cfa173667ef395b3b6f5447 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Sat, 6 Apr 2019 08:53:32 -0700 Subject: [PATCH 507/580] Fixed code style issues. --- src/Composer/Plugin/PluginManager.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index da9fb5c20..cca4efe54 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -255,7 +255,8 @@ class PluginManager { $packages = $repo->getPackages(); $generator = $this->composer->getAutoloadGenerator(); - $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $this->composer->getPackage(), $packages); + $rootPackage = $this->composer->getPackage(); /** @var PackageInterface $rootPackage */ + $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $rootPackage, $packages); $sortedPackageMap = array_reverse($generator->sortPackageMap($packageMap)); foreach ($sortedPackageMap as $fullPackage) { $package = $fullPackage[0]; /** @var PackageInterface $package */ From 5633a68689a9653dc04a81bd06a37fa83e3a9c17 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Mon, 8 Apr 2019 22:44:08 -0700 Subject: [PATCH 508/580] Add a helper to disable process timeouts The helper can be included in custom script definitions by calling "Composer\\Config::disableProcessTimeout". Example: { "scripts": { "watch": [ "Composer\\Config::disableProcessTimeout", "vendor/bin/long-running-script --watch" ] } } --- src/Composer/Config.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 4d8199ccc..7abca7dfa 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -16,6 +16,7 @@ use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; /** * @author Jordi Boggiano @@ -459,4 +460,20 @@ class Config } } } + + /** + * Used by long-running custom scripts in composer.json + * + * "scripts": { + * "watch": [ + * "Composer\\Config::disableProcessTimeout", + * "vendor/bin/long-running-script --watch" + * ] + * } + */ + public static function disableProcessTimeout() + { + // Override global timeout set earlier by environment or config + ProcessExecutor::setTimeout(0); + } } From 17810b2621cea5f09436a395ed630fb29a6928e7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Apr 2019 12:47:24 +0200 Subject: [PATCH 509/580] Revert composer.json changes if update process throws, fixes #8062 --- src/Composer/Command/RequireCommand.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 4cad91023..508514eb4 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -25,6 +25,7 @@ use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; +use Composer\IO\IOInterface; /** * @author Jérémy Romey @@ -160,16 +161,27 @@ EOT if ($input->getOption('no-update')) { return 0; } - $updateDevMode = !$input->getOption('update-no-dev'); - $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); - $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); - $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + try { + return $this->doUpdate($input, $output, $io, $requirements); + } catch (\Exception $e) { + $this->revertComposerFile(false); + throw $e; + } + } + + private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements) + { // Update packages $this->resetComposer(); $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); + $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); From 12e683e2ee74ace0d3843307843762f4a8186d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 25 Mar 2019 12:12:32 +0200 Subject: [PATCH 510/580] ext-imagick: support version string from ImageMagick 6.x --- src/Composer/Repository/PlatformRepository.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 50cbb4649..3126f860a 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -166,8 +166,14 @@ class PlatformRepository extends ArrayRepository case 'imagick': $imagick = new \Imagick(); $imageMagickVersion = $imagick->getVersion(); - preg_match('/^ImageMagick ([\d.]+)-(\d+)/', $imageMagickVersion['versionString'], $matches); - $prettyVersion = "{$matches[1]}.{$matches[2]}"; + // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org + // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org + preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches); + if (isset($matches[2])) { + $prettyVersion = "{$matches[1]}.{$matches[2]}"; + } else { + $prettyVersion = $matches[1]; + } break; case 'libxml': From d2ab4f66fd79e9f3fe906a8c8b2c8c183819b27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 21 Mar 2019 19:17:55 +0100 Subject: [PATCH 511/580] Extract job packageName & constraint to variables --- src/Composer/DependencyResolver/Problem.php | 67 +++++++++++---------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 0dcc938fd..bb497b549 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -81,8 +81,11 @@ class Problem $job = $reason['job']; - if (isset($job['constraint'])) { - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + $packageName = $job['packageName']; + $constraint = $job['constraint']; + + if (isset($constraint)) { + $packages = $this->pool->whatProvides($packageName, $constraint); } else { $packages = array(); } @@ -90,9 +93,9 @@ class Problem if ($job && $job['cmd'] === 'install' && empty($packages)) { // handle php/hhvm - if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { + if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') { $version = phpversion(); - $available = $this->pool->whatProvides($job['packageName']); + $available = $this->pool->whatProvides($packageName); if (count($available)) { $firstAvailable = reset($available); @@ -103,13 +106,13 @@ class Problem } } - $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; + $msg = "\n - This package requires ".$packageName.$this->constraintToText($constraint).' but '; if (defined('HHVM_VERSION') || count($available)) { return $msg . 'your HHVM version does not satisfy that requirement.'; } - if ($job['packageName'] === 'hhvm') { + if ($packageName === 'hhvm') { return $msg . 'you are running this with PHP and not HHVM.'; } @@ -117,43 +120,43 @@ class Problem } // handle php extensions - if (0 === stripos($job['packageName'], 'ext-')) { - if (false !== strpos($job['packageName'], ' ')) { - return "\n - The requested PHP extension ".$job['packageName'].' should be required as '.str_replace(' ', '-', $job['packageName']).'.'; + if (0 === stripos($packageName, 'ext-')) { + if (false !== strpos($packageName, ' ')) { + return "\n - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.'; } - $ext = substr($job['packageName'], 4); + $ext = substr($packageName, 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; - return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; + return "\n - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; } // handle linked libs - if (0 === stripos($job['packageName'], 'lib-')) { - if (strtolower($job['packageName']) === 'lib-icu') { + if (0 === stripos($packageName, 'lib-')) { + if (strtolower($packageName) === 'lib-icu') { $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.'; - return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error; + return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error; } - return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; + return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; } - if (!preg_match('{^[A-Za-z0-9_./-]+$}', $job['packageName'])) { - $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $job['packageName']); + if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) { + $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName); - return "\n - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; + return "\n - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; } - if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) { - return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; + if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) { + return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; } - if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) { - return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; + if ($providers = $this->pool->whatProvides($packageName, null, true, true)) { + return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; } - return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; + return "\n - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.'; } } @@ -202,27 +205,29 @@ class Problem */ protected function jobToText($job) { + $packageName = $job['packageName']; + $constraint = $job['constraint']; switch ($job['cmd']) { case 'install': - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + $packages = $this->pool->whatProvides($packageName, $constraint); if (!$packages) { - return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']); + return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint); } - return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.'; + return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.'; case 'update': - return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.'; + return 'Update request for '.$packageName.$this->constraintToText($constraint).'.'; case 'remove': - return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).''; + return 'Removal request for '.$packageName.$this->constraintToText($constraint).''; } - if (isset($job['constraint'])) { - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + if (isset($constraint)) { + $packages = $this->pool->whatProvides($packageName, $constraint); } else { $packages = array(); } - return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])'; + return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])'; } protected function getPackageList($packages) From 974a3305ae0f3703c81927dc650b66d823fbf538 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Apr 2019 17:41:09 +0200 Subject: [PATCH 512/580] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ceddfc9..c079b6922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +### [1.8.5] 2019-04-09 + + * HHVM 4.0 is no longer compatible with Composer. Please use PHP instead going forward. + * Added forward compatibility with upcoming 2.0 changes + * Fixed support for PHP 7.3-style heredoc/nowdoc syntax changes in autoload generation + * Fixed require command usage when combined with --ignore-platform-reqs + * Fixed and cleaned up various Windows junctions handling issues + ### [1.8.4] 2019-02-11 * Fixed long standing solver bug leading to odd solving issues in edge cases, see #7946 @@ -737,6 +745,7 @@ * Initial release +[1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5 [1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4 [1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3 [1.8.2]: https://github.com/composer/composer/compare/1.8.1...1.8.2 From de0251953d6196a5931fc991d9e2a595d20ac46c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Apr 2019 17:46:33 +0200 Subject: [PATCH 513/580] Update composer deps --- composer.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.lock b/composer.lock index 05df052f8..849e32752 100644 --- a/composer.lock +++ b/composer.lock @@ -64,16 +64,16 @@ }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -122,28 +122,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -183,7 +182,7 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-03-26T10:23:26+00:00" }, { "name": "composer/xdebug-handler", @@ -1360,6 +1359,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2015-10-02T06:51:40+00:00" }, { From 266a41e0464c8ccf23949e3794189dc4ccf3caba Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Tue, 9 Apr 2019 10:55:33 -0700 Subject: [PATCH 514/580] Refactor sortPackageMap to depend on separate sortPackage function. --- src/Composer/Autoload/AutoloadGenerator.php | 54 +++++++++++++++------ src/Composer/Plugin/PluginManager.php | 7 +-- src/Composer/Util/PackageSorter.php | 10 ++++ 3 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 src/Composer/Util/PackageSorter.php diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 272c6b920..6fc602429 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -17,6 +17,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; +use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; @@ -956,27 +957,18 @@ INITIALIZER; * * Packages of equal weight retain the original order * - * @param array $packageMap + * @param array $packages * @return array */ - public function sortPackageMap(array $packageMap) - { - $packages = array(); - $paths = array(); + public function sortPackages(array $packages) { $usageList = array(); - foreach ($packageMap as $item) { - list($package, $path) = $item; - $name = $package->getName(); - $packages[$name] = $package; - $paths[$name] = $path; - - foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { + foreach ($packages as $package) { /** @var PackageInterface $package */ + foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */ $target = $link->getTarget(); - $usageList[$target][] = $name; + $usageList[$target][] = $package->getName(); } } - $computing = array(); $computed = array(); $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { @@ -1034,9 +1026,41 @@ INITIALIZER; $stable_sort($weightList); - $sortedPackageMap = array(); + $sortedPackages = array(); foreach (array_keys($weightList) as $name) { + $sortedPackages[] = $packages[$name]; + } + return $sortedPackages; + } + + /** + * Sorts packages by dependency weight + * + * Packages of equal weight retain the original order + * + * @param array $packageMap + * @return array + */ + public function sortPackageMap(array $packageMap) + { + $packages = array(); + $paths = array(); + + foreach ($packageMap as $item) { + list($package, $path) = $item; + $name = $package->getName(); + $packages[$name] = $package; + $paths[$name] = $path; + } + + $sortedPackages = $this->sortPackages($packages); + + + $sortedPackageMap = array(); + + foreach ($sortedPackages as $package) { + $name = $package->getName(); $sortedPackageMap[] = array($packages[$name], $paths[$name]); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index cca4efe54..3810992d0 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -255,11 +255,8 @@ class PluginManager { $packages = $repo->getPackages(); $generator = $this->composer->getAutoloadGenerator(); - $rootPackage = $this->composer->getPackage(); /** @var PackageInterface $rootPackage */ - $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $rootPackage, $packages); - $sortedPackageMap = array_reverse($generator->sortPackageMap($packageMap)); - foreach ($sortedPackageMap as $fullPackage) { - $package = $fullPackage[0]; /** @var PackageInterface $package */ + $sortedPackages = array_reverse($generator->sortPackages($packages)); + foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; } diff --git a/src/Composer/Util/PackageSorter.php b/src/Composer/Util/PackageSorter.php new file mode 100644 index 000000000..2899fc959 --- /dev/null +++ b/src/Composer/Util/PackageSorter.php @@ -0,0 +1,10 @@ + Date: Tue, 9 Apr 2019 10:58:36 -0700 Subject: [PATCH 515/580] Move sortPackages to static helper class. --- src/Composer/Autoload/AutoloadGenerator.php | 86 +-------------------- src/Composer/Plugin/PluginManager.php | 4 +- src/Composer/Util/PackageSorter.php | 82 ++++++++++++++++++++ 3 files changed, 86 insertions(+), 86 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 6fc602429..5637157e1 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -17,11 +17,11 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; -use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; use Composer\Script\ScriptEvents; +use Composer\Util\PackageSorter; /** * @author Igor Wiedler @@ -952,88 +952,6 @@ INITIALIZER; ); } - /** - * Sorts packages by dependency weight - * - * Packages of equal weight retain the original order - * - * @param array $packages - * @return array - */ - public function sortPackages(array $packages) { - $usageList = array(); - - foreach ($packages as $package) { /** @var PackageInterface $package */ - foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */ - $target = $link->getTarget(); - $usageList[$target][] = $package->getName(); - } - } - $computing = array(); - $computed = array(); - $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { - // reusing computed importance - if (isset($computed[$name])) { - return $computed[$name]; - } - - // canceling circular dependency - if (isset($computing[$name])) { - return 0; - } - - $computing[$name] = true; - $weight = 0; - - if (isset($usageList[$name])) { - foreach ($usageList[$name] as $user) { - $weight -= 1 - $computeImportance($user); - } - } - - unset($computing[$name]); - $computed[$name] = $weight; - - return $weight; - }; - - $weightList = array(); - - foreach ($packages as $name => $package) { - $weight = $computeImportance($name); - $weightList[$name] = $weight; - } - - $stable_sort = function (&$array) { - static $transform, $restore; - - $i = 0; - - if (!$transform) { - $transform = function (&$v, $k) use (&$i) { - $v = array($v, ++$i, $k, $v); - }; - - $restore = function (&$v, $k) { - $v = $v[3]; - }; - } - - array_walk($array, $transform); - asort($array); - array_walk($array, $restore); - }; - - $stable_sort($weightList); - - $sortedPackages = array(); - - foreach (array_keys($weightList) as $name) { - $sortedPackages[] = $packages[$name]; - } - return $sortedPackages; - } - /** * Sorts packages by dependency weight * @@ -1054,7 +972,7 @@ INITIALIZER; $paths[$name] = $path; } - $sortedPackages = $this->sortPackages($packages); + $sortedPackages = PackageSorter::sortPackages($packages); $sortedPackageMap = array(); diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 3810992d0..bb9b66d83 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -24,6 +24,7 @@ use Composer\Package\Link; use Composer\Semver\Constraint\Constraint; use Composer\DependencyResolver\Pool; use Composer\Plugin\Capability\Capability; +use Composer\Util\PackageSorter; /** * Plugin manager @@ -254,8 +255,7 @@ class PluginManager private function loadRepository(RepositoryInterface $repo) { $packages = $repo->getPackages(); - $generator = $this->composer->getAutoloadGenerator(); - $sortedPackages = array_reverse($generator->sortPackages($packages)); + $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; diff --git a/src/Composer/Util/PackageSorter.php b/src/Composer/Util/PackageSorter.php index 2899fc959..8d8c9a06c 100644 --- a/src/Composer/Util/PackageSorter.php +++ b/src/Composer/Util/PackageSorter.php @@ -3,8 +3,90 @@ namespace Composer\Util; +use Composer\Package\Link; +use Composer\Package\PackageInterface; class PackageSorter { + /** + * Sorts packages by dependency weight + * + * Packages of equal weight retain the original order + * + * @param array $packages + * @return array + */ + public static function sortPackages(array $packages) { + $usageList = array(); + foreach ($packages as $package) { /** @var PackageInterface $package */ + foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */ + $target = $link->getTarget(); + $usageList[$target][] = $package->getName(); + } + } + $computing = array(); + $computed = array(); + $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { + // reusing computed importance + if (isset($computed[$name])) { + return $computed[$name]; + } + + // canceling circular dependency + if (isset($computing[$name])) { + return 0; + } + + $computing[$name] = true; + $weight = 0; + + if (isset($usageList[$name])) { + foreach ($usageList[$name] as $user) { + $weight -= 1 - $computeImportance($user); + } + } + + unset($computing[$name]); + $computed[$name] = $weight; + + return $weight; + }; + + $weightList = array(); + + foreach ($packages as $name => $package) { + $weight = $computeImportance($name); + $weightList[$name] = $weight; + } + + $stable_sort = function (&$array) { + static $transform, $restore; + + $i = 0; + + if (!$transform) { + $transform = function (&$v, $k) use (&$i) { + $v = array($v, ++$i, $k, $v); + }; + + $restore = function (&$v) { + $v = $v[3]; + }; + } + + array_walk($array, $transform); + asort($array); + array_walk($array, $restore); + }; + + $stable_sort($weightList); + + $sortedPackages = array(); + + foreach (array_keys($weightList) as $name) { + $sortedPackages[] = $packages[$name]; + } + return $sortedPackages; + } } From 3501423eabe7348a6f0db9f547e7e211ff5a4210 Mon Sep 17 00:00:00 2001 From: Dane Powell Date: Tue, 9 Apr 2019 11:15:19 -0700 Subject: [PATCH 516/580] Undo previous change. --- src/Composer/Autoload/AutoloadGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 5637157e1..325fb2c87 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -960,7 +960,7 @@ INITIALIZER; * @param array $packageMap * @return array */ - public function sortPackageMap(array $packageMap) + protected function sortPackageMap(array $packageMap) { $packages = array(); $paths = array(); From dd40d74bf6041ddcefa1ebe46fb9117d031b1ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Tue, 9 Apr 2019 18:28:45 +0200 Subject: [PATCH 517/580] Exclude more files from being exported --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitattributes b/.gitattributes index 32378b23e..51b431136 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,8 @@ # Exclude non-essential files from dist /tests export-ignore +.github export-ignore +.php_cs export-ignore +.travis.yml export-ignore +appveyor.yml export-ignore +phpunit.xml.dist export-ignore From a2b647a99eb92e123b7e36ab135baeb7b219207a Mon Sep 17 00:00:00 2001 From: ShiraNai7 Date: Thu, 11 Apr 2019 20:23:31 +0200 Subject: [PATCH 518/580] Handle absolute phar:// paths in autoload_static.php --- src/Composer/Autoload/AutoloadGenerator.php | 18 +++- .../Test/Autoload/AutoloadGeneratorTest.php | 41 +++++++++ .../Test/Autoload/Fixtures/autoload_phar.php | 13 +++ .../Autoload/Fixtures/autoload_phar_psr4.php | 13 +++ .../Fixtures/autoload_phar_static.php | 87 +++++++++++++++++++ 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 tests/Composer/Test/Autoload/Fixtures/autoload_phar.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 7ea1a3444..4d04f7a2f 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -545,7 +545,7 @@ EOF; } } - if (preg_match('/\.phar.+$/', $path)) { + if (strpos($path, '.phar') !== false) { $baseDir = "'phar://' . " . $baseDir; } @@ -769,10 +769,14 @@ HEADER; $filesystem = new Filesystem(); $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); + $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1); $initializer = ''; $prefix = "\0Composer\Autoload\ClassLoader\0"; @@ -795,9 +799,15 @@ HEADER; // See https://bugs.php.net/68057 $staticPhpVersion = 70000; } - $value = var_export($value, true); - $value = str_replace($absoluteVendorPathCode, $vendorPathCode, $value); - $value = str_replace($absoluteAppBaseDirCode, $appBaseDirCode, $value); + $value = strtr( + var_export($value, true), + array( + $absoluteVendorPathCode => $vendorPathCode, + $absoluteVendorPharPathCode => $vendorPharPathCode, + $absoluteAppBaseDirCode => $appBaseDirCode, + $absoluteAppBaseDirPharCode => $appBaseDirPharCode, + ) + ); $value = ltrim(preg_replace('/^ */m', ' $0$0', $value)); $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index c1605bf97..84ac16df7 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -486,6 +486,47 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testPharAutoload() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $package->setAutoload(array( + 'psr-0' => array( + 'Foo' => 'foo.phar', + 'Bar' => 'dir/bar.phar/src', + ), + 'psr-4' => array( + 'Baz\\' => 'baz.phar', + 'Qux\\' => 'dir/qux.phar/src', + ), + )); + + $vendorPackage = new Package('a/a', '1.0', '1.0'); + $vendorPackage->setAutoload(array( + 'psr-0' => array( + 'Lorem' => 'lorem.phar', + 'Ipsum' => 'dir/ipsum.phar/src', + ), + 'psr-4' => array( + 'Dolor\\' => 'dolor.phar', + 'Sit\\' => 'dir/sit.phar/src', + ), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue(array($vendorPackage))); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, 'Phar'); + + $this->assertAutoloadFiles('phar', $this->vendorDir . '/composer'); + $this->assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4'); + $this->assertAutoloadFiles('phar_static', $this->vendorDir . '/composer', 'static'); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php new file mode 100644 index 000000000..7654005f3 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php @@ -0,0 +1,13 @@ + array('phar://' . $vendorDir . '/a/a/lorem.phar'), + 'Ipsum' => array('phar://' . $vendorDir . '/a/a/dir/ipsum.phar/src'), + 'Foo' => array('phar://' . $baseDir . '/foo.phar'), + 'Bar' => array('phar://' . $baseDir . '/dir/bar.phar/src'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php new file mode 100644 index 000000000..f6142a001 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php @@ -0,0 +1,13 @@ + array('phar://' . $vendorDir . '/a/a/dir/sit.phar/src'), + 'Qux\\' => array('phar://' . $baseDir . '/dir/qux.phar/src'), + 'Dolor\\' => array('phar://' . $vendorDir . '/a/a/dolor.phar'), + 'Baz\\' => array('phar://' . $baseDir . '/baz.phar'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php new file mode 100644 index 000000000..486a5c0dc --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php @@ -0,0 +1,87 @@ + + array ( + 'Sit\\' => 4, + ), + 'Q' => + array ( + 'Qux\\' => 4, + ), + 'D' => + array ( + 'Dolor\\' => 6, + ), + 'B' => + array ( + 'Baz\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Sit\\' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/sit.phar/src', + ), + 'Qux\\' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/dir/qux.phar/src', + ), + 'Dolor\\' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dolor.phar', + ), + 'Baz\\' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/baz.phar', + ), + ); + + public static $prefixesPsr0 = array ( + 'L' => + array ( + 'Lorem' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/lorem.phar', + ), + ), + 'I' => + array ( + 'Ipsum' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/ipsum.phar/src', + ), + ), + 'F' => + array ( + 'Foo' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/foo.phar', + ), + ), + 'B' => + array ( + 'Bar' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/dir/bar.phar/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitPhar::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitPhar::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitPhar::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} From 794234946c3b06e942e3d153ad08a9ba8d3a37df Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 15 Apr 2019 16:23:38 +0200 Subject: [PATCH 519/580] Let curl handle proxy and cipher list itself --- src/Composer/Util/Http/CurlDownloader.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 989e63d12..f7ae28a24 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -51,11 +51,9 @@ class CurlDownloader 'http' => array( 'method' => CURLOPT_CUSTOMREQUEST, 'content' => CURLOPT_POSTFIELDS, - 'proxy' => CURLOPT_PROXY, 'header' => CURLOPT_HTTPHEADER, ), 'ssl' => array( - 'ciphers' => CURLOPT_SSL_CIPHER_LIST, 'cafile' => CURLOPT_CAINFO, 'capath' => CURLOPT_CAPATH, ), From 4e14ac7640eed7833b2aa780a84e5b068b465ecd Mon Sep 17 00:00:00 2001 From: Gregor Hyneck Date: Tue, 30 Apr 2019 13:46:17 +0200 Subject: [PATCH 520/580] Add documentation how to run plugins manually --- doc/articles/plugins.md | 5 +++++ doc/articles/scripts.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 228cbac9e..86e24d87b 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -261,6 +261,11 @@ Now the `custom-plugin-command` is available alongside Composer commands. > _Composer commands are based on the [Symfony Console Component][10]._ +## Running plugins manually + +Plugins for an event can be run manually by the `run-script` command. This works the same way as +[running scripts manually](scripts.md#running-scripts-manually). + ## Using Plugins Plugin packages are automatically loaded as soon as they are installed and will diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index e0c27b10f..a3c3ab091 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -189,7 +189,7 @@ composer run-script [--dev] [--no-dev] script ``` For example `composer run-script post-install-cmd` will run any -**post-install-cmd** scripts that have been defined. +**post-install-cmd** scripts and [plugins](plugins.md) that have been defined. You can also give additional arguments to the script handler by appending `--` followed by the handler arguments. e.g. From e33560a33f4c137aed8923f254b581dcf61b5aa8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 30 Apr 2019 14:07:46 +0200 Subject: [PATCH 521/580] Fixed 2.0 branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3a18fe45b..725f211e1 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "2.0-dev" } }, "autoload": { From e37ffb2a44df9cd8c7ab7c667a225a930db9698f Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Tue, 30 Apr 2019 16:38:03 +0100 Subject: [PATCH 522/580] Fix: Bitbucket getChangeDate throws exception for branches containing a slash --- src/Composer/Repository/Vcs/BitbucketDriver.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 556ca5012..730edec3c 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -218,6 +218,13 @@ abstract class BitbucketDriver extends VcsDriver return $this->fallbackDriver->getChangeDate($identifier); } + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', $this->owner, From c35a3c1c071be68fabfb4862dcb7f861d0f053ad Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 2 May 2019 09:35:13 +0200 Subject: [PATCH 523/580] update composer.lock --- composer.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index c678c96ea..eb15db515 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b078b12b2912d599e0c6904f64def484", + "content-hash": "280f5d5184039085b5f22236d267ae82", "packages": [ { "name": "composer/ca-bundle", @@ -1404,7 +1404,6 @@ "mock", "xunit" ], - "abandoned": true, "time": "2015-10-02T06:51:40+00:00" }, { From 5d615a16d175fcbdb67a536ef9d6fc4e8a1f6f2b Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Wed, 10 Apr 2019 10:44:35 -0700 Subject: [PATCH 524/580] Add documentation for Composer\\Config::disableProcessTimeout --- doc/06-config.md | 14 ++++++++++++++ doc/articles/scripts.md | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/doc/06-config.md b/doc/06-config.md index 87d73f8a1..f3afc4eb1 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -9,6 +9,20 @@ Defaults to `300`. The duration processes like git clones can run before Composer assumes they died out. You may need to make this higher if you have a slow connection or huge vendors. +To disable the process timeout on a custom command under `scripts`, a static +helper is available: + +```json +{ + "scripts": { + "test": [ + "Composer\\Config::disableProcessTimeout", + "phpunit" + ] + } +} +``` + ## use-include-path Defaults to `false`. If `true`, the Composer autoloader will also look for classes diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index e0c27b10f..52ed86073 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -221,6 +221,26 @@ to the `phpunit` script. > are easily accessible. In this example no matter if the `phpunit` binary is > actually in `vendor/bin/phpunit` or `bin/phpunit` it will be found and executed. +Although Composer is not intended to manage long-running processes and other +such aspects of PHP projects, it can sometimes be handy to disable the process +timeout on custom commands. This timeout defaults to 300 seconds and can be +overridden for all commands using the config key `process-timeout`, or for +specific commands using an argument to the `run-script` command. + +A static helper also exists that can disable the process timeout for a specific +script directly in composer.json: + +```json +{ + "scripts": { + "test": [ + "Composer\\Config::disableProcessTimeout", + "phpunit" + ] + } +} +``` + ## Referencing scripts To enable script re-use and avoid duplicates, you can call a script from another From 51753bc08ce443e42ec9893a2836a58b476b0653 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 8 May 2019 14:58:46 +0200 Subject: [PATCH 525/580] fixes #8131 --- src/Composer/Json/JsonManipulator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 40c0c09a2..8fe6a9f0a 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -22,7 +22,7 @@ class JsonManipulator private static $DEFINES = '(?(DEFINE) (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (? true | false | null ) - (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) + (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) (? \[ (?: (?&json) \s* (?: , (?&json) \s* )* )? \s* \] ) (? \s* (?&string) \s* : (?&json) \s* ) (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) From 080b0f27e9ac283345b6080ab0999ac49e3c24ac Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 8 May 2019 15:58:02 +0200 Subject: [PATCH 526/580] add missing testcase --- .../Test/Json/JsonManipulatorTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index 05de454ca..d8bc7c200 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -2374,6 +2374,26 @@ class JsonManipulatorTest extends TestCase "package/a": "*" } } +', $manipulator->getContents()); + } + + public function testEscapedUnicodeDoesNotCauseBacktrackLimitErrorGithubIssue8131() + { + $manipulator = new JsonManipulator('{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0" + } +}'); + + $this->assertTrue($manipulator->addLink('require', 'foo/baz', '^1.0')); + $this->assertEquals('{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0", + "foo/baz": "^1.0" + } +} ', $manipulator->getContents()); } } From 8288d2c456287d4ec2d746db1d285d6bd82d52ec Mon Sep 17 00:00:00 2001 From: Sam L Date: Wed, 1 May 2019 11:25:26 -0400 Subject: [PATCH 527/580] Display branches and tags if verbose is specified --- src/Composer/Repository/VcsRepository.php | 61 ++++++++++++----------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index edd0dabf8..8d8bcbdca 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -30,7 +30,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt { protected $url; protected $packageName; - protected $verbose; + protected $isVerbose; + protected $isVeryVerbose; protected $io; protected $config; protected $versionParser; @@ -64,7 +65,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->url = $repoConfig['url']; $this->io = $io; $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; - $this->verbose = $io->isVeryVerbose(); + $this->isVerbose = $io->isVerbose(); + $this->isVeryVerbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; @@ -127,7 +129,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt { parent::initialize(); - $verbose = $this->verbose; + $isVerbose = $this->isVerbose; + $isVeryVerbose = $this->isVeryVerbose; $driver = $this->getDriver(); if (!$driver) { @@ -145,23 +148,23 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->packageName = !empty($data['name']) ? $data['name'] : null; } } catch (\Exception $e) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); } } foreach ($driver->getTags() as $tag => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } // strip the release- prefix from tags if present $tag = str_replace('release-', '', $tag); - $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $verbose); + $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); @@ -173,7 +176,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } if (!$parsedTag = $this->validateTag($tag)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); } continue; @@ -181,7 +184,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt try { if (!$data = $driver->getComposerInformation($identifier)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', no composer file'); } $this->emptyReferences[] = $identifier; @@ -203,7 +206,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt // broken package, version doesn't match tag if ($data['version_normalized'] !== $parsedTag) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); } continue; @@ -211,13 +214,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName; if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); } continue; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } @@ -226,35 +229,35 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e instanceof TransportException && $e->getCode() === 404) { $this->emptyReferences[] = $identifier; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); } continue; } } - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->overwriteError('', false); } $branches = $driver->getBranches(); foreach ($branches as $branch => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } if ($branch === 'trunk' && isset($branches['master'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally'); } continue; } if (!$parsedBranch = $this->validateBranch($branch)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', invalid name'); } continue; @@ -268,7 +271,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch); } - $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $verbose); + $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); @@ -281,7 +284,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt try { if (!$data = $driver->getComposerInformation($identifier)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file'); } $this->emptyReferences[] = $identifier; @@ -292,7 +295,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $data['version'] = $version; $data['version_normalized'] = $parsedBranch; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } @@ -306,12 +309,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } continue; } catch (\Exception $e) { - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->writeError(''); } $this->branchErrorOccurred = true; @@ -322,7 +325,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } $driver->cleanup(); - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->overwriteError('', false); } @@ -367,7 +370,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt return false; } - private function getCachedPackageVersion($version, $identifier, $verbose) + private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose) { if (!$this->versionCache) { return; @@ -375,7 +378,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); if ($cachedPackage === false) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); } @@ -384,14 +387,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($cachedPackage) { $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); } $cachedPackage = null; From c7519144105bf319fd7ce437f9733b3cdc443ce7 Mon Sep 17 00:00:00 2001 From: pfofi <7479939+pfofi@users.noreply.github.com> Date: Fri, 10 May 2019 13:55:31 +0200 Subject: [PATCH 528/580] Fix URL resolution for Composer repositories Composer was unable canonicalize URLs in non-HTTP(S) Composer repositories. For example it was not possible to use a `providers-url` in a repository loaded via the `file://` scheme. See also: #8115 --- .../Repository/ComposerRepository.php | 6 +- .../Repository/ComposerRepositoryTest.php | 67 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 38b865103..1b03885fe 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -562,7 +562,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected function canonicalizeUrl($url) { if ('/' === $url[0]) { - return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url); + if (preg_match('{[^:]+://[^/]*}', $this->url, $matches)) { + return $matches[0] . $url; + } + + return $this->url; } return $url; diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 3e29e8023..8e9216b35 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -204,4 +204,71 @@ class ComposerRepositoryTest extends TestCase $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library') ); } + + /** + * @dataProvider canonicalizeUrlProvider + * + * @param string $expected + * @param string $url + * @param string $repositoryUrl + */ + public function testCanonicalizeUrl($expected, $url, $repositoryUrl) + { + $repository = new ComposerRepository( + array('url' => $repositoryUrl), + new NullIO(), + FactoryMock::createConfig() + ); + + $object = new \ReflectionObject($repository); + + $method = $object->getMethod('canonicalizeUrl'); + $method->setAccessible(true); + + // ComposerRepository::__construct ensures that the repository URL has a + // protocol, so reset it here in order to test all cases. + $property = $object->getProperty('url'); + $property->setAccessible(true); + $property->setValue($repository, $repositoryUrl); + + $this->assertSame($expected, $method->invoke($repository, $url)); + } + + public function canonicalizeUrlProvider() + { + return array( + array( + 'https://example.org/path/to/file', + '/path/to/file', + 'https://example.org', + ), + array( + 'https://example.org/canonic_url', + 'https://example.org/canonic_url', + 'https://should-not-see-me.test', + ), + array( + 'file:///path/to/repository/file', + '/path/to/repository/file', + 'file:///path/to/repository', + ), + array( + // Assert that the repository URL is returned unchanged if it is + // not a URL. + // (Backward compatibility test) + 'invalid_repo_url', + '/path/to/file', + 'invalid_repo_url', + ), + array( + // Assert that URLs can contain sequences resembling pattern + // references as understood by preg_replace() without messing up + // the result. + // (Regression test) + 'https://example.org/path/to/unusual_$0_filename', + '/path/to/unusual_$0_filename', + 'https://example.org', + ), + ); + } } From e7f02be9ffec80384df5d49e463d9f127d6957bc Mon Sep 17 00:00:00 2001 From: pfofi <7479939+pfofi@users.noreply.github.com> Date: Sat, 11 May 2019 16:27:39 +0200 Subject: [PATCH 529/580] Anchor pattern --- src/Composer/Repository/ComposerRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 1b03885fe..8fc40c812 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -562,7 +562,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected function canonicalizeUrl($url) { if ('/' === $url[0]) { - if (preg_match('{[^:]+://[^/]*}', $this->url, $matches)) { + if (preg_match('{^[^:]+://[^/]*}', $this->url, $matches)) { return $matches[0] . $url; } From ce8afe1c95c2594fa0a26c0b13d95df05f993754 Mon Sep 17 00:00:00 2001 From: Guilherme Rossato Date: Mon, 13 May 2019 10:26:27 -0300 Subject: [PATCH 530/580] Document the alternatives to disable the default script timeout Mentioning and giving an example of the usage of the 4 options to disable the default script timeout of 300 seconds: 1. Static helper (already exists and kept). 2. Config key "process-timeout". 3. Environment variable "COMPOSER_PROCESS_TIMEOUT". 4. The "--timeout" parameter. --- doc/articles/scripts.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 52ed86073..99ffd94dc 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -224,11 +224,13 @@ to the `phpunit` script. Although Composer is not intended to manage long-running processes and other such aspects of PHP projects, it can sometimes be handy to disable the process timeout on custom commands. This timeout defaults to 300 seconds and can be -overridden for all commands using the config key `process-timeout`, or for -specific commands using an argument to the `run-script` command. +overridden in a variety of ways depending on the desired effect: it's possible +to disable it for all command using the config key `process-timeout`, or for +a specific call using the `--timeout` parameter of the `run` (`run-scripts`) +command, or using a static helper for specific scripts. -A static helper also exists that can disable the process timeout for a specific -script directly in composer.json: +To disable the timeout for specific scripts with the static helper directly in +composer.json: ```json { @@ -241,6 +243,31 @@ script directly in composer.json: } ``` +To disable the timeout for every script on a given project, you can use the +composer.json configuration: + +```json +{ + "config": { + "process-timeout": 0 + } +} +``` + +It's also possible to set the global environment variable to disable the timeout +of all following scripts in the current terminal environment: + +``` +export COMPOSER_PROCESS_TIMEOUT=0 +``` + +To disable the timeout of a single script call, you must use the `run` composer +command and specify the `--timeout` parameter: + +``` +composer run test --timeout=0 +``` + ## Referencing scripts To enable script re-use and avoid duplicates, you can call a script from another From 1976da9ee951e1c7027b25b364d68b7b7aa10257 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 14 May 2019 10:18:13 +0200 Subject: [PATCH 531/580] modify text --- doc/articles/scripts.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 99ffd94dc..18417bfef 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -224,10 +224,13 @@ to the `phpunit` script. Although Composer is not intended to manage long-running processes and other such aspects of PHP projects, it can sometimes be handy to disable the process timeout on custom commands. This timeout defaults to 300 seconds and can be -overridden in a variety of ways depending on the desired effect: it's possible -to disable it for all command using the config key `process-timeout`, or for -a specific call using the `--timeout` parameter of the `run` (`run-scripts`) -command, or using a static helper for specific scripts. +overridden in a variety of ways depending on the desired effect: + +- disable it for all commands using the config key `process-timeout`, +- disable it for the current or future invocations of composer using the + environment variable `COMPOSER_PROCESS_TIMEOUT`, +- for a specific invocation using the `--timeout` flag of the `run-script` command, +- using a static helper for specific scripts. To disable the timeout for specific scripts with the static helper directly in composer.json: From 7f34189f91a06531bb8a8a3afcb9cf88f03fdbc7 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 14 May 2019 10:19:37 +0200 Subject: [PATCH 532/580] use full command name, not abbreviated/alias --- doc/articles/scripts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 18417bfef..771093610 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -264,11 +264,11 @@ of all following scripts in the current terminal environment: export COMPOSER_PROCESS_TIMEOUT=0 ``` -To disable the timeout of a single script call, you must use the `run` composer +To disable the timeout of a single script call, you must use the `run-script` composer command and specify the `--timeout` parameter: ``` -composer run test --timeout=0 +composer run-script test --timeout=0 ``` ## Referencing scripts From d63bf33848f396c58c5e0de834cf15f9d7094b95 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 14 May 2019 10:20:32 +0200 Subject: [PATCH 533/580] flag should come before script name --- doc/articles/scripts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 771093610..9516b82bc 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -268,7 +268,7 @@ To disable the timeout of a single script call, you must use the `run-script` co command and specify the `--timeout` parameter: ``` -composer run-script test --timeout=0 +composer run-script --timeout=0 test ``` ## Referencing scripts From faa7c5eea2de7e053f38c0726c01147531687361 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sun, 19 May 2019 20:52:53 +0200 Subject: [PATCH 534/580] Allow overriding self-update target file with envvar COMPOSER_SELF_UPDATE_TARGET Useful if Composer is provided on a read-only filesystems, to allow self-update to work with a different destination --- src/Composer/Command/SelfUpdateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 78b27460e..226aafeb5 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -351,6 +351,10 @@ TAGSPUBKEY @copy($localFilename, $backupTarget); } + if ($targetFilename = getenv('COMPOSER_SELF_UPDATE_TARGET')) { + $localFilename = realpath($targetFilename) ?: $targetFilename; + } + rename($newFilename, $localFilename); return null; From bd6b758a1be2ffd722690e27e83a0ab685a06d37 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Wed, 29 May 2019 08:45:05 +0200 Subject: [PATCH 535/580] fixes #8159 expand interface and add missing methods to aliaspackage --- src/Composer/Package/AliasPackage.php | 10 ++++++++ src/Composer/Package/PackageInterface.php | 28 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 09ed4fb9b..89f197856 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -401,4 +401,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')'; } + + public function setDistUrl($url) + { + return $this->aliasOf->setDistUrl($url); + } + + public function setDistType($type) + { + return $this->aliasOf->setDistType($type); + } } diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 73d2ade41..cb16efa7e 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -358,4 +358,32 @@ interface PackageInterface * @return array */ public function getTransportOptions(); + + /** + * @param string $reference + * + * @return void + */ + public function setSourceReference($reference); + + /** + * @param string $url + * + * @return void + */ + public function setDistUrl($url); + + /** + * @param string $type + * + * @return void + */ + public function setDistType($type); + + /** + * @param string $reference + * + * @return void + */ + public function setDistReference($reference); } From 9d79c69199b348789981e99ee3cf192f36337e1d Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 27 May 2019 19:54:30 +0100 Subject: [PATCH 536/580] Update xdebug-handler to 1.3.3 --- composer.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 849e32752..08679183c 100644 --- a/composer.lock +++ b/composer.lock @@ -186,16 +186,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "d17708133b6c276d6e42ef887a877866b909d892" + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", - "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", "shasum": "" }, "require": { @@ -226,7 +226,7 @@ "Xdebug", "performance" ], - "time": "2019-01-28T20:25:53+00:00" + "time": "2019-05-27T17:52:04+00:00" }, { "name": "justinrainbow/json-schema", From 82825ccc74b977a86010dc78a80502e0dd5dc5dd Mon Sep 17 00:00:00 2001 From: pfofi <7479939+pfofi@users.noreply.github.com> Date: Fri, 7 Jun 2019 09:13:11 +0200 Subject: [PATCH 537/580] Use possessive quantifiers --- src/Composer/Repository/ComposerRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8fc40c812..9d5b727cc 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -562,7 +562,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected function canonicalizeUrl($url) { if ('/' === $url[0]) { - if (preg_match('{^[^:]+://[^/]*}', $this->url, $matches)) { + if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) { return $matches[0] . $url; } From 659c72f9c8db708ea6490b9daf8195e2ad7319ab Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 7 Jun 2019 13:11:53 +0200 Subject: [PATCH 538/580] Read classmap-authoritative and apcu-autoloader from project config when installing via create-project, fixes #8155 --- src/Composer/Command/CreateProjectCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 3702c3595..2985aa57a 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -184,7 +184,9 @@ EOT ->setRunScripts(!$noScripts) ->setIgnorePlatformRequirements($ignorePlatformReqs) ->setSuggestedPackagesReporter($this->suggestedPackagesReporter) - ->setOptimizeAutoloader($config->get('optimize-autoloader')); + ->setOptimizeAutoloader($config->get('optimize-autoloader')) + ->setClassMapAuthoritative($config->get('classmap-authoritative')) + ->setApcuAutoloader($config->get('apcu-autoloader')); if ($disablePlugins) { $installer->disablePlugins(); From 088fb56c3d6f1264760d495d40edf7569ee83068 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 7 Jun 2019 16:27:40 +0200 Subject: [PATCH 539/580] Fix display of HHVM warning appearing when HHVM is not in use, fixes #8138 --- src/Composer/DependencyResolver/Problem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 073f64e2d..c7e529889 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -106,7 +106,7 @@ class Problem $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; - if (defined('HHVM_VERSION') || count($available)) { + if (defined('HHVM_VERSION') || (count($available) && $job['packageName'] === 'hhvm')) { return $msg . 'your HHVM version does not satisfy that requirement.'; } From e7eecc6901698ea352bff11ccabe5a1b6a6122c6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 7 Jun 2019 16:49:07 +0200 Subject: [PATCH 540/580] Add docs for COMPOSER_SELF_UPDATE_TARGET, refs #8151 --- doc/03-cli.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 6460e9e1d..5fc527c49 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -917,6 +917,10 @@ if you use Composer as super user at all times like in docker containers. If set, the value is used as php's memory_limit. +### COMPOSER_SELF_UPDATE_TARGET + +If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem. + ### COMPOSER_MIRROR_PATH_REPOS If set to 1, this env changes the default path repository strategy to `mirror` instead From b4e5db1c7055ede2952cb8f77f03cc9aafe00f09 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 7 Jun 2019 17:24:34 +0200 Subject: [PATCH 541/580] Revert "Allow overriding self-update target file with envvar COMPOSER_SELF_UPDATE_TARGET" Revert "Add docs for COMPOSER_SELF_UPDATE_TARGET, refs #8151" This reverts commit e7eecc6901698ea352bff11ccabe5a1b6a6122c6. This reverts commit faa7c5eea2de7e053f38c0726c01147531687361. --- doc/03-cli.md | 4 ---- src/Composer/Command/SelfUpdateCommand.php | 4 ---- 2 files changed, 8 deletions(-) diff --git a/doc/03-cli.md b/doc/03-cli.md index 5fc527c49..6460e9e1d 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -917,10 +917,6 @@ if you use Composer as super user at all times like in docker containers. If set, the value is used as php's memory_limit. -### COMPOSER_SELF_UPDATE_TARGET - -If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem. - ### COMPOSER_MIRROR_PATH_REPOS If set to 1, this env changes the default path repository strategy to `mirror` instead diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 226aafeb5..78b27460e 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -351,10 +351,6 @@ TAGSPUBKEY @copy($localFilename, $backupTarget); } - if ($targetFilename = getenv('COMPOSER_SELF_UPDATE_TARGET')) { - $localFilename = realpath($targetFilename) ?: $targetFilename; - } - rename($newFilename, $localFilename); return null; From b73120cbbe16f758f3e9189d49970a8627211a9f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 11 Jun 2019 15:02:45 +0200 Subject: [PATCH 542/580] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c079b6922..a150aaad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### [1.8.6] 2019-06-11 + + * Fixed handling of backslash-escapes handling in compoesr.json when using the require command + * Fixed create-project not following classmap-authoritative and apcu-autoloader config values + * Fixed HHVM version warning showing up in some cases when it was not in use + ### [1.8.5] 2019-04-09 * HHVM 4.0 is no longer compatible with Composer. Please use PHP instead going forward. @@ -745,6 +751,7 @@ * Initial release +[1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6 [1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5 [1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4 [1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3 From 76da8d792eeae2e5e136c78454ed5bc9e8aee7d6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 11 Jun 2019 15:08:33 +0200 Subject: [PATCH 543/580] Update deps --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index febcdbe86..8b7b3cb57 100644 --- a/composer.lock +++ b/composer.lock @@ -436,7 +436,7 @@ }, { "name": "symfony/console", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -497,7 +497,7 @@ }, { "name": "symfony/debug", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -554,7 +554,7 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -604,7 +604,7 @@ }, { "name": "symfony/finder", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -653,16 +653,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -674,7 +674,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -707,20 +707,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -732,7 +732,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -766,11 +766,11 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -1736,7 +1736,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", From 6c76310810ebdbdfd004441f42509fd2d6640907 Mon Sep 17 00:00:00 2001 From: "Theodore R. Smith" Date: Tue, 11 Jun 2019 09:35:51 -0500 Subject: [PATCH 544/580] [minor] Fixed a typo in the CHANGELOG.md. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a150aaad8..2e6075148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ### [1.8.6] 2019-06-11 - * Fixed handling of backslash-escapes handling in compoesr.json when using the require command + * Fixed handling of backslash-escapes handling in composer.json when using the require command * Fixed create-project not following classmap-authoritative and apcu-autoloader config values * Fixed HHVM version warning showing up in some cases when it was not in use From 81a4f74b5b78beadaea6443973951fbe6827720a Mon Sep 17 00:00:00 2001 From: Ken Love Date: Wed, 12 Jun 2019 16:54:09 -0400 Subject: [PATCH 545/580] Composer\Script\Event should have access to originating event details --- .../EventDispatcher/EventDispatcher.php | 4 ++- src/Composer/Script/Event.php | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index ee02381e5..d5bdb4c97 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -196,7 +196,9 @@ class EventDispatcher } try { - $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); + $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); } catch (ScriptExecutionException $e) { $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); throw $e; diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index 138f43c1a..bbad249b2 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -39,6 +39,11 @@ class Event extends BaseEvent */ private $devMode; + /** + * @var BaseEvent + */ + private $originatingEvent; + /** * Constructor. * @@ -55,6 +60,7 @@ class Event extends BaseEvent $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; + $this->originatingEvent = null; } /** @@ -86,4 +92,27 @@ class Event extends BaseEvent { return $this->devMode; } + + /** + * Set the originating event. + * + * @return \Composer\EventDispatcher\Event|null + */ + public function getOriginatingEvent() + { + return $this->originatingEvent; + } + + /** + * Get the originating event. + * + * @param \Composer\EventDispatcher\Event $event + * @return $this + */ + public function setOriginatingEvent(BaseEvent $event) + { + $this->originatingEvent = $event; + + return $this; + } } From b51cfce8e6983671b9b732ce88ae603f298378d1 Mon Sep 17 00:00:00 2001 From: Ken Love Date: Thu, 13 Jun 2019 14:51:27 -0400 Subject: [PATCH 546/580] return the upper-most event in chain --- src/Composer/Script/Event.php | 29 +++++++--- tests/Composer/Test/Script/EventTest.php | 73 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 tests/Composer/Test/Script/EventTest.php diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index bbad249b2..21aeb83ed 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -103,16 +103,31 @@ class Event extends BaseEvent return $this->originatingEvent; } - /** - * Get the originating event. - * - * @param \Composer\EventDispatcher\Event $event - * @return $this - */ + /** + * Set the originating event. + * + * @param \Composer\EventDispatcher\Event $event + * @return $this + */ public function setOriginatingEvent(BaseEvent $event) { - $this->originatingEvent = $event; + $this->originatingEvent = $this->calculateOriginatingEvent($event); return $this; } + + /** + * Returns the upper-most event in chain. + * + * @param \Composer\EventDispatcher\Event $event + * @return \Composer\EventDispatcher\Event + */ + private function calculateOriginatingEvent(BaseEvent $event) + { + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } + + return $event; + } } diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php new file mode 100644 index 000000000..2b8818500 --- /dev/null +++ b/tests/Composer/Test/Script/EventTest.php @@ -0,0 +1,73 @@ +getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); + + $scriptEvent = new Event('test', $composer, $io, true); + + $this->assertNull( + $scriptEvent->getOriginatingEvent(), + 'originatingEvent is initialized as null' + ); + + $scriptEvent->setOriginatingEvent($originatingEvent); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return test event' + ); + + } + + public function testEventCalculatesNestedOriginatingEvent() { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + + $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); + $intermediateEvent = new Event('intermediate', $composer, $io, true); + $intermediateEvent->setOriginatingEvent($originatingEvent); + + $scriptEvent = new Event('test', $composer, $io, true); + $scriptEvent->setOriginatingEvent($intermediateEvent); + + $this->assertNotSame( + $intermediateEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD NOT return intermediate events' + ); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return upper-most event' + ); + + } + + private function createComposerInstance() + { + $composer = new Composer; + $config = new Config; + $composer->setConfig($config); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $composer->setPackage($package); + + return $composer; + } +} \ No newline at end of file From 7399638e43fb7e7c361efe8ec38b0baf3fc49676 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Tue, 11 Jun 2019 13:33:05 +0200 Subject: [PATCH 547/580] fixes #8179 --- src/Composer/Repository/Vcs/HgDriver.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 45f13d5fe..f943a8a0a 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -61,8 +61,9 @@ class HgDriver extends VcsDriver // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); - $command = function ($url) { - return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir)); + $repoDir = $this->repoDir; + $command = function ($url) use ($repoDir) { + return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir)); }; $hgUtils->runCommand($command, $this->url, $this->repoDir); From 89d5d8f1824d66163ee04761567c11e81f814a61 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jun 2019 18:34:16 +0200 Subject: [PATCH 548/580] Free $solver asap --- src/Composer/Installer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8573f3695..9583c3eb1 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -473,6 +473,8 @@ class Installer $solver = new Solver($policy, $pool, $installedRepo, $this->io); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); + $ruleSetSize = $solver->getRuleSetSize(); + $solver = null; } catch (SolverProblemsException $e) { $this->io->writeError('Your requirements could not be resolved to an installable set of packages.', true, IOInterface::QUIET); $this->io->writeError($e->getMessage()); @@ -489,7 +491,7 @@ class Installer $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations); $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); - $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); + $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); // execute operations if (!$operations) { From 8da046e4e982b7a8c01b038b19242dc339847cd8 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Sun, 23 Jun 2019 18:59:36 +0100 Subject: [PATCH 549/580] SVN: hide passwords for debug output --- src/Composer/Util/ProcessExecutor.php | 1 + .../Test/Util/ProcessExecutorTest.php | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index f5e1ef610..d72a02981 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -51,6 +51,7 @@ class ProcessExecutor return '://'.$m['user'].':***@'; }, $command); + $safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand); $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); } diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index e98898417..db16b8c02 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -61,11 +61,25 @@ class ProcessExecutorTest extends TestCase ProcessExecutor::setTimeout(60); } - public function testHidePasswords() + /** + * @dataProvider hidePasswordProvider + */ + public function testHidePasswords($command, $expectedCommandOutput) { $process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG)); - $process->execute('echo https://foo:bar@example.org/ && echo http://foo@example.org && echo http://abcdef1234567890234578:x-oauth-token@github.com/', $output); - $this->assertEquals('Executing command (CWD): echo https://foo:***@example.org/ && echo http://foo@example.org && echo http://***:***@github.com/', trim($buffer->getOutput())); + $process->execute($command, $output); + $this->assertEquals('Executing command (CWD): ' . $expectedCommandOutput, trim($buffer->getOutput())); + } + + public function hidePasswordProvider() + { + return array( + array('echo https://foo:bar@example.org/', 'echo https://foo:***@example.org/'), + array('echo http://foo@example.org', 'echo http://foo@example.org'), + array('echo http://abcdef1234567890234578:x-oauth-token@github.com/', 'echo http://***:***@github.com/'), + array("svn ls --verbose --non-interactive --username 'foo' --password 'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"), + array("svn ls --verbose --non-interactive --username 'foo' --password 'bar \'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"), + ); } public function testDoesntHidePorts() From 8def53c4b3cd7ef09d35ee566b92cfafb4e55421 Mon Sep 17 00:00:00 2001 From: kpitn Date: Tue, 9 Jul 2019 15:29:21 +0200 Subject: [PATCH 550/580] Update only one package This PR come with https://github.com/composer/satis/pull/509 --- .../handling-private-packages-with-satis.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index cdf31f6e4..3ef604fe7 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -112,6 +112,19 @@ Note that this will still need to pull and scan all of your VCS repositories because any VCS repository might contain (on any branch) one of the selected packages. +If you want to scan only the selected package and not all VCS repositories you need +to declare a *name* for all your package (this only work on VCS repositories type) : + +```json +{ + "repositories": [ + { "name": "company/privaterepo", "type": "vcs", "url": "https://github.com/mycompany/privaterepo" }, + { "name": "private/repo", "type": "vcs", "url": "http://svn.example.org/private/repo" }, + { "name": "mycompany/privaterepo2", "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" } + ] +} +``` + If you want to scan only a single repository and update all packages found in it, pass the VCS repository URL as an optional argument: From a4611d511fcd7719f3343e20058b04b3c39d200f Mon Sep 17 00:00:00 2001 From: Baptiste Lafontaine Date: Thu, 11 Jul 2019 16:48:57 +0200 Subject: [PATCH 551/580] Ignore platform reqs now handle conflict rules --- src/Composer/DependencyResolver/RuleSetGenerator.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 6117c1d95..e8714a405 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -233,7 +233,7 @@ class RuleSetGenerator } } - protected function addConflictRules() + protected function addConflictRules($ignorePlatformReqs = false) { /** @var PackageInterface $package */ foreach ($this->addedPackages as $package) { @@ -242,6 +242,10 @@ class RuleSetGenerator continue; } + if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { + continue; + } + /** @var PackageInterface $possibleConflict */ foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) { $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true); @@ -362,7 +366,7 @@ class RuleSetGenerator $this->addRulesForJobs($ignorePlatformReqs); - $this->addConflictRules(); + $this->addConflictRules($ignorePlatformReqs); // Remove references to packages $this->addedPackages = $this->addedPackagesByNames = null; From b935d1c8129f4f97e7d7a1cc9e5eea64a1d32890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=BDurek?= Date: Fri, 12 Jul 2019 18:34:12 +0200 Subject: [PATCH 552/580] fixed phpstan error --- tests/Composer/Test/Repository/ComposerRepositoryTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 47df3a443..22a58cf4f 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -227,7 +227,9 @@ class ComposerRepositoryTest extends TestCase $repository = new ComposerRepository( array('url' => $repositoryUrl), new NullIO(), - FactoryMock::createConfig() + FactoryMock::createConfig(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); $object = new \ReflectionObject($repository); From fedc69f828d138d13eeb3afc32969ec93c497e90 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Sun, 14 Jul 2019 15:25:01 +0300 Subject: [PATCH 553/580] add php 7.4snapshot --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f02fefcb1..c1b0db05b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ matrix: - php: 7.3 env: deps=high - php: nightly + - php: 7.4snapshot fast_finish: true allow_failures: - php: nightly From 54cd1290cbe56ee26244bef9e1c534435fb5b296 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Tue, 16 Jul 2019 14:41:39 +0300 Subject: [PATCH 554/580] add to allow_failures --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c1b0db05b..2e3270b65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: fast_finish: true allow_failures: - php: nightly + - php: 7.4snapshot before_install: # disable xdebug if available From b4fc3b7eefc6c6437ca1da7815848abf6ae07568 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:39:40 +0200 Subject: [PATCH 555/580] Make usage of foreach to improve readability Instead of count and comparing, we can simple use a foreach. --- tests/Composer/Test/AllFunctionalTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 7a3ef3ee0..5e8ebb5c4 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -162,18 +162,18 @@ class AllFunctionalTest extends TestCase } }; - for ($i = 0, $c = count($tokens); $i < $c; $i++) { - if ('' === $tokens[$i] && null === $section) { + foreach ($tokens as $token) { + if ('' === $token && null === $section) { continue; } // Handle section headers. if (null === $section) { - $section = $tokens[$i]; + $section = $token; continue; } - $sectionData = $tokens[$i]; + $sectionData = $token; // Allow sections to validate, or modify their section data. switch ($section) { From 6c8ddd4d57a2cf36568bdc5353d00cdad73a90eb Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:48:48 +0200 Subject: [PATCH 556/580] Remove unused private properties --- src/Composer/Util/TlsHelper.php | 2 -- tests/Composer/Test/Downloader/ZipDownloaderTest.php | 1 - tests/Composer/Test/Util/GitHubTest.php | 3 --- tests/Composer/Test/Util/GitLabTest.php | 1 - 4 files changed, 7 deletions(-) diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 34336d06c..a53212f2d 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -19,8 +19,6 @@ use Composer\CaBundle\CaBundle; */ final class TlsHelper { - private static $useOpensslParse; - /** * Match hostname against a certificate. * diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 466fd35c7..239ec9221 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -23,7 +23,6 @@ class ZipDownloaderTest extends TestCase * @var string */ private $testDir; - private $prophet; private $io; private $config; diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 28d00ce69..9be1307e2 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -23,12 +23,9 @@ use RecursiveIteratorIterator; */ class GitHubTest extends TestCase { - private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'github.com'; - private $token = 'githubtoken'; public function testUsernamePasswordAuthenticationFlow() { diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 27f46b4ad..8a1b97af0 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -23,7 +23,6 @@ class GitLabTest extends TestCase { private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'gitlab.com'; private $token = 'gitlabtoken'; From 4cb2b303ec3bc9b709cb47d398593da3f558067d Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:57:08 +0200 Subject: [PATCH 557/580] Remove override assignment --- tests/Composer/Test/Package/Loader/ArrayLoaderTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index e3b9dc491..ca1cec0db 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -148,7 +148,6 @@ class ArrayLoaderTest extends TestCase { $package = $this->loader->load($config); $dumper = new ArrayDumper; - $expectedConfig = $config; $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config); $this->assertEquals($expectedConfig, $dumper->dump($package)); } From 8b5be1d08c74926d25a740691d45e3afe7b780bd Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 03:07:25 +0200 Subject: [PATCH 558/580] Remove explicts void returns --- src/Composer/Downloader/PerforceDownloader.php | 2 -- src/Composer/Util/Perforce.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index a472b84c6..92091e2c9 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -87,8 +87,6 @@ class PerforceDownloader extends VcsDownloader public function getLocalChanges(PackageInterface $package, $path) { $this->io->writeError('Perforce driver does not check for local changes before overriding', true); - - return; } /** diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index 31ddeffec..52080d663 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -363,8 +363,6 @@ class Perforce while ($line !== false) { $line = fgets($pipe); } - - return; } public function windowsLogin($password) From 1d05d4171c1179a0fbeccef8c22d40492bca8c85 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 03:01:00 +0200 Subject: [PATCH 559/580] Remove unused private methods --- tests/Composer/Test/DependencyResolver/RuleSetTest.php | 7 ------- tests/Composer/Test/Repository/Vcs/FossilDriverTest.php | 9 --------- tests/Composer/Test/Repository/Vcs/SvnDriverTest.php | 9 --------- 3 files changed, 25 deletions(-) diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index bd6efbc1b..22c1c15d0 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -157,11 +157,4 @@ class RuleSetTest extends TestCase $this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($this->pool)); } - - private function getRuleMock() - { - return $this->getMockBuilder('Composer\DependencyResolver\Rule') - ->disableOriginalConstructor() - ->getMock(); - } } diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index cbb4342e9..f39a3b8f7 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -40,15 +40,6 @@ class FossilDriverTest extends TestCase $fs->removeDirectory($this->home); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 029d20160..4ef0d9bcc 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -70,15 +70,6 @@ class SvnDriverTest extends TestCase $svn->initialize(); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( From 45591597f6e239c59d11ad7303e2d50220144469 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 16:37:35 +0200 Subject: [PATCH 560/580] Clarify how check-platform-reqs works, fixes #8191 --- doc/03-cli.md | 4 ++++ src/Composer/Command/CheckPlatformReqsCommand.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 49879d54d..8cfd5cac0 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -259,6 +259,10 @@ match the platform requirements of the installed packages. This can be used to verify that a production server has all the extensions needed to run a project after installing it for example. +Unlike update/install, this command will ignore config.platform settings and +check the real platform packages so you can be certain you have the required +platform dependencies. + ## global The global command allows you to run other commands like `install`, `remove`, `require` diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index de7f4b4d3..8ce0b0186 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -34,6 +34,8 @@ class CheckPlatformReqsCommand extends BaseCommand <<php composer.phar check-platform-reqs EOT From 369e8a22474a9a85da43c6b627c89c1fa0b6fd0e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 16:43:24 +0200 Subject: [PATCH 561/580] Fix indenting --- src/Composer/Script/Event.php | 20 ++--- tests/Composer/Test/Script/EventTest.php | 109 ++++++++++++----------- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index 21aeb83ed..5fab172bf 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -39,9 +39,9 @@ class Event extends BaseEvent */ private $devMode; - /** - * @var BaseEvent - */ + /** + * @var BaseEvent + */ private $originatingEvent; /** @@ -100,7 +100,7 @@ class Event extends BaseEvent */ public function getOriginatingEvent() { - return $this->originatingEvent; + return $this->originatingEvent; } /** @@ -111,9 +111,9 @@ class Event extends BaseEvent */ public function setOriginatingEvent(BaseEvent $event) { - $this->originatingEvent = $this->calculateOriginatingEvent($event); + $this->originatingEvent = $this->calculateOriginatingEvent($event); - return $this; + return $this; } /** @@ -124,10 +124,10 @@ class Event extends BaseEvent */ private function calculateOriginatingEvent(BaseEvent $event) { - if ($event instanceof Event && $event->getOriginatingEvent()) { - return $this->calculateOriginatingEvent($event->getOriginatingEvent()); - } + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } - return $event; + return $event; } } diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php index 2b8818500..b7c8cd9ff 100644 --- a/tests/Composer/Test/Script/EventTest.php +++ b/tests/Composer/Test/Script/EventTest.php @@ -1,73 +1,80 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Composer\Test\Script; - use Composer\Composer; use Composer\Config; use Composer\Script\Event; use Composer\Test\TestCase; -class EventTest extends TestCase { +class EventTest extends TestCase +{ + public function testEventSetsOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); - public function testEventSetsOriginatingEvent() { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $composer = $this->createComposerInstance(); + $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); - $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); + $scriptEvent = new Event('test', $composer, $io, true); - $scriptEvent = new Event('test', $composer, $io, true); + $this->assertNull( + $scriptEvent->getOriginatingEvent(), + 'originatingEvent is initialized as null' + ); - $this->assertNull( - $scriptEvent->getOriginatingEvent(), - 'originatingEvent is initialized as null' - ); + $scriptEvent->setOriginatingEvent($originatingEvent); - $scriptEvent->setOriginatingEvent($originatingEvent); + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return test event' + ); + } - $this->assertSame( - $originatingEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD return test event' - ); + public function testEventCalculatesNestedOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); - } + $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); + $intermediateEvent = new Event('intermediate', $composer, $io, true); + $intermediateEvent->setOriginatingEvent($originatingEvent); - public function testEventCalculatesNestedOriginatingEvent() { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $composer = $this->createComposerInstance(); + $scriptEvent = new Event('test', $composer, $io, true); + $scriptEvent->setOriginatingEvent($intermediateEvent); + $this->assertNotSame( + $intermediateEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD NOT return intermediate events' + ); - $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); - $intermediateEvent = new Event('intermediate', $composer, $io, true); - $intermediateEvent->setOriginatingEvent($originatingEvent); + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return upper-most event' + ); + } - $scriptEvent = new Event('test', $composer, $io, true); - $scriptEvent->setOriginatingEvent($intermediateEvent); + private function createComposerInstance() + { + $composer = new Composer; + $config = new Config; + $composer->setConfig($config); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $composer->setPackage($package); - $this->assertNotSame( - $intermediateEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD NOT return intermediate events' - ); - - $this->assertSame( - $originatingEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD return upper-most event' - ); - - } - - private function createComposerInstance() - { - $composer = new Composer; - $config = new Config; - $composer->setConfig($config); - $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); - $composer->setPackage($package); - - return $composer; - } -} \ No newline at end of file + return $composer; + } +} From 33759d02c4d730860dd31e949207fc43601caef5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 17:42:34 +0200 Subject: [PATCH 562/580] Fix require command to allow working on network mounts, fixes #8231 --- src/Composer/Command/RequireCommand.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 508514eb4..9b12941b8 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -26,6 +26,7 @@ use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\IO\IOInterface; +use Composer\Util\Silencer; /** * @author Jérémy Romey @@ -103,11 +104,6 @@ EOT return 1; } - if (!is_writable($this->file)) { - $io->writeError(''.$this->file.' is not writable.'); - - return 1; - } if (filesize($this->file) === 0) { file_put_contents($this->file, "{\n}\n"); @@ -116,6 +112,14 @@ EOT $this->json = new JsonFile($this->file); $this->composerBackup = file_get_contents($this->json->getPath()); + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { + $io->writeError(''.$this->file.' is not writable.'); + + return 1; + } + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $repos = $composer->getRepositoryManager()->getRepositories(); From 8958f40f94239320f9849b2c3cfe31cad2f6b40c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 17:57:18 +0200 Subject: [PATCH 563/580] Make sure resetting composer also resets the IO and configuration, fixes #8224 --- src/Composer/Console/Application.php | 3 +++ src/Composer/IO/BaseIO.php | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index fe5dbaccf..d7d113492 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -373,6 +373,9 @@ class Application extends BaseApplication public function resetComposer() { $this->composer = null; + if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) { + $this->getIO()->resetAuthentications(); + } } /** diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index b327f1bbf..8f61c863d 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -29,6 +29,14 @@ abstract class BaseIO implements IOInterface, LoggerInterface return $this->authentications; } + /** + * {@inheritDoc} + */ + public function resetAuthentications() + { + $this->authentications = array(); + } + /** * {@inheritDoc} */ From 1a391b572cc82b681ce9f52b6db6aa87718fb137 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 09:18:19 +0200 Subject: [PATCH 564/580] Prevent require command from allowing a package to require itself, fixes #8247 --- src/Composer/Command/RequireCommand.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 9b12941b8..8f91b6675 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -145,7 +145,12 @@ EOT // validate requirements format $versionParser = new VersionParser(); - foreach ($requirements as $constraint) { + foreach ($requirements as $package => $constraint) { + if (strtolower($package) === $composer->getPackage()->getName()) { + $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); + + return 1; + } $versionParser->parseConstraints($constraint); } From 14f2a6dd9accf8a0b4807876eaf47b80bec1d034 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 09:48:49 +0200 Subject: [PATCH 565/580] Fix remove command not working with escaped slashes (e.g. foo\/bar), fixes #8249 --- src/Composer/Json/JsonManipulator.php | 5 +++-- tests/Composer/Test/Json/JsonManipulatorTest.php | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 8fe6a9f0a..e64a56f71 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -326,9 +326,10 @@ class JsonManipulator } // try and find a match for the subkey - if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i', $children)) { + $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); + if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) { // find best match for the value of "name" - if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x', $children, $matches)) { + if (preg_match_all('{'.self::$DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { if (strlen($bestMatch) < strlen($match)) { diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index d8bc7c200..8bc7831af 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -1448,6 +1448,22 @@ class JsonManipulatorTest extends TestCase "repositories": { } } +', + ), + 'works on simple ones escaped slash' => array( + '{ + "repositories": { + "foo\/bar": { + "bar": "baz" + } + } +}', + 'foo/bar', + true, + '{ + "repositories": { + } +} ', ), 'works on simple ones middle' => array( From 3f5e4f0399ba108029b77500ab0cbf13fa19d75b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 10:58:36 +0200 Subject: [PATCH 566/580] Add support for defining a {"packagist.org":false} repo in composer init, fixes #8210 --- src/Composer/Command/InitCommand.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 66a56f978..6cd722ad5 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -168,13 +168,25 @@ EOT if ($repositories) { $config = Factory::createConfig($io); $repos = array(new PlatformRepository); + $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { - $repos[] = RepositoryFactory::fromString($io, $config, $repo); + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo); + if ( + (isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false)) + || (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false)) + ) { + $createDefaultPackagistRepo = false; + continue; + } + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig); + } + + if ($createDefaultPackagistRepo) { + $repos[] = RepositoryFactory::createRepo($io, $config, array( + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + )); } - $repos[] = RepositoryFactory::createRepo($io, $config, array( - 'type' => 'composer', - 'url' => 'https://repo.packagist.org', - )); $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); From 3e66d0514a69b4057c02685b633dff97a3917a19 Mon Sep 17 00:00:00 2001 From: Thomas Perez Date: Tue, 30 Jul 2019 18:45:41 +0200 Subject: [PATCH 567/580] Fix error_handler return type declaration --- src/Composer/Util/ErrorHandler.php | 3 +++ src/Composer/Util/RemoteFilesystem.php | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 83e6b5ede..c4dabd1d7 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -33,6 +33,7 @@ class ErrorHandler * * @static * @throws \ErrorException + * @return bool */ public static function handle($level, $message, $file, $line) { @@ -63,6 +64,8 @@ class ErrorHandler }, array_slice(debug_backtrace(), 2)))); } } + + return true; } /** diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e1a93fcf9..2b66585db 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -321,6 +321,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); + + return true; }); try { $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); @@ -494,6 +496,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg); + + return true; }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); From 5ddc40e93ce49955013553d985fe47f3b341fd01 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 15:21:32 +0200 Subject: [PATCH 568/580] Load packages from the lock file for check-platform-reqs if no dependencies have been installed yet, fixes #8058 --- src/Composer/Command/CheckPlatformReqsCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 8ce0b0186..195a2c490 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -51,6 +51,10 @@ EOT $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); } else { $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + // fallback to lockfile if installed repo is empty + if (!$dependencies) { + $dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages(); + } $requires += $composer->getPackage()->getDevRequires(); } foreach ($requires as $require => $link) { From 0261ce809279a4789c8bd882097c4d59c10475f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 17:41:18 +0200 Subject: [PATCH 569/580] Improve handling of non-standard ports for GitLab and GitHub installs, fixes #8173 --- src/Composer/Downloader/FileDownloader.php | 4 +-- .../Repository/ComposerRepository.php | 12 ++++---- src/Composer/Repository/Vcs/GitHubDriver.php | 4 +++ src/Composer/Repository/Vcs/GitLabDriver.php | 28 +++++++++---------- src/Composer/Util/GitLab.php | 11 +++++++- src/Composer/Util/RemoteFilesystem.php | 13 +++++++++ 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index e63df021a..819fbcefb 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -125,7 +125,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $fileName = $this->getFileName($package, $path); $processedUrl = $this->processUrl($package, $url); - $hostname = parse_url($processedUrl, PHP_URL_HOST); + $origin = RemoteFilesystem::getOrigin($processedUrl); $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); if ($this->eventDispatcher) { @@ -150,7 +150,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $retries = 3; while ($retries--) { try { - $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); + $rfs->copy($origin, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 9d5b727cc..c595b20c2 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -206,8 +206,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl); - $hostname = parse_url($url, PHP_URL_HOST) ?: $url; - $json = $this->rfs->getContents($hostname, $url, false); + $origin = RemoteFilesystem::getOrigin($url); + $json = $this->rfs->getContents($origin, $url, false); $search = JsonFile::parseJson($json, $url); if (empty($search['results'])) { @@ -681,10 +681,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; + $origin = RemoteFilesystem::getOrigin($filename); $rfs = $preFileDownloadEvent->getRemoteFilesystem(); - $json = $rfs->getContents($hostname, $filename, false); + $json = $rfs->getContents($origin, $filename, false); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow if ($this->allowSslDowngrade) { @@ -760,10 +760,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; + $origin = RemoteFilesystem::getOrigin($filename); $rfs = $preFileDownloadEvent->getRemoteFilesystem(); $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); - $json = $rfs->getContents($hostname, $filename, false, $options); + $json = $rfs->getContents($origin, $filename, false, $options); if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) { return true; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 8c051bd50..529112811 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -304,6 +304,10 @@ class GitHubDriver extends VcsDriver */ protected function generateSshUrl() { + if (false !== strpos($this->originUrl, ':')) { + return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 2044ff702..7b593084f 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -67,9 +67,9 @@ class GitLabDriver extends VcsDriver private $isPrivate = true; /** - * @var int port number + * @var bool true if the origin has a port number or a path component in it */ - protected $portNumber; + private $hasNonstandardOrigin = false; const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; @@ -94,11 +94,10 @@ class GitLabDriver extends VcsDriver ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') ; - $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts); + $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); - if (!empty($match['port']) && true === is_numeric($match['port'])) { - // If it is an HTTP based URL, and it has a port - $this->portNumber = (int) $match['port']; + if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { + $this->hasNonstandardOrigin = true; } $this->namespace = implode('/', $urlParts); @@ -259,10 +258,7 @@ class GitLabDriver extends VcsDriver */ public function getApiUrl() { - $domainName = $this->originUrl; - $portNumber = (true === is_numeric($this->portNumber)) ? sprintf(':%s', $this->portNumber) : ''; - - return $this->scheme.'://'.$domainName.$portNumber.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); + return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } /** @@ -360,6 +356,10 @@ class GitLabDriver extends VcsDriver */ protected function generateSshUrl() { + if ($this->hasNonstandardOrigin) { + return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } @@ -458,7 +458,7 @@ class GitLabDriver extends VcsDriver $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; $urlParts = explode('/', $match['parts']); - if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) { + if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { return false; } @@ -492,16 +492,16 @@ class GitLabDriver extends VcsDriver * @param array $urlParts * @return bool|string */ - private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts) + private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) { - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { return $guessedDomain; } while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) { return $guessedDomain; } } diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 475c5e7ee..7a69ad251 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -53,7 +53,10 @@ class GitLab */ public function authorizeOAuth($originUrl) { - if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) { + // before composer 1.9, origin URLs had no port number in them + $bcOriginUrl = preg_replace('{:\d+}', '', $originUrl); + + if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { return false; } @@ -73,6 +76,12 @@ class GitLab return true; } + if (isset($authTokens[$bcOriginUrl])) { + $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token'); + + return true; + } + return false; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 2b66585db..32767c161 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -1110,4 +1110,17 @@ class RemoteFilesystem $io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].''); } } + + public static function getOrigin($urlOrPath) + { + $hostPort = parse_url($urlOrPath, PHP_URL_HOST); + if (!$hostPort) { + return $urlOrPath; + } + if (parse_url($urlOrPath, PHP_URL_PORT)) { + $hostPort .= ':'.parse_url($urlOrPath, PHP_URL_PORT); + } + + return $hostPort; + } } From 0fe200d6d950b0774688713be3153bb410eb70b8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:01:08 +0200 Subject: [PATCH 570/580] Fix origin computation --- src/Composer/Repository/Vcs/GitLabDriver.php | 9 ++++++++- tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 7b593084f..0d8925bc8 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -495,13 +495,20 @@ class GitLabDriver extends VcsDriver private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) { if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { + if ($portNumber) { + return $guessedDomain.':'.$portNumber; + } return $guessedDomain; } + if ($portNumber) { + $guessedDomain .= ':'.$portNumber; + } + while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; - if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{:\d+}', '', $guessedDomain), $configuredDomains))) { return $guessedDomain; } } diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index a5eb799f2..81e623182 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -207,7 +207,7 @@ JSON; JSON; $this->remoteFilesystem - ->getContents($domain, $apiUrl, false, array()) + ->getContents($domain.':'.$port, $apiUrl, false, array()) ->willReturn(sprintf($projectData, $domain, $port, $namespace)) ->shouldBeCalledTimes(1); From db6882b57fec3721bd0e35ceb41c62a7b81ff8bf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:36:27 +0200 Subject: [PATCH 571/580] Fix updating or URLs to include dist type and shasum, fixes #8216 --- src/Composer/Installer.php | 6 ++- .../installer/update-changes-url.test | 40 +++++++++---------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 9583c3eb1..f3bfc5363 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl()); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); @@ -1190,7 +1190,7 @@ class Installer } } - private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl) + private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl, $distType, $distShaSum) { $oldSourceRef = $package->getSourceReference(); @@ -1204,6 +1204,8 @@ class Installer // but for other urls this is ambiguous and could result in bad outcomes if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) { $package->setDistUrl($distUrl); + $package->setDistType($distType); + $package->setDistSha1Checksum($distShaSum); $this->updateInstallReferences($package, $sourceReference); } diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index d774ea188..b199f90d4 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -17,37 +17,37 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "a/a", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "b/b", "version": "2.0.3", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/c/newc", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "d/d", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/d/newd", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "e/e", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/e/newe", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "f/f", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/g/newg", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } } ] } @@ -67,32 +67,32 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "a/a", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "b/b", "version": "2.0.3", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "d/d", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" }, - "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" } + "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" } } ] --EXPECT-LOCK-- @@ -101,43 +101,43 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "a/a", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "b/b", "version": "2.0.3", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/newc", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "d/d", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/newd", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "e/e", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/e/newe", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "g/g", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/g/newg", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" } ], From 362ebe4f68ac397b3294110c4944884e9bd90495 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:43:18 +0200 Subject: [PATCH 572/580] Fix update mirrors to also update transport-options, fixes #7672 --- src/Composer/Installer.php | 3 ++- .../Test/Fixtures/installer/update-changes-url.test | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f3bfc5363..010e56ddc 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum(), $newPackage); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); @@ -1186,6 +1186,7 @@ class Installer $package->setDistMirrors($newPackage->getDistMirrors()); $package->setSourceMirrors($newPackage->getSourceMirrors()); + $package->setTransportOptions($newPackage->getTransportOptions()); } } } diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index b199f90d4..0a0d47507 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -42,7 +42,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "f/f", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, + "transport-options": { "foo": "bar2" } }, { "name": "g/g", "version": "dev-master", @@ -87,12 +88,14 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }, + "transport-options": { "foo": "bar" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" }, - "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" } + "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" }, + "transport-options": { "foo": "bar" } } ] --EXPECT-LOCK-- @@ -132,7 +135,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, - "type": "library" + "type": "library", + "transport-options": { "foo": "bar2" } }, { "name": "g/g", "version": "dev-master", From b5014e9aed08bd143f0f6944a156665fb86841ff Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 12:19:12 +0200 Subject: [PATCH 573/580] Fix use of decodeJson --- src/Composer/Command/DiagnoseCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index c2123e066..dee37502f 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -254,7 +254,7 @@ EOT $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { - $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->parseJson(); + $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson(); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); @@ -375,7 +375,7 @@ EOT } $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; - $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->parseJson(); + $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->decodeJson(); return $data['resources']['core']; } From 872604ab36b00acb64dd97691c0ccfc77500f3d8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 13:23:03 +0200 Subject: [PATCH 574/580] Allow path repos to point to their own source dir as install target, resulting in noop, fixes #8254 --- src/Composer/Downloader/PathDownloader.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 4c71fb4f4..5c4cbcf61 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -49,6 +49,18 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter )); } + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(sprintf( + ' - Installing %s (%s): Source already present', + $package->getName(), + $package->getFullPrettyVersion() + )); + } + + return; + } + if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. // @@ -146,6 +158,16 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter */ public function remove(PackageInterface $package, $path, $output = true) { + $realUrl = realpath($package->getDistUrl()); + + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "), source is still present in $path"); + } + + return; + } + /** * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which From 96ad0aa01fa49a39539c089ca4a1aa1a54cba98d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 13:45:43 +0200 Subject: [PATCH 575/580] Remove extra arg --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 010e56ddc..11edcbe72 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum(), $newPackage); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); From 76a2c63bf8f2b77f68d1b4252781450d06981487 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 14:00:34 +0200 Subject: [PATCH 576/580] Show best possible version in diagnose command --- src/Composer/Command/DiagnoseCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index dee37502f..3bcc665dc 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -156,7 +156,7 @@ EOT $this->outputResult($this->checkVersion($config)); } - $io->write(sprintf('Composer version: %s', Composer::VERSION)); + $io->write(sprintf('Composer version: %s', Composer::getVersion())); $platformOverrides = $config->get('platform') ?: array(); $platformRepo = new PlatformRepository(array(), $platformOverrides); From f7c1b04a6ccbcf23ed8b8450162abe58da6aa216 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 14:26:42 +0200 Subject: [PATCH 577/580] Improve output when installing packages --- src/Composer/Downloader/ArchiveDownloader.php | 8 +++----- src/Composer/Downloader/PathDownloader.php | 8 ++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 3c53a086e..96aad4b9a 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -33,16 +33,14 @@ abstract class ArchiveDownloader extends FileDownloader public function install(PackageInterface $package, $path, $output = true) { if ($output) { - $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): Extracting archive"); + } else { + $this->io->writeError('Extracting archive', false); } $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $fileName = $this->getFileName($package, $path); - if ($output) { - $this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE); - } - try { $this->filesystem->ensureDirectoryExists($temporaryDir); try { diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 23c51398e..e289c0173 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -82,6 +82,8 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $package->getName(), $package->getFullPrettyVersion() )); + } else { + $this->io->writeError('Source already present', false); } return; @@ -163,7 +165,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $fileSystem->mirror($realUrl, $path, $iterator); } - $this->io->writeError(''); + if ($output) { + $this->io->writeError(''); + } } /** @@ -173,7 +177,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter { $realUrl = realpath($package->getDistUrl()); - if (realpath($path) === $realUrl) { + if ($path === $realUrl) { if ($output) { $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "), source is still present in $path"); } From 898ba6f869c49eb86d2d94c3f3719df05b259301 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 15:53:10 +0200 Subject: [PATCH 578/580] Only empty dir before actually installing packages, fixes #7929 --- src/Composer/Downloader/ArchiveDownloader.php | 2 ++ src/Composer/Downloader/FileDownloader.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 96aad4b9a..be863f1d3 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -38,6 +38,8 @@ abstract class ArchiveDownloader extends FileDownloader $this->io->writeError('Extracting archive', false); } + $this->filesystem->emptyDirectory($path); + $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $fileName = $this->getFileName($package, $path); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 2c66c23a3..da2955638 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -101,7 +101,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface ); } - $this->filesystem->emptyDirectory($path); $fileName = $this->getFileName($package, $path); $io = $this->io; @@ -229,6 +228,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } + $this->filesystem->emptyDirectory($path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); } From 6a7220fed8b6fa00ad4dd962ac147593b0616131 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 15:57:33 +0200 Subject: [PATCH 579/580] Avoid wiping the whole target package if download of the new one fails, refs #7929 --- src/Composer/Downloader/FileDownloader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index da2955638..6a08bd67d 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -175,7 +175,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) { // clean up - $filesystem->removeDirectory($path); + if (file_exists($fileName)) { + $filesystem->unlink($fileName); + } $self->clearLastCacheWrite($package); if ($e instanceof TransportException) { From 9ee345ed29985e4e5b1142bf4442910aab8fa684 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 16:33:39 +0200 Subject: [PATCH 580/580] Make sure the directory exists and will not block installation later when downloading --- src/Composer/Downloader/FileDownloader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 6a08bd67d..ab72bbaed 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -101,6 +101,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface ); } + $this->filesystem->ensureDirectoryExists($path); $fileName = $this->getFileName($package, $path); $io = $this->io;