diff --git a/.travis.yml b/.travis.yml index 4aed11625..818224376 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,34 @@ language: php +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +addons: + apt_packages: + - parallel + php: - 5.3.3 - 5.3 - 5.4 - 5.5 - 5.6 + - 7.0 - hhvm + - hhvm-nightly + +matrix: + fast_finish: true + allow_failures: + - php: hhvm-nightly before_script: - - sudo apt-get install parallel - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - - composer install --prefer-source - - bin/composer install --prefer-source + - composer install + - bin/composer install - git config --global user.name travis-ci - git config --global user.email travis@example.com diff --git a/bin/composer b/bin/composer index 3a1b5df02..401efb5b0 100755 --- a/bin/composer +++ b/bin/composer @@ -32,9 +32,9 @@ if (function_exists('ini_set')) { }; $memoryLimit = trim(ini_get('memory_limit')); - // Increase memory_limit if it is lower than 512M - if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 512 * 1024 * 1024) { - @ini_set('memory_limit', '512M'); + // Increase memory_limit if it is lower than 1GB + if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1024) { + @ini_set('memory_limit', '1G'); } unset($memoryInBytes, $memoryLimit); } diff --git a/composer.json b/composer.json index 9fca7d74c..f3eee609a 100644 --- a/composer.json +++ b/composer.json @@ -23,14 +23,14 @@ }, "require": { "php": ">=5.3.2", - "justinrainbow/json-schema": "~1.3", + "justinrainbow/json-schema": "~1.4", "seld/jsonlint": "~1.0", - "symfony/console": "~2.3", + "symfony/console": "~2.5", "symfony/finder": "~2.2", "symfony/process": "~2.1" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.5" }, "suggest": { "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic", diff --git a/composer.lock b/composer.lock index 7d28fcb09..b0ae61425 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2bc9cc8aa706b68d611d7058e4eb8de7", + "hash": "8317ca3b690ea80633fe3fb2b0d440cb", "packages": [ { "name": "justinrainbow/json-schema", - "version": "1.3.7", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "87b54b460febed69726c781ab67462084e97a105" + "reference": "680d026082c3aa234b2d8617c50e9c73999913ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/87b54b460febed69726c781ab67462084e97a105", - "reference": "87b54b460febed69726c781ab67462084e97a105", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/680d026082c3aa234b2d8617c50e9c73999913ba", + "reference": "680d026082c3aa234b2d8617c50e9c73999913ba", "shasum": "" }, "require": { @@ -70,20 +70,20 @@ "json", "schema" ], - "time": "2014-08-25 02:48:14" + "time": "2015-03-23 20:38:38" }, { "name": "seld/jsonlint", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "a7bc2ec9520ad15382292591b617c43bdb1fec35" + "reference": "863ae85c6d3ef60ca49cb12bd051c4a0648c40c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/a7bc2ec9520ad15382292591b617c43bdb1fec35", - "reference": "a7bc2ec9520ad15382292591b617c43bdb1fec35", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/863ae85c6d3ef60ca49cb12bd051c4a0648c40c4", + "reference": "863ae85c6d3ef60ca49cb12bd051c4a0648c40c4", "shasum": "" }, "require": { @@ -116,21 +116,21 @@ "parser", "validator" ], - "time": "2014-09-05 15:36:20" + "time": "2015-01-04 21:18:15" }, { "name": "symfony/console", - "version": "v2.6.1", + "version": "v2.6.5", "target-dir": "Symfony/Component/Console", "source": { "type": "git", "url": "https://github.com/symfony/Console.git", - "reference": "ef825fd9f809d275926547c9e57cbf14968793e8" + "reference": "53f86497ccd01677e22435cfb7262599450a90d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8", - "reference": "ef825fd9f809d275926547c9e57cbf14968793e8", + "url": "https://api.github.com/repos/symfony/Console/zipball/53f86497ccd01677e22435cfb7262599450a90d1", + "reference": "53f86497ccd01677e22435cfb7262599450a90d1", "shasum": "" }, "require": { @@ -139,6 +139,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", "symfony/process": "~2.1" }, "suggest": { @@ -173,26 +174,29 @@ ], "description": "Symfony Console Component", "homepage": "http://symfony.com", - "time": "2014-12-02 20:19:20" + "time": "2015-03-13 17:37:22" }, { "name": "symfony/finder", - "version": "v2.6.1", + "version": "v2.6.5", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", "url": "https://github.com/symfony/Finder.git", - "reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721" + "reference": "bebc7479c566fa4f14b9bcef9e32e719eabec74e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721", - "reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721", + "url": "https://api.github.com/repos/symfony/Finder/zipball/bebc7479c566fa4f14b9bcef9e32e719eabec74e", + "reference": "bebc7479c566fa4f14b9bcef9e32e719eabec74e", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, "type": "library", "extra": { "branch-alias": { @@ -220,26 +224,29 @@ ], "description": "Symfony Finder Component", "homepage": "http://symfony.com", - "time": "2014-12-02 20:19:20" + "time": "2015-03-12 10:28:44" }, { "name": "symfony/process", - "version": "v2.6.1", + "version": "v2.6.5", "target-dir": "Symfony/Component/Process", "source": { "type": "git", "url": "https://github.com/symfony/Process.git", - "reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a" + "reference": "4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/bf0c9bd625f13b0b0bbe39919225cf145dfb935a", - "reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a", + "url": "https://api.github.com/repos/symfony/Process/zipball/4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc", + "reference": "4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, "type": "library", "extra": { "branch-alias": { @@ -267,7 +274,7 @@ ], "description": "Symfony Process Component", "homepage": "http://symfony.com", - "time": "2014-12-02 20:19:20" + "time": "2015-03-12 10:28:44" } ], "packages-dev": [ @@ -326,17 +333,125 @@ "time": "2014-10-13 12:58:55" }, { - "name": "phpunit/php-code-coverage", - "version": "2.0.14", + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca158276c1200cc27f5409a5e338486bc0b4fc94", - "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "~1.0,>=1.0.2", + "phpdocumentor/reflection-docblock": "~2.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "http://phpspec.org", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2014-11-17 16:23:49" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.0.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "34cc484af1ca149188d0d9e91412191e398e0b67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/34cc484af1ca149188d0d9e91412191e398e0b67", + "reference": "34cc484af1ca149188d0d9e91412191e398e0b67", "shasum": "" }, "require": { @@ -349,7 +464,7 @@ }, "require-dev": { "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4.1" + "phpunit/phpunit": "~4" }, "suggest": { "ext-dom": "*", @@ -368,9 +483,6 @@ ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], @@ -388,7 +500,7 @@ "testing", "xunit" ], - "time": "2014-12-26 13:28:33" + "time": "2015-01-24 10:06:35" }, { "name": "phpunit/php-file-iterator", @@ -525,16 +637,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "f8d5d08c56de5cfd592b3340424a81733259a876" + "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876", - "reference": "f8d5d08c56de5cfd592b3340424a81733259a876", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db32c18eba00b121c145575fcbcd4d4d24e6db74", + "reference": "db32c18eba00b121c145575fcbcd4d4d24e6db74", "shasum": "" }, "require": { @@ -547,7 +659,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -570,20 +682,20 @@ "keywords": [ "tokenizer" ], - "time": "2014-08-31 06:12:13" + "time": "2015-01-17 09:51:32" }, { "name": "phpunit/phpunit", - "version": "4.4.1", + "version": "4.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a" + "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a5e49a86ce5e33b8d0657abe145057fc513543a", - "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5b578d3865a9128b9c209b011fda6539ec06e7a5", + "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5", "shasum": "" }, "require": { @@ -593,15 +705,16 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", + "phpspec/prophecy": "~1.3.1", "phpunit/php-code-coverage": "~2.0", "phpunit/php-file-iterator": "~1.3.2", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "~1.0.2", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.0", + "sebastian/comparator": "~1.1", "sebastian/diff": "~1.1", - "sebastian/environment": "~1.1", - "sebastian/exporter": "~1.0", + "sebastian/environment": "~1.2", + "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", "sebastian/version": "~1.0", "symfony/yaml": "~2.0" @@ -615,7 +728,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4.x-dev" + "dev-master": "4.5.x-dev" } }, "autoload": { @@ -641,7 +754,7 @@ "testing", "xunit" ], - "time": "2014-12-28 07:57:05" + "time": "2015-02-05 15:51:19" }, { "name": "phpunit/phpunit-mock-objects", @@ -700,25 +813,25 @@ }, { "name": "sebastian/comparator", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "c484a80f97573ab934e37826dba0135a3301b26a" + "reference": "1dd8869519a225f7f2b9eb663e225298fade819e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c484a80f97573ab934e37826dba0135a3301b26a", - "reference": "c484a80f97573ab934e37826dba0135a3301b26a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e", + "reference": "1dd8869519a225f7f2b9eb663e225298fade819e", "shasum": "" }, "require": { "php": ">=5.3.3", - "sebastian/diff": "~1.1", - "sebastian/exporter": "~1.0" + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" }, "require-dev": { - "phpunit/phpunit": "~4.1" + "phpunit/phpunit": "~4.4" }, "type": "library", "extra": { @@ -760,7 +873,7 @@ "compare", "equality" ], - "time": "2014-11-16 21:32:38" + "time": "2015-01-29 16:28:08" }, { "name": "sebastian/diff", @@ -866,28 +979,29 @@ }, { "name": "sebastian/exporter", - "version": "1.0.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0" + "reference": "84839970d05254c73cde183a721c7af13aede943" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", - "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943", + "reference": "84839970d05254c73cde183a721c7af13aede943", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -927,7 +1041,7 @@ "export", "exporter" ], - "time": "2014-09-10 00:51:36" + "time": "2015-01-27 07:23:06" }, { "name": "sebastian/global-state", @@ -980,6 +1094,59 @@ ], "time": "2014-10-06 09:23:50" }, + { + "name": "sebastian/recursion-context", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "3989662bbb30a29d20d9faa04a846af79b276252" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252", + "reference": "3989662bbb30a29d20d9faa04a846af79b276252", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-01-24 09:48:32" + }, { "name": "sebastian/version", "version": "1.0.4", @@ -1017,22 +1184,25 @@ }, { "name": "symfony/yaml", - "version": "v2.6.1", + "version": "v2.6.5", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20" + "reference": "0cd8e72071e46e15fc072270ae39ea1b66b10a9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20", - "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/0cd8e72071e46e15fc072270ae39ea1b66b10a9d", + "reference": "0cd8e72071e46e15fc072270ae39ea1b66b10a9d", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, "type": "library", "extra": { "branch-alias": { @@ -1060,7 +1230,7 @@ ], "description": "Symfony Yaml Component", "homepage": "http://symfony.com", - "time": "2014-12-02 20:19:20" + "time": "2015-03-12 10:28:44" } ], "aliases": [], diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index ef72f556c..bc18f3357 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -140,7 +140,7 @@ versions from `composer.json` and create the lock file after executing the `upd command. This means that if any of the dependencies get a new version, you won't get the updates -automatically. To update to the new version, use `update` command. This will fetch +automatically. To update to the new version, use the `update` command. This will fetch the latest matching versions (according to your `composer.json` file) and also update the lock file with the new version. diff --git a/doc/03-cli.md b/doc/03-cli.md index 40478ee9b..dd1121c80 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -507,8 +507,13 @@ Lists the name, version and license of every package installed. Use ## run-script +### Options + +* **--no-dev:** Disable dev mode +* **--list:** List user defined scripts + To run [scripts](articles/scripts.md) manually you can use this command, -just give it the script name and optionally --no-dev to disable the dev mode. +just give it the script name and optionally any required arguments. ## diagnose diff --git a/doc/04-schema.md b/doc/04-schema.md index 2dbab14aa..688f579df 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -894,4 +894,36 @@ The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`, Optional. -← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) → +### non-feature-branches + +A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something), that will NOT be handled as feature branches. This is an array of string. + +If you have non-numeric branch names, for example like "latest", "current", "latest-stable" +or something, that do not look like a version number, then composer handles such branches +as feature branches. This means it searches for parent branches, that look like a version +or ends at special branches (like master) and the root package version number becomes the +version of the parent branch or at least master or something. + +To handle non-numeric named branches as versions instead of searching for a parent branch +with a valid version or special branch name like master, you can set patterns for branch +names, that should be handled as dev version branches. + +This is really helpful when you have dependencies using "self.version", so that not dev-master, +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`. + +If you configure latest-.* as a pattern for non-feature-branches like this: + + { + "non-feature-branches": ["latest-.*"] + } + +Then "composer show -s" will give you `versions : * dev-latest-testing`. + +Optional. + +← [Command-line interface](03-cli.md) | [Repositories](05-repositories.md) → \ No newline at end of file diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 80d15c561..3b0bd4d38 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -234,7 +234,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. For version constraint naming conventions see +point to your custom branch. Your custom branch name must be prefixed with `"dev-"`. For version constraint naming conventions see [Libraries](02-libraries.md) for more information. Example assuming you patched monolog to fix a bug in the `bugfix` branch: @@ -533,10 +533,9 @@ There are a few tools that can help you create a `composer` repository. ### Packagist The underlying application used by packagist is open source. This means that you -can just install your own copy of packagist, re-brand, and use it. It's really -quite straight-forward to do. However due to its size and complexity, for most -small and medium sized companies willing to track a few packages will be better -off using Satis. +can technically install your own copy of packagist. However it is not a +supported use case and changes will happen without caring for third parties +using the code. Packagist is a Symfony2 application, and it is [available on GitHub](https://github.com/composer/packagist). It uses composer internally and @@ -544,8 +543,11 @@ acts as a proxy between VCS repositories and the composer users. It holds a list of all VCS packages, periodically re-crawls them, and exposes them as a composer repository. -To set your own copy, simply follow the instructions from the [packagist -github repository](https://github.com/composer/packagist). +### Toran Proxy + +[Toran Proxy](https://toranproxy.com/) is a web app much like Packagist but +providing private package hosting as well as mirroring/proxying of GitHub and packagist.org. Check its homepage and the [Satis/Toran Proxy article](articles/handling-private-packages-with-satis.md) +for more information. ### Satis diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index 5fce5377b..79e5e27a2 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -6,9 +6,15 @@ # Toran Proxy -[Toran Proxy](https://toranproxy.com/) is a commercial alternative to Satis offering professional support as well as a web UI to manage everything and a better integration with Composer. +[Toran Proxy](https://toranproxy.com/) is a commercial alternative to Satis +offering professional support as well as a web UI to manage everything and a +better integration with Composer. It also provides proxying/mirroring for git +repos and package zip files which makes installs faster and independent from +third party systems. -Toran's revenue is also used to pay for Composer and Packagist development and hosting so using it is a good way to support open source financially. You can find more information about how to set it up and use it on the [Toran Proxy](https://toranproxy.com/) website. +Toran's revenue is also used to pay for Composer and Packagist development and +hosting so using it is a good way to support open source financially. You can +find more information about how to set it up and use it on the [Toran Proxy](https://toranproxy.com/) website. # Satis @@ -151,6 +157,24 @@ Example using HTTP over SSL using a client certificate: > **Tip:** See [ssl context options](http://www.php.net/manual/en/context.ssl.php) for more information. +### Authentification + +When your private repositories are password protected, you can store the authentification details permanently. +The first time Composer needs to authenticate against some domain it will prompt you for a username/password +and then you will be asked whether you want to store it. + +The storage can be done either globally in the `COMPOSER_HOME/auth.json` file (`COMPOSER_HOME` defaults to +`~/.composer` or `%APPDATA%/Composer` on Windows) or also in the project directory directly sitting besides your +composer.json. + +You can also configure these by hand using the config command if you need to configure a production machine +to be able to run non-interactive installs. For example to enter credentials for example.org one could type: + + composer config http-basic.example.org username password + +That will store it in the current directory's auth.json, but if you want it available globally you can use the +`--global` (`-g`) flag. + ### Downloads When GitHub or BitBucket repositories are mirrored on your local satis, the build process will include diff --git a/doc/articles/http-basic-authentication.md b/doc/articles/http-basic-authentication.md index 1add2d7a6..2119513ce 100644 --- a/doc/articles/http-basic-authentication.md +++ b/doc/articles/http-basic-authentication.md @@ -40,7 +40,7 @@ username/password pairs, for example: ```json { - "basic-auth": { + "http-basic": { "repo.example1.org": { "username": "my-username1", "password": "my-secret-password1" diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index 65884fd18..9da3badad 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -82,15 +82,7 @@ Furthermore plugins may implement the event handlers automatically registered with the `EventDispatcher` when the plugin is loaded. -The events available for plugins are: - -* **COMMAND**, is called at the beginning of all commands that load plugins. - It provides you with access to the input and output objects of the program. -* **PRE_FILE_DOWNLOAD**, is triggered before files are downloaded and allows - you to manipulate the `RemoteFilesystem` object prior to downloading files - based on the URL to be downloaded. - -> A plugin can also subscribe to [script events][7]. +Plugin can subscribe to any of the available [script events](scripts.md#event-names). Example: @@ -148,7 +140,7 @@ list of installed packages. Additionally all plugin packages installed in the local project plugins are loaded. > You may pass the `--no-plugins` option to composer commands to disable all -> installed commands. This may be particularly helpful if any of the plugins +> installed plugins. This may be particularly helpful if any of the plugins > causes errors and you wish to update or uninstall it. [1]: ../04-schema.md#type @@ -157,4 +149,3 @@ local project plugins are loaded. [4]: https://github.com/composer/composer/blob/master/src/Composer/Composer.php [5]: https://github.com/composer/composer/blob/master/src/Composer/IO/IOInterface.php [6]: https://github.com/composer/composer/blob/master/src/Composer/EventDispatcher/EventSubscriberInterface.php -[7]: ./scripts.md#event-names diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 3e6ef54cf..dec1fa06d 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -20,20 +20,16 @@ the Composer execution process. Composer fires the following named events during its execution process: +### Command Events + - **pre-install-cmd**: occurs before the `install` command is executed. - **post-install-cmd**: occurs after the `install` command is executed. - **pre-update-cmd**: occurs before the `update` command is executed. - **post-update-cmd**: occurs after the `update` command is executed. - **pre-status-cmd**: occurs before the `status` command is executed. - **post-status-cmd**: occurs after the `status` command is executed. -- **pre-dependencies-solving**: occurs before the dependencies are resolved. -- **post-dependencies-solving**: occurs after the dependencies are resolved. -- **pre-package-install**: occurs before a package is installed. -- **post-package-install**: occurs after a package is installed. -- **pre-package-update**: occurs before a package is updated. -- **post-package-update**: occurs after a package is updated. -- **pre-package-uninstall**: occurs before a package has been uninstalled. -- **post-package-uninstall**: occurs after a package has been uninstalled. +- **pre-archive-cmd**: occurs before the `archive` command is executed. +- **post-archive-cmd**: occurs after the `archive` command is executed. - **pre-autoload-dump**: occurs before the autoloader is dumped, either during `install`/`update`, or via the `dump-autoload` command. - **post-autoload-dump**: occurs after the autoloader is dumped, either @@ -42,8 +38,28 @@ Composer fires the following named events during its execution process: installed, during the `create-project` command. - **post-create-project-cmd**: occurs after the `create-project` command is executed. -- **pre-archive-cmd**: occurs before the `archive` command is executed. -- **post-archive-cmd**: occurs after the `archive` command is executed. + +### Installer Events + +- **pre-dependencies-solving**: occurs before the dependencies are resolved. +- **post-dependencies-solving**: occurs after the dependencies are resolved. + +### Package Events + +- **pre-package-install**: occurs before a package is installed. +- **post-package-install**: occurs after a package is installed. +- **pre-package-update**: occurs before a package is updated. +- **post-package-update**: occurs after a package is updated. +- **pre-package-uninstall**: occurs before a package has been uninstalled. +- **post-package-uninstall**: occurs after a package has been uninstalled. + +### Plugin Events + +- **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 + based on the URL to be downloaded. > **Note:** Composer makes no assumptions about the state of your dependencies > prior to `install` or `update`. Therefore, you should not specify scripts @@ -96,6 +112,7 @@ that might be used to execute the PHP callbacks: namespace MyVendor; use Composer\Script\Event; +use Composer\Installer\PackageEvent; class MyClass { @@ -105,7 +122,7 @@ class MyClass // do stuff } - public static function postPackageInstall(Event $event) + public static function postPackageInstall(PackageEvent $event) { $installedPackage = $event->getOperation()->getPackage(); // do stuff @@ -118,14 +135,21 @@ class MyClass } ``` -When an event is fired, Composer's internal event handler receives a -`Composer\Script\Event` object, which is passed as the first argument to your -PHP callback. This `Event` object has getters for other contextual objects: +When an event is fired, your PHP callback receives as first argument an +`Composer\EventDispatcher\Event` object. This object has a `getName()` method +that lets you retrieve event name. -- `getComposer()`: returns the current instance of `Composer\Composer` -- `getName()`: returns the name of the event being fired as a string -- `getIO()`: returns the current input/output stream which implements -`Composer\IO\IOInterface` for writing to the console +Depending on the script types (see list above) you will get various event +subclasses containing various getters with relevant data and associated +objects: + +- Base class: [`Composer\EventDispatcher\Event`](https://getcomposer.org/apidoc/master/Composer/EventDispatcher/Event.html) +- Command Events: [`Composer\Script\Event`](https://getcomposer.org/apidoc/master/Composer/Script/Event.html) +- Installer Events: [`Composer\Installer\InstallerEvent`](https://getcomposer.org/apidoc/master/Composer/Installer/InstallerEvent.html) +- Package Events: [`Composer\Installer\PackageEvent`](https://getcomposer.org/apidoc/master/Composer/Installer/PackageEvent.html) +- Plugin Events: + - command: [`Composer\Plugin\CommandEvent`](https://getcomposer.org/apidoc/master/Composer/Plugin/CommandEvent.html) + - pre-file-download: [`Composer\Plugin\PreFileDownloadEvent`](https://getcomposer.org/apidoc/master/Composer/Plugin/PreFileDownloadEvent.html) ## Running scripts manually 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 183403948..56d152c82 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 follow [semantic versioning](http://semver.org). +works very well with libraries following [semantic versioning](http://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 diff --git a/res/composer-schema.json b/res/composer-schema.json index 749d2055b..af70816a5 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -413,6 +413,13 @@ "format": "uri" } } + }, + "non-feature-branches": { + "type": ["array"], + "description": "A set of string or regex patterns for non-numeric branch names that will not be handles as feature branches.", + "items": { + "type": "string" + } } } } diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 3fceb443a..bb1b8a003 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -216,7 +216,16 @@ EOF; $classmapFile .= ");\n"; if (!$suffix) { - $suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true)); + if (is_readable($vendorPath.'/autoload.php')) { + $content = file_get_contents($vendorPath.'/autoload.php'); + if (preg_match('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { + $suffix = $match[1]; + } + } + + if (!$suffix) { + $suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true)); + } } file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile); @@ -393,7 +402,7 @@ EOF; } if (!$filesCode) { - return FALSE; + return false; } return <<write( + $io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.' ); @@ -112,7 +112,10 @@ class ClassMapGenerator */ private static function findClasses($path) { - $traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait'; + $extraTypes = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait'; + if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) { + $extraTypes .= '|enum'; + } try { $contents = @php_strip_whitespace($path); @@ -129,7 +132,7 @@ class ClassMapGenerator } // return early if there is no chance of matching anything in this file - if (!preg_match('{\b(?:class|interface'.$traits.')\s}i', $contents)) { + if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) { return array(); } @@ -154,7 +157,7 @@ class ClassMapGenerator preg_match_all('{ (?: - \b(?])(?Pclass|interface'.$traits.') \s+ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*) + \b(?])(?Pclass|interface'.$extraTypes.') \s+ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*) | \b(?])(?Pnamespace) (?P\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;] ) }ix', $contents, $matches); @@ -170,6 +173,12 @@ class ClassMapGenerator if ($name[0] === ':') { // This is an XHP class, https://github.com/facebook/xhp $name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1); + } else if ($matches['type'][$i] === 'enum') { + // In Hack, something like: + // enum Foo: int { HERP = '123'; } + // The regex above captures the colon, which isn't part of + // the class name. + $name = rtrim($name, ':'); } $classes[] = ltrim($namespace . $name, '\\'); } diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 7341f61c2..4fae9be33 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -65,7 +65,7 @@ class Cache $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled && file_exists($this->root . $file)) { if ($this->io->isDebug()) { - $this->io->write('Reading '.$this->root . $file.' from cache'); + $this->io->writeError('Reading '.$this->root . $file.' from cache'); } return file_get_contents($this->root . $file); @@ -80,7 +80,7 @@ class Cache $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->io->isDebug()) { - $this->io->write('Writing '.$this->root . $file.' into cache'); + $this->io->writeError('Writing '.$this->root . $file.' into cache'); } try { @@ -98,7 +98,7 @@ class Cache @disk_free_space($this->root . dirname($file)) ); - $this->io->write($message); + $this->io->writeError($message); return false; } @@ -120,7 +120,7 @@ class Cache $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); if ($this->io->isDebug()) { - $this->io->write('Writing '.$this->root . $file.' into cache'); + $this->io->writeError('Writing '.$this->root . $file.' into cache'); } return copy($source, $this->root . $file); @@ -139,7 +139,7 @@ class Cache touch($this->root . $file); if ($this->io->isDebug()) { - $this->io->write('Reading '.$this->root . $file.' from cache'); + $this->io->writeError('Reading '.$this->root . $file.' from cache'); } return copy($this->root . $file, $target); diff --git a/src/Composer/Command/AboutCommand.php b/src/Composer/Command/AboutCommand.php index ead7604df..5c79c65a0 100644 --- a/src/Composer/Command/AboutCommand.php +++ b/src/Composer/Command/AboutCommand.php @@ -34,7 +34,7 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $output->writeln(<<getIO()->write(<<Composer - Package Management for PHP Composer is a dependency manager tracking local dependencies of your projects and libraries. See http://getcomposer.org/ for more information. diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 913a56a42..17157a82f 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -97,7 +97,7 @@ EOT $package = $this->getComposer()->getPackage(); } - $io->write('Creating the archive.'); + $io->writeError('Creating the archive.'); $archiveManager->archive($package, $format, $dest); return 0; @@ -105,14 +105,14 @@ EOT protected function selectPackage(IOInterface $io, $packageName, $version = null) { - $io->write('Searching for the specified package.'); + $io->writeError('Searching for the specified package.'); if ($composer = $this->getComposer(false)) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repos = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $io->write('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); + $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); $repos = new CompositeRepository($defaultRepos); } @@ -125,14 +125,14 @@ EOT if (count($packages) > 1) { $package = reset($packages); - $io->write('Found multiple matches, selected '.$package->getPrettyString().'.'); - $io->write('Alternatives were '.implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)).'.'); - $io->write('Please use a more specific constraint to pick a different package.'); + $io->writeError('Found multiple matches, selected '.$package->getPrettyString().'.'); + $io->writeError('Alternatives were '.implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)).'.'); + $io->writeError('Please use a more specific constraint to pick a different package.'); } elseif ($packages) { $package = reset($packages); - $io->write('Found an exact match '.$package->getPrettyString().'.'); + $io->writeError('Found an exact match '.$package->getPrettyString().'.'); } else { - $io->write('Could not find a package matching '.$packageName.'.'); + $io->writeError('Could not find a package matching '.$packageName.'.'); return false; } diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index b1b9ecd9a..818b8ee30 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -51,21 +51,21 @@ EOT foreach ($cachePaths as $key => $cachePath) { $cachePath = realpath($cachePath); if (!$cachePath) { - $io->write("Cache directory does not exist ($key): $cachePath"); + $io->writeError("Cache directory does not exist ($key): $cachePath"); - return; + continue; } $cache = new Cache($io, $cachePath); if (!$cache->isEnabled()) { - $io->write("Cache is not enabled ($key): $cachePath"); + $io->writeError("Cache is not enabled ($key): $cachePath"); - return; + continue; } - $io->write("Clearing cache ($key): $cachePath"); + $io->writeError("Clearing cache ($key): $cachePath"); $cache->gc(0, 0); } - $io->write('All caches cleared.'); + $io->writeError('All caches cleared.'); } } diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index e21c99c8a..f170c9327 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -33,15 +33,25 @@ class ConfigCommand extends Command protected $config; /** - * @var Composer\Json\JsonFile + * @var JsonFile */ protected $configFile; /** - * @var Composer\Config\JsonConfigSource + * @var JsonConfigSource */ protected $configSource; + /** + * @var JsonFile + */ + protected $authConfigFile; + + /** + * @var JsonConfigSource + */ + protected $authConfigSource; + /** * {@inheritDoc} */ @@ -65,6 +75,15 @@ class ConfigCommand extends Command This command allows you to edit some basic composer settings in either the local composer.json file or the global config.json file. +To set a config setting: + + %command.full_name% bin-dir bin/ + +To read a config setting: + + %command.full_name% bin-dir + Outputs: bin + To edit the global config.json file: %command.full_name% --global @@ -73,7 +92,15 @@ To add a repository: %command.full_name% repositories.foo vcs http://bar.com -You can add a repository to the global config.json file by passing in the +To remove a repository (repo is a short alias for repositories): + + %command.full_name% --unset repo.foo + +To disable packagist: + + %command.full_name% repo.packagist false + +You can alter repositories in the global config.json file by passing in the --global option. To edit the file in an external editor: @@ -230,55 +257,13 @@ EOT $value = json_encode($value); } - $output->writeln($value); + $this->getIO()->write($value); return 0; } $values = $input->getArgument('setting-value'); // what the user is trying to add/change - // handle repositories - if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { - if ($input->getOption('unset')) { - return $this->configSource->removeRepository($matches[1]); - } - - if (2 !== count($values)) { - throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs http://bar.com'); - } - - return $this->configSource->addRepository($matches[1], array( - 'type' => $values[0], - 'url' => $values[1], - )); - } - - // handle github-oauth - if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) { - if ($input->getOption('unset')) { - $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); - $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); - - return; - } - - if ($matches[1] === 'github-oauth') { - if (1 !== count($values)) { - throw new \RuntimeException('Too many arguments, expected only one token'); - } - $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); - $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); - } elseif ($matches[1] === 'http-basic') { - if (2 !== count($values)) { - throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); - } - $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); - $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1])); - } - - return; - } - $booleanValidator = function ($val) { return in_array($val, array('true', 'false', '1', '0'), true); }; $booleanNormalizer = function ($val) { return $val !== 'false' && (bool) $val; }; @@ -402,6 +387,55 @@ EOT } } + // handle repositories + if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + return $this->configSource->removeRepository($matches[1]); + } + + if (2 === count($values)) { + return $this->configSource->addRepository($matches[1], array( + 'type' => $values[0], + 'url' => $values[1], + )); + } + + if (1 === count($values)) { + $bool = strtolower($values[0]); + if (true === $booleanValidator($bool) && false === $booleanNormalizer($bool)) { + return $this->configSource->addRepository($matches[1], false); + } + } + + throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs http://bar.com'); + } + + // handle github-oauth + if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) { + if ($input->getOption('unset')) { + $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + + return; + } + + if ($matches[1] === 'github-oauth') { + if (1 !== count($values)) { + throw new \RuntimeException('Too many arguments, expected only one token'); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); + } elseif ($matches[1] === 'http-basic') { + if (2 !== count($values)) { + throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); + } + $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); + $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1])); + } + + return; + } + throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); } @@ -450,9 +484,9 @@ EOT } if (is_string($rawVal) && $rawVal != $value) { - $output->writeln('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); + $this->getIO()->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); } else { - $output->writeln('[' . $k . $key . '] ' . $value . ''); + $this->getIO()->write('[' . $k . $key . '] ' . $value . ''); } } } diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 0242afcd3..65366dfe2 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -106,7 +106,7 @@ EOT $this->updatePreferredOptions($config, $input, $preferSource, $preferDist); if ($input->getOption('no-custom-installers')) { - $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } @@ -145,11 +145,13 @@ EOT } $composer = Factory::create($io, null, $disablePlugins); + $composer->getDownloadManager()->setOutputProgress(!$noProgress); + $fs = new Filesystem(); if ($noScripts === false) { // dispatch event - $composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); } $rootPackageConfig = $composer->getConfig(); @@ -196,7 +198,7 @@ EOT } } } catch (\Exception $e) { - $io->write('An error occurred while removing the VCS metadata: '.$e->getMessage().''); + $io->writeError('An error occurred while removing the VCS metadata: '.$e->getMessage().''); } $hasVcs = false; @@ -217,7 +219,7 @@ EOT if ($noScripts === false) { // dispatch event - $composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); } chdir($oldCwd); @@ -288,10 +290,10 @@ EOT $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); } - $io->write('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')'); + $io->writeError('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')'); if ($disablePlugins) { - $io->write('Plugins have been disabled.'); + $io->writeError('Plugins have been disabled.'); } if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) { @@ -311,10 +313,11 @@ EOT $installedFromVcs = 'source' === $package->getInstallationSource(); - $io->write('Created project in ' . $directory . ''); + $io->writeError('Created project in ' . $directory . ''); chdir($directory); - putenv('COMPOSER_ROOT_VERSION='.$package->getPrettyVersion()); + $_SERVER['COMPOSER_ROOT_VERSION'] = $package->getPrettyVersion(); + putenv('COMPOSER_ROOT_VERSION='.$_SERVER['COMPOSER_ROOT_VERSION']); return $installedFromVcs; } diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 755b40b90..97b9d0f39 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -96,9 +96,9 @@ EOT if ($messages) { sort($messages); - $output->writeln($messages); + $this->getIO()->write($messages); } else { - $output->writeln('There is no installed package depending on "'.$needle.'".'); + $this->getIO()->writeError('There is no installed package depending on "'.$needle.'".'); } } } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index b1fcc8dbe..423213741 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -59,8 +59,8 @@ EOT $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); - $output->write('Checking composer.json: '); - $this->outputResult($output, $this->checkComposerSchema()); + $this->getIO()->write('Checking composer.json: ', false); + $this->outputResult($this->checkComposerSchema()); } if ($composer) { @@ -72,37 +72,37 @@ EOT $this->rfs = new RemoteFilesystem($this->getIO(), $config); $this->process = new ProcessExecutor($this->getIO()); - $output->write('Checking platform settings: '); - $this->outputResult($output, $this->checkPlatform()); + $this->getIO()->write('Checking platform settings: ', false); + $this->outputResult($this->checkPlatform()); - $output->write('Checking git settings: '); - $this->outputResult($output, $this->checkGit()); + $this->getIO()->write('Checking git settings: ', false); + $this->outputResult($this->checkGit()); - $output->write('Checking http connectivity: '); - $this->outputResult($output, $this->checkHttp()); + $this->getIO()->write('Checking http connectivity: ', false); + $this->outputResult($this->checkHttp()); $opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org')); if (!empty($opts['http']['proxy'])) { - $output->write('Checking HTTP proxy: '); - $this->outputResult($output, $this->checkHttpProxy()); - $output->write('Checking HTTP proxy support for request_fulluri: '); - $this->outputResult($output, $this->checkHttpProxyFullUriRequestParam()); - $output->write('Checking HTTPS proxy support for request_fulluri: '); - $this->outputResult($output, $this->checkHttpsProxyFullUriRequestParam()); + $this->getIO()->write('Checking HTTP proxy: ', false); + $this->outputResult($this->checkHttpProxy()); + $this->getIO()->write('Checking HTTP proxy support for request_fulluri: ', false); + $this->outputResult($this->checkHttpProxyFullUriRequestParam()); + $this->getIO()->write('Checking HTTPS proxy support for request_fulluri: ', false); + $this->outputResult($this->checkHttpsProxyFullUriRequestParam()); } if ($oauth = $config->get('github-oauth')) { foreach ($oauth as $domain => $token) { - $output->write('Checking '.$domain.' oauth access: '); - $this->outputResult($output, $this->checkGithubOauth($domain, $token)); + $this->getIO()->write('Checking '.$domain.' oauth access: ', false); + $this->outputResult($this->checkGithubOauth($domain, $token)); } } else { - $output->write('Checking github.com rate limit: '); + $this->getIO()->write('Checking github.com rate limit: ', false); $rate = $this->getGithubRateLimit('github.com'); if (10 > $rate['remaining']) { - $output->writeln('WARNING'); - $output->writeln(sprintf( + $this->getIO()->write('WARNING'); + $this->getIO()->write(sprintf( 'Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL @@ -112,15 +112,15 @@ EOT $rate['limit'] )); } else { - $output->writeln('OK'); + $this->getIO()->write('OK'); } } - $output->write('Checking disk free space: '); - $this->outputResult($output, $this->checkDiskSpace($config)); + $this->getIO()->write('Checking disk free space: ', false); + $this->outputResult($this->checkDiskSpace($config)); - $output->write('Checking composer version: '); - $this->outputResult($output, $this->checkVersion()); + $this->getIO()->write('Checking composer version: ', false); + $this->outputResult($this->checkVersion()); return $this->failures; } @@ -308,17 +308,17 @@ EOT return true; } - private function outputResult(OutputInterface $output, $result) + private function outputResult($result) { if (true === $result) { - $output->writeln('OK'); + $this->getIO()->write('OK'); } else { $this->failures++; - $output->writeln('FAIL'); + $this->getIO()->write('FAIL'); if ($result instanceof \Exception) { - $output->writeln('['.get_class($result).'] '.$result->getMessage()); + $this->getIO()->write('['.get_class($result).'] '.$result->getMessage()); } elseif ($result) { - $output->writeln(trim($result)); + $this->getIO()->write(trim($result)); } } } @@ -343,10 +343,34 @@ EOT } $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; + if (!function_exists('json_decode')) { + $errors['json'] = true; + } + + if (!extension_loaded('Phar')) { + $errors['phar'] = true; + } + + if (!extension_loaded('filter')) { + $errors['filter'] = true; + } + + if (!extension_loaded('hash')) { + $errors['hash'] = true; + } + + if (!extension_loaded('ctype')) { + $errors['ctype'] = true; + } + if (!ini_get('allow_url_fopen')) { $errors['allow_url_fopen'] = true; } + if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { + $errors['ioncube'] = ioncube_loader_version(); + } + if (version_compare(PHP_VERSION, '5.3.2', '<')) { $errors['php'] = PHP_VERSION; } @@ -356,19 +380,13 @@ EOT } if (!extension_loaded('openssl')) { - $warnings['openssl'] = true; + $errors['openssl'] = true; } if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { $warnings['apc_cli'] = true; } - if (ini_get('xdebug.profiler_enabled')) { - $warnings['xdebug_profile'] = true; - } elseif (extension_loaded('xdebug')) { - $warnings['xdebug_loaded'] = true; - } - ob_start(); phpinfo(INFO_GENERAL); $phpinfo = ob_get_clean(); @@ -384,19 +402,76 @@ EOT } } + if (ini_get('xdebug.profiler_enabled')) { + $warnings['xdebug_profile'] = true; + } elseif (extension_loaded('xdebug')) { + $warnings['xdebug_loaded'] = true; + } + if (!empty($errors)) { foreach ($errors as $error => $current) { switch ($error) { + case 'json': + $text = PHP_EOL."The json extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-json"; + break; + + case 'phar': + $text = PHP_EOL."The phar extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-phar"; + break; + + case 'filter': + $text = PHP_EOL."The filter extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-filter"; + break; + + case 'hash': + $text = PHP_EOL."The hash extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-hash"; + break; + + case 'ctype': + $text = PHP_EOL."The ctype extension is missing.".PHP_EOL; + $text .= "Install it or recompile php without --disable-ctype"; + 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 = "Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher."; + $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher."; break; case 'allow_url_fopen': - $text = "The allow_url_fopen setting is incorrect.".PHP_EOL; + $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; - $text .= " allow_url_fopen = On"; + $text .= " allow_url_fopen = On"; $displayIniMessage = true; break; + + case 'ioncube': + $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL; + $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL; + $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; + $displayIniMessage = true; + break; + + case 'openssl': + $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL; + $text .= "If possible you should enable it or recompile php with --with-openssl"; + break; } $out($text, 'error'); } @@ -425,11 +500,6 @@ EOT $text .= " Recompile it without this flag if possible"; break; - case 'openssl': - $text = "The openssl extension is missing, which will reduce the security and stability of Composer.".PHP_EOL; - $text .= " If possible you should enable it or recompile php with --with-openssl"; - break; - case 'php': $text = "Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL; $text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues."; diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index adcc7adfd..f560953b8 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -55,9 +55,9 @@ EOT $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); if ($optimize) { - $output->writeln('Generating optimized autoload files'); + $this->getIO()->writeError('Generating optimized autoload files'); } else { - $output->writeln('Generating autoload files'); + $this->getIO()->writeError('Generating autoload files'); } $generator = $composer->getAutoloadGenerator(); diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index 15f1fff08..502ce5e8a 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -72,7 +72,7 @@ EOT // change to global dir $config = Factory::createConfig(); chdir($config->get('home')); - $output->writeln('Changed current directory to '.$config->get('home').''); + $this->getIO()->writeError('Changed current directory to '.$config->get('home').''); // create new input without "global" command prefix $input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index 7dd8113cb..51a135c68 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -57,15 +57,21 @@ EOT */ protected function execute(InputInterface $input, OutputInterface $output) { - $repo = $this->initializeRepo(); + $repos = $this->initializeRepos(); $return = 0; foreach ($input->getArgument('packages') as $packageName) { + foreach ($repos as $repo) { + $package = $this->getPackage($repo, $packageName); + if ($package instanceof CompletePackageInterface) { + break; + } + } $package = $this->getPackage($repo, $packageName); if (!$package instanceof CompletePackageInterface) { $return = 1; - $output->writeln('Package '.$packageName.' not found'); + $this->getIO()->writeError('Package '.$packageName.' not found'); continue; } @@ -78,13 +84,13 @@ EOT if (!filter_var($url, FILTER_VALIDATE_URL)) { $return = 1; - $output->writeln(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); + $this->getIO()->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); continue; } if ($input->getOption('show')) { - $output->writeln(sprintf('%s', $url)); + $this->getIO()->write(sprintf('%s', $url)); } else { $this->openBrowser($url); } @@ -139,26 +145,30 @@ EOT } elseif (0 === $osx) { passthru('open ' . $url); } else { - $this->getIO()->write('no suitable browser opening command found, open yourself: ' . $url); + $this->getIO()->writeError('no suitable browser opening command found, open yourself: ' . $url); } } /** - * Initializes the repo + * Initializes repositories * - * @return CompositeRepository + * Returns an array of repos in order they should be checked in + * + * @return RepositoryInterface[] */ - private function initializeRepo() + private function initializeRepos() { $composer = $this->getComposer(false); if ($composer) { - $repo = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); - } else { - $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $repo = new CompositeRepository($defaultRepos); + return array( + $composer->getRepositoryManager()->getLocalRepository(), + new CompositeRepository($composer->getRepositoryManager()->getRepositories()) + ); } - return $repo; + $defaultRepos = Factory::createDefaultRepositories($this->getIO()); + + return array(new CompositeRepository($defaultRepos)); } } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 38dc2c4a5..e8826687a 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -117,13 +117,13 @@ EOT $json = $file->encode($options); if ($input->isInteractive()) { - $output->writeln(array( + $this->getIO()->writeError(array( '', $json, '' )); if (!$dialog->askConfirmation($output, $dialog->getQuestion('Do you confirm generation', 'yes', '?'), true)) { - $output->writeln('Command aborted'); + $this->getIO()->writeError('Command aborted'); return 1; } @@ -154,14 +154,14 @@ EOT $dialog = $this->getHelperSet()->get('dialog'); $formatter = $this->getHelperSet()->get('formatter'); - $output->writeln(array( + $this->getIO()->writeError(array( '', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), '' )); // namespace - $output->writeln(array( + $this->getIO()->writeError(array( '', 'This command will guide you through creating your composer.json config.', '', @@ -266,7 +266,7 @@ EOT ); $input->setOption('license', $license); - $output->writeln(array( + $this->getIO()->writeError(array( '', 'Define your dependencies.', '' @@ -316,7 +316,7 @@ EOT $version = $this->findBestVersionForPackage($input, $requirement['name']); $requirement['version'] = $version; - $output->writeln(sprintf( + $this->getIO()->writeError(sprintf( 'Using version %s for %s', $requirement['version'], $requirement['name'] @@ -329,6 +329,7 @@ EOT return $result; } + $versionParser = new VersionParser(); while (null !== $package = $dialog->ask($output, $prompt)) { $matches = $this->findPackages($package); @@ -345,31 +346,41 @@ EOT // no match, prompt which to pick if (!$exactMatch) { - $output->writeln(array( + $this->getIO()->writeError(array( '', sprintf('Found %s packages matching %s', count($matches), $package), '' )); - $output->writeln($choices); - $output->writeln(''); + $this->getIO()->writeError($choices); + $this->getIO()->writeError(''); - $validator = function ($selection) use ($matches) { + $validator = function ($selection) use ($matches, $versionParser) { if ('' === $selection) { return false; } - if (!is_numeric($selection) && preg_match('{^\s*(\S+)\s+(\S.*)\s*$}', $selection, $matches)) { - return $matches[1].' '.$matches[2]; + if (is_numeric($selection) && isset($matches[(int) $selection])) { + $package = $matches[(int) $selection]; + + return $package['name']; } - if (!isset($matches[(int) $selection])) { - throw new \Exception('Not a valid selection'); + if (preg_match('{^\s*(?P[\S/]+)(?:\s+(?P\S+))?\s*$}', $selection, $packageMatches)) { + if (isset($packageMatches['version'])) { + // parsing `acme/example ~2.3` + + // validate version constraint + $versionParser->parseConstraints($packageMatches['version']); + + return $packageMatches['name'].' '.$packageMatches['version']; + } + + // parsing `acme/example` + return $packageMatches['name']; } - $package = $matches[(int) $selection]; - - return $package['name']; + throw new \Exception('Not a valid selection'); }; $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or the complete package name if it is not listed', false, ':'), $validator, 3); @@ -392,7 +403,7 @@ EOT if (false === $constraint) { $constraint = $this->findBestVersionForPackage($input, $package); - $output->writeln(sprintf( + $this->getIO()->writeError(sprintf( 'Using version %s for %s', $constraint, $package diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 115e1d6af..e548b8d69 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -65,18 +65,18 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { if ($args = $input->getArgument('packages')) { - $output->writeln('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); + $this->getIO()->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); return 1; } if ($input->getOption('no-custom-installers')) { - $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { - $output->writeln('You are using the deprecated option "dev". Dev packages are installed by default now.'); + $this->getIO()->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index 8ab9d94b4..513e51aac 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -18,7 +18,8 @@ use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\PackageInterface; use Composer\Repository\RepositoryInterface; -use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -68,14 +69,17 @@ EOT switch ($format = $input->getOption('format')) { case 'text': - $output->writeln('Name: '.$root->getPrettyName().''); - $output->writeln('Version: '.$versionParser->formatVersion($root).''); - $output->writeln('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); - $output->writeln('Dependencies:'); + $this->getIO()->write('Name: '.$root->getPrettyName().''); + $this->getIO()->write('Version: '.$versionParser->formatVersion($root).''); + $this->getIO()->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); + $this->getIO()->write('Dependencies:'); + $this->getIO()->write(''); - $table = $this->getHelperSet()->get('table'); - $table->setLayout(TableHelper::LAYOUT_BORDERLESS); - $table->setHorizontalBorderChar(''); + $table = new Table($output); + $table->setStyle('compact'); + $table->getStyle()->setVerticalBorderChar(''); + $table->getStyle()->setCellRowContentFormat('%s '); + $table->setHeaders(array('Name', 'Version', 'License')); foreach ($packages as $package) { $table->addRow(array( $package->getPrettyName(), @@ -83,7 +87,7 @@ EOT implode(', ', $package->getLicense()) ?: 'none', )); } - $table->render($output); + $table->render(); break; case 'json': @@ -94,7 +98,7 @@ EOT ); } - $output->writeln(JsonFile::encode(array( + $this->getIO()->write(JsonFile::encode(array( 'name' => $root->getPrettyName(), 'version' => $versionParser->formatVersion($root), 'license' => $root->getLicense(), diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index c292a2812..8821bd991 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -73,7 +73,7 @@ EOT if (isset($composer[$type][$package])) { $json->removeLink($type, $package); } elseif (isset($composer[$altType][$package])) { - $output->writeln(''.$package.' could not be found in '.$type.' but it is present in '.$altType.''); + $this->getIO()->writeError(''.$package.' could not be found in '.$type.' but it is present in '.$altType.''); $dialog = $this->getHelperSet()->get('dialog'); if ($this->getIO()->isInteractive()) { if ($dialog->askConfirmation($output, $dialog->getQuestion('Do you want to remove it from '.$altType, 'yes', '?'), true)) { @@ -81,7 +81,7 @@ EOT } } } else { - $output->writeln(''.$package.' is not required in your composer.json and has not been removed'); + $this->getIO()->writeError(''.$package.' is not required in your composer.json and has not been removed'); } } @@ -111,7 +111,7 @@ EOT $status = $install->run(); if ($status !== 0) { - $output->writeln("\n".'Removal failed, reverting '.$file.' to its original content.'); + $this->getIO()->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); file_put_contents($jsonFile->getPath(), $composerBackup); } diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index ea972aaf0..4391f1d05 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -67,17 +67,17 @@ EOT $newlyCreated = !file_exists($file); if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) { - $output->writeln(''.$file.' could not be created.'); + $this->getIO()->writeError(''.$file.' could not be created.'); return 1; } if (!is_readable($file)) { - $output->writeln(''.$file.' is not readable.'); + $this->getIO()->writeError(''.$file.' is not readable.'); return 1; } if (!is_writable($file)) { - $output->writeln(''.$file.' is not writable.'); + $this->getIO()->writeError(''.$file.' is not writable.'); return 1; } @@ -122,7 +122,7 @@ EOT $json->write($composerDefinition); } - $output->writeln(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); + $this->getIO()->writeError(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; @@ -154,10 +154,10 @@ EOT $status = $install->run(); if ($status !== 0) { if ($newlyCreated) { - $output->writeln("\n".'Installation failed, deleting '.$file.'.'); + $this->getIO()->writeError("\n".'Installation failed, deleting '.$file.'.'); unlink($json->getPath()); } else { - $output->writeln("\n".'Installation failed, reverting '.$file.' to its original content.'); + $this->getIO()->writeError("\n".'Installation failed, reverting '.$file.' to its original content.'); file_put_contents($json->getPath(), $composerBackup); } } diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index f01a5febe..8b5b13d2a 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -27,7 +27,7 @@ class RunScriptCommand extends Command /** * @var array Array with command events */ - protected $commandEvents = array( + protected $scriptEvents = array( ScriptEvents::PRE_INSTALL_CMD, ScriptEvents::POST_INSTALL_CMD, ScriptEvents::PRE_UPDATE_CMD, @@ -35,17 +35,11 @@ class RunScriptCommand extends Command ScriptEvents::PRE_STATUS_CMD, ScriptEvents::POST_STATUS_CMD, ScriptEvents::POST_ROOT_PACKAGE_INSTALL, - ScriptEvents::POST_CREATE_PROJECT_CMD - ); - - /** - * @var array Array with script events - */ - protected $scriptEvents = array( + ScriptEvents::POST_CREATE_PROJECT_CMD, ScriptEvents::PRE_ARCHIVE_CMD, ScriptEvents::POST_ARCHIVE_CMD, ScriptEvents::PRE_AUTOLOAD_DUMP, - ScriptEvents::POST_AUTOLOAD_DUMP + ScriptEvents::POST_AUTOLOAD_DUMP, ); protected function configure() @@ -54,10 +48,11 @@ class RunScriptCommand extends Command ->setName('run-script') ->setDescription('Run the scripts defined in composer.json.') ->setDefinition(array( - new InputArgument('script', InputArgument::REQUIRED, 'Script name to run.'), + new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), + new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), )) ->setHelp(<<run-script command runs scripts defined in composer.json: @@ -70,8 +65,14 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption('list')) { + return $this->listScripts(); + } elseif (!$input->getArgument('script')) { + throw new \RunTimeException('Missing required argument "script"'); + } + $script = $input->getArgument('script'); - if (!in_array($script, $this->commandEvents) && !in_array($script, $this->scriptEvents)) { + if (!in_array($script, $this->scriptEvents)) { if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script)); } @@ -86,15 +87,28 @@ EOT // add the bin dir to the PATH to make local binaries of deps usable in scripts $binDir = $composer->getConfig()->get('bin-dir'); if (is_dir($binDir)) { - putenv('PATH='.realpath($binDir).PATH_SEPARATOR.getenv('PATH')); + $_SERVER['PATH'] = realpath($binDir).PATH_SEPARATOR.getenv('PATH'); + putenv('PATH='.$_SERVER['PATH']); } $args = $input->getArgument('args'); - if (in_array($script, $this->commandEvents)) { - return $composer->getEventDispatcher()->dispatchCommandEvent($script, $input->getOption('dev') || !$input->getOption('no-dev'), $args); - } - return $composer->getEventDispatcher()->dispatchScript($script, $input->getOption('dev') || !$input->getOption('no-dev'), $args); } + + protected function listScripts() + { + $scripts = $this->getComposer()->getPackage()->getScripts(); + + if (!count($scripts)) { + return 0; + } + + $this->getIO()->writeError('scripts:'); + foreach ($scripts as $name => $script) { + $this->getIO()->write(' ' . $name); + } + + return 0; + } } diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index 958678068..b8f0abff5 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -57,7 +57,8 @@ EOT // add the bin dir to the PATH to make local binaries of deps usable in scripts $binDir = $composer->getConfig()->get('bin-dir'); if (is_dir($binDir)) { - putenv('PATH='.realpath($binDir).PATH_SEPARATOR.getenv('PATH')); + $_SERVER['PATH'] = realpath($binDir).PATH_SEPARATOR.getenv('PATH'); + putenv('PATH='.$_SERVER['PATH']); } $args = $input->getArguments(); diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index b9aaa8d74..54990f30d 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -62,7 +62,7 @@ EOT $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $output->writeln('No composer.json found in the current directory, showing packages from ' . implode(', ', array_keys($defaultRepos))); + $this->getIO()->writeError('No composer.json found in the current directory, showing packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = $platformRepo; $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } @@ -78,7 +78,7 @@ EOT $results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags); foreach ($results as $result) { - $output->writeln($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); + $this->getIO()->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); } } } diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index eb0de083e..e4e96e8f4 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -84,13 +84,13 @@ EOT $updateVersion = $input->getArgument('version') ?: $latestVersion; if (preg_match('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { - $output->writeln('You can not update to a specific SHA-1 as those phars are not available for download'); + $this->getIO()->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); return 1; } if (Composer::VERSION === $updateVersion) { - $output->writeln('You are already using composer version '.$updateVersion.'.'); + $this->getIO()->writeError('You are already using composer version '.$updateVersion.'.'); return 0; } @@ -104,11 +104,11 @@ EOT self::OLD_INSTALL_EXT ); - $output->writeln(sprintf("Updating to version %s.", $updateVersion)); + $this->getIO()->writeError(sprintf("Updating to version %s.", $updateVersion)); $remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar"); $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress')); if (!file_exists($tempFilename)) { - $output->writeln('The download of the new composer version failed for an unexpected reason'); + $this->getIO()->writeError('The download of the new composer version failed for an unexpected reason'); return 1; } @@ -120,22 +120,22 @@ EOT $fs = new Filesystem; foreach ($finder as $file) { $file = (string) $file; - $output->writeln('Removing: '.$file.''); + $this->getIO()->writeError('Removing: '.$file.''); $fs->remove($file); } } if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { - $output->writeln('The file is corrupted ('.$err->getMessage().').'); - $output->writeln('Please re-run the self-update command to try again.'); + $this->getIO()->writeError('The file is corrupted ('.$err->getMessage().').'); + $this->getIO()->writeError('Please re-run the self-update command to try again.'); return 1; } if (file_exists($backupFile)) { - $output->writeln('Use composer self-update --rollback to return to version '.Composer::VERSION); + $this->getIO()->writeError('Use composer self-update --rollback to return to version '.Composer::VERSION); } else { - $output->writeln('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); + $this->getIO()->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); } } @@ -160,9 +160,9 @@ EOT } $oldFile = $rollbackDir . "/{$rollbackVersion}" . self::OLD_INSTALL_EXT; - $output->writeln(sprintf("Rolling back to version %s.", $rollbackVersion)); + $this->getIO()->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); if ($err = $this->setLocalPhar($localFilename, $oldFile)) { - $output->writeln('The backup file was corrupted ('.$err->getMessage().') and has been removed.'); + $this->getIO()->writeError('The backup file was corrupted ('.$err->getMessage().') and has been removed.'); return 1; } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 907e99f90..6f2a1dab6 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -84,7 +84,7 @@ EOT } else { $defaultRepos = Factory::createDefaultRepositories($this->getIO()); $repos = new CompositeRepository($defaultRepos); - $output->writeln('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $this->getIO()->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); @@ -92,7 +92,7 @@ EOT $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $output->writeln('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $this->getIO()->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = $platformRepo; $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } @@ -119,9 +119,9 @@ EOT $this->printLinks($input, $output, $package, 'requires'); $this->printLinks($input, $output, $package, 'devRequires', 'requires (dev)'); if ($package->getSuggests()) { - $output->writeln("\nsuggests"); + $this->getIO()->write("\nsuggests"); foreach ($package->getSuggests() as $suggested => $reason) { - $output->writeln($suggested . ' ' . $reason . ''); + $this->getIO()->write($suggested . ' ' . $reason . ''); } } $this->printLinks($input, $output, $package, 'provides'); @@ -172,7 +172,7 @@ EOT foreach (array('platform:' => true, 'available:' => false, 'installed:' => true) as $type => $showVersion) { if (isset($packages[$type])) { if ($tree) { - $output->writeln($type); + $this->getIO()->write($type); } ksort($packages[$type]); @@ -222,10 +222,10 @@ EOT } else { $output->write($indent . $package); } - $output->writeln(''); + $this->getIO()->write(''); } if ($tree) { - $output->writeln(''); + $this->getIO()->write(''); } } } @@ -285,53 +285,53 @@ EOT */ protected function printMeta(InputInterface $input, OutputInterface $output, CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, RepositoryInterface $repos) { - $output->writeln('name : ' . $package->getPrettyName()); - $output->writeln('descrip. : ' . $package->getDescription()); - $output->writeln('keywords : ' . join(', ', $package->getKeywords() ?: array())); + $this->getIO()->write('name : ' . $package->getPrettyName()); + $this->getIO()->write('descrip. : ' . $package->getDescription()); + $this->getIO()->write('keywords : ' . join(', ', $package->getKeywords() ?: array())); $this->printVersions($input, $output, $package, $versions, $installedRepo, $repos); - $output->writeln('type : ' . $package->getType()); - $output->writeln('license : ' . implode(', ', $package->getLicense())); - $output->writeln('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); - $output->writeln('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); - $output->writeln('names : ' . implode(', ', $package->getNames())); + $this->getIO()->write('type : ' . $package->getType()); + $this->getIO()->write('license : ' . implode(', ', $package->getLicense())); + $this->getIO()->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); + $this->getIO()->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); + $this->getIO()->write('names : ' . implode(', ', $package->getNames())); if ($package->isAbandoned()) { $replacement = ($package->getReplacementPackage() !== null) ? ' The author suggests using the ' . $package->getReplacementPackage(). ' package instead.' : null; - $output->writeln( + $this->getIO()->writeError( sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) ); } if ($package->getSupport()) { - $output->writeln("\nsupport"); + $this->getIO()->write("\nsupport"); foreach ($package->getSupport() as $type => $value) { - $output->writeln('' . $type . ' : '.$value); + $this->getIO()->write('' . $type . ' : '.$value); } } if ($package->getAutoload()) { - $output->writeln("\nautoload"); + $this->getIO()->write("\nautoload"); foreach ($package->getAutoload() as $type => $autoloads) { - $output->writeln('' . $type . ''); + $this->getIO()->write('' . $type . ''); if ($type === 'psr-0') { foreach ($autoloads as $name => $path) { - $output->writeln(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + $this->getIO()->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'psr-4') { foreach ($autoloads as $name => $path) { - $output->writeln(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + $this->getIO()->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'classmap') { - $output->writeln(implode(', ', $autoloads)); + $this->getIO()->write(implode(', ', $autoloads)); } } if ($package->getIncludePaths()) { - $output->writeln('include-path'); - $output->writeln(implode(', ', $package->getIncludePaths())); + $this->getIO()->write('include-path'); + $this->getIO()->write(implode(', ', $package->getIncludePaths())); } } } @@ -355,7 +355,7 @@ EOT $versions = implode(', ', $versions); - $output->writeln('versions : ' . $versions); + $this->getIO()->write('versions : ' . $versions); } /** @@ -371,10 +371,10 @@ EOT { $title = $title ?: $linkType; if ($links = $package->{'get'.ucfirst($linkType)}()) { - $output->writeln("\n" . $title . ""); + $this->getIO()->write("\n" . $title . ""); foreach ($links as $link) { - $output->writeln($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); + $this->getIO()->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); } } } diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 65662c048..220327cb6 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -57,7 +57,7 @@ EOT $im = $composer->getInstallationManager(); // Dispatch pre-status-command - $composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::PRE_STATUS_CMD, true); + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); $errors = array(); @@ -76,9 +76,9 @@ EOT // output errors/warnings if (!$errors) { - $output->writeln('No local changes'); + $this->getIO()->writeError('No local changes'); } else { - $output->writeln('You have changes in the following dependencies:'); + $this->getIO()->writeError('You have changes in the following dependencies:'); } foreach ($errors as $path => $changes) { @@ -86,19 +86,19 @@ EOT $indentedChanges = implode("\n", array_map(function ($line) { return ' ' . ltrim($line); }, explode("\n", $changes))); - $output->writeln(''.$path.':'); - $output->writeln($indentedChanges); + $this->getIO()->write(''.$path.':'); + $this->getIO()->write($indentedChanges); } else { - $output->writeln($path); + $this->getIO()->write($path); } } if ($errors && !$input->getOption('verbose')) { - $output->writeln('Use --verbose (-v) to see modified files'); + $this->getIO()->writeError('Use --verbose (-v) to see modified files'); } // Dispatch post-status-command - $composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_STATUS_CMD, true); + $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); return $errors ? 1 : 0; } diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 460075f0f..579236143 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -75,12 +75,12 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('no-custom-installers')) { - $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { - $output->writeln('You are using the deprecated option "dev". Dev packages are installed by default now.'); + $this->getIO()->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index e7e0860e1..9298e4175 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -37,6 +37,7 @@ class ValidateCommand extends Command ->setDescription('Validates a composer.json') ->setDefinition(array( new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), + new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json') )) ->setHelp(<<getArgument('file'); if (!file_exists($file)) { - $output->writeln('' . $file . ' not found.'); + $this->getIO()->writeError('' . $file . ' not found.'); return 1; } if (!is_readable($file)) { - $output->writeln('' . $file . ' is not readable.'); + $this->getIO()->writeError('' . $file . ' is not readable.'); return 1; } $validator = new ConfigValidator($this->getIO()); $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; + $checkPublish = !$input->getOption('no-check-publish'); list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); // output errors/warnings if (!$errors && !$publishErrors && !$warnings) { - $output->writeln('' . $file . ' is valid'); + $this->getIO()->write('' . $file . ' is valid'); } elseif (!$errors && !$publishErrors) { - $output->writeln('' . $file . ' is valid, but with a few warnings'); - $output->writeln('See http://getcomposer.org/doc/04-schema.md for details on the schema'); + $this->getIO()->writeError('' . $file . ' is valid, but with a few warnings'); + $this->getIO()->writeError('See http://getcomposer.org/doc/04-schema.md for details on the schema'); } elseif (!$errors) { - $output->writeln('' . $file . ' is valid for simple usage with composer but has'); - $output->writeln('strict errors that make it unable to be published as a package:'); - $output->writeln('See http://getcomposer.org/doc/04-schema.md for details on the schema'); + $this->getIO()->writeError('' . $file . ' is valid for simple usage with composer but has'); + $this->getIO()->writeError('strict errors that make it unable to be published as a package:'); + $this->getIO()->writeError('See http://getcomposer.org/doc/04-schema.md for details on the schema'); } else { - $output->writeln('' . $file . ' is invalid, the following errors/warnings were found:'); + $this->getIO()->writeError('' . $file . ' is invalid, the following errors/warnings were found:'); } $messages = array( - 'error' => array_merge($errors, $publishErrors), + 'error' => $errors, 'warning' => $warnings, ); + // If checking publish errors, display them errors, otherwise just show them as warnings + if ($checkPublish) { + $messages['error'] = array_merge($messages['error'], $publishErrors); + } else { + $messages['warning'] = array_merge($messages['warning'], $publishErrors); + } + foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { - $output->writeln('<' . $style . '>' . $msg . ''); + $this->getIO()->writeError('<' . $style . '>' . $msg . ''); } } - return $errors || $publishErrors ? 1 : 0; + return $errors || ($publishErrors && $checkPublish) ? 1 : 0; } } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 8697d8caf..b5db79304 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -102,10 +102,13 @@ class Compiler $finder->files() ->ignoreVCS(true) ->name('*.php') + ->name('LICENSE') ->exclude('Tests') + ->exclude('tests') + ->exclude('docs') ->in(__DIR__.'/../../vendor/symfony/') - ->in(__DIR__.'/../../vendor/seld/jsonlint/src/') - ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/src/') + ->in(__DIR__.'/../../vendor/seld/jsonlint/') + ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/') ; foreach ($finder as $file) { diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 1c8246f88..2ccaec890 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -15,6 +15,7 @@ namespace Composer\Console; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Formatter\OutputFormatter; @@ -91,7 +92,7 @@ class Application extends BaseApplication $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); if (version_compare(PHP_VERSION, '5.3.2', '<')) { - $output->writeln('Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.'); + $this->getIO()->writeError('Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.'); } if (defined('COMPOSER_DEV_WARNING_TIME')) { @@ -104,7 +105,7 @@ class Application extends BaseApplication } if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { if (time() > COMPOSER_DEV_WARNING_TIME) { - $output->writeln(sprintf('Warning: This development build of composer is over 30 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); + $this->getIO()->writeError(sprintf('Warning: This development build of composer is over 30 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); } } } @@ -117,8 +118,8 @@ class Application extends BaseApplication if ($newWorkDir = $this->getNewWorkingDir($input)) { $oldWorkingDir = getcwd(); chdir($newWorkDir); - if ($output->getVerbosity() >= 4) { - $output->writeln('Changed CWD to ' . getcwd()); + if ($this->getIO()->isDebug() >= 4) { + $this->getIO()->writeError('Changed CWD to ' . getcwd()); } } @@ -129,7 +130,7 @@ class Application extends BaseApplication foreach ($composer['scripts'] as $script => $dummy) { if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { if ($this->has($script)) { - $output->writeln('A script named '.$script.' would override a native Composer function and has been skipped'); + $this->getIO()->writeError('A script named '.$script.' would override a native Composer function and has been skipped'); } else { $this->add(new Command\ScriptAliasCommand($script)); } @@ -150,7 +151,7 @@ class Application extends BaseApplication } if (isset($startTime)) { - $output->writeln('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'); + $this->getIO()->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'); } return $result; @@ -186,23 +187,27 @@ class Application extends BaseApplication || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { - $output->writeln('The disk hosting '.$dir.' is full, this may be the cause of the following exception'); + $this->getIO()->writeError('The disk hosting '.$dir.' is full, this may be the cause of the following exception'); } } } catch (\Exception $e) { } if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { - $output->writeln('The following exception may be caused by a stale entry in your cmd.exe AutoRun'); - $output->writeln('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details'); + $this->getIO()->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun'); + $this->getIO()->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details'); } if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { - $output->writeln('The following exception is caused by a lack of memory and not having swap configured'); - $output->writeln('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details'); + $this->getIO()->writeError('The following exception is caused by a lack of memory and not having swap configured'); + $this->getIO()->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details'); } - return parent::renderException($exception, $output); + if ($output instanceof ConsoleOutputInterface) { + parent::renderException($exception, $output->getErrorOutput()); + } else { + parent::renderException($exception, $output); + } } /** @@ -218,7 +223,7 @@ class Application extends BaseApplication $this->composer = Factory::create($this->io, null, $disablePlugins); } catch (\InvalidArgumentException $e) { if ($required) { - $this->io->write($e->getMessage()); + $this->io->writeError($e->getMessage()); exit(1); } } catch (JsonValidationException $e) { @@ -323,7 +328,6 @@ class Application extends BaseApplication protected function getDefaultHelperSet() { $helperSet = parent::getDefaultHelperSet(); - $helperSet->set(new DialogHelper()); return $helperSet; diff --git a/src/Composer/Console/HtmlOutputFormatter.php b/src/Composer/Console/HtmlOutputFormatter.php index 56652bb69..8a79dba08 100644 --- a/src/Composer/Console/HtmlOutputFormatter.php +++ b/src/Composer/Console/HtmlOutputFormatter.php @@ -83,6 +83,6 @@ class HtmlOutputFormatter extends OutputFormatter } } - return $out . '">'.$matches[2].''; + return $out.'">'.$matches[2].''; } } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index e0d2d8a13..0063a44ba 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -65,6 +65,11 @@ class Pool } $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 setWhitelist($whitelist) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 9373a7b7b..b1d2bb9b4 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -87,6 +87,19 @@ class Problem } if ($job && $job['cmd'] === 'install' && empty($packages)) { + // handle php/hhvm + if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { + $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; + + if (defined('HHVM_VERSION')) { + return $msg . 'your HHVM version does not satisfy that requirement.'; + } elseif ($job['packageName'] === 'hhvm') { + return $msg . 'you are running this with PHP and not HHVM.'; + } + + return $msg . 'your PHP version does not satisfy that requirement.'; + } + // handle php extensions if (0 === stripos($job['packageName'], 'ext-')) { $ext = substr($job['packageName'], 4); diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index cba9400ef..bdb82afca 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -217,7 +217,15 @@ class Rule $targetName = $this->reasonData->getTarget(); // handle php extensions - if (0 === strpos($targetName, 'ext-')) { + if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') { + if (defined('HHVM_VERSION')) { + $text .= ' -> your HHVM version does not satisfy that requirement.'; + } elseif ($targetName === 'hhvm') { + $text .= ' -> you are running this with PHP and not HHVM.'; + } else { + $text .= ' -> your PHP version does not satisfy that requirement.'; + } + } elseif (0 === strpos($targetName, 'ext-')) { $ext = substr($targetName, 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 19e69913b..77a945f20 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -40,6 +40,7 @@ class Solver protected $branches = array(); protected $problems = array(); protected $learnedPool = array(); + protected $learnedWhy = array(); public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed) { diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 204c09154..35dfe308a 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -35,7 +35,7 @@ abstract class ArchiveDownloader extends FileDownloader $fileName = parent::download($package, $path); if ($this->io->isVerbose()) { - $this->io->write(' Extracting archive'); + $this->io->writeError(' Extracting archive'); } try { @@ -77,7 +77,7 @@ abstract class ArchiveDownloader extends FileDownloader // retry downloading if we have an invalid zip file if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) { - $this->io->write(' Invalid zip file, retrying...'); + $this->io->writeError(' Invalid zip file, retrying...'); usleep(500000); continue; } @@ -88,7 +88,7 @@ abstract class ArchiveDownloader extends FileDownloader break; } - $this->io->write(''); + $this->io->writeError(''); } /** diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 4bbbae5a9..5c0980725 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -192,7 +192,7 @@ class DownloadManager foreach ($sources as $i => $source) { if (isset($e)) { - $this->io->write(' Now trying to download from ' . $source . ''); + $this->io->writeError(' Now trying to download from ' . $source . ''); } $package->setInstallationSource($source); try { @@ -206,7 +206,7 @@ class DownloadManager throw $e; } - $this->io->write( + $this->io->writeError( ' Failed to download '. $package->getPrettyName(). ' from ' . $source . ': '. diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 96bd57c06..04f8fc4b5 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -81,7 +81,7 @@ class FileDownloader implements DownloaderInterface throw new \InvalidArgumentException('The given package is missing url information'); } - $this->io->write(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); $urls = $package->getDistUrls(); while ($url = array_shift($urls)) { @@ -89,11 +89,11 @@ class FileDownloader implements DownloaderInterface return $this->doDownload($package, $path, $url); } catch (\Exception $e) { if ($this->io->isDebug()) { - $this->io->write(''); - $this->io->write('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); + $this->io->writeError(''); + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); } elseif (count($urls)) { - $this->io->write(''); - $this->io->write(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); + $this->io->writeError(''); + $this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); } if (!count($urls)) { @@ -102,7 +102,7 @@ class FileDownloader implements DownloaderInterface } } - $this->io->write(''); + $this->io->writeError(''); } protected function doDownload(PackageInterface $package, $path, $url) @@ -127,7 +127,7 @@ class FileDownloader implements DownloaderInterface // 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)) { if (!$this->outputProgress) { - $this->io->write(' Downloading'); + $this->io->writeError(' Downloading'); } // try to download 3 times then fail hard @@ -142,7 +142,7 @@ class FileDownloader implements DownloaderInterface throw $e; } if ($this->io->isVerbose()) { - $this->io->write(' Download failed, retrying...'); + $this->io->writeError(' Download failed, retrying...'); } usleep(500000); } @@ -152,7 +152,7 @@ class FileDownloader implements DownloaderInterface $this->cache->copyFrom($cacheKey, $fileName); } } else { - $this->io->write(' Loading from cache'); + $this->io->writeError(' Loading from cache'); } if (!file_exists($fileName)) { @@ -205,7 +205,7 @@ class FileDownloader implements DownloaderInterface */ public function remove(PackageInterface $package, $path) { - $this->io->write(" - Removing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Removing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index bdebaf4cf..dd8086b84 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -45,7 +45,7 @@ class GitDownloader extends VcsDownloader $ref = $package->getSourceReference(); $flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : ''; $command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer'; - $this->io->write(" Cloning ".$ref); + $this->io->writeError(" Cloning ".$ref); $commandCallable = function ($url) use ($ref, $path, $command) { return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($ref)); @@ -78,7 +78,7 @@ class GitDownloader extends VcsDownloader } $ref = $target->getSourceReference(); - $this->io->write(" Checking out ".$ref); + $this->io->writeError(" Checking out ".$ref); $command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer'; $commandCallable = function ($url) use ($command) { @@ -143,10 +143,10 @@ class GitDownloader extends VcsDownloader $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); - $this->io->write(' The package has modified files:'); - $this->io->write(array_slice($changes, 0, 10)); + $this->io->writeError(' The package has modified files:'); + $this->io->writeError(array_slice($changes, 0, 10)); if (count($changes) > 10) { - $this->io->write(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); + $this->io->writeError(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); } while (true) { @@ -167,21 +167,21 @@ class GitDownloader extends VcsDownloader throw new \RuntimeException('Update aborted'); case 'v': - $this->io->write($changes); + $this->io->writeError($changes); break; case '?': default: help: - $this->io->write(array( + $this->io->writeError(array( ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', )); if ($update) { - $this->io->write(' s - stash changes and try to reapply them after the update'); + $this->io->writeError(' s - stash changes and try to reapply them after the update'); } - $this->io->write(' ? - print help'); + $this->io->writeError(' ? - print help'); break; } } @@ -195,7 +195,7 @@ class GitDownloader extends VcsDownloader $path = $this->normalizePath($path); if ($this->hasStashedChanges) { $this->hasStashedChanges = false; - $this->io->write(' Re-applying stashed changes'); + $this->io->writeError(' Re-applying stashed changes'); if (0 !== $this->process->execute('git stash pop', $output, $path)) { throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); } @@ -261,7 +261,7 @@ class GitDownloader extends VcsDownloader // reference was not found (prints "fatal: reference is not a tree: $ref") if (false !== strpos($this->process->getErrorOutput(), $reference)) { - $this->io->write(' '.$reference.' is gone (history was rewritten?)'); + $this->io->writeError(' '.$reference.' is gone (history was rewritten?)'); } throw new \RuntimeException('Failed to execute ' . GitUtil::sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput()); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 3d5cc6209..117cc5a17 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -27,7 +27,7 @@ class HgDownloader extends VcsDownloader { $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($package->getSourceReference()); - $this->io->write(" Cloning ".$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()); @@ -45,7 +45,7 @@ class HgDownloader extends VcsDownloader { $url = ProcessExecutor::escape($url); $ref = ProcessExecutor::escape($target->getSourceReference()); - $this->io->write(" Updating to ".$target->getSourceReference()); + $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!is_dir($path.'/.hg')) { throw new \RuntimeException('The .hg directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information'); diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index 683ea9f34..ae2769999 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -31,7 +31,7 @@ class PerforceDownloader extends VcsDownloader $ref = $package->getSourceReference(); $label = $this->getLabelFromSourceReference($ref); - $this->io->write(' Cloning ' . $ref); + $this->io->writeError(' Cloning ' . $ref); $this->initPerforce($package, $path, $url); $this->perforce->setStream($ref); $this->perforce->p4Login($this->io); @@ -85,7 +85,7 @@ class PerforceDownloader extends VcsDownloader */ public function getLocalChanges(PackageInterface $package, $path) { - $this->io->write('Perforce driver does not check for local changes before overriding', true); + $this->io->writeError('Perforce driver does not check for local changes before overriding', true); return; } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 689781f6c..975780eb8 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -29,7 +29,7 @@ class SvnDownloader extends VcsDownloader SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); - $this->io->write(" Checking out ".$package->getSourceReference()); + $this->io->writeError(" Checking out ".$package->getSourceReference()); $this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path); } @@ -52,7 +52,7 @@ class SvnDownloader extends VcsDownloader } } - $this->io->write(" Checking out " . $ref); + $this->io->writeError(" Checking out " . $ref); $this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path); } @@ -114,10 +114,10 @@ class SvnDownloader extends VcsDownloader $changes = array_map(function ($elem) { return ' '.$elem; }, preg_split('{\s*\r?\n\s*}', $changes)); - $this->io->write(' The package has modified files:'); - $this->io->write(array_slice($changes, 0, 10)); + $this->io->writeError(' The package has modified files:'); + $this->io->writeError(array_slice($changes, 0, 10)); if (count($changes) > 10) { - $this->io->write(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); + $this->io->writeError(' '.count($changes) - 10 . ' more files modified, choose "v" to view the full list'); } while (true) { @@ -130,12 +130,12 @@ class SvnDownloader extends VcsDownloader throw new \RuntimeException('Update aborted'); case 'v': - $this->io->write($changes); + $this->io->writeError($changes); break; case '?': default: - $this->io->write(array( + $this->io->writeError(array( ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index e653794ca..254f6143d 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -54,7 +54,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } - $this->io->write(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); $this->filesystem->emptyDirectory($path); $urls = $package->getSourceUrls(); @@ -67,9 +67,9 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa break; } catch (\Exception $e) { if ($this->io->isDebug()) { - $this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage()); + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); } elseif (count($urls)) { - $this->io->write(' Failed, trying the next URL'); + $this->io->writeError(' Failed, trying the next URL'); } if (!count($urls)) { throw $e; @@ -77,7 +77,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } } - $this->io->write(''); + $this->io->writeError(''); } /** @@ -104,7 +104,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $to = VersionParser::formatVersion($target); } - $this->io->write(" - Updating " . $name . " (" . $from . " => " . $to . ")"); + $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . ")"); $this->cleanChanges($initial, $path, true); $urls = $target->getSourceUrls(); @@ -117,9 +117,9 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa break; } catch (\Exception $e) { if ($this->io->isDebug()) { - $this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage()); + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); } elseif (count($urls)) { - $this->io->write(' Failed, trying the next URL'); + $this->io->writeError(' Failed, trying the next URL'); } else { // in case of failed update, try to reapply the changes before aborting $this->reapplyChanges($path); @@ -146,12 +146,12 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa return ' ' . $line; }, explode("\n", $logs))); - $this->io->write(' '.$message); - $this->io->write($logs); + $this->io->writeError(' '.$message); + $this->io->writeError($logs); } } - $this->io->write(''); + $this->io->writeError(''); } /** @@ -159,7 +159,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa */ public function remove(PackageInterface $package, $path) { - $this->io->write(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); + $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); $this->cleanChanges($package, $path, false); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index dff5456e1..3bb1c8496 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -21,7 +21,6 @@ use Composer\Composer; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Repository\CompositeRepository; use Composer\Script; -use Composer\Script\CommandEvent; use Composer\Script\PackageEvent; use Composer\Util\ProcessExecutor; @@ -95,36 +94,28 @@ class EventDispatcher /** * Dispatch a package event. * - * @param string $eventName The constant in ScriptEvents - * @param boolean $devMode Whether or not we are in dev mode - * @param OperationInterface $operation The package being installed/updated/removed - * @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, OperationInterface $operation) - { - return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $operation)); - } - - /** - * Dispatch a command event. + * @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 CompositeRepository $installedRepo The installed repository + * @param Request $request The request + * @param array $operations The list of operations + * @param OperationInterface $operation The package being installed/updated/removed * - * @param string $eventName The constant in ScriptEvents - * @param boolean $devMode Whether or not we are in dev mode - * @param array $additionalArgs Arguments passed by the user - * @param array $flags Optional flags to pass data not as argument - * @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 + * @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 dispatchCommandEvent($eventName, $devMode, $additionalArgs = array(), $flags = array()) + public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation) { - return $this->doDispatch(new CommandEvent($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); + return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations, $operation)); } /** * Dispatch a installer event. * * @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 CompositeRepository $installedRepo The installed repository @@ -134,9 +125,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, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) + public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) { - return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $policy, $pool, $installedRepo, $request, $operations)); + return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations)); } /** @@ -163,11 +154,11 @@ class EventDispatcher $methodName = substr($callable, strpos($callable, '::') + 2); if (!class_exists($className)) { - $this->io->write('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script'); + $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script'); continue; } if (!is_callable($callable)) { - $this->io->write('Method '.$callable.' is not callable, can not call '.$event->getName().' script'); + $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script'); continue; } @@ -175,13 +166,13 @@ class EventDispatcher $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; - $this->io->write(''.sprintf($message, $callable, $event->getName()).''); + $this->io->writeError(''.sprintf($message, $callable, $event->getName()).''); throw $e; } } else { $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor','escape'), $event->getArguments())); if (0 !== ($exitCode = $this->process->execute($callable . ($args === '' ? '' : ' '.$args)))) { - $this->io->write(sprintf('Script %s handling the %s event returned with an error', $callable, $event->getName())); + $this->io->writeError(sprintf('Script %s handling the %s event returned with an error', $callable, $event->getName())); throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } @@ -214,10 +205,6 @@ class EventDispatcher */ protected function checkListenerExpectedEvent($target, Event $event) { - if (!$event instanceof Script\Event) { - return $event; - } - try { $reflected = new \ReflectionParameter($target, 0); } catch (\Exception $e) { @@ -232,8 +219,24 @@ class EventDispatcher $expected = $typehint->getName(); + // BC support if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') { - $event = new CommandEvent($event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments()); + $event = new \Composer\Script\CommandEvent( + $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments() + ); + } + if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') { + $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() + ); + } + if (!$event instanceof $expected && $expected === 'Composer\Script\Event') { + $event = new \Composer\Script\Event( + $event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), + $event->getArguments(), $event->getFlags() + ); } return $event; diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 3290b51dc..504ae8815 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -113,10 +113,10 @@ class Factory $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir))); // load global config - $file = new JsonFile($home.'/config.json'); + $file = new JsonFile($config->get('home').'/config.json'); if ($file->exists()) { if ($io && $io->isDebug()) { - $io->write('Loading config file ' . $file->getPath()); + $io->writeError('Loading config file ' . $file->getPath()); } $config->merge($file->read()); } @@ -126,7 +126,7 @@ class Factory $file = new JsonFile($config->get('home').'/auth.json'); if ($file->exists()) { if ($io && $io->isDebug()) { - $io->write('Loading config file ' . $file->getPath()); + $io->writeError('Loading config file ' . $file->getPath()); } $config->merge(array('config' => $file->read())); } @@ -227,12 +227,12 @@ class Factory $config->merge($localConfig); if (isset($composerFile)) { if ($io && $io->isDebug()) { - $io->write('Loading config file ' . $composerFile); + $io->writeError('Loading config file ' . $composerFile); } $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json'); if ($localAuthFile->exists()) { if ($io && $io->isDebug()) { - $io->write('Loading config file ' . $localAuthFile->getPath()); + $io->writeError('Loading config file ' . $localAuthFile->getPath()); } $config->merge(array('config' => $localAuthFile->read())); $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true)); @@ -362,7 +362,7 @@ class Factory $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false); } catch (\Exception $e) { if ($io->isDebug()) { - $io->write('Failed to initialize global composer: '.$e->getMessage()); + $io->writeError('Failed to initialize global composer: '.$e->getMessage()); } } diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index fc675ce70..581680b7c 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -23,16 +23,19 @@ use Symfony\Component\Console\Helper\HelperSet; class BufferIO extends ConsoleIO { /** - * @param string $input - * @param int $verbosity + * @param string $input + * @param int $verbosity * @param OutputFormatterInterface $formatter */ - public function __construct($input = '', $verbosity = null, OutputFormatterInterface $formatter = null) - { + public function __construct( + $input = '', + $verbosity = StreamOutput::VERBOSITY_NORMAL, + OutputFormatterInterface $formatter = null + ) { $input = new StringInput($input); $input->setInteractive(false); - $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity === null ? StreamOutput::VERBOSITY_NORMAL : $verbosity, !empty($formatter), $formatter); + $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, !empty($formatter), $formatter); parent::__construct($input, $output, new HelperSet(array())); } diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 7df26eabf..38e722bf5 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -13,6 +13,7 @@ namespace Composer\IO; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Process\ExecutableFinder; @@ -29,6 +30,7 @@ class ConsoleIO extends BaseIO protected $output; protected $helperSet; protected $lastMessage; + protected $lastMessageErr; private $startTime; /** @@ -94,6 +96,24 @@ class ConsoleIO extends BaseIO * {@inheritDoc} */ public function write($messages, $newline = true) + { + $this->doWrite($messages, $newline, false); + } + + /** + * {@inheritDoc} + */ + public function writeError($messages, $newline = true) + { + $this->doWrite($messages, $newline, true); + } + + /** + * @param array $messages + * @param boolean $newline + * @param boolean $stderr + */ + private function doWrite($messages, $newline, $stderr) { if (null !== $this->startTime) { $memoryUsage = memory_get_usage() / 1024 / 1024; @@ -102,6 +122,13 @@ class ConsoleIO extends BaseIO return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } + + if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { + $this->output->getErrorOutput()->write($messages, $newline); + $this->lastMessageErr = join($newline ? "\n" : '', (array) $messages); + return; + } + $this->output->write($messages, $newline); $this->lastMessage = join($newline ? "\n" : '', (array) $messages); } @@ -111,12 +138,29 @@ class ConsoleIO extends BaseIO */ public function overwrite($messages, $newline = true, $size = null) { - if (!$this->output->isDecorated()) { - if (!$messages) { - return; - } + $this->doOverwrite($messages, $newline, $size, false); + } - return $this->write($messages, count($messages) === 1 || $newline); + /** + * {@inheritDoc} + */ + public function overwriteError($messages, $newline = true, $size = null) + { + $this->doOverwrite($messages, $newline, $size, true); + } + + /** + * @param array $messages + * @param boolean $newline + * @param integer $size + * @param boolean $stderr + */ + private function doOverwrite($messages, $newline, $size, $stderr) + { + if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { + $output = $this->output->getErrorOutput(); + } else { + $output = $this->output; } // messages can be an array, let's convert it to string anyway @@ -125,26 +169,31 @@ class ConsoleIO extends BaseIO // since overwrite is supposed to overwrite last message... if (!isset($size)) { // removing possible formatting of lastMessage with strip_tags - $size = strlen(strip_tags($this->lastMessage)); + $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); } // ...let's fill its length with backspaces - $this->write(str_repeat("\x08", $size), false); + $this->doWrite(str_repeat("\x08", $size), false, $stderr); // write the new message - $this->write($messages, false); + $this->doWrite($messages, false, $stderr); $fill = $size - strlen(strip_tags($messages)); if ($fill > 0) { // whitespace whatever has left - $this->write(str_repeat(' ', $fill), false); + $this->doWrite(str_repeat(' ', $fill), false, $stderr); // move the cursor back - $this->write(str_repeat("\x08", $fill), false); + $this->doWrite(str_repeat("\x08", $fill), false, $stderr); } if ($newline) { - $this->write(''); + $this->doWrite('', true, $stderr); + } + + if ($stderr) { + $this->lastMessageErr = $messages; + } else { + $this->lastMessage = $messages; } - $this->lastMessage = $messages; } /** @@ -152,7 +201,16 @@ class ConsoleIO extends BaseIO */ public function ask($question, $default = null) { - return $this->helperSet->get('dialog')->ask($this->output, $question, $default); + $output = $this->output; + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + /** @var \Symfony\Component\Console\Helper\DialogHelper $dialog */ + $dialog = $this->helperSet->get('dialog'); + + return $dialog->ask($output, $question, $default); } /** @@ -160,7 +218,16 @@ class ConsoleIO extends BaseIO */ public function askConfirmation($question, $default = true) { - return $this->helperSet->get('dialog')->askConfirmation($this->output, $question, $default); + $output = $this->output; + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + /** @var \Symfony\Component\Console\Helper\DialogHelper $dialog */ + $dialog = $this->helperSet->get('dialog'); + + return $dialog->askConfirmation($output, $question, $default); } /** @@ -168,7 +235,16 @@ class ConsoleIO extends BaseIO */ public function askAndValidate($question, $validator, $attempts = false, $default = null) { - return $this->helperSet->get('dialog')->askAndValidate($this->output, $question, $validator, $attempts, $default); + $output = $this->output; + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + /** @var \Symfony\Component\Console\Helper\DialogHelper $dialog */ + $dialog = $this->helperSet->get('dialog'); + + return $dialog->askAndValidate($output, $question, $validator, $attempts, $default); } /** @@ -182,9 +258,9 @@ class ConsoleIO extends BaseIO // use bash if it's present if ($finder->find('bash') && $finder->find('stty')) { - $this->write($question, false); + $this->writeError($question, false); $value = rtrim(shell_exec('bash -c "stty -echo; read -n0 discard; read -r mypassword; stty echo; echo $mypassword"')); - $this->write(''); + $this->writeError(''); return $value; } @@ -208,9 +284,9 @@ class ConsoleIO extends BaseIO $exe = $tmpExe; } - $this->write($question, false); + $this->writeError($question, false); $value = rtrim(shell_exec($exe)); - $this->write(''); + $this->writeError(''); // clean up if (isset($tmpExe)) { @@ -230,11 +306,11 @@ class ConsoleIO extends BaseIO } } if (isset($shell)) { - $this->write($question, false); + $this->writeError($question, false); $readCmd = ($shell === 'csh') ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); - $this->write(''); + $this->writeError(''); return $value; } diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index f117ce974..0fd63fe4a 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -64,6 +64,14 @@ interface IOInterface */ public function write($messages, $newline = true); + /** + * Writes a message to the error output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + */ + public function writeError($messages, $newline = true); + /** * Overwrites a previous message to the output. * @@ -73,6 +81,15 @@ interface IOInterface */ public function overwrite($messages, $newline = true, $size = null); + /** + * Overwrites a previous message to the error output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param bool $newline Whether to add a newline or not + * @param integer $size The size of line + */ + public function overwriteError($messages, $newline = true, $size = null); + /** * Asks a question to the user. * diff --git a/src/Composer/IO/NullIO.php b/src/Composer/IO/NullIO.php index f3ecde0cb..1a88395d3 100644 --- a/src/Composer/IO/NullIO.php +++ b/src/Composer/IO/NullIO.php @@ -66,6 +66,13 @@ class NullIO extends BaseIO { } + /** + * {@inheritDoc} + */ + public function writeError($messages, $newline = true) + { + } + /** * {@inheritDoc} */ @@ -73,6 +80,13 @@ class NullIO extends BaseIO { } + /** + * {@inheritDoc} + */ + public function overwriteError($messages, $newline = true, $size = 80) + { + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 3594336e3..89ecd0643 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -177,7 +177,7 @@ class Installer // purge old require-dev packages to avoid conflicts with the new way of handling dev requirements $devRepo = new InstalledFilesystemRepository(new JsonFile($this->config->get('vendor-dir').'/composer/installed_dev.json')); if ($devRepo->getPackages()) { - $this->io->write('BC Notice: Removing old dev packages to migrate to the new require-dev handling.'); + $this->io->writeError('BC Notice: Removing old dev packages to migrate to the new require-dev handling.'); foreach ($devRepo->getPackages() as $package) { if ($this->installationManager->isPackageInstalled($devRepo, $package)) { $this->installationManager->uninstall($devRepo, new UninstallOperation($package)); @@ -191,7 +191,7 @@ class Installer if ($this->runScripts) { // dispatch pre event $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; - $this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode); + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } $this->downloadManager->setPreferSource($this->preferSource); @@ -243,7 +243,7 @@ class Installer } } - $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')'); + $this->io->writeError($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')'); } } @@ -257,7 +257,7 @@ class Installer ? 'Use ' . $package->getReplacementPackage() . ' instead' : 'No replacement was suggested'; - $this->io->write( + $this->io->writeError( sprintf( "Package %s is abandoned, you should avoid using it. %s.", $package->getPrettyName(), @@ -288,10 +288,10 @@ class Installer $request->install($link->getTarget(), $link->getConstraint()); } - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request); $solver = new Solver($policy, $pool, $installedRepo); $ops = $solver->solve($request, $this->ignorePlatformReqs); - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request, $ops); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops); foreach ($ops as $op) { if ($op->getJobType() === 'uninstall') { $devPackages[] = $op->getPackage(); @@ -314,16 +314,16 @@ class Installer $this->preferLowest ); if ($updatedLock) { - $this->io->write('Writing lock file'); + $this->io->writeError('Writing lock file'); } } if ($this->dumpAutoloader) { // write autoloader if ($this->optimizeAutoloader) { - $this->io->write('Generating optimized autoload files'); + $this->io->writeError('Generating optimized autoload files'); } else { - $this->io->write('Generating autoload files'); + $this->io->writeError('Generating autoload files'); } $this->autoloadGenerator->setDevMode($this->devMode); @@ -333,7 +333,7 @@ class Installer if ($this->runScripts) { // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; - $this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode); + $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } $vendorDir = $this->config->get('vendor-dir'); @@ -374,11 +374,11 @@ class Installer $this->package->getDevRequires() ); - $this->io->write('Loading composer repositories with package information'); + $this->io->writeError('Loading composer repositories with package information'); // creating repository pool $policy = $this->createPolicy(); - $pool = $this->createPool($withDevReqs); + $pool = $this->createPool($withDevReqs, $lockedRepository); $pool->addRepository($installedRepo, $aliases); if ($installFromLock) { $pool->addRepository($lockedRepository, $aliases); @@ -409,7 +409,7 @@ class Installer } if ($this->update) { - $this->io->write('Updating dependencies'.($withDevReqs ? ' (including require-dev)' : '').''); + $this->io->writeError('Updating dependencies'.($withDevReqs ? ' (including require-dev)' : '').''); $request->updateAll(); @@ -460,10 +460,10 @@ class Installer } } } elseif ($installFromLock) { - $this->io->write('Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').' from lock file'); + $this->io->writeError('Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').' from lock file'); if (!$this->locker->isFresh()) { - $this->io->write('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.'); + $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.'); } foreach ($lockedRepository->getPackages() as $package) { @@ -480,7 +480,7 @@ class Installer $request->install($link->getTarget(), $link->getConstraint()); } } else { - $this->io->write('Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').''); + $this->io->writeError('Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').''); if ($withDevReqs) { $links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); @@ -497,14 +497,14 @@ class Installer $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links'); // solve dependencies - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request); $solver = new Solver($policy, $pool, $installedRepo); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); - $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request, $operations); + $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations); } catch (SolverProblemsException $e) { - $this->io->write('Your requirements could not be resolved to an installable set of packages.'); - $this->io->write($e->getMessage()); + $this->io->writeError('Your requirements could not be resolved to an installable set of packages.'); + $this->io->writeError($e->getMessage()); return max(1, $e->getCode()); } @@ -514,7 +514,7 @@ class Installer // execute operations if (!$operations) { - $this->io->write('Nothing to install or update'); + $this->io->writeError('Nothing to install or update'); } $operations = $this->movePluginsToFront($operations); @@ -553,26 +553,26 @@ class Installer && $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference() ) { if ($this->io->isDebug()) { - $this->io->write(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version'); - $this->io->write(''); + $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version'); + $this->io->writeError(''); } continue; } } - $event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType()); + $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType()); if (defined($event) && $this->runScripts) { - $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation); + $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } // output non-alias ops in dry run, output alias ops in debug verbosity if ($this->dryRun && false === strpos($operation->getJobType(), 'Alias')) { - $this->io->write(' - ' . $operation); - $this->io->write(''); + $this->io->writeError(' - ' . $operation); + $this->io->writeError(''); } elseif ($this->io->isDebug() && false !== strpos($operation->getJobType(), 'Alias')) { - $this->io->write(' - ' . $operation); - $this->io->write(''); + $this->io->writeError(' - ' . $operation); + $this->io->writeError(''); } $this->installationManager->execute($localRepo, $operation); @@ -583,20 +583,20 @@ class Installer if ($reason instanceof Rule) { switch ($reason->getReason()) { case Rule::RULE_JOB_INSTALL: - $this->io->write(' REASON: Required by root: '.$reason->getPrettyString($pool)); - $this->io->write(''); + $this->io->writeError(' REASON: Required by root: '.$reason->getPrettyString($pool)); + $this->io->writeError(''); break; case Rule::RULE_PACKAGE_REQUIRES: - $this->io->write(' REASON: '.$reason->getPrettyString($pool)); - $this->io->write(''); + $this->io->writeError(' REASON: '.$reason->getPrettyString($pool)); + $this->io->writeError(''); break; } } } - $event = 'Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType()); + $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($operation->getJobType()); if (defined($event) && $this->runScripts) { - $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation); + $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation); } if (!$this->dryRun) { @@ -671,27 +671,39 @@ class Installer return array_merge($uninstOps, $operations); } - private function createPool($withDevReqs) + private function createPool($withDevReqs, RepositoryInterface $lockedRepository = null) { - $minimumStability = $this->package->getMinimumStability(); - $stabilityFlags = $this->package->getStabilityFlags(); - - if (!$this->update && $this->locker->isLocked()) { + if (!$this->update && $this->locker->isLocked()) { // install from lock $minimumStability = $this->locker->getMinimumStability(); $stabilityFlags = $this->locker->getStabilityFlags(); + + $requires = array(); + foreach ($lockedRepository->getPackages() as $package) { + $constraint = new VersionConstraint('=', $package->getVersion()); + $constraint->setPrettyString($package->getPrettyVersion()); + $requires[$package->getName()] = $constraint; + } + } else { + $minimumStability = $this->package->getMinimumStability(); + $stabilityFlags = $this->package->getStabilityFlags(); + + $requires = $this->package->getRequires(); + if ($withDevReqs) { + $requires = array_merge($requires, $this->package->getDevRequires()); + } } - $requires = $this->package->getRequires(); - if ($withDevReqs) { - $requires = array_merge($requires, $this->package->getDevRequires()); - } $rootConstraints = array(); foreach ($requires as $req => $constraint) { // skip platform requirements from the root package to avoid filtering out existing platform packages if ($this->ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) { continue; } - $rootConstraints[$req] = $constraint->getConstraint(); + if ($constraint instanceof Link) { + $rootConstraints[$req] = $constraint->getConstraint(); + } else { + $rootConstraints[$req] = $constraint; + } } return new Pool($minimumStability, $stabilityFlags, $rootConstraints); @@ -989,7 +1001,7 @@ class Installer } if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock'))) { - $this->io->write('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); + $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } foreach ($depPackages as $depPackage) { @@ -1166,7 +1178,6 @@ class Installer return $this; } - /** * set whether to run autoloader or not * @@ -1180,7 +1191,6 @@ class Installer return $this; } - /** * set whether to run scripts or not * diff --git a/src/Composer/Installer/InstallerEvent.php b/src/Composer/Installer/InstallerEvent.php index a9f5a728a..87153bd51 100644 --- a/src/Composer/Installer/InstallerEvent.php +++ b/src/Composer/Installer/InstallerEvent.php @@ -38,6 +38,11 @@ class InstallerEvent extends Event */ private $io; + /** + * @var bool + */ + private $devMode; + /** * @var PolicyInterface */ @@ -69,18 +74,20 @@ class InstallerEvent extends Event * @param string $eventName * @param Composer $composer * @param IOInterface $io + * @param bool $devMode * @param PolicyInterface $policy * @param Pool $pool * @param CompositeRepository $installedRepo * @param Request $request * @param OperationInterface[] $operations */ - public function __construct($eventName, Composer $composer, IOInterface $io, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) + public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array()) { parent::__construct($eventName); $this->composer = $composer; $this->io = $io; + $this->devMode = $devMode; $this->policy = $policy; $this->pool = $pool; $this->installedRepo = $installedRepo; @@ -104,6 +111,14 @@ class InstallerEvent extends Event return $this->io; } + /** + * @return bool + */ + public function isDevMode() + { + return $this->devMode; + } + /** * @return PolicyInterface */ diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 05cd420d7..5d6180bb0 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -197,7 +197,7 @@ class LibraryInstaller implements InstallerInterface foreach ($binaries as $bin) { $binPath = $this->getInstallPath($package).'/'.$bin; if (!file_exists($binPath)) { - $this->io->write(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); continue; } @@ -216,7 +216,7 @@ class LibraryInstaller implements InstallerInterface // is a fresh install of the vendor. @chmod($link, 0777 & ~umask()); } - $this->io->write(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); + $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); continue; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { @@ -226,7 +226,7 @@ class LibraryInstaller implements InstallerInterface @chmod($link, 0777 & ~umask()); $link .= '.bat'; if (file_exists($link)) { - $this->io->write(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); + $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); } } if (!file_exists($link)) { diff --git a/src/Composer/Installer/PackageEvent.php b/src/Composer/Installer/PackageEvent.php new file mode 100644 index 000000000..e1c5e6080 --- /dev/null +++ b/src/Composer/Installer/PackageEvent.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +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\EventDispatcher\Event; +use Composer\Repository\CompositeRepository; + +/** + * The Package Event. + * + * @author Jordi Boggiano + */ +class PackageEvent extends InstallerEvent +{ + /** + * @var OperationInterface The package instance + */ + private $operation; + + /** + * Constructor. + * + * @param string $eventName + * @param Composer $composer + * @param IOInterface $io + * @param bool $devMode + * @param PolicyInterface $policy + * @param Pool $pool + * @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) + { + parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations); + + $this->operation = $operation; + } + + /** + * Returns the package instance. + * + * @return OperationInterface + */ + public function getOperation() + { + return $this->operation; + } +} diff --git a/src/Composer/Installer/PackageEvents.php b/src/Composer/Installer/PackageEvents.php new file mode 100644 index 000000000..637b7fb45 --- /dev/null +++ b/src/Composer/Installer/PackageEvents.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\Installer; + +/** + * Package Events. + * + * @author Jordi Boggiano + */ +class PackageEvents +{ + /** + * The PRE_PACKAGE_INSTALL event occurs before a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_INSTALL = 'pre-package-install'; + + /** + * The POST_PACKAGE_INSTALL event occurs after a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_INSTALL = 'post-package-install'; + + /** + * The PRE_PACKAGE_UPDATE event occurs before a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_UPDATE = 'pre-package-update'; + + /** + * The POST_PACKAGE_UPDATE event occurs after a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_UPDATE = 'post-package-update'; + + /** + * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; + + /** + * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; +} diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php index fd3bbd976..146e68b95 100644 --- a/src/Composer/Installer/PearInstaller.php +++ b/src/Composer/Installer/PearInstaller.php @@ -76,7 +76,7 @@ class PearInstaller extends LibraryInstaller $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars); if ($this->io->isVerbose()) { - $this->io->write(' Cleaning up'); + $this->io->writeError(' Cleaning up'); } $this->filesystem->unlink($packageArchive); } diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 3375329c7..cf9dea5f4 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -184,6 +184,9 @@ class JsonFile { if (version_compare(PHP_VERSION, '5.4', '>=')) { $json = json_encode($data, $options); + if (false === $json) { + self::throwEncodeError(json_last_error()); + } // compact brackets to follow recent php versions if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512) || (defined('JSON_C_VERSION') && version_compare(phpversion('json'), '1.3.6', '<'))) { @@ -195,6 +198,9 @@ class JsonFile } $json = json_encode($data); + if (false === $json) { + self::throwEncodeError(json_last_error()); + } $prettyPrint = (bool) ($options & self::JSON_PRETTY_PRINT); $unescapeUnicode = (bool) ($options & self::JSON_UNESCAPED_UNICODE); @@ -209,6 +215,34 @@ class JsonFile return $result; } + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @throws \RuntimeException + */ + private static function throwEncodeError($code) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg); + } + /** * Parses json string and returns hash. * @@ -219,6 +253,9 @@ class JsonFile */ public static function parseJson($json, $file = null) { + if (null === $json) { + return; + } $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { self::validateSyntax($json, $file); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 558a24e35..8e210375d 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -249,7 +249,7 @@ class ArrayLoader implements LoaderInterface } // If using numeric aliases ensure the alias is a valid subversion - if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 250f2952f..6fe8899fb 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -277,7 +277,18 @@ class RootPackageLoader extends ArrayLoader ) { $branch = preg_replace('{^dev-}', '', $version); $length = PHP_INT_MAX; + + $nonFeatureBranches = ''; + if (!empty($config['non-feature-branches'])) { + $nonFeatureBranches = implode('|', $config['non-feature-branches']); + } + foreach ($branches as $candidate) { + // return directly, if branch is configured to be non-feature branch + if ($candidate === $branch && preg_match('{^(' . $nonFeatureBranches . ')$}', $candidate)) { + return $version; + } + // do not compare against other feature branches if ($candidate === $branch || !preg_match('{^(master|trunk|default|develop|\d+\..+)$}', $candidate, $match)) { continue; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 9a6f4dd32..65655c4a6 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -256,7 +256,7 @@ class ValidatingArrayLoader implements LoaderInterface } // If using numeric aliases ensure the alias is a valid subversion - if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) + if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 40ff8907c..171cf0362 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -258,7 +258,10 @@ class Locker $lock['packages-dev'] = $this->lockPackages($devPackages); } - if (empty($lock['packages']) && empty($lock['packages-dev'])) { + $lock['platform'] = $platformReqs; + $lock['platform-dev'] = $platformDevReqs; + + if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) { if ($this->lockFile->exists()) { unlink($this->lockFile->getPath()); } @@ -266,9 +269,6 @@ class Locker return false; } - $lock['platform'] = $platformReqs; - $lock['platform-dev'] = $platformDevReqs; - if (!$this->isLocked() || $lock !== $this->getLockData()) { $this->lockFile->write($lock); $this->lockDataCache = null; diff --git a/src/Composer/Package/RootPackageInterface.php b/src/Composer/Package/RootPackageInterface.php index 7bee86324..7d39ff7bc 100644 --- a/src/Composer/Package/RootPackageInterface.php +++ b/src/Composer/Package/RootPackageInterface.php @@ -20,7 +20,7 @@ namespace Composer\Package; interface RootPackageInterface extends CompletePackageInterface { /** - * Returns a set of package names and theirs aliases + * Returns a set of package names and their aliases * * @return array */ diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 9e4c12391..6bf5d4cf5 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -79,6 +79,9 @@ class PluginManager */ public function addPlugin(PluginInterface $plugin) { + if ($this->io->isDebug()) { + $this->io->writeError('Loading plugin '.get_class($plugin)); + } $this->plugins[] = $plugin; $plugin->activate($this->composer, $this->io); @@ -127,7 +130,7 @@ class PluginManager } if (!$requiresComposer->matches(new VersionConstraint('==', $this->versionParser->normalize(PluginInterface::PLUGIN_API_VERSION)))) { - $this->io->write("The plugin ".$package->getName()." requires a version of composer-plugin-api that does not match your composer installation. You may need to run composer update with the '--no-plugins' option."); + $this->io->writeError("The plugin ".$package->getName()." requires a version of composer-plugin-api that does not match your composer installation. You may need to run composer update with the '--no-plugins' option."); } $this->registerPackage($package); diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 38936e40a..2bb518b8f 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -60,14 +60,14 @@ class ArtifactRepository extends ArrayRepository $package = $this->getComposerInformation($file); if (!$package) { if ($io->isVerbose()) { - $io->write("File {$file->getBasename()} doesn't seem to hold a package"); + $io->writeError("File {$file->getBasename()} doesn't seem to hold a package"); } continue; } if ($io->isVerbose()) { $template = 'Found package %s (%s) in file %s'; - $io->write(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename())); + $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename())); } $this->addPackage($package); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index ad3c9996b..bd84ae187 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -434,7 +434,7 @@ class ComposerRepository extends ArrayRepository } if (!empty($data['warning'])) { - $this->io->write('Warning from '.$this->url.': '.$data['warning'].''); + $this->io->writeError('Warning from '.$this->url.': '.$data['warning'].''); } if (!empty($data['providers-lazy-url'])) { @@ -613,8 +613,8 @@ class ComposerRepository extends ArrayRepository if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { if (!$this->degradedMode) { - $this->io->write(''.$e->getMessage().''); - $this->io->write(''.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); + $this->io->writeError(''.$e->getMessage().''); + $this->io->writeError(''.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date'); } $this->degradedMode = true; $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey); diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 6086df1ed..1f882eb80 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -66,13 +66,13 @@ class PearRepository extends ArrayRepository { parent::initialize(); - $this->io->write('Initializing PEAR repository '.$this->url); + $this->io->writeError('Initializing PEAR repository '.$this->url); $reader = new ChannelReader($this->rfs); try { $channelInfo = $reader->read($this->url); } catch (\Exception $e) { - $this->io->write('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); + $this->io->writeError('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); return; } @@ -98,7 +98,7 @@ class PearRepository extends ArrayRepository $normalizedVersion = $versionParser->normalize($version); } catch (\UnexpectedValueException $e) { if ($this->io->isVerbose()) { - $this->io->write('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage()); + $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage()); } continue; } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 68389dc33..199fc48c8 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -149,7 +149,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface if (!extension_loaded('openssl')) { if ($io->isVerbose()) { - $io->write('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.'); + $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.'); } return false; diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 298639bc0..e9730412d 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -66,7 +66,7 @@ class GitDriver extends VcsDriver }; $gitUtil->runCommand($commandCallable, $this->url, $this->repoDir); } catch (\Exception $e) { - $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$e->getMessage().')'); + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$e->getMessage().')'); } } else { // clean up directory and do a fresh clone into it @@ -242,7 +242,7 @@ class GitDriver extends VcsDriver } $process = new ProcessExecutor($io); - if($process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url), $output) === 0) { + if ($process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url), $output) === 0) { return true; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 9dbd21059..fd2e71545 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -266,7 +266,7 @@ class GitHubDriver extends VcsDriver if (!extension_loaded('openssl')) { if ($io->isVerbose()) { - $io->write('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.'); + $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.'); } return false; @@ -333,7 +333,7 @@ class GitHubDriver extends VcsDriver if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { - $this->io->write('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); + $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); throw $e; } @@ -344,7 +344,7 @@ class GitHubDriver extends VcsDriver if ($rateLimited) { $rateLimit = $this->getRateLimit($e->getHeaders()); - $this->io->write(sprintf( + $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'], $rateLimit['reset'] @@ -435,7 +435,7 @@ class GitHubDriver extends VcsDriver } catch (\RuntimeException $e) { $this->gitDriver = null; - $this->io->write('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); + $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); throw $e; } } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index cc2b386eb..569b83521 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -159,7 +159,7 @@ class HgBitbucketDriver extends VcsDriver if (!extension_loaded('openssl')) { if ($io->isVerbose()) { - $io->write('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.'); + $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.'); } return false; diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index ed8e927b9..06382f039 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -50,7 +50,7 @@ class HgDriver extends VcsDriver // 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)) { - $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { // clean up directory and do a fresh clone into it diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 6c9dc5d4b..034260d0c 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -127,16 +127,16 @@ class VcsRepository extends ArrayRepository } } catch (\Exception $e) { if ($verbose) { - $this->io->write('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); + $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) { - $this->io->write($msg); + $this->io->writeError($msg); } else { - $this->io->overwrite($msg, false); + $this->io->overwriteError($msg, false); } // strip the release- prefix from tags if present @@ -144,7 +144,7 @@ class VcsRepository extends ArrayRepository if (!$parsedTag = $this->validateTag($tag)) { if ($verbose) { - $this->io->write('Skipped tag '.$tag.', invalid tag name'); + $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); } continue; } @@ -152,7 +152,7 @@ class VcsRepository extends ArrayRepository try { if (!$data = $driver->getComposerInformation($identifier)) { if ($verbose) { - $this->io->write('Skipped tag '.$tag.', no composer file'); + $this->io->writeError('Skipped tag '.$tag.', no composer file'); } continue; } @@ -173,39 +173,39 @@ class VcsRepository extends ArrayRepository // broken package, version doesn't match tag if ($data['version_normalized'] !== $parsedTag) { if ($verbose) { - $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); + $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); } continue; } if ($verbose) { - $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')'); + $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { if ($verbose) { - $this->io->write('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); + $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); } continue; } } if (!$verbose) { - $this->io->overwrite('', false); + $this->io->overwriteError('', false); } foreach ($driver->getBranches() as $branch => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; if ($verbose) { - $this->io->write($msg); + $this->io->writeError($msg); } else { - $this->io->overwrite($msg, false); + $this->io->overwriteError($msg, false); } if (!$parsedBranch = $this->validateBranch($branch)) { if ($verbose) { - $this->io->write('Skipped branch '.$branch.', invalid name'); + $this->io->writeError('Skipped branch '.$branch.', invalid name'); } continue; } @@ -213,7 +213,7 @@ class VcsRepository extends ArrayRepository try { if (!$data = $driver->getComposerInformation($identifier)) { if ($verbose) { - $this->io->write('Skipped branch '.$branch.', no composer file'); + $this->io->writeError('Skipped branch '.$branch.', no composer file'); } continue; } @@ -230,7 +230,7 @@ class VcsRepository extends ArrayRepository } if ($verbose) { - $this->io->write('Importing branch '.$branch.' ('.$data['version'].')'); + $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } $packageData = $this->preProcess($driver, $data, $identifier); @@ -241,23 +241,23 @@ class VcsRepository extends ArrayRepository $this->addPackage($package); } catch (TransportException $e) { if ($verbose) { - $this->io->write('Skipped branch '.$branch.', no composer file was found'); + $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } continue; } catch (\Exception $e) { if (!$verbose) { - $this->io->write(''); + $this->io->writeError(''); } $this->branchErrorOccurred = true; - $this->io->write('Skipped branch '.$branch.', '.$e->getMessage().''); - $this->io->write(''); + $this->io->writeError('Skipped branch '.$branch.', '.$e->getMessage().''); + $this->io->writeError(''); continue; } } $driver->cleanup(); if (!$verbose) { - $this->io->overwrite('', false); + $this->io->overwriteError('', false); } if (!$this->getPackages()) { diff --git a/src/Composer/Script/CommandEvent.php b/src/Composer/Script/CommandEvent.php index 48ea2246a..84c52008c 100644 --- a/src/Composer/Script/CommandEvent.php +++ b/src/Composer/Script/CommandEvent.php @@ -15,7 +15,7 @@ namespace Composer\Script; /** * The Command Event. * - * @author François Pluchino + * @deprecated use Composer\Script\Event instead */ class CommandEvent extends Event { diff --git a/src/Composer/Script/PackageEvent.php b/src/Composer/Script/PackageEvent.php index 735de0021..531b86a40 100644 --- a/src/Composer/Script/PackageEvent.php +++ b/src/Composer/Script/PackageEvent.php @@ -12,44 +12,13 @@ namespace Composer\Script; -use Composer\Composer; -use Composer\IO\IOInterface; -use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Installer\PackageEvent as BasePackageEvent; /** * The Package Event. * - * @author Jordi Boggiano + * @deprecated Use Composer\Installer\PackageEvent instead */ -class PackageEvent extends Event +class PackageEvent extends BasePackageEvent { - /** - * @var OperationInterface The package instance - */ - private $operation; - - /** - * Constructor. - * - * @param string $name The event name - * @param Composer $composer The composer object - * @param IOInterface $io The IOInterface object - * @param boolean $devMode Whether or not we are in dev mode - * @param OperationInterface $operation The operation object - */ - public function __construct($name, Composer $composer, IOInterface $io, $devMode, OperationInterface $operation) - { - parent::__construct($name, $composer, $io, $devMode); - $this->operation = $operation; - } - - /** - * Returns the package instance. - * - * @return OperationInterface - */ - public function getOperation() - { - return $this->operation; - } } diff --git a/src/Composer/Script/ScriptEvents.php b/src/Composer/Script/ScriptEvents.php index 616b2b97e..65baf2cc4 100644 --- a/src/Composer/Script/ScriptEvents.php +++ b/src/Composer/Script/ScriptEvents.php @@ -74,59 +74,7 @@ class ScriptEvents */ const POST_STATUS_CMD = 'post-status-cmd'; - /** - * The PRE_PACKAGE_INSTALL event occurs before a package is installed. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const PRE_PACKAGE_INSTALL = 'pre-package-install'; - - /** - * The POST_PACKAGE_INSTALL event occurs after a package is installed. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const POST_PACKAGE_INSTALL = 'post-package-install'; - - /** - * The PRE_PACKAGE_UPDATE event occurs before a package is updated. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const PRE_PACKAGE_UPDATE = 'pre-package-update'; - - /** - * The POST_PACKAGE_UPDATE event occurs after a package is updated. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const POST_PACKAGE_UPDATE = 'post-package-update'; - - /** - * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; - - /** - * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. - * - * The event listener method receives a Composer\Script\PackageEvent instance. - * - * @var string - */ - const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; + /** Deprecated constants below */ /** * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated. @@ -182,4 +130,64 @@ class ScriptEvents * @var string */ const POST_ARCHIVE_CMD = 'post-archive-cmd'; + + /** + * The PRE_PACKAGE_INSTALL event occurs before a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_INSTALL instead. + * @var string + */ + const PRE_PACKAGE_INSTALL = 'pre-package-install'; + + /** + * The POST_PACKAGE_INSTALL event occurs after a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_INSTALL instead. + * @var string + */ + const POST_PACKAGE_INSTALL = 'post-package-install'; + + /** + * The PRE_PACKAGE_UPDATE event occurs before a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UPDATE instead. + * @var string + */ + const PRE_PACKAGE_UPDATE = 'pre-package-update'; + + /** + * The POST_PACKAGE_UPDATE event occurs after a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UPDATE instead. + * @var string + */ + const POST_PACKAGE_UPDATE = 'post-package-update'; + + /** + * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UNINSTALL instead. + * @var string + */ + const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; + + /** + * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UNINSTALL instead. + * @var string + */ + const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; } diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index c3c5eb02c..34b686d9a 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -52,12 +52,13 @@ class Git } } + $protocols = $this->config->get('github-protocols'); + if (!is_array($protocols)) { + throw new \RuntimeException('Config value "github-protocols" must be an array, got '.gettype($protocols)); + } + // public github, autoswitch protocols if (preg_match('{^(?:https?|git)://'.self::getGitHubDomainsRegex($this->config).'/(.*)}', $url, $match)) { - $protocols = $this->config->get('github-protocols'); - if (!is_array($protocols)) { - throw new \RuntimeException('Config value "github-protocols" must be an array, got '.gettype($protocols)); - } $messages = array(); foreach ($protocols as $protocol) { if ('ssh' === $protocol) { @@ -79,8 +80,11 @@ class Git $this->throwException('Failed to clone ' . self::sanitizeUrl($url) .' via '.implode(', ', $protocols).' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); } + // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https + $bypassSshForGitHub = preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); + $command = call_user_func($commandCallable, $url); - if (0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { + if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { // private github repository without git access, try https with auth if (preg_match('{^git@'.self::getGitHubDomainsRegex($this->config).':(.+?)\.git$}i', $url, $match)) { if (!$this->io->hasAuthentication($match[1])) { @@ -122,7 +126,7 @@ class Git } } - $this->io->write(' Authentication required ('.parse_url($url, PHP_URL_HOST).'):'); + $this->io->writeError(' Authentication required ('.parse_url($url, PHP_URL_HOST).'):'); $auth = array( 'username' => $this->io->ask(' Username: ', $defaultUsername), 'password' => $this->io->askAndHideAnswer(' Password: '), @@ -160,18 +164,22 @@ class Git // added in git 1.7.1, prevents prompting the user for username/password if (getenv('GIT_ASKPASS') !== 'echo') { putenv('GIT_ASKPASS=echo'); + unset($_SERVER['GIT_ASKPASS']); } // clean up rogue git env vars in case this is running in a git hook if (getenv('GIT_DIR')) { putenv('GIT_DIR'); + unset($_SERVER['GIT_DIR']); } if (getenv('GIT_WORK_TREE')) { putenv('GIT_WORK_TREE'); + unset($_SERVER['GIT_WORK_TREE']); } // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 putenv("DYLD_LIBRARY_PATH"); + unset($_SERVER['DYLD_LIBRARY_PATH']); } public static function getGitHubDomainsRegex(Config $config) diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 6008dc062..a994de97b 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -77,11 +77,11 @@ class GitHub public function authorizeOAuthInteractively($originUrl, $message = null) { if ($message) { - $this->io->write($message); + $this->io->writeError($message); } - $this->io->write(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName())); - $this->io->write('To revoke access to this token you can visit https://github.com/settings/applications'); + $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', $this->config->getAuthConfigSource()->getName())); + $this->io->writeError('To revoke access to this token you can visit https://github.com/settings/applications'); $otp = null; $attemptCounter = 0; @@ -105,13 +105,13 @@ class GitHub } if (401 === $e->getCode()) { - $this->io->write('Bad credentials.'); + $this->io->writeError('Bad credentials.'); } else { - $this->io->write('Maximum number of login attempts exceeded. Please try again later.'); + $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } - $this->io->write('You can also manually create a personal token at https://github.com/settings/applications'); - $this->io->write('Add it using "composer config github-oauth.github.com "'); + $this->io->writeError('You can also manually create a personal token at https://github.com/settings/applications'); + $this->io->writeError('Add it using "composer config github-oauth.github.com "'); continue; } @@ -166,7 +166,7 @@ class GitHub ) )); - $this->io->write('Token successfully created'); + $this->io->writeError('Token successfully created'); return JsonFile::parseJson($json); } @@ -184,14 +184,14 @@ class GitHub list($required, $method) = array_map('trim', explode(';', substr(strstr($headers[$key], ':'), 1))); if ('required' === $required) { - $this->io->write('Two-factor Authentication'); + $this->io->writeError('Two-factor Authentication'); if ('app' === $method) { - $this->io->write('Open the two-factor authentication app on your device to view your authentication code and verify your identity.'); + $this->io->writeError('Open the two-factor authentication app on your device to view your authentication code and verify your identity.'); } if ('sms' === $method) { - $this->io->write('You have been sent an SMS message with an authentication code to verify your identity.'); + $this->io->writeError('You have been sent an SMS message with an authentication code to verify your identity.'); } return $this->io->ask('Authentication Code: '); diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 46dafb0ae..e7cfa3dcc 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -45,7 +45,7 @@ class ProcessExecutor { if ($this->io && $this->io->isDebug()) { $safeCommand = preg_replace('{(://[^:/\s]+:)[^@\s/]+}i', '$1****', $command); - $this->io->write('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); + $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); } // make sure that null translate to the proper directory in case the dir is a symlink @@ -56,7 +56,7 @@ class ProcessExecutor $this->captureOutput = count(func_get_args()) > 1; $this->errorOutput = null; - $process = new Process($command, $cwd, null, null, static::getTimeout()); + $process = new Process($command, $cwd, array_replace($_ENV, $_SERVER, array('LANGUAGE' => 'C')), null, static::getTimeout()); $callback = is_callable($output) ? $output : array($this, 'outputHandler'); $process->run($callback); diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 455bda92e..7c72c21c0 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -146,7 +146,7 @@ class RemoteFilesystem $options = $this->getOptionsForUrl($originUrl, $additionalOptions); if ($this->io->isDebug()) { - $this->io->write((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl); + $this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl); } if (isset($options['github-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token']; @@ -158,7 +158,7 @@ class RemoteFilesystem $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); if ($this->progress) { - $this->io->write(" Downloading: connection...", false); + $this->io->writeError(" Downloading: Connecting...", false); } $errorMessage = ''; @@ -228,7 +228,7 @@ class RemoteFilesystem } if ($this->progress && !$this->retry) { - $this->io->overwrite(" Downloading: 100%"); + $this->io->overwriteError(" Downloading: 100%"); } // handle copy command if download was successful @@ -327,9 +327,9 @@ class RemoteFilesystem $progression = round($bytesTransferred / $this->bytesMax * 100); } - if ((0 === $progression % 5) && $progression !== $this->lastProgress) { + if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; - $this->io->overwrite(" Downloading: $progression%", false); + $this->io->overwriteError(" Downloading: $progression%", false); } } break; @@ -371,7 +371,7 @@ class RemoteFilesystem throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus); } - $this->io->overwrite(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); + $this->io->overwriteError(' 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); diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 4949aa271..9ba08bf85 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -81,6 +81,7 @@ class Svn { // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 putenv("DYLD_LIBRARY_PATH"); + unset($_SERVER['DYLD_LIBRARY_PATH']); } /** @@ -111,7 +112,7 @@ class Svn } $output .= $buffer; if ($verbose) { - $io->write($buffer, false); + $io->writeError($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); @@ -169,7 +170,7 @@ class Svn ); } - $this->io->write("The Subversion server ({$this->url}) requested credentials:"); + $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); $this->hasAuth = true; $this->credentials['username'] = $this->io->ask("Username: "); diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 4a23c5717..1b720ad29 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -42,7 +42,8 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase } if ($this->oldenv) { $fs->removeDirectory(getenv('COMPOSER_HOME')); - putenv('COMPOSER_HOME='.$this->oldenv); + $_SERVER['COMPOSER_HOME'] = $this->oldenv; + putenv('COMPOSER_HOME='.$_SERVER['COMPOSER_HOME']); $this->oldenv = null; } } @@ -86,10 +87,11 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase $testData = $this->parseTestFile($testFile); $this->oldenv = getenv('COMPOSER_HOME'); - putenv('COMPOSER_HOME='.$this->testDir.'home'); + $_SERVER['COMPOSER_HOME'] = $this->testDir.'home'; + putenv('COMPOSER_HOME='.$_SERVER['COMPOSER_HOME']); $cmd = 'php '.escapeshellarg(self::$pharPath).' --no-ansi '.$testData['RUN']; - $proc = new Process($cmd, __DIR__.'/Fixtures/functional'); + $proc = new Process($cmd, __DIR__.'/Fixtures/functional', null, null, 300); $exitcode = $proc->run(); if (isset($testData['EXPECT'])) { diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index c99022671..0f1a076b4 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -29,7 +29,7 @@ class ApplicationTest extends TestCase ->will($this->returnValue('list')); $outputMock->expects($this->once()) - ->method("writeln") + ->method("write") ->with($this->equalTo(sprintf('Warning: This development build of composer is over 30 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF']))); if (!defined('COMPOSER_DEV_WARNING_TIME')) { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 99ef17fa4..5336de6ed 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -106,6 +106,7 @@ class AutoloadGeneratorTest extends TestCase $ret = $ret(); } } + return $ret; })); diff --git a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php index 1ef68d459..b8efc0c80 100644 --- a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php +++ b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php @@ -74,6 +74,13 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase 'Foo\\CBar' => __DIR__.'/Fixtures/php5.4/traits.php', )); } + if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) { + $data[] = array(__DIR__.'/Fixtures/hhvm3.3', array( + 'FooEnum' => __DIR__.'/Fixtures/hhvm3.3/HackEnum.php', + 'Foo\BarEnum' => __DIR__.'/Fixtures/hhvm3.3/NamespacedHackEnum.php', + 'GenericsClass' => __DIR__.'/Fixtures/hhvm3.3/Generics.php', + )); + } return $data; } @@ -128,7 +135,7 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase $msg = ''; $io->expects($this->once()) - ->method('write') + ->method('writeError') ->will($this->returnCallback(function ($text) use (&$msg) { $msg = $text; })); diff --git a/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/Generics.php b/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/Generics.php new file mode 100644 index 000000000..e5d1aa12a --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/Generics.php @@ -0,0 +1,4 @@ + { +} diff --git a/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/HackEnum.php b/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/HackEnum.php new file mode 100644 index 000000000..4b8dbfd40 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/hhvm3.3/HackEnum.php @@ -0,0 +1,6 @@ +workingDir.'/composer.json'; + copy($this->fixturePath('composer-repositories.json'), $config); + $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); + $jsonConfigSource->addRepository('example_tld', array('type' => 'git', 'url' => 'example.tld')); + + $this->assertFileEquals($this->fixturePath('config/config-with-exampletld-repository.json'), $config); + } + + public function testRemoveRepository() + { + $config = $this->workingDir.'/composer.json'; + copy($this->fixturePath('config/config-with-exampletld-repository.json'), $config); + $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); + $jsonConfigSource->removeRepository('example_tld'); + + $this->assertFileEquals($this->fixturePath('composer-repositories.json'), $config); + } + + public function testAddPackagistRepositoryWithFalseValue() + { + $config = $this->workingDir.'/composer.json'; + copy($this->fixturePath('composer-repositories.json'), $config); + $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); + $jsonConfigSource->addRepository('packagist', false); + + $this->assertFileEquals($this->fixturePath('config/config-with-packagist-false.json'), $config); + } + + public function testRemovePackagist() + { + $config = $this->workingDir.'/composer.json'; + copy($this->fixturePath('config/config-with-packagist-false.json'), $config); + $jsonConfigSource = new JsonConfigSource(new JsonFile($config)); + $jsonConfigSource->removeRepository('packagist'); + + $this->assertFileEquals($this->fixturePath('composer-repositories.json'), $config); + } + + /** + * Test addLink() + * + * @param string $sourceFile Source file + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + * @param string $value Value + * @param string $compareAgainst File to compare against after making changes + * + * @dataProvider provideAddLinkData + */ + public function testAddLink($sourceFile, $type, $name, $value, $compareAgainst) + { + $composerJson = $this->workingDir.'/composer.json'; + copy($sourceFile, $composerJson); + $jsonConfigSource = new JsonConfigSource(new JsonFile($composerJson)); + + $jsonConfigSource->addLink($type, $name, $value); + + $this->assertFileEquals($compareAgainst, $composerJson); + } + + /** + * Test removeLink() + * + * @param string $sourceFile Source file + * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) + * @param string $name Name + * @param string $compareAgainst File to compare against after making changes + * + * @dataProvider provideRemoveLinkData + */ + public function testRemoveLink($sourceFile, $type, $name, $compareAgainst) + { + $composerJson = $this->workingDir.'/composer.json'; + copy($sourceFile, $composerJson); + $jsonConfigSource = new JsonConfigSource(new JsonFile($composerJson)); + + $jsonConfigSource->removeLink($type, $name); + + $this->assertFileEquals($compareAgainst, $composerJson); + } + protected function addLinkDataArguments($type, $name, $value, $fixtureBasename, $before) { return array( @@ -88,28 +174,6 @@ class JsonConfigSourceTest extends \PHPUnit_Framework_TestCase ); } - /** - * Test addLink() - * - * @param string $sourceFile Source file - * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) - * @param string $name Name - * @param string $value Value - * @param string $compareAgainst File to compare against after making changes - * - * @dataProvider provideAddLinkData - */ - public function testAddLink($sourceFile, $type, $name, $value, $compareAgainst) - { - $composerJson = $this->workingDir.'/composer.json'; - copy($sourceFile, $composerJson); - $jsonConfigSource = new JsonConfigSource(new JsonFile($composerJson)); - - $jsonConfigSource->addLink($type, $name, $value); - - $this->assertFileEquals($compareAgainst, $composerJson); - } - protected function removeLinkDataArguments($type, $name, $fixtureBasename, $after = null) { return array( @@ -156,25 +220,4 @@ class JsonConfigSourceTest extends \PHPUnit_Framework_TestCase $this->removeLinkDataArguments('conflict', 'my-vend/my-old-app', 'conflict-to-twoOfEverything', $twoOfEverything), ); } - - /** - * Test removeLink() - * - * @param string $sourceFile Source file - * @param string $type Type (require, require-dev, provide, suggest, replace, conflict) - * @param string $name Name - * @param string $compareAgainst File to compare against after making changes - * - * @dataProvider provideRemoveLinkData - */ - public function testRemoveLink($sourceFile, $type, $name, $compareAgainst) - { - $composerJson = $this->workingDir.'/composer.json'; - copy($sourceFile, $composerJson); - $jsonConfigSource = new JsonConfigSource(new JsonFile($composerJson)); - - $jsonConfigSource->removeLink($type, $name); - - $this->assertFileEquals($compareAgainst, $composerJson); - } } diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 3e2bce2b4..2c113b5ae 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -120,7 +120,7 @@ class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase $ref = 'SOURCE_REF@123'; $label = 123; $this->package->expects($this->once())->method('getSourceReference')->will($this->returnValue($ref)); - $this->io->expects($this->once())->method('write')->with($this->stringContains('Cloning '.$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->expects($this->at(0))->method('initializePath')->with($this->equalTo($this->testPath)); @@ -143,7 +143,7 @@ class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase $ref = 'SOURCE_REF'; $label = null; $this->package->expects($this->once())->method('getSourceReference')->will($this->returnValue($ref)); - $this->io->expects($this->once())->method('write')->with($this->stringContains('Cloning '.$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->expects($this->at(0))->method('initializePath')->with($this->equalTo($this->testPath)); diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index aaf8b6267..06d9c652d 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -33,10 +33,10 @@ class EventDispatcherTest extends TestCase ), $io); $io->expects($this->once()) - ->method('write') + ->method('writeError') ->with('Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception'); - $dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } public function testDispatcherCanConvertScriptEventToCommandEventForListener() @@ -48,7 +48,7 @@ class EventDispatcherTest extends TestCase $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false)); } - + public function testDispatcherDoesNotAttemptConversionForListenerWithoutTypehint() { $io = $this->getMock('Composer\IO\IOInterface'); @@ -85,7 +85,7 @@ class EventDispatcherTest extends TestCase ->with($command) ->will($this->returnValue(0)); - $dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack() @@ -121,7 +121,7 @@ class EventDispatcherTest extends TestCase ->with('Composer\Test\EventDispatcher\EventDispatcherTest', 'someMethod') ->will($this->returnValue(true)); - $dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } private function getDispatcherStubForListenersTest($listeners, $io) @@ -167,7 +167,7 @@ class EventDispatcherTest extends TestCase ->will($this->returnValue($listener)); ob_start(); - $dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); $this->assertEquals('foo', trim(ob_get_clean())); } @@ -189,11 +189,11 @@ class EventDispatcherTest extends TestCase ->will($this->returnValue($listener)); $io->expects($this->once()) - ->method('write') + ->method('writeError') ->with($this->equalTo('Script '.$code.' handling the post-install-cmd event returned with an error')); $this->setExpectedException('RuntimeException'); - $dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } public function testDispatcherInstallerEvents() @@ -217,8 +217,8 @@ class EventDispatcherTest extends TestCase $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); - $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request); - $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request, array()); + $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request); + $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request, array()); } public static function call() diff --git a/tests/Composer/Test/Fixtures/functional/create-project-command.test b/tests/Composer/Test/Fixtures/functional/create-project-command.test index 2e4b2762a..6282e6357 100644 --- a/tests/Composer/Test/Fixtures/functional/create-project-command.test +++ b/tests/Composer/Test/Fixtures/functional/create-project-command.test @@ -1,6 +1,6 @@ --RUN-- create-project seld/jsonlint %testDir% 1.0.0 --prefer-source -n ---EXPECT-- +--EXPECT-ERROR-- Installing seld/jsonlint (1.0.0) - Installing seld/jsonlint (1.0.0) Cloning 3b4bc2a96ff5d3fe6866bfe9dd0c845246705791 @@ -9,4 +9,5 @@ Created project in %testDir% Loading composer repositories with package information Installing dependencies (including require-dev) Nothing to install or update +Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Fixtures/functional/create-project-shows-full-hash-for-dev-packages.test b/tests/Composer/Test/Fixtures/functional/create-project-shows-full-hash-for-dev-packages.test index 0e5188e41..fa7ecbe32 100644 --- a/tests/Composer/Test/Fixtures/functional/create-project-shows-full-hash-for-dev-packages.test +++ b/tests/Composer/Test/Fixtures/functional/create-project-shows-full-hash-for-dev-packages.test @@ -1,4 +1,4 @@ --RUN-- create-project --repository-url=packages.json -v seld/jsonlint %testDir% dev-master ---EXPECT-REGEX-- +--EXPECT-ERROR-REGEX-- {^Installing seld/jsonlint \(dev-master [a-f0-9]{40}\)} 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 new file mode 100644 index 000000000..2721772ae --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install-from-lock.test @@ -0,0 +1,41 @@ +--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) diff --git a/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test b/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test new file mode 100644 index 000000000..8971f8069 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/replaced-packages-wrong-version-install.test @@ -0,0 +1,41 @@ +--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) +--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-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": [] +} +--EXPECT-EXIT-CODE-- +0 +--EXPECT-- +Installing foo/original (1.0.0) +Installing foo/replaced (2.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test new file mode 100644 index 000000000..15d1b4ef5 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/root-requirements-do-not-affect-locked-versions.test @@ -0,0 +1,41 @@ +--TEST-- +The locked version will not get overwritten by an install +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "foo/bar", "version": "1.0.0" }, + { "name": "foo/baz", "version": "1.0.0" }, + { "name": "foo/baz", "version": "2.0.0" } + ] + } + ], + "require": { + "foo/bar": "2.0.0", + "foo/baz": "2.0.0" + } +} +--LOCK-- +{ + "packages": [ + { "name": "foo/bar", "version": "1.0.0" }, + { "name": "foo/baz", "version": "2.0.0" } + ], + "packages-dev": null, + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false +} +--INSTALLED-- +[ + { "name": "foo/bar", "version": "1.0.0" }, + { "name": "foo/baz", "version": "1.0.0" } +] +--RUN-- +install +--EXPECT-- +Updating foo/baz (1.0.0) to foo/baz (2.0.0) diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index c83ec6296..d0af8cb42 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -49,6 +49,22 @@ class ConsoleIOTest extends TestCase $consoleIO->write('some information about something', false); } + public function testWriteError() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface'); + $outputMock->expects($this->once()) + ->method('getErrorOutput') + ->willReturn($outputMock); + $outputMock->expects($this->once()) + ->method('write') + ->with($this->equalTo('some information about something'), $this->equalTo(false)); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->writeError('some information about something', false); + } + public function testWriteWithMultipleLineStringWhenDebugging() { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); @@ -56,9 +72,10 @@ class ConsoleIOTest extends TestCase $outputMock->expects($this->once()) ->method('write') ->with( - $this->callback(function($messages){ + $this->callback(function ($messages) { $result = preg_match("[(.*)/(.*) First line]", $messages[0]) > 0; $result &= preg_match("[(.*)/(.*) Second line]", $messages[1]) > 0; + return $result; }), $this->equalTo(false) @@ -82,27 +99,21 @@ class ConsoleIOTest extends TestCase ->method('write') ->with($this->equalTo('something (strlen = 23)')); $outputMock->expects($this->at(1)) - ->method('isDecorated') - ->willReturn(true); - $outputMock->expects($this->at(2)) ->method('write') ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); - $outputMock->expects($this->at(3)) + $outputMock->expects($this->at(2)) ->method('write') ->with($this->equalTo('shorter (12)'), $this->equalTo(false)); - $outputMock->expects($this->at(4)) + $outputMock->expects($this->at(3)) ->method('write') ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); - $outputMock->expects($this->at(5)) + $outputMock->expects($this->at(4)) ->method('write') ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)); - $outputMock->expects($this->at(6)) - ->method('isDecorated') - ->willReturn(true); - $outputMock->expects($this->at(7)) + $outputMock->expects($this->at(5)) ->method('write') ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)); - $outputMock->expects($this->at(8)) + $outputMock->expects($this->at(6)) ->method('write') ->with($this->equalTo('something longer than initial (34)')); diff --git a/tests/Composer/Test/Installer/InstallerEventTest.php b/tests/Composer/Test/Installer/InstallerEventTest.php index 7cd63f6b5..1489f5f0e 100644 --- a/tests/Composer/Test/Installer/InstallerEventTest.php +++ b/tests/Composer/Test/Installer/InstallerEventTest.php @@ -25,11 +25,12 @@ class InstallerEventTest extends \PHPUnit_Framework_TestCase $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock(); $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); $operations = array($this->getMock('Composer\DependencyResolver\Operation\OperationInterface')); - $event = new InstallerEvent('EVENT_NAME', $composer, $io, $policy, $pool, $installedRepo, $request, $operations); + $event = new InstallerEvent('EVENT_NAME', $composer, $io, true, $policy, $pool, $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\CompositeRepository', $event->getInstalledRepo()); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 2c77f9bc3..7fa85d76d 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -149,11 +149,15 @@ class InstallerTest extends TestCase $output = null; $io = $this->getMock('Composer\IO\IOInterface'); + $callback = function ($text, $newline) use (&$output) { + $output .= $text . ($newline ? "\n" : ""); + }; $io->expects($this->any()) ->method('write') - ->will($this->returnCallback(function ($text, $newline) use (&$output) { - $output .= $text . ($newline ? "\n" : ""); - })); + ->will($this->returnCallback($callback)); + $io->expects($this->any()) + ->method('writeError') + ->will($this->returnCallback($callback)); $composer = FactoryMock::create($io, $composerConfig); @@ -195,10 +199,7 @@ class InstallerTest extends TestCase $composer->setAutoloadGenerator($autoloadGenerator); $composer->setEventDispatcher($eventDispatcher); - $installer = Installer::create( - $io, - $composer - ); + $installer = Installer::create($io, $composer); $application = new Application; $application->get('install')->setCode(function ($input, $output) use ($installer) { @@ -330,8 +331,7 @@ class InstallerTest extends TestCase ); $section = null; - foreach ($tokens as $i => $token) - { + foreach ($tokens as $i => $token) { if (null === $section && empty($token)) { continue; // skip leading blank } diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index 52c3fbf2e..cb4c5ee25 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -14,7 +14,6 @@ namespace Composer\Test\Mock; use Composer\Composer; use Composer\Config; use Composer\Factory; -use Composer\Repository; use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; use Composer\Installer; diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index bc74be1e9..e9233f3cb 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -187,6 +187,8 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase $this->finder = new ArchivableFilesFinder($this->sources, array()); $this->assertArchivableFiles($this->getArchivedFiles('git init && '. + 'git config user.email "you@example.com" && '. + 'git config user.name "Your Name" && '. '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 aa98a62a2..f4d343e63 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -78,6 +78,18 @@ class ArchiveManagerTest extends ArchiverTest throw new \RuntimeException('Could not init: '.$this->process->getErrorOutput()); } + $result = $this->process->execute('git config user.email "you@example.com"', $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); + throw new \RuntimeException('Could not config: '.$this->process->getErrorOutput()); + } + $result = file_put_contents('composer.json', '{"name":"faker/faker", "description": "description", "license": "MIT"}'); if (false === $result) { chdir($currentWorkDir); diff --git a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php index d1f3be1f8..07aaf7034 100644 --- a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php @@ -155,4 +155,82 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase 'zux/complex' => BasePackage::STABILITY_DEV, ), $package->getStabilityFlags()); } + + public function testFeatureBranchPrettyVersion() + { + if (!function_exists('proc_open')) { + $this->markTestSkipped('proc_open() is not available'); + } + + $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') + ->disableOriginalConstructor() + ->getMock(); + + $self = $this; + + /* Can do away with this mock object when https://github.com/sebastianbergmann/phpunit-mock-objects/issues/81 is fixed */ + $processExecutor = new ProcessExecutorMock(function ($command, &$output = null, $cwd = null) use ($self) { + if (0 === strpos($command, 'git rev-list')) { + $output = ""; + + return 0; + } + + if ('git branch --no-color --no-abbrev -v' !== $command) { + return 1; //0; + } + + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + + $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; + + return 0; + }); + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $loader = new RootPackageLoader($manager, $config, null, $processExecutor); + $package = $loader->load(array('require' => array('foo/bar' => 'self.version'))); + + $this->assertEquals("dev-master", $package->getPrettyVersion()); + } + + public function testNonFeatureBranchPrettyVersion() + { + if (!function_exists('proc_open')) { + $this->markTestSkipped('proc_open() is not available'); + } + + $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') + ->disableOriginalConstructor() + ->getMock(); + + $self = $this; + + /* Can do away with this mock object when https://github.com/sebastianbergmann/phpunit-mock-objects/issues/81 is fixed */ + $processExecutor = new ProcessExecutorMock(function ($command, &$output = null, $cwd = null) use ($self) { + if (0 === strpos($command, 'git rev-list')) { + $output = ""; + + return 0; + } + + if ('git branch --no-color --no-abbrev -v' !== $command) { + return 1; //0; + } + + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + + $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; + + return 0; + }); + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $loader = new RootPackageLoader($manager, $config, null, $processExecutor); + $package = $loader->load(array('require' => array('foo/bar' => 'self.version'), "non-feature-branches" => array("latest-.*"))); + + $this->assertEquals("dev-latest-production", $package->getPrettyVersion()); + } } diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 4a1f3a35a..488507ec3 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -34,7 +34,7 @@ class GitHubTest extends \PHPUnit_Framework_TestCase $io = $this->getIOMock(); $io ->expects($this->at(0)) - ->method('write') + ->method('writeError') ->with($this->message) ; $io @@ -166,6 +166,7 @@ class GitHubTest extends \PHPUnit_Framework_TestCase return true; } } + return false; }) ) diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index bbeba908e..04502ebdc 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -119,7 +119,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase $io = $this->getMock('Composer\IO\IOInterface'); $io ->expects($this->once()) - ->method('overwrite') + ->method('overwriteError') ; $fs = new RemoteFilesystem($io);