diff --git a/.php_cs b/.php_cs index ba9d1251a..0c6b9ead7 100644 --- a/.php_cs +++ b/.php_cs @@ -10,9 +10,32 @@ $finder = Symfony\CS\Finder\DefaultFinder::create() return Symfony\CS\Config\Config::create() ->setUsingCache(true) - ->level(Symfony\CS\FixerInterface::NONE_LEVEL) - ->fixers(array( - 'psr0', 'encoding', 'short_tag', 'braces', 'elseif', 'eof_ending', 'function_declaration', 'indentation', 'line_after_namespace', 'linefeed', 'lowercase_constants', 'lowercase_keywords', 'multiple_use', 'php_closing_tag', 'trailing_spaces', 'visibility', 'duplicate_semicolon', 'extra_empty_lines', 'include', 'namespace_no_leading_whitespace', 'object_operator', 'operators_spaces', 'phpdoc_params', 'return', 'single_array_no_trailing_comma', 'spaces_cast', 'standardize_not_equal', 'ternary_spaces', 'unused_use', 'whitespacy_lines', 'multiline_array_tailing_comma', + ->setRiskyAllowed(true) + ->setRules(array( + '@PSR2' => true, + 'duplicate_semicolon' => true, + 'extra_empty_lines' => true, + 'include' => true, + 'multiline_array_trailing_comma' => true, + 'namespace_no_leading_whitespace' => true, + 'object_operator' => true, + 'operators_spaces' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_type_to_var' => true, + 'psr0' => true, + 'return' => true, + 'single_array_no_trailing_comma' => true, + 'spaces_cast' => true, + 'standardize_not_equal' => true, + 'ternary_spaces' => true, + 'unused_use' => true, + 'whitespacy_lines' => true, )) ->finder($finder) ; diff --git a/.travis.yml b/.travis.yml index b1aa4414d..383ad0334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,9 @@ cache: - $HOME/.composer/cache addons: - apt_packages: - - parallel + apt: + packages: + - parallel php: - 5.3.3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 786b92efd..6c3be2943 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Contributing to Composer ======================== Please note that this project is released with a -[Contributor Code of Conduct](http://contributor-covenant.org/version/1/0/0/). +[Contributor Code of Conduct](http://contributor-covenant.org/version/1/2/0/). By participating in this project you agree to abide by its terms. Reporting Issues @@ -42,6 +42,8 @@ Contributing policy Fork the project, create a feature branch, and send us a pull request. To ensure a consistent code base, you should make sure the code follows -the [PSR-2 Coding Standards](http://www.php-fig.org/psr/psr-2/). +the [PSR-2 Coding Standards](http://www.php-fig.org/psr/psr-2/). You can also +run [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) with the +configuration file that can be found in the project root directory. -If you would like to help, take a look at the [list of issues](https://github.com/composer/composer/issues). +If you would like to help, take a look at the [list of open issues](https://github.com/composer/composer/issues). diff --git a/README.md b/README.md index c7bcca110..ccbfdcd34 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Composer helps you declare, manage and install dependencies of PHP projects, ens See [https://getcomposer.org/](https://getcomposer.org/) for more information and documentation. [![Build Status](https://travis-ci.org/composer/composer.svg?branch=master)](https://travis-ci.org/composer/composer) -[![Dependency Status](https://www.versioneye.com/php/composer:composer/dev-master/badge.svg)](https://www.versioneye.com/php/composer:composer/dev-master) +[![Dependency Status](https://www.versioneye.com/php/composer:composer/dev-master/badge.svg)](https://www.versioneye.com/php/composer:composer/dev-master) [![Reference Status](https://www.versioneye.com/php/composer:composer/reference_badge.svg?style=flat)](https://www.versioneye.com/php/composer:composer/references) Installation / Usage @@ -45,7 +45,6 @@ Updating Composer Running `php composer.phar self-update` or equivalent will update a phar install to the latest version. - Community --------- @@ -56,7 +55,7 @@ For support, Stack Overflow also offers a good collection of [Composer related questions](https://stackoverflow.com/questions/tagged/composer-php). Please note that this project is released with a -[Contributor Code of Conduct](http://contributor-covenant.org/version/1/0/0/). +[Contributor Code of Conduct](http://contributor-covenant.org/version/1/2/0/). By participating in this project and its community you agree to abide by those terms. Requirements diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..efc7c624e --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,33 @@ +build: false +shallow_clone: true +platform: x86 +clone_folder: c:\projects\composer + +cache: + - c:\tools\php -> appveyor.yml + +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET PHP=1 + - SET ANSICON=121x90 (121x90) + +install: + - IF EXIST c:\tools\php (SET PHP=0) + - IF %PHP%==1 cinst -y OpenSSL.Light + - IF %PHP%==1 cinst -y php + - cd c:\tools\php + - IF %PHP%==1 copy php.ini-production php.ini /Y + - IF %PHP%==1 echo date.timezone="UTC" >> php.ini + - IF %PHP%==1 echo extension_dir=ext >> php.ini + - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini + - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini + - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat + - appveyor DownloadFile https://getcomposer.org/composer.phar + - cd c:\projects\composer + - composer install --prefer-source --no-progress + +test_script: + - cd c:\projects\composer + - vendor/bin/phpunit --colors=always diff --git a/bin/update-spdx-licenses b/bin/update-spdx-licenses deleted file mode 100644 index e509ded93..000000000 --- a/bin/update-spdx-licenses +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env php -update(); diff --git a/composer.json b/composer.json index ffac4f254..b4e372ac4 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,14 @@ }, "require": { "php": ">=5.3.2", - "justinrainbow/json-schema": "~1.4", + "justinrainbow/json-schema": "^1.4.4", + "composer/spdx-licenses": "^1.0", + "composer/semver": "^1.0", "seld/jsonlint": "~1.0", "symfony/console": "~2.5", "symfony/finder": "~2.2", "symfony/process": "~2.1", + "symfony/filesystem": "~2.5", "seld/phar-utils": "~1.0", "seld/cli-prompt": "~1.0" }, @@ -35,7 +38,7 @@ "phpunit/phpunit": "~4.5", "phpunit/phpunit-mock-objects": "2.3.0" }, - "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223", + "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis", "config": { "platform": { "php": "5.3.3" diff --git a/composer.lock b/composer.lock index b45ec64a2..8edea7f7d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,146 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "556ac817fc0b456bddc48918ef09930d", + "hash": "af3956ae4c1a09e3e72cd66346a6176d", + "content-hash": "96817117d0ca449e7deff55c98eb7bdc", "packages": [ + { + "name": "composer/semver", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "d0e1ccc6d44ab318b758d709e19176037da6b1ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/d0e1ccc6d44ab318b758d709e19176037da6b1ba", + "reference": "d0e1ccc6d44ab318b758d709e19176037da6b1ba", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com" + }, + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2015-09-21 09:42:36" + }, + { + "name": "composer/spdx-licenses", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "324b3530ac3e6277ff4bedf82a34fbf35836eb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/324b3530ac3e6277ff4bedf82a34fbf35836eb8d", + "reference": "324b3530ac3e6277ff4bedf82a34fbf35836eb8d", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com" + }, + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "time": "2015-09-07 16:25:20" + }, { "name": "justinrainbow/json-schema", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "2465fe486c864e30badaa4d005ebdf89dbc503f3" + "reference": "a4bee9f4b344b66e0a0d96c7afae1e92edf385fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2465fe486c864e30badaa4d005ebdf89dbc503f3", - "reference": "2465fe486c864e30badaa4d005ebdf89dbc503f3", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/a4bee9f4b344b66e0a0d96c7afae1e92edf385fe", + "reference": "a4bee9f4b344b66e0a0d96c7afae1e92edf385fe", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.3.2" }, "require-dev": { "json-schema/json-schema-test-suite": "1.1.0", @@ -38,8 +160,8 @@ } }, "autoload": { - "psr-0": { - "JsonSchema": "src/" + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" } }, "notification-url": "https://packagist.org/downloads/", @@ -70,7 +192,7 @@ "json", "schema" ], - "time": "2015-03-27 16:41:39" + "time": "2015-09-08 22:28:04" }, { "name": "seld/cli-prompt", @@ -212,17 +334,17 @@ }, { "name": "symfony/console", - "version": "v2.6.9", + "version": "v2.6.11", "target-dir": "Symfony/Component/Console", "source": { "type": "git", "url": "https://github.com/symfony/Console.git", - "reference": "b5ec0c11a204718f2b656357f5505a8e578f30dd" + "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/b5ec0c11a204718f2b656357f5505a8e578f30dd", - "reference": "b5ec0c11a204718f2b656357f5505a8e578f30dd", + "url": "https://api.github.com/repos/symfony/Console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", + "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", "shasum": "" }, "require": { @@ -266,21 +388,71 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-05-29 14:42:58" + "time": "2015-07-26 09:08:40" + }, + { + "name": "symfony/filesystem", + "version": "v2.6.11", + "target-dir": "Symfony/Component/Filesystem", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "823c035b1a5c13a4924e324d016eb07e70f94735" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/823c035b1a5c13a4924e324d016eb07e70f94735", + "reference": "823c035b1a5c13a4924e324d016eb07e70f94735", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2015-07-08 05:59:48" }, { "name": "symfony/finder", - "version": "v2.6.9", + "version": "v2.6.11", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", "url": "https://github.com/symfony/Finder.git", - "reference": "ffedd3e0ff8155188155e9322fe21b9ee012ac14" + "reference": "203a10f928ae30176deeba33512999233181dd28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/ffedd3e0ff8155188155e9322fe21b9ee012ac14", - "reference": "ffedd3e0ff8155188155e9322fe21b9ee012ac14", + "url": "https://api.github.com/repos/symfony/Finder/zipball/203a10f928ae30176deeba33512999233181dd28", + "reference": "203a10f928ae30176deeba33512999233181dd28", "shasum": "" }, "require": { @@ -316,21 +488,21 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-05-15 13:32:45" + "time": "2015-07-09 16:02:48" }, { "name": "symfony/process", - "version": "v2.6.9", + "version": "v2.6.11", "target-dir": "Symfony/Component/Process", "source": { "type": "git", "url": "https://github.com/symfony/Process.git", - "reference": "7856d78ab6cce6e59d02d9e1a873441f6bd21306" + "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/7856d78ab6cce6e59d02d9e1a873441f6bd21306", - "reference": "7856d78ab6cce6e59d02d9e1a873441f6bd21306", + "url": "https://api.github.com/repos/symfony/Process/zipball/57f1e88bb5dafa449b83f9f265b11d52d517b3e9", + "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9", "shasum": "" }, "require": { @@ -366,22 +538,22 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-05-15 13:32:45" + "time": "2015-06-30 16:10:16" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119" + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119", - "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", "shasum": "" }, "require": { @@ -392,7 +564,7 @@ "ext-pdo": "*", "ext-phar": "*", "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "2.0.*@ALPHA" + "squizlabs/php_codesniffer": "~2.0" }, "type": "library", "extra": { @@ -401,8 +573,8 @@ } }, "autoload": { - "psr-0": { - "Doctrine\\Instantiator\\": "src" + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -422,7 +594,7 @@ "constructor", "instantiate" ], - "time": "2014-10-13 12:58:55" + "time": "2015-06-14 21:17:01" }, { "name": "phpdocumentor/reflection-docblock", @@ -475,16 +647,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.4.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373" + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", - "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", "shasum": "" }, "require": { @@ -531,20 +703,20 @@ "spy", "stub" ], - "time": "2015-04-27 22:15:08" + "time": "2015-08-13 10:07:40" }, { "name": "phpunit/php-code-coverage", - "version": "2.1.5", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e" + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be2286cb8c7e1773eded49d9719219e6f74f9e3e", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c", "shasum": "" }, "require": { @@ -552,7 +724,7 @@ "phpunit/php-file-iterator": "~1.3", "phpunit/php-text-template": "~1.2", "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "~1.0", + "sebastian/environment": "^1.3.2", "sebastian/version": "~1.0" }, "require-dev": { @@ -567,7 +739,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -593,20 +765,20 @@ "testing", "xunit" ], - "time": "2015-06-09 13:05:42" + "time": "2015-08-04 03:42:39" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb" + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb", - "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", "shasum": "" }, "require": { @@ -640,20 +812,20 @@ "filesystem", "iterator" ], - "time": "2015-04-02 05:19:05" + "time": "2015-06-21 13:08:43" }, { "name": "phpunit/php-text-template", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", "shasum": "" }, "require": { @@ -662,20 +834,17 @@ "type": "library", "autoload": { "classmap": [ - "Text/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -684,20 +853,20 @@ "keywords": [ "template" ], - "time": "2014-01-30 17:20:04" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", - "version": "1.0.5", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", "shasum": "" }, "require": { @@ -706,13 +875,10 @@ "type": "library", "autoload": { "classmap": [ - "PHP/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], @@ -728,20 +894,20 @@ "keywords": [ "timer" ], - "time": "2013-08-02 07:42:54" + "time": "2015-06-21 08:01:12" }, { "name": "phpunit/php-token-stream", - "version": "1.4.1", + "version": "1.4.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "eab81d02569310739373308137284e0158424330" + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/eab81d02569310739373308137284e0158424330", - "reference": "eab81d02569310739373308137284e0158424330", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b", "shasum": "" }, "require": { @@ -777,20 +943,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-04-08 04:46:07" + "time": "2015-08-16 08:51:00" }, { "name": "phpunit/phpunit", - "version": "4.7.2", + "version": "4.8.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8e0c63329c8c4185296b8d357daa5c6bae43080f" + "reference": "2246830f4a1a551c67933e4171bf2126dc29d357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e0c63329c8c4185296b8d357daa5c6bae43080f", - "reference": "8e0c63329c8c4185296b8d357daa5c6bae43080f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2246830f4a1a551c67933e4171bf2126dc29d357", + "reference": "2246830f4a1a551c67933e4171bf2126dc29d357", "shasum": "" }, "require": { @@ -800,15 +966,15 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", - "phpspec/prophecy": "~1.3,>=1.3.1", + "phpspec/prophecy": "^1.3.1", "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "~1.0", + "phpunit/php-timer": ">=1.0.6", "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", - "sebastian/environment": "~1.2", + "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", "sebastian/version": "~1.0", @@ -823,7 +989,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.7.x-dev" + "dev-master": "4.8.x-dev" } }, "autoload": { @@ -849,7 +1015,7 @@ "testing", "xunit" ], - "time": "2015-06-06 08:36:08" + "time": "2015-08-24 04:09:38" }, { "name": "phpunit/phpunit-mock-objects", @@ -908,16 +1074,16 @@ }, { "name": "sebastian/comparator", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dd8869519a225f7f2b9eb663e225298fade819e" + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e", - "reference": "1dd8869519a225f7f2b9eb663e225298fade819e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", "shasum": "" }, "require": { @@ -931,7 +1097,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -968,7 +1134,7 @@ "compare", "equality" ], - "time": "2015-01-29 16:28:08" + "time": "2015-07-26 15:48:44" }, { "name": "sebastian/diff", @@ -1024,16 +1190,16 @@ }, { "name": "sebastian/environment", - "version": "1.2.2", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e" + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5a8c7d31914337b69923db26c4221b81ff5a196e", - "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", "shasum": "" }, "require": { @@ -1070,20 +1236,20 @@ "environment", "hhvm" ], - "time": "2015-01-01 10:01:08" + "time": "2015-08-03 06:14:51" }, { "name": "sebastian/exporter", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "84839970d05254c73cde183a721c7af13aede943" + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943", - "reference": "84839970d05254c73cde183a721c7af13aede943", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", "shasum": "" }, "require": { @@ -1136,7 +1302,7 @@ "export", "exporter" ], - "time": "2015-01-27 07:23:06" + "time": "2015-06-21 07:55:53" }, { "name": "sebastian/global-state", @@ -1191,16 +1357,16 @@ }, { "name": "sebastian/recursion-context", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "3989662bbb30a29d20d9faa04a846af79b276252" + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252", - "reference": "3989662bbb30a29d20d9faa04a846af79b276252", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", "shasum": "" }, "require": { @@ -1240,20 +1406,20 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-01-24 09:48:32" + "time": "2015-06-21 08:04:50" }, { "name": "sebastian/version", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4" + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", "shasum": "" }, "type": "library", @@ -1275,21 +1441,21 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-02-24 06:35:25" + "time": "2015-06-21 13:59:46" }, { "name": "symfony/yaml", - "version": "v2.6.9", + "version": "v2.6.11", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "f157ab074e453ecd4c0fa775f721f6e67a99d9e2" + "reference": "c044d1744b8e91aaaa0d9bac683ab87ec7cbf359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/f157ab074e453ecd4c0fa775f721f6e67a99d9e2", - "reference": "f157ab074e453ecd4c0fa775f721f6e67a99d9e2", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/c044d1744b8e91aaaa0d9bac683ab87ec7cbf359", + "reference": "c044d1744b8e91aaaa0d9bac683ab87ec7cbf359", "shasum": "" }, "require": { @@ -1325,7 +1491,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-05-02 15:18:45" + "time": "2015-07-26 08:59:42" } ], "aliases": [], diff --git a/doc/00-intro.md b/doc/00-intro.md index 0496390ca..d984eb7b9 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -1,54 +1,41 @@ # Introduction Composer is a tool for dependency management in PHP. It allows you to declare -the dependent libraries your project needs and it will install them in your -project for you. +the libraries your project depends on and it will manage (install/update) them +for you. ## Dependency management -Composer is not a package manager. Yes, it deals with "packages" or libraries, but -it manages them on a per-project basis, installing them in a directory (e.g. `vendor`) -inside your project. By default it will never install anything globally. Thus, -it is a dependency manager. +Composer is **not** a package manager in the same sense as Yum or Apt are. Yes, +it deals with "packages" or libraries, but it manages them on a per-project +basis, installing them in a directory (e.g. `vendor`) inside your project. By +default it will never install anything globally. Thus, it is a dependency +manager. -This idea is not new and Composer is strongly inspired by node's [npm](https://npmjs.org/) -and ruby's [bundler](http://bundler.io/). But there has not been such a tool -for PHP. +This idea is not new and Composer is strongly inspired by node's +[npm](https://npmjs.org/) and ruby's [bundler](http://bundler.io/). -The problem that Composer solves is this: +Suppose: a) You have a project that depends on a number of libraries. b) Some of those libraries depend on other libraries. -c) You declare the things you depend on. +Composer: -d) Composer finds out which versions of which packages need to be installed, and - installs them (meaning it downloads them into your project). - -## Declaring dependencies +c) Enables you to declare the libraries you depend on. -Let's say you are creating a project, and you need a library that does logging. -You decide to use [monolog](https://github.com/Seldaek/monolog). In order to -add it to your project, all you need to do is create a `composer.json` file -which describes the project's dependencies. - -```json -{ - "require": { - "monolog/monolog": "1.2.*" - } -} -``` +d) Finds out which versions of which packages can and need to be installed, and + installs them (meaning it downloads them into your project). -We are simply stating that our project requires some `monolog/monolog` package, -any version beginning with `1.2`. +See the [Basic usage](01-basic-usage.md) chapter for more details on declaring +dependencies. ## System Requirements Composer requires PHP 5.3.2+ to run. A few sensitive php settings and compile -flags are also required, but when using the installer you will be warned about any -incompatibilities. +flags are also required, but when using the installer you will be warned about +any incompatibilities. To install packages from sources instead of simple zip archives, you will need git, svn or hg depending on how the package is version-controlled. @@ -60,6 +47,12 @@ Linux and OSX. ### Downloading the Composer Executable +Composer offers a convenient installer that you can execute directly from the +commandline. Feel free to [download this file](https://getcomposer.org/installer) +or review it on [GitHub](https://github.com/composer/getcomposer.org/blob/master/web/installer) +if you wish to know more about the inner workings of the installer. The source +is plain PHP. + There are in short, two ways to install Composer. Locally as part of your project, or globally as a system wide executable. @@ -79,37 +72,54 @@ curl -sS https://getcomposer.org/installer | php php -r "readfile('https://getcomposer.org/installer');" | php ``` -The installer will just check a few PHP settings and then download `composer.phar` -to your working directory. This file is the Composer binary. It is a PHAR (PHP -archive), which is an archive format for PHP which can be run on the command -line, amongst other things. +The installer will just check a few PHP settings and then download +`composer.phar` to your working directory. This file is the Composer binary. It +is a PHAR (PHP archive), which is an archive format for PHP which can be run on +the command line, amongst other things. + +Now just run `php composer.phar` in order to run Composer. You can install Composer to a specific directory by using the `--install-dir` -option and providing a target directory (it can be an absolute or relative path): +option and additionally (re)name it as well using the `--filename` option: ```sh -curl -sS https://getcomposer.org/installer | php -- --install-dir=bin +curl -sS https://getcomposer.org/installer | php -- --install-dir=bin --filename=composer ``` +Now just run `php bin/composer` in order to run Composer. + #### Globally -You can place this file anywhere you wish. If you put it in your `PATH`, -you can access it globally. On unixy systems you can even make it -executable and invoke it without `php`. +You can place the Composer PHAR anywhere you wish. If you put it in a directory +that is part of your `PATH`, you can access it globally. On unixy systems you +can even make it executable and invoke it without directly using the `php` +interpreter. -You can run these commands to easily access `composer` from anywhere on your system: +Run these commands to globally install `composer` on your system: ```sh curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer ``` -> **Note:** If the above fails due to permissions, run the `mv` line -> again with sudo. +> **Note:** If the above fails due to permissions, run the `mv` line again +> with sudo. + +A quick copy-paste version including sudo: -> **Note:** In OSX Yosemite the `/usr` directory does not exist by default. If you receive the error "/usr/local/bin/composer: No such file or directory" then you must create `/usr/local/bin/` manually before proceeding. +```sh +curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer +``` -Then, just run `composer` in order to run Composer instead of `php composer.phar`. +> **Note:** On some versions of OSX the `/usr` directory does not exist by +> default. If you receive the error "/usr/local/bin/composer: No such file or +> directory" then you must create the directory manually before proceeding: +> `mkdir -p /usr/local/bin`. + +> **Note:** For information on changing your PATH, please read the +> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google. + +Now just run `composer` in order to run Composer instead of `php composer.phar`. ## Installation - Windows @@ -117,24 +127,26 @@ Then, just run `composer` in order to run Composer instead of `php composer.phar This is the easiest way to get Composer set up on your machine. -Download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe), -it will install the latest Composer version and set up your PATH so that you can -just call `composer` from any directory in your command line. +Download and run +[Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). It will +install the latest Composer version and set up your PATH so that you can just +call `composer` from any directory in your command line. -> **Note:** Close your current terminal. Test usage with a new terminal: -> That is important since the PATH only gets loaded when the terminal starts. +> **Note:** Close your current terminal. Test usage with a new terminal: This is +> important since the PATH only gets loaded when the terminal starts. ### Manual Installation Change to a directory on your `PATH` and run the install snippet to download -composer.phar: +`composer.phar`: ```sh C:\Users\username>cd C:\bin C:\bin>php -r "readfile('https://getcomposer.org/installer');" | php ``` -> **Note:** If the above fails due to readfile, use the `http` url or enable php_openssl.dll in php.ini +> **Note:** If the above fails due to readfile, use the `http` url or enable +> php_openssl.dll in php.ini Create a new `composer.bat` file alongside `composer.phar`: @@ -153,38 +165,7 @@ Composer version 27d8904 ## Using Composer -We will now use Composer to install the dependencies of the project. If you -don't have a `composer.json` file in the current directory please skip to the -[Basic Usage](01-basic-usage.md) chapter. - -To resolve and download dependencies, run the `install` command: - -```sh -php composer.phar install -``` - -If you did a global install and do not have the phar in that directory -run this instead: - -```sh -composer install -``` - -Following the [example above](#declaring-dependencies), this will download -monolog into the `vendor/monolog/monolog` directory. - -## Autoloading - -Besides downloading the library, Composer also prepares an autoload file that's -capable of autoloading all of the classes in any of the libraries that it -downloads. To use it, just add the following line to your code's bootstrap -process: - -```php -require __DIR__ . '/vendor/autoload.php'; -``` - -Woah! Now start using monolog! To keep learning more about Composer, keep -reading the "Basic Usage" chapter. +Now that you've installed Composer, you are ready to use it! Head on over to the +next chapter for a short and simple demonstration. -[Basic Usage](01-basic-usage.md) → +[Basic usage](01-basic-usage.md) → diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 3d4f2b77d..b33f06ac6 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -1,8 +1,13 @@ # Basic usage -## Installing +## Introduction -If you have not yet installed Composer, refer to the [Intro](00-intro.md) chapter. +For our basic usage introduction, we will be installing `monolog/monolog`, +a logging library. If you have not yet installed Composer, refer to the +[Intro](00-intro.md) chapter. + +> **Note:** for the sake of simplicity, this introduction will assume you +> have performed a [local](00-intro.md#locally) install of Composer. ## `composer.json`: Project Setup @@ -10,14 +15,11 @@ To start using Composer in your project, all you need is a `composer.json` file. This file describes the dependencies of your project and may contain other metadata as well. -The [JSON format](http://json.org/) is quite easy to write. It allows you to -define nested structures. - ### The `require` Key The first (and often only) thing you specify in `composer.json` is the -`require` key. You're simply telling Composer which packages your project -depends on. +[`require`](04-schema.md#require) key. You're simply telling Composer which +packages your project depends on. ```json { @@ -27,15 +29,16 @@ depends on. } ``` -As you can see, `require` takes an object that maps **package names** (e.g. `monolog/monolog`) -to **package versions** (e.g. `1.0.*`). +As you can see, [`require`](04-schema.md#require) takes an object that maps +**package names** (e.g. `monolog/monolog`) to **version constraints** (e.g. +`1.0.*`). ### Package Names The package name consists of a vendor name and the project's name. Often these -will be identical - the vendor name just exists to prevent naming clashes. It allows -two different people to create a library named `json`, which would then just be -named `igorw/json` and `seldaek/json`. +will be identical - the vendor name just exists to prevent naming clashes. It +allows two different people to create a library named `json`, which would then +just be named `igorw/json` and `seldaek/json`. Here we are requiring `monolog/monolog`, so the vendor name is the same as the project's name. For projects with a unique name this is recommended. It also @@ -45,66 +48,26 @@ smaller decoupled parts. ### Package Versions -In the previous example we were requiring version [`1.0.*`](http://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*) of monolog. This -means any version in the `1.0` development branch. It would match `1.0.0`, -`1.0.2` or `1.0.20`. - -Version constraints can be specified in a few different ways. - -Name | Example | Description --------------- | ------------------------------------------------------------------------ | ----------- -Exact version | `1.0.2` | You can specify the exact version of a package. -Range | `>=1.0` `>=1.0 <2.0` >=1.0 <1.1 || >=1.2 | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`.
You can define multiple ranges. Ranges separated by a space ( ) or comma (`,`) will be treated as a **logical AND**. A double pipe (||) will be treated as a **logical OR**. AND has higher precedence than OR. -Hyphen Range | `1.0 - 2.0` | Inclusive set of versions. Partial versions on the right include are completed with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the `2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to `>=1.0.0 <=2.1.0`. -Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0 <1.1`. -Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2 <2.0`. For more details, read the next section below. -Caret Operator | `^1.2.3` | Very useful for projects that follow semantic versioning. `^1.2.3` is equivalent to `>=1.2.3 <2.0`. For more details, read the next section below. - -### Next Significant Release (Tilde and Caret Operators) - -The `~` operator is best explained by example: `~1.2` is equivalent to -`>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see -it is mostly useful for projects respecting [semantic -versioning](http://semver.org/). A common usage would be to mark the minimum -minor version you depend on, like `~1.2` (which allows anything up to, but not -including, 2.0). Since in theory there should be no backwards compatibility -breaks until 2.0, that works well. Another way of looking at it is that using -`~` specifies a minimum version, but allows the last digit specified to go up. - -The `^` operator behaves very similarly but it sticks closer to semantic -versioning, and will always allow non-breaking updates. For example `^1.2.3` -is equivalent to `>=1.2.3 <2.0.0` as none of the releases until 2.0 should -break backwards compatibility. For pre-1.0 versions it also acts with safety -in mind and treats `^0.3` as `>=0.3.0 <0.4.0` - -> **Note:** Though `2.0-beta.1` is strictly before `2.0`, a version constraint -> like `~1.2` would not install it. As said above `~1.2` only means the `.2` -> can change but the `1.` part is fixed. - -> **Note:** The `~` operator has an exception on its behavior for the major -> release number. This means for example that `~1` is the same as `~1.0` as -> it will not allow the major number to increase trying to keep backwards -> compatibility. +In the previous example we were requiring version +[`1.0.*`](http://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*) of +Monolog. This means any version in the `1.0` development branch. It is the +equivalent of saying versions that match `>=1.0 <1.1`. + +Version constraints can be specified in several ways, read +[versions](articles/versions.md) for more in-depth information on this topic. ### Stability -By default only stable releases are taken into consideration. If you would like -to also get RC, beta, alpha or dev versions of your dependencies you can do -so using [stability flags](04-schema.md#package-links). To change that for all -packages instead of doing per dependency you can also use the +By default only stable releases are taken into consideration. If you would +like to also get RC, beta, alpha or dev versions of your dependencies you can +do so using [stability flags](04-schema.md#package-links). To change that for +all packages instead of doing per dependency you can also use the [minimum-stability](04-schema.md#minimum-stability) setting. -### Test version constraints - -You can test version constraints using [semver.mwl.be](http://semver.mwl.be). Fill in -a package name and it will autofill the default version constraint which Composer would add -to your `composer.json` file. You can adjust the version constraint and the tool will highlight -all releases that match. - ## Installing Dependencies -To fetch the defined dependencies into your local project, just run the -`install` command of `composer.phar`. +To install the defined dependencies for your project, just run the +[`install`](03-cli.md#install) command. ```sh php composer.phar install @@ -113,14 +76,14 @@ php composer.phar install This will find the latest version of `monolog/monolog` that matches the supplied version constraint and download it into the `vendor` directory. It's a convention to put third party code into a directory named `vendor`. -In case of monolog it will put it into `vendor/monolog/monolog`. +In case of Monolog it will put it into `vendor/monolog/monolog`. > **Tip:** If you are using git for your project, you probably want to add -> `vendor` into your `.gitignore`. You really don't want to add all of that +> `vendor` in your `.gitignore`. You really don't want to add all of that > code to your repository. -Another thing that the `install` command does is it adds a `composer.lock` -file into your project root. +You will notice the [`install`](03-cli.md#install) command also created a +`composer.lock` file. ## `composer.lock` - The Lock File @@ -128,82 +91,82 @@ After installing the dependencies, Composer writes the list of the exact versions it installed into a `composer.lock` file. This locks the project to those specific versions. -**Commit your application's `composer.lock` (along with `composer.json`) into version control.** +**Commit your application's `composer.lock` (along with `composer.json`) +into version control.** -This is important because the `install` command checks if a lock file is present, -and if it is, it downloads the versions specified there (regardless of what `composer.json` -says). +This is important because the [`install`](03-cli.md#install) command checks +if a lock file is present, and if it is, it downloads the versions specified +there (regardless of what `composer.json` says). -This means that anyone who sets up the project will download the exact -same version of the dependencies. Your CI server, production machines, other -developers in your team, everything and everyone runs on the same dependencies, which -mitigates the potential for bugs affecting only some parts of the deployments. Even if you -develop alone, in six months when reinstalling the project you can feel confident the -dependencies installed are still working even if your dependencies released -many new versions since then. +This means that anyone who sets up the project will download the exact same +version of the dependencies. Your CI server, production machines, other +developers in your team, everything and everyone runs on the same dependencies, +which mitigates the potential for bugs affecting only some parts of the +deployments. Even if you develop alone, in six months when reinstalling the +project you can feel confident the dependencies installed are still working even +if your dependencies released many new versions since then. If no `composer.lock` file exists, Composer will read the dependencies and -versions from `composer.json` and create the lock file after executing the `update` or the `install` -command. +versions from `composer.json` and create the lock file after executing the +[`update`](03-cli.md#update) or the [`install`](03-cli.md#install) 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 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. +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 the +[`update`](03-cli.md#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. ```sh php composer.phar update ``` -> **Note:** Composer will display a Warning when executing an `install` command if - `composer.lock` and `composer.json` are not synchronized. - +> **Note:** Composer will display a Warning when executing an `install` command +> if `composer.lock` and `composer.json` are not synchronized. + If you only want to install or update one dependency, you can whitelist them: ```sh php composer.phar update monolog/monolog [...] ``` -> **Note:** For libraries it is not necessarily recommended to commit the lock file, -> see also: [Libraries - Lock file](02-libraries.md#lock-file). +> **Note:** For libraries it is not necessary to commit the lock +> file, see also: [Libraries - Lock file](02-libraries.md#lock-file). ## Packagist [Packagist](https://packagist.org/) is the main Composer repository. A Composer repository is basically a package source: a place where you can get packages from. Packagist aims to be the central repository that everybody uses. This -means that you can automatically `require` any package that is available -there. +means that you can automatically `require` any package that is available there. -If you go to the [packagist website](https://packagist.org/) (packagist.org), +If you go to the [Packagist website](https://packagist.org/) (packagist.org), you can browse and search for packages. -Any open source project using Composer should publish their packages on -packagist. A library doesn't need to be on packagist to be used by Composer, -but it makes life quite a bit simpler. +Any open source project using Composer is recommended to publish their packages +on Packagist. A library doesn't need to be on Packagist to be used by Composer, +but it enables discovery and adoption by other developers more quickly. ## Autoloading For libraries that specify autoload information, Composer generates a -`vendor/autoload.php` file. You can simply include this file and you -will get autoloading for free. +`vendor/autoload.php` file. You can simply include this file and you will get +autoloading for free. ```php -require 'vendor/autoload.php'; +require __DIR__ . '/vendor/autoload.php'; ``` -This makes it really easy to use third party code. For example: If your -project depends on monolog, you can just start using classes from it, and they -will be autoloaded. +This makes it really easy to use third party code. For example: If your project +depends on Monolog, you can just start using classes from it, and they will be +autoloaded. ```php $log = new Monolog\Logger('name'); $log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING)); - $log->addWarning('Foo'); ``` -You can even add your own code to the autoloader by adding an `autoload` field -to `composer.json`. +You can even add your own code to the autoloader by adding an +[`autoload`](04-schema.md#autoload) field to `composer.json`. ```json { @@ -220,24 +183,25 @@ You define a mapping from namespaces to directories. The `src` directory would be in your project root, on the same level as `vendor` directory is. An example filename would be `src/Foo.php` containing an `Acme\Foo` class. -After adding the `autoload` field, you have to re-run `dump-autoload` to re-generate -the `vendor/autoload.php` file. +After adding the [`autoload`](04-schema.md#autoload) field, you have to re-run +[`dump-autoload`](03-cli.md#dump-autoload) to re-generate the +`vendor/autoload.php` file. Including that file will also return the autoloader instance, so you can store the return value of the include call in a variable and add more namespaces. This can be useful for autoloading classes in a test suite, for example. ```php -$loader = require 'vendor/autoload.php'; +$loader = require __DIR__ . '/vendor/autoload.php'; $loader->add('Acme\\Test\\', __DIR__); ``` -In addition to PSR-4 autoloading, classmap is also supported. This allows -classes to be autoloaded even if they do not conform to PSR-4. See the -[autoload reference](04-schema.md#autoload) for more details. +In addition to PSR-4 autoloading, Composer also supports PSR-0, classmap and +files autoloading. See the [`autoload`](04-schema.md#autoload) reference for +more information. -> **Note:** Composer provides its own autoloader. If you don't want to use -that one, you can just include `vendor/composer/autoload_*.php` files, -which return associative arrays allowing you to configure your own autoloader. +> **Note:** Composer provides its own autoloader. If you don't want to use that +> one, you can just include `vendor/composer/autoload_*.php` files, which return +> associative arrays allowing you to configure your own autoloader. ← [Intro](00-intro.md) | [Libraries](02-libraries.md) → diff --git a/doc/02-libraries.md b/doc/02-libraries.md index 0749ac53f..da5725e4d 100644 --- a/doc/02-libraries.md +++ b/doc/02-libraries.md @@ -1,16 +1,17 @@ # Libraries -This chapter will tell you how to make your library installable through Composer. +This chapter will tell you how to make your library installable through +Composer. ## Every project is a package As soon as you have a `composer.json` in a directory, that directory is a -package. When you add a `require` to a project, you are making a package that -depends on other packages. The only difference between your project and -libraries is that your project is a package without a name. +package. When you add a [`require`](04-schema.md#require) to a project, you are +making a package that depends on other packages. The only difference between +your project and libraries is that your project is a package without a name. In order to make that package installable you need to give it a name. You do -this by adding a `name` to `composer.json`: +this by adding the [`name`](04-schema.md#name) property in `composer.json`: ```json { @@ -21,12 +22,12 @@ this by adding a `name` to `composer.json`: } ``` -In this case the project name is `acme/hello-world`, where `acme` is the -vendor name. Supplying a vendor name is mandatory. +In this case the project name is `acme/hello-world`, where `acme` is the vendor +name. Supplying a vendor name is mandatory. > **Note:** If you don't know what to use as a vendor name, your GitHub -username is usually a good bet. While package names are case insensitive, the -convention is all lowercase and dashes for word separation. +> username is usually a good bet. While package names are case insensitive, the +> convention is all lowercase and dashes for word separation. ## Platform packages @@ -50,15 +51,14 @@ includes PHP itself, PHP extensions and some system libraries. PHP. The following are available: `curl`, `iconv`, `icu`, `libxml`, `openssl`, `pcre`, `uuid`, `xsl`. -You can use `composer show --platform` to get a list of your locally available -platform packages. +You can use [`show --platform`](03-cli.md#show) to get a list of your locally +available platform packages. ## Specifying the version -You need to specify the package's version some way. When you publish your -package on Packagist, it is able to infer the version from the VCS (git, svn, -hg) information, so in that case you do not have to specify it, and it is -recommended not to. See [tags](#tags) and [branches](#branches) to see how +When you publish your package on Packagist, it is able to infer the version +from the VCS (git, svn, hg) information. This means you don't have to +explicitly declare it. Read [tags](#tags) and [branches](#branches) to see how version numbers are extracted from these. If you are creating packages by hand and really have to specify it explicitly, @@ -76,9 +76,9 @@ you can just add a `version` field: ### Tags For every tag that looks like a version, a package version of that tag will be -created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix -of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes -can also be followed by a number. +created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix of +`-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffix can also +be followed by a number. Here are a few examples of valid tag names: @@ -89,19 +89,20 @@ Here are a few examples of valid tag names: - v2.0.0-alpha - v2.0.4-p1 -> **Note:** Even if your tag is prefixed with `v`, a [version constraint](01-basic-usage.md#package-versions) -> in a `require` statement has to be specified without prefix -> (e.g. tag `v1.0.0` will result in version `1.0.0`). +> **Note:** Even if your tag is prefixed with `v`, a +> [version constraint](01-basic-usage.md#package-versions) in a `require` +> statement has to be specified without prefix (e.g. tag `v1.0.0` will result +> in version `1.0.0`). ### Branches For every branch, a package development version will be created. If the branch name looks like a version, the version will be `{branchname}-dev`. For example, -the branch `2.0` will get the `2.0.x-dev` version (the `.x` is added for technical -reasons, to make sure it is recognized as a branch). The `2.0.x` branch would also -be valid and be turned into `2.0.x-dev` as well. If the branch does not look -like a version, it will be `dev-{branchname}`. `master` results in a -`dev-master` version. +the branch `2.0` will get the `2.0.x-dev` version (the `.x` is added for +technical reasons, to make sure it is recognized as a branch). The `2.0.x` +branch would also be valid and be turned into `2.0.x-dev` as well. If the +branch does not look like a version, it will be `dev-{branchname}`. `master` +results in a `dev-master` version. Here are some examples of version branch names: @@ -116,8 +117,8 @@ Here are some examples of version branch names: ### Aliases It is possible to alias branch names to versions. For example, you could alias -`dev-master` to `1.0.x-dev`, which would allow you to require `1.0.x-dev` in all -the packages. +`dev-master` to `1.0.x-dev`, which would allow you to require `1.0.x-dev` in +all the packages. See [Aliases](articles/aliases.md) for more information. @@ -133,7 +134,7 @@ the `.gitignore`. ## Publishing to a VCS -Once you have a vcs repository (version control system, e.g. git) containing a +Once you have a VCS repository (version control system, e.g. git) containing a `composer.json` file, your library is already composer-installable. In this example we will publish the `acme/hello-world` library on GitHub under `github.com/username/hello-world`. @@ -180,11 +181,11 @@ For more details on how package repositories work and what other types are available, see [Repositories](05-repositories.md). That's all. You can now install the dependencies by running Composer's -`install` command! +[`install`](03-cli.md#install) command! **Recap:** Any git/svn/hg repository containing a `composer.json` can be added to your project by specifying the package repository and declaring the -dependency in the `require` field. +dependency in the [`require`](04-schema.md#require) field. ## Publishing to packagist @@ -196,15 +197,16 @@ repository for `monolog/monolog`. How did that work? The answer is Packagist. [Packagist](https://packagist.org/) is the main package repository for Composer, and it is enabled by default. Anything that is published on -Packagist is available automatically through Composer. Since monolog -[is on packagist](https://packagist.org/packages/monolog/monolog), we can depend -on it without having to specify any additional repositories. +Packagist is available automatically through Composer. Since +[Monolog is on Packagist](https://packagist.org/packages/monolog/monolog), we +can depend on it without having to specify any additional repositories. If we wanted to share `hello-world` with the world, we would publish it on Packagist as well. Doing so is really easy. -You simply hit the big "Submit Package" button and sign up. Then you submit -the URL to your VCS repository, at which point Packagist will start crawling -it. Once it is done, your package will be available to anyone. +You simply visit [Packagist](https://packagist.org) and hit the "Submit". This +will prompt you to sign up if you haven't already, and then allows you to +submit the URL to your VCS repository, at which point Packagist will start +crawling it. Once it is done, your package will be available to anyone! ← [Basic usage](01-basic-usage.md) | [Command-line interface](03-cli.md) → diff --git a/doc/03-cli.md b/doc/03-cli.md index 2bb29001b..a8c1c1d40 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -87,7 +87,7 @@ resolution. installing a package, you can use `--dry-run`. This will simulate the installation and show you what would happen. * **--dev:** Install packages listed in `require-dev` (this is the default behavior). -* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader +* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules. * **--no-autoloader:** Skips autoloader generation. * **--no-scripts:** Skips execution of scripts defined in `composer.json`. @@ -97,6 +97,8 @@ resolution. * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. ## update @@ -140,9 +142,11 @@ php composer.phar update vendor/* * **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. * **--lock:** Only updates the lock file hash to suppress warning about the lock file being out of date. -* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist. +* **--with-dependencies:** Add also all dependencies of whitelisted packages to the whitelist. * **--prefer-stable:** Prefer stable versions of dependencies. * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal versions of requirements, generally used with `--prefer-stable`. @@ -177,9 +181,15 @@ php composer.phar require vendor/package:2.* vendor/package2:dev-master * **--no-update:** Disables the automatic update of the dependencies. * **--no-progress:** Removes the progress display that can mess with some terminals or scripts which don't handle backspace characters. -* **--update-no-dev** Run the dependency update with the --no-dev option. -* **--update-with-dependencies** Also update dependencies of the newly +* **--update-no-dev:** Run the dependency update with the `--no-dev` option. +* **--update-with-dependencies:** Also update dependencies of the newly required packages. +* **--sort-packages:** Keep packages sorted in `composer.json`. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to + get a faster autoloader. This is recommended especially for production, but + can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. ## remove @@ -201,8 +211,13 @@ uninstalled. * **--no-update:** Disables the automatic update of the dependencies. * **--no-progress:** Removes the progress display that can mess with some terminals or scripts which don't handle backspace characters. -* **--update-no-dev** Run the dependency update with the --no-dev option. -* **--update-with-dependencies** Also update dependencies of the removed packages. +* **--update-no-dev:** Run the dependency update with the --no-dev option. +* **--update-with-dependencies:** Also update dependencies of the removed packages. +* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to + get a faster autoloader. This is recommended especially for production, but + can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize-autoloader`. ## global @@ -294,6 +309,18 @@ in your browser. * **--homepage (-H):** Open the homepage instead of the repository URL. +## suggests + +Lists all packages suggested by currently installed set of packages. You can +optionally pass one or multiple package names in the format of `vendor/package` +to limit output to suggestions made by those packages only. + +### Options + +* **--no-dev:** Excludes suggestions from `require-dev` packages. +* **--verbose (-v):** Increased verbosity adds suggesting package name and + reason for suggestion. + ## depends The `depends` command tells you which other packages depend on a certain @@ -327,7 +354,9 @@ php composer.phar validate ### Options -* **--no-check-all:** Whether or not Composer does a complete validation. +* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound version constraints. +* **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. +* **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid. ## status @@ -375,7 +404,7 @@ sudo composer self-update ### Options * **--rollback (-r):** Rollback to the last version you had installed. -* **--clean-backups:** Delete old backups during an update. This makes the +* **--clean-backups:** Delete old backups during an update. This makes the current version of Composer the only backup available after the update. ## config @@ -490,6 +519,8 @@ performance. * **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster autoloader. This is recommended especially for production, but can take a bit of time to run so it is currently not done by default. +* **--classmap-authoritative (-a):** Autoload classes from the classmap only. + Implicitly enables `--optimize`. * **--no-dev:** Disables autoload-dev rules. ## clear-cache @@ -568,6 +599,8 @@ For example: COMPOSER=composer-other.json php composer.phar install ``` +The generated lock file will use the same name: `composer-other.lock` in this example. + ### COMPOSER_ROOT_VERSION By setting this var you can specify the version of the root package, if it can diff --git a/doc/05-repositories.md b/doc/05-repositories.md index ffa29ede8..9308e1279 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -263,6 +263,10 @@ custom repository has priority over packagist. If you want to rename the package, you should do so in the default (often master) branch and not in a feature branch, since the package name is taken from the default branch. +Also note that the override will not work if you change the `name` property +in your forked repository's composer.json file as this needs to match the +original for the override to work. + If other dependencies rely on the package you forked, it is possible to inline-alias it so that it matches a constraint that it otherwise would not. For more information [see the aliases article](articles/aliases.md). @@ -602,6 +606,42 @@ imported. When an archive with a newer version is added in the artifact folder and you run `update`, that version will be imported as well and Composer will update to the latest version. +### Path + +In addition to the artifact repository, you can use the path one, which allows +you to depend on a relative directory. This can be especially useful when dealing +with monolith repositories. + +For instance, if you have the following directory structure in your repository: +``` +- apps +\_ my-app + \_ composer.json +- packages +\_ my-package + \_ composer.json +``` + +Then, to add the package `my/package` as a dependency, in your `apps/my-app/composer.json` +file, you can use the following configuration: + +```json +{ + "repositories": [ + { + "type": "path", + "url": "../../packages/my-package" + } + ], + "require": { + "my/package": "*@dev" + } +} +``` + +> **Note:** Repository paths can also contain wildcards like ``*`` and ``?``. +> For details, see the [PHP glob function](http://php.net/glob). + ## Disabling Packagist You can disable the default Packagist repository by adding this to your diff --git a/doc/06-config.md b/doc/06-config.md index 0ee1d4fcb..b3e2909c6 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -46,6 +46,11 @@ A list of domain names and username/passwords to authenticate against them. For example using `{"example.org": {"username": "alice", "password": "foo"}` as the value of this option will let Composer authenticate against example.org. +> **Note:** Authentication-related config options like `http-basic` and +> `github-oauth` can also be specified inside a `auth.json` file that goes +> besides your `composer.json`. That way you can gitignore it and every +> developer can place their own credentials in there. + ## platform Lets you fake platform packages (PHP and extensions) so that you can emulate a @@ -100,7 +105,7 @@ first until the cache fits. ## prepend-autoloader -Defaults to `true`. If false, the Composer autoloader will not be prepended to +Defaults to `true`. If `false`, the Composer autoloader will not be prepended to existing autoloaders. This is sometimes required to fix interoperability issues with other autoloaders. @@ -111,13 +116,12 @@ autoloader. When null a random one will be generated. ## optimize-autoloader -Defaults to `false`. Always optimize when dumping the autoloader. +Defaults to `false`. If `true`, always optimize when dumping the autoloader. ## classmap-authoritative -Defaults to `false`. If `true`, the Composer autoloader will not scan the -filesystem for classes that are not found in the class map. Implies -'optimize-autoloader'. +Defaults to `false`. If `true`, the Composer autoloader will only load classes +from the classmap. Implies `optimize-autoloader`. ## github-domains @@ -126,7 +130,7 @@ used for GitHub Enterprise setups. ## github-expose-hostname -Defaults to `true`. If set to `false`, the OAuth tokens created to access the +Defaults to `true`. If `false`, the OAuth tokens created to access the github API will have a date instead of the machine hostname. ## notify-on-install @@ -164,9 +168,4 @@ Example: } ``` -> **Note:** Authentication-related config options like `http-basic` and -> `github-oauth` can also be specified inside a `auth.json` file that goes -> besides your `composer.json`. That way you can gitignore it and every -> developer can place their own credentials in there. - ← [Repositories](05-repositories.md) | [Community](07-community.md) → diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index e7ce665b6..4294de4fb 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -73,7 +73,7 @@ constraint if you want really specific versions. ``` Once you've done this, you just run `php bin/satis build `. -For example `php bin/satis build config.json web/` would read the `config.json` +For example `php bin/satis build satis.json web/` would read the `satis.json` file and build a static repository inside the `web/` directory. When you ironed out that process, what you would typically do is run this diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index dec1fa06d..14e5b5a63 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -23,34 +23,34 @@ 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. +- **post-install-cmd**: occurs after the `install` command has been executed. - **pre-update-cmd**: occurs before the `update` command is executed. -- **post-update-cmd**: occurs after the `update` command is executed. +- **post-update-cmd**: occurs after the `update` command has been executed. - **pre-status-cmd**: occurs before the `status` command is executed. -- **post-status-cmd**: occurs after the `status` command is executed. +- **post-status-cmd**: occurs after the `status` command has been executed. - **pre-archive-cmd**: occurs before the `archive` command is executed. -- **post-archive-cmd**: occurs after the `archive` command is executed. +- **post-archive-cmd**: occurs after the `archive` command has been 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 +- **post-autoload-dump**: occurs after the autoloader has been dumped, either during `install`/`update`, or via the `dump-autoload` command. - **post-root-package-install**: occurs after the root package has been installed, during the `create-project` command. -- **post-create-project-cmd**: occurs after the `create-project` command is - executed. +- **post-create-project-cmd**: occurs after the `create-project` command has + been executed. ### Installer Events - **pre-dependencies-solving**: occurs before the dependencies are resolved. -- **post-dependencies-solving**: occurs after the dependencies are resolved. +- **post-dependencies-solving**: occurs after the dependencies have been resolved. ### Package Events - **pre-package-install**: occurs before a package is installed. -- **post-package-install**: occurs after a package is installed. +- **post-package-install**: occurs after a package has been 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-update**: occurs after a package has been updated. +- **pre-package-uninstall**: occurs before a package is uninstalled. - **post-package-uninstall**: occurs after a package has been uninstalled. ### Plugin Events @@ -82,6 +82,10 @@ For any given event: and command-line executable commands. - PHP classes containing defined callbacks must be autoloadable via Composer's autoload functionality. +- Callbacks can only autoload classes from psr-0, psr-4 and classmap +definitions. If a defined callback relies on functions defined outside of a +class, the callback itself is responsible for loading the file containing these +functions. Script definition example: @@ -96,7 +100,10 @@ Script definition example: "MyVendor\\MyClass::warmCache", "phpunit -c app/" ], - "post-create-project-cmd" : [ + "post-autoload-dump": [ + "MyVendor\\MyClass::postAutoloadDump" + ], + "post-create-project-cmd": [ "php -r \"copy('config/local-example.php', 'config/local.php');\"" ] } @@ -122,6 +129,14 @@ class MyClass // do stuff } + public static function postAutoloadDump(Event $event) + { + $vendorDir = $event->getComposer()->getConfig()->get('vendor-dir'); + require $vendorDir . '/autoload.php'; + + some_function_from_an_autoloaded_file(); + } + public static function postPackageInstall(PackageEvent $event) { $installedPackage = $event->getOperation()->getPackage(); diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 25279bddb..a6f082ab7 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -43,6 +43,10 @@ This is a list of common pitfalls on using Composer, and how to avoid them. 5. If you are updating to a recently published version of a package, be aware that Packagist has a delay of up to 1 minute before new packages are visible to Composer. +6. If you are updating a single package, it may depend on newer versions itself. + In this case add the `--with-dependencies` argument **or** add all dependencies which + need an update to the command. + ## Package not found on travis-ci.org 1. Check the ["Package not found"](#package-not-found) item above. @@ -61,13 +65,13 @@ This is a list of common pitfalls on using Composer, and how to avoid them. ## Package not found in a Jenkins-build 1. Check the ["Package not found"](#package-not-found) item above. -2. Reason for failing is similar to the problem which can occur on travis-ci.org: The - git-clone / checkout within Jenkins leaves the branch in a "detached HEAD"-state. As - a result, Composer is not able to identify the version of the current checked out branch - and may not be able to resolve a cyclic dependency. To solve this problem, you can use - the "Additional Behaviours" -> "Check out to specific local branch" in your Git-settings - for your Jenkins-job, where your "local branch" shall be the same branch as you are - checking out. Using this, the checkout will not be in detached state any more and cyclic +2. Reason for failing is similar to the problem which can occur on travis-ci.org: The + git-clone / checkout within Jenkins leaves the branch in a "detached HEAD"-state. As + a result, Composer is not able to identify the version of the current checked out branch + and may not be able to resolve a cyclic dependency. To solve this problem, you can use + the "Additional Behaviours" -> "Check out to specific local branch" in your Git-settings + for your Jenkins-job, where your "local branch" shall be the same branch as you are + checking out. Using this, the checkout will not be in detached state any more and cyclic dependency is recognized correctly. ## Need to override a package version @@ -168,3 +172,28 @@ To enable the swap you can use for example: /sbin/mkswap /var/swap.1 /sbin/swapon /var/swap.1 ``` + +## Degraded Mode + +Due to some intermittent issues on Travis and other systems, we introduced a +degraded network mode which helps Composer finish successfully but disables +a few optimizations. This is enabled automatically when an issue is first +detected. If you see this issue sporadically you probably don't have to worry +(a slow or overloaded network can also cause those time outs), but if it +appears repeatedly you might want to look at the options below to identify +and resolve it. + +If you have been pointed to this page, you want to check a few things: + +- If you are using ESET antivirus, go in "Advanced Settings" and disable "HTTP-scanner" + under "web access protection" +- If you are using IPv6, try disabling it. If that solves your issues, get in touch + with your ISP or server host, the problem is not at the Packagist level but in the + routing rules between you and Packagist (i.e. the internet at large). The best way to get + these fixed is raise awareness to the network engineers that have the power to fix it. + + To disable IPv6 on Linux, try using this command which appends a + rule preferring IPv4 over IPv6 to your config: + + `sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf"` +- If none of the above helped, please report the error. diff --git a/doc/articles/versions.md b/doc/articles/versions.md new file mode 100644 index 000000000..226fd84e6 --- /dev/null +++ b/doc/articles/versions.md @@ -0,0 +1,118 @@ + + +# Versions + +## Basic Constraints + +### Exact + +You can specify the exact version of a package. This will tell Composer to +install this version and this version only. If other dependencies require +a different version, the solver will ultimately fail and abort any install +or update procedures. + +Example: `1.0.2` + +### Range + +By using comparison operators you can specify ranges of valid versions. Valid +operators are `>`, `>=`, `<`, `<=`, `!=`. + +You can define multiple ranges. Ranges separated by a space ( ) +or comma (`,`) will be treated as a **logical AND**. A double pipe (`||`) +will be treated as a **logical OR**. AND has higher precedence than OR. + +> **Note:** Be careful when using unbounded ranges as you might end up +> unexpectedly installing versions that break backwards compatibility. +> Consider using the [caret](#caret) operator instead for safety. + +Examples: + +* `>=1.0` +* `>=1.0 <2.0` +* `>=1.0 <1.1 || >=1.2` + +### Range (Hyphen) + +Inclusive set of versions. Partial versions on the right include are completed +with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the +`2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to +`>=1.0.0 <=2.1.0`. + +Example: `1.0 - 2.0` + +### Wildcard + +You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of +`>=1.0 <1.1`. + +Example: `1.0.*` + +## Next Significant Release Operators + +### Tilde + +The `~` operator is best explained by example: `~1.2` is equivalent to +`>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see +it is mostly useful for projects respecting [semantic +versioning](http://semver.org/). A common usage would be to mark the minimum +minor version you depend on, like `~1.2` (which allows anything up to, but not +including, 2.0). Since in theory there should be no backwards compatibility +breaks until 2.0, that works well. Another way of looking at it is that using +`~` specifies a minimum version, but allows the last digit specified to go up. + +Example: `~1.2` + +> **Note:** Though `2.0-beta.1` is strictly before `2.0`, a version constraint +> like `~1.2` would not install it. As said above `~1.2` only means the `.2` +> can change but the `1.` part is fixed. + +> **Note:** The `~` operator has an exception on its behavior for the major +> release number. This means for example that `~1` is the same as `~1.0` as +> it will not allow the major number to increase trying to keep backwards +> compatibility. + +### Caret + +The `^` operator behaves very similarly but it sticks closer to semantic +versioning, and will always allow non-breaking updates. For example `^1.2.3` +is equivalent to `>=1.2.3 <2.0.0` as none of the releases until 2.0 should +break backwards compatibility. For pre-1.0 versions it also acts with safety +in mind and treats `^0.3` as `>=0.3.0 <0.4.0`. + +This is the recommended operator for maximum interoperability when writing +library code. + +Example: `^1.2.3` + +## Stability + +If you are using a constraint that does not explicitly define a stability, +Composer will default internally to `-dev` or `-stable`, depending on the +operator(s) used. This happens transparently. + +If you wish to explicitly consider only the stable release in the comparison, +add the suffix `-stable`. + +Examples: + + Constraint | Internally +------------------- | ------------------------ + `1.2.3` | `=1.2.3.0-stable` + `>1.2` | `>1.2.0.0-stable` + `>=1.2` | `>=1.2.0.0-dev` + `>=1.2-stable` | `>=1.2.0.0-stable` + `<1.3` | `<1.3.0.0-dev` + `<=1.3` | `<=1.3.0.0-stable` + `1 - 2` | `>=1.0.0.0-dev <3.0.0.0-dev` + `~1.3` | `>=1.3.0.0-dev <2.0.0.0-dev` + `1.4.*` | `>=1.4.0.0-dev <1.5.0.0-dev` + +## Test version constraints + +You can test version constraints using [semver.mwl.be](http://semver.mwl.be). +Fill in a package name and it will autofill the default version constraint +which Composer would add to your `composer.json` file. You can adjust the +version constraint and the tool will highlight all releases that match. diff --git a/res/spdx-licenses.json b/res/spdx-licenses.json deleted file mode 100644 index 3e1d93c35..000000000 --- a/res/spdx-licenses.json +++ /dev/null @@ -1,1226 +0,0 @@ -{ - "Glide": [ - "3dfx Glide License", - false - ], - "Abstyles": [ - "Abstyles License", - false - ], - "AFL-1.1": [ - "Academic Free License v1.1", - true - ], - "AFL-1.2": [ - "Academic Free License v1.2", - true - ], - "AFL-2.0": [ - "Academic Free License v2.0", - true - ], - "AFL-2.1": [ - "Academic Free License v2.1", - true - ], - "AFL-3.0": [ - "Academic Free License v3.0", - true - ], - "AMPAS": [ - "Academy of Motion Picture Arts and Sciences BSD", - false - ], - "APL-1.0": [ - "Adaptive Public License 1.0", - true - ], - "Adobe-Glyph": [ - "Adobe Glyph List License", - false - ], - "APAFML": [ - "Adobe Postscript AFM License", - false - ], - "Adobe-2006": [ - "Adobe Systems Incorporated Source Code License Agreement", - false - ], - "AGPL-1.0": [ - "Affero General Public License v1.0", - false - ], - "Afmparse": [ - "Afmparse License", - false - ], - "Aladdin": [ - "Aladdin Free Public License", - false - ], - "ADSL": [ - "Amazon Digital Services License", - false - ], - "AMDPLPA": [ - "AMD's plpa_map.c License", - false - ], - "ANTLR-PD": [ - "ANTLR Software Rights Notice", - false - ], - "Apache-1.0": [ - "Apache License 1.0", - false - ], - "Apache-1.1": [ - "Apache License 1.1", - true - ], - "Apache-2.0": [ - "Apache License 2.0", - true - ], - "AML": [ - "Apple MIT License", - false - ], - "APSL-1.0": [ - "Apple Public Source License 1.0", - true - ], - "APSL-1.1": [ - "Apple Public Source License 1.1", - true - ], - "APSL-1.2": [ - "Apple Public Source License 1.2", - true - ], - "APSL-2.0": [ - "Apple Public Source License 2.0", - true - ], - "Artistic-1.0": [ - "Artistic License 1.0", - true - ], - "Artistic-1.0-Perl": [ - "Artistic License 1.0 (Perl)", - true - ], - "Artistic-1.0-cl8": [ - "Artistic License 1.0 w/clause 8", - true - ], - "Artistic-2.0": [ - "Artistic License 2.0", - true - ], - "AAL": [ - "Attribution Assurance License", - true - ], - "Bahyph": [ - "Bahyph License", - false - ], - "Barr": [ - "Barr License", - false - ], - "Beerware": [ - "Beerware License", - false - ], - "BitTorrent-1.0": [ - "BitTorrent Open Source License v1.0", - false - ], - "BitTorrent-1.1": [ - "BitTorrent Open Source License v1.1", - false - ], - "BSL-1.0": [ - "Boost Software License 1.0", - true - ], - "Borceux": [ - "Borceux license", - false - ], - "BSD-2-Clause": [ - "BSD 2-clause \"Simplified\" License", - true - ], - "BSD-2-Clause-FreeBSD": [ - "BSD 2-clause FreeBSD License", - false - ], - "BSD-2-Clause-NetBSD": [ - "BSD 2-clause NetBSD License", - false - ], - "BSD-3-Clause": [ - "BSD 3-clause \"New\" or \"Revised\" License", - true - ], - "BSD-3-Clause-Clear": [ - "BSD 3-clause Clear License", - false - ], - "BSD-4-Clause": [ - "BSD 4-clause \"Original\" or \"Old\" License", - false - ], - "BSD-Protection": [ - "BSD Protection License", - false - ], - "BSD-3-Clause-Attribution": [ - "BSD with attribution", - false - ], - "BSD-4-Clause-UC": [ - "BSD-4-Clause (University of California-Specific)", - false - ], - "bzip2-1.0.5": [ - "bzip2 and libbzip2 License v1.0.5", - false - ], - "bzip2-1.0.6": [ - "bzip2 and libbzip2 License v1.0.6", - false - ], - "Caldera": [ - "Caldera License", - false - ], - "CECILL-1.0": [ - "CeCILL Free Software License Agreement v1.0", - false - ], - "CECILL-1.1": [ - "CeCILL Free Software License Agreement v1.1", - false - ], - "CECILL-2.0": [ - "CeCILL Free Software License Agreement v2.0", - false - ], - "CECILL-B": [ - "CeCILL-B Free Software License Agreement", - false - ], - "CECILL-C": [ - "CeCILL-C Free Software License Agreement", - false - ], - "ClArtistic": [ - "Clarified Artistic License", - false - ], - "MIT-CMU": [ - "CMU License", - false - ], - "CNRI-Python": [ - "CNRI Python License", - true - ], - "CNRI-Python-GPL-Compatible": [ - "CNRI Python Open Source GPL Compatible License Agreement", - false - ], - "CPOL-1.02": [ - "Code Project Open License 1.02", - false - ], - "CDDL-1.0": [ - "Common Development and Distribution License 1.0", - true - ], - "CDDL-1.1": [ - "Common Development and Distribution License 1.1", - false - ], - "CPAL-1.0": [ - "Common Public Attribution License 1.0", - true - ], - "CPL-1.0": [ - "Common Public License 1.0", - true - ], - "CATOSL-1.1": [ - "Computer Associates Trusted Open Source License 1.1", - true - ], - "Condor-1.1": [ - "Condor Public License v1.1", - false - ], - "CC-BY-1.0": [ - "Creative Commons Attribution 1.0", - false - ], - "CC-BY-2.0": [ - "Creative Commons Attribution 2.0", - false - ], - "CC-BY-2.5": [ - "Creative Commons Attribution 2.5", - false - ], - "CC-BY-3.0": [ - "Creative Commons Attribution 3.0", - false - ], - "CC-BY-4.0": [ - "Creative Commons Attribution 4.0", - false - ], - "CC-BY-ND-1.0": [ - "Creative Commons Attribution No Derivatives 1.0", - false - ], - "CC-BY-ND-2.0": [ - "Creative Commons Attribution No Derivatives 2.0", - false - ], - "CC-BY-ND-2.5": [ - "Creative Commons Attribution No Derivatives 2.5", - false - ], - "CC-BY-ND-3.0": [ - "Creative Commons Attribution No Derivatives 3.0", - false - ], - "CC-BY-ND-4.0": [ - "Creative Commons Attribution No Derivatives 4.0", - false - ], - "CC-BY-NC-1.0": [ - "Creative Commons Attribution Non Commercial 1.0", - false - ], - "CC-BY-NC-2.0": [ - "Creative Commons Attribution Non Commercial 2.0", - false - ], - "CC-BY-NC-2.5": [ - "Creative Commons Attribution Non Commercial 2.5", - false - ], - "CC-BY-NC-3.0": [ - "Creative Commons Attribution Non Commercial 3.0", - false - ], - "CC-BY-NC-4.0": [ - "Creative Commons Attribution Non Commercial 4.0", - false - ], - "CC-BY-NC-ND-1.0": [ - "Creative Commons Attribution Non Commercial No Derivatives 1.0", - false - ], - "CC-BY-NC-ND-2.0": [ - "Creative Commons Attribution Non Commercial No Derivatives 2.0", - false - ], - "CC-BY-NC-ND-2.5": [ - "Creative Commons Attribution Non Commercial No Derivatives 2.5", - false - ], - "CC-BY-NC-ND-3.0": [ - "Creative Commons Attribution Non Commercial No Derivatives 3.0", - false - ], - "CC-BY-NC-ND-4.0": [ - "Creative Commons Attribution Non Commercial No Derivatives 4.0", - false - ], - "CC-BY-NC-SA-1.0": [ - "Creative Commons Attribution Non Commercial Share Alike 1.0", - false - ], - "CC-BY-NC-SA-2.0": [ - "Creative Commons Attribution Non Commercial Share Alike 2.0", - false - ], - "CC-BY-NC-SA-2.5": [ - "Creative Commons Attribution Non Commercial Share Alike 2.5", - false - ], - "CC-BY-NC-SA-3.0": [ - "Creative Commons Attribution Non Commercial Share Alike 3.0", - false - ], - "CC-BY-NC-SA-4.0": [ - "Creative Commons Attribution Non Commercial Share Alike 4.0", - false - ], - "CC-BY-SA-1.0": [ - "Creative Commons Attribution Share Alike 1.0", - false - ], - "CC-BY-SA-2.0": [ - "Creative Commons Attribution Share Alike 2.0", - false - ], - "CC-BY-SA-2.5": [ - "Creative Commons Attribution Share Alike 2.5", - false - ], - "CC-BY-SA-3.0": [ - "Creative Commons Attribution Share Alike 3.0", - false - ], - "CC-BY-SA-4.0": [ - "Creative Commons Attribution Share Alike 4.0", - false - ], - "CC0-1.0": [ - "Creative Commons Zero v1.0 Universal", - false - ], - "Crossword": [ - "Crossword License", - false - ], - "CUA-OPL-1.0": [ - "CUA Office Public License v1.0", - true - ], - "Cube": [ - "Cube License", - false - ], - "D-FSL-1.0": [ - "Deutsche Freie Software Lizenz", - false - ], - "diffmark": [ - "diffmark license", - false - ], - "WTFPL": [ - "Do What The F*ck You Want To Public License", - false - ], - "DOC": [ - "DOC License", - false - ], - "Dotseqn": [ - "Dotseqn License", - false - ], - "DSDP": [ - "DSDP License", - false - ], - "dvipdfm": [ - "dvipdfm License", - false - ], - "EPL-1.0": [ - "Eclipse Public License 1.0", - true - ], - "eCos-2.0": [ - "eCos license version 2.0", - false - ], - "ECL-1.0": [ - "Educational Community License v1.0", - true - ], - "ECL-2.0": [ - "Educational Community License v2.0", - true - ], - "eGenix": [ - "eGenix.com Public License 1.1.0", - false - ], - "EFL-1.0": [ - "Eiffel Forum License v1.0", - true - ], - "EFL-2.0": [ - "Eiffel Forum License v2.0", - true - ], - "MIT-advertising": [ - "Enlightenment License (e16)", - false - ], - "MIT-enna": [ - "enna License", - false - ], - "Entessa": [ - "Entessa Public License v1.0", - true - ], - "ErlPL-1.1": [ - "Erlang Public License v1.1", - false - ], - "EUDatagrid": [ - "EU DataGrid Software License", - true - ], - "EUPL-1.0": [ - "European Union Public License 1.0", - false - ], - "EUPL-1.1": [ - "European Union Public License 1.1", - true - ], - "Eurosym": [ - "Eurosym License", - false - ], - "Fair": [ - "Fair License", - true - ], - "MIT-feh": [ - "feh License", - false - ], - "Frameworx-1.0": [ - "Frameworx Open License 1.0", - true - ], - "FTL": [ - "Freetype Project License", - false - ], - "FSFUL": [ - "FSF Unlimited License", - false - ], - "FSFULLR": [ - "FSF Unlimited License (with License Retention)", - false - ], - "Giftware": [ - "Giftware License", - false - ], - "GL2PS": [ - "GL2PS License", - false - ], - "Glulxe": [ - "Glulxe License", - false - ], - "AGPL-3.0": [ - "GNU Affero General Public License v3.0", - true - ], - "GFDL-1.1": [ - "GNU Free Documentation License v1.1", - false - ], - "GFDL-1.2": [ - "GNU Free Documentation License v1.2", - false - ], - "GFDL-1.3": [ - "GNU Free Documentation License v1.3", - false - ], - "GPL-1.0": [ - "GNU General Public License v1.0 only", - false - ], - "GPL-1.0+": [ - "GNU General Public License v1.0 or later", - false - ], - "GPL-2.0": [ - "GNU General Public License v2.0 only", - true - ], - "GPL-2.0+": [ - "GNU General Public License v2.0 or later", - true - ], - "GPL-2.0-with-autoconf-exception": [ - "GNU General Public License v2.0 w/Autoconf exception", - true - ], - "GPL-2.0-with-bison-exception": [ - "GNU General Public License v2.0 w/Bison exception", - true - ], - "GPL-2.0-with-classpath-exception": [ - "GNU General Public License v2.0 w/Classpath exception", - true - ], - "GPL-2.0-with-font-exception": [ - "GNU General Public License v2.0 w/Font exception", - true - ], - "GPL-2.0-with-GCC-exception": [ - "GNU General Public License v2.0 w/GCC Runtime Library exception", - true - ], - "GPL-3.0": [ - "GNU General Public License v3.0 only", - true - ], - "GPL-3.0+": [ - "GNU General Public License v3.0 or later", - true - ], - "GPL-3.0-with-autoconf-exception": [ - "GNU General Public License v3.0 w/Autoconf exception", - true - ], - "GPL-3.0-with-GCC-exception": [ - "GNU General Public License v3.0 w/GCC Runtime Library exception", - true - ], - "LGPL-2.1": [ - "GNU Lesser General Public License v2.1 only", - true - ], - "LGPL-2.1+": [ - "GNU Lesser General Public License v2.1 or later", - true - ], - "LGPL-3.0": [ - "GNU Lesser General Public License v3.0 only", - true - ], - "LGPL-3.0+": [ - "GNU Lesser General Public License v3.0 or later", - true - ], - "LGPL-2.0": [ - "GNU Library General Public License v2 only", - true - ], - "LGPL-2.0+": [ - "GNU Library General Public License v2 or later", - true - ], - "gnuplot": [ - "gnuplot License", - false - ], - "gSOAP-1.3b": [ - "gSOAP Public License v1.3b", - false - ], - "HaskellReport": [ - "Haskell Language Report License", - false - ], - "HPND": [ - "Historic Permission Notice and Disclaimer", - true - ], - "IBM-pibs": [ - "IBM PowerPC Initialization and Boot Software", - false - ], - "IPL-1.0": [ - "IBM Public License v1.0", - true - ], - "ImageMagick": [ - "ImageMagick License", - false - ], - "iMatix": [ - "iMatix Standard Function Library Agreement", - false - ], - "Imlib2": [ - "Imlib2 License", - false - ], - "IJG": [ - "Independent JPEG Group License", - false - ], - "Intel-ACPI": [ - "Intel ACPI Software License Agreement", - false - ], - "Intel": [ - "Intel Open Source License", - true - ], - "IPA": [ - "IPA Font License", - true - ], - "ISC": [ - "ISC License", - true - ], - "JasPer-2.0": [ - "JasPer License", - false - ], - "JSON": [ - "JSON License", - false - ], - "LPPL-1.3a": [ - "LaTeX Project Public License 1.3a", - false - ], - "LPPL-1.0": [ - "LaTeX Project Public License v1.0", - false - ], - "LPPL-1.1": [ - "LaTeX Project Public License v1.1", - false - ], - "LPPL-1.2": [ - "LaTeX Project Public License v1.2", - false - ], - "LPPL-1.3c": [ - "LaTeX Project Public License v1.3c", - true - ], - "Latex2e": [ - "Latex2e License", - false - ], - "BSD-3-Clause-LBNL": [ - "Lawrence Berkeley National Labs BSD variant license", - false - ], - "Leptonica": [ - "Leptonica License", - false - ], - "Libpng": [ - "libpng License", - false - ], - "libtiff": [ - "libtiff License", - false - ], - "LPL-1.02": [ - "Lucent Public License v1.02", - true - ], - "LPL-1.0": [ - "Lucent Public License Version 1.0", - true - ], - "MakeIndex": [ - "MakeIndex License", - false - ], - "MTLL": [ - "Matrix Template Library License", - false - ], - "MS-PL": [ - "Microsoft Public License", - true - ], - "MS-RL": [ - "Microsoft Reciprocal License", - true - ], - "MirOS": [ - "MirOS Licence", - true - ], - "MITNFA": [ - "MIT +no-false-attribs license", - false - ], - "MIT": [ - "MIT License", - true - ], - "Motosoto": [ - "Motosoto License", - true - ], - "MPL-1.0": [ - "Mozilla Public License 1.0", - true - ], - "MPL-1.1": [ - "Mozilla Public License 1.1", - true - ], - "MPL-2.0": [ - "Mozilla Public License 2.0", - true - ], - "MPL-2.0-no-copyleft-exception": [ - "Mozilla Public License 2.0 (no copyleft exception)", - true - ], - "mpich2": [ - "mpich2 License", - false - ], - "Multics": [ - "Multics License", - true - ], - "Mup": [ - "Mup License", - false - ], - "NASA-1.3": [ - "NASA Open Source Agreement 1.3", - true - ], - "Naumen": [ - "Naumen Public License", - true - ], - "NBPL-1.0": [ - "Net Boolean Public License v1", - false - ], - "NetCDF": [ - "NetCDF license", - false - ], - "NGPL": [ - "Nethack General Public License", - true - ], - "NOSL": [ - "Netizen Open Source License", - false - ], - "NPL-1.0": [ - "Netscape Public License v1.0", - false - ], - "NPL-1.1": [ - "Netscape Public License v1.1", - false - ], - "Newsletr": [ - "Newsletr License", - false - ], - "NLPL": [ - "No Limit Public License", - false - ], - "Nokia": [ - "Nokia Open Source License", - true - ], - "NPOSL-3.0": [ - "Non-Profit Open Software License 3.0", - true - ], - "Noweb": [ - "Noweb License", - false - ], - "NRL": [ - "NRL License", - false - ], - "NTP": [ - "NTP License", - true - ], - "Nunit": [ - "Nunit License", - false - ], - "OCLC-2.0": [ - "OCLC Research Public License 2.0", - true - ], - "ODbL-1.0": [ - "ODC Open Database License v1.0", - false - ], - "PDDL-1.0": [ - "ODC Public Domain Dedication & License 1.0", - false - ], - "OGTSL": [ - "Open Group Test Suite License", - true - ], - "OLDAP-2.2.2": [ - "Open LDAP Public License 2.2.2", - false - ], - "OLDAP-1.1": [ - "Open LDAP Public License v1.1", - false - ], - "OLDAP-1.2": [ - "Open LDAP Public License v1.2", - false - ], - "OLDAP-1.3": [ - "Open LDAP Public License v1.3", - false - ], - "OLDAP-1.4": [ - "Open LDAP Public License v1.4", - false - ], - "OLDAP-2.0": [ - "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", - false - ], - "OLDAP-2.0.1": [ - "Open LDAP Public License v2.0.1", - false - ], - "OLDAP-2.1": [ - "Open LDAP Public License v2.1", - false - ], - "OLDAP-2.2": [ - "Open LDAP Public License v2.2", - false - ], - "OLDAP-2.2.1": [ - "Open LDAP Public License v2.2.1", - false - ], - "OLDAP-2.3": [ - "Open LDAP Public License v2.3", - false - ], - "OLDAP-2.4": [ - "Open LDAP Public License v2.4", - false - ], - "OLDAP-2.5": [ - "Open LDAP Public License v2.5", - false - ], - "OLDAP-2.6": [ - "Open LDAP Public License v2.6", - false - ], - "OLDAP-2.7": [ - "Open LDAP Public License v2.7", - false - ], - "OML": [ - "Open Market License", - false - ], - "OPL-1.0": [ - "Open Public License v1.0", - false - ], - "OSL-1.0": [ - "Open Software License 1.0", - true - ], - "OSL-1.1": [ - "Open Software License 1.1", - false - ], - "OSL-2.0": [ - "Open Software License 2.0", - true - ], - "OSL-2.1": [ - "Open Software License 2.1", - true - ], - "OSL-3.0": [ - "Open Software License 3.0", - true - ], - "OLDAP-2.8": [ - "OpenLDAP Public License v2.8", - false - ], - "OpenSSL": [ - "OpenSSL License", - false - ], - "PHP-3.0": [ - "PHP License v3.0", - true - ], - "PHP-3.01": [ - "PHP License v3.01", - false - ], - "Plexus": [ - "Plexus Classworlds License", - false - ], - "PostgreSQL": [ - "PostgreSQL License", - true - ], - "psfrag": [ - "psfrag License", - false - ], - "psutils": [ - "psutils License", - false - ], - "Python-2.0": [ - "Python License 2.0", - true - ], - "QPL-1.0": [ - "Q Public License 1.0", - true - ], - "Qhull": [ - "Qhull License", - false - ], - "Rdisc": [ - "Rdisc License", - false - ], - "RPSL-1.0": [ - "RealNetworks Public Source License v1.0", - true - ], - "RPL-1.1": [ - "Reciprocal Public License 1.1", - true - ], - "RPL-1.5": [ - "Reciprocal Public License 1.5", - true - ], - "RHeCos-1.1": [ - "Red Hat eCos Public License v1.1", - false - ], - "RSCPL": [ - "Ricoh Source Code Public License", - true - ], - "Ruby": [ - "Ruby License", - false - ], - "SAX-PD": [ - "Sax Public Domain Notice", - false - ], - "Saxpath": [ - "Saxpath License", - false - ], - "SCEA": [ - "SCEA Shared Source License", - false - ], - "SWL": [ - "Scheme Widget Library (SWL) Software License Agreement", - false - ], - "SGI-B-1.0": [ - "SGI Free Software License B v1.0", - false - ], - "SGI-B-1.1": [ - "SGI Free Software License B v1.1", - false - ], - "SGI-B-2.0": [ - "SGI Free Software License B v2.0", - false - ], - "OFL-1.0": [ - "SIL Open Font License 1.0", - false - ], - "OFL-1.1": [ - "SIL Open Font License 1.1", - true - ], - "SimPL-2.0": [ - "Simple Public License 2.0", - true - ], - "Sleepycat": [ - "Sleepycat License", - true - ], - "SNIA": [ - "SNIA Public License 1.1", - false - ], - "SMLNJ": [ - "Standard ML of New Jersey License", - false - ], - "StandardML-NJ": [ - "Standard ML of New Jersey License", - false - ], - "SugarCRM-1.1.3": [ - "SugarCRM Public License v1.1.3", - false - ], - "SISSL": [ - "Sun Industry Standards Source License v1.1", - true - ], - "SISSL-1.2": [ - "Sun Industry Standards Source License v1.2", - false - ], - "SPL-1.0": [ - "Sun Public License v1.0", - true - ], - "Watcom-1.0": [ - "Sybase Open Watcom Public License 1.0", - true - ], - "TCL": [ - "TCL/TK License", - false - ], - "Unlicense": [ - "The Unlicense", - false - ], - "TMate": [ - "TMate Open Source License", - false - ], - "TORQUE-1.1": [ - "TORQUE v2.5+ Software License v1.1", - false - ], - "TOSL": [ - "Trusster Open Source License", - false - ], - "Unicode-TOU": [ - "Unicode Terms of Use", - false - ], - "NCSA": [ - "University of Illinois/NCSA Open Source License", - true - ], - "Vim": [ - "Vim License", - false - ], - "VOSTROM": [ - "VOSTROM Public License for Open Source", - false - ], - "VSL-1.0": [ - "Vovida Software License v1.0", - true - ], - "W3C": [ - "W3C Software Notice and License", - true - ], - "Wsuipa": [ - "Wsuipa License", - false - ], - "WXwindows": [ - "wxWindows Library License", - true - ], - "Xnet": [ - "X.Net License", - true - ], - "X11": [ - "X11 License", - false - ], - "Xerox": [ - "Xerox License", - false - ], - "XFree86-1.1": [ - "XFree86 License 1.1", - false - ], - "xinetd": [ - "xinetd License", - false - ], - "xpp": [ - "XPP License", - false - ], - "XSkat": [ - "XSkat License", - false - ], - "YPL-1.0": [ - "Yahoo! Public License v1.0", - false - ], - "YPL-1.1": [ - "Yahoo! Public License v1.1", - false - ], - "Zed": [ - "Zed License", - false - ], - "Zend-2.0": [ - "Zend License v2.0", - false - ], - "Zimbra-1.3": [ - "Zimbra Public License v1.3", - false - ], - "Zlib": [ - "zlib License", - true - ], - "zlib-acknowledgement": [ - "zlib/libpng License with Acknowledgement", - false - ], - "ZPL-1.1": [ - "Zope Public License 1.1", - false - ], - "ZPL-2.0": [ - "Zope Public License 2.0", - true - ], - "ZPL-2.1": [ - "Zope Public License 2.1", - false - ] -} \ No newline at end of file diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 3df98f700..0c32dc750 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -38,8 +38,16 @@ class AutoloadGenerator */ private $io; + /** + * @var bool + */ private $devMode = false; + /** + * @var bool + */ + private $classMapAuthoritative = false; + public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null) { $this->eventDispatcher = $eventDispatcher; @@ -51,8 +59,23 @@ class AutoloadGenerator $this->devMode = (boolean) $devMode; } + /** + * Whether or not generated autoloader considers the class map + * authoritative. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = (boolean) $classMapAuthoritative; + } + public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '') { + if ($this->classMapAuthoritative) { + // Force scanPsr0Packages when classmap is authoritative + $scanPsr0Packages = true; + } $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array( 'optimize' => (bool) $scanPsr0Packages, )); @@ -63,7 +86,6 @@ class AutoloadGenerator $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir'))); $useGlobalIncludePath = (bool) $config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; - $classMapAuthoritative = $config->get('classmap-authoritative'); $targetDir = $vendorPath.'/'.$targetDir; $filesystem->ensureDirectoryExists($targetDir); @@ -174,10 +196,21 @@ EOF; // flatten array $classMap = array(); if ($scanPsr0Packages) { + $namespacesToScan = array(); + // Scan the PSR-0/4 directories for class files, and add them to the class map foreach (array('psr-0', 'psr-4') as $psrType) { foreach ($autoloads[$psrType] as $namespace => $paths) { - foreach ($paths as $dir) { + $namespacesToScan[$namespace][] = array('paths' => $paths, 'type' => $psrType); + } + } + + krsort($namespacesToScan); + + foreach ($namespacesToScan as $namespace => $groups) { + foreach ($groups as $group) { + $psrType = $group['type']; + foreach ($group['paths'] as $dir) { $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); if (!is_dir($dir)) { continue; @@ -190,9 +223,14 @@ EOF; $namespaceFilter = $namespace === '' ? null : $namespace; foreach (ClassMapGenerator::createMap($dir, $whitelist, $this->io, $namespaceFilter) as $class => $path) { + $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; if (!isset($classMap[$class])) { - $path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); - $classMap[$class] = $path.",\n"; + $classMap[$class] = $pathCode; + } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found in both "'.str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]).'" and "'.$path.'", the first will be used.' + ); } } } @@ -202,8 +240,15 @@ EOF; foreach ($autoloads['classmap'] as $dir) { foreach (ClassMapGenerator::createMap($dir, null, $this->io) as $class => $path) { - $path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); - $classMap[$class] = $path.",\n"; + $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) { + $this->io->writeError( + 'Warning: Ambiguous class resolution, "'.$class.'"'. + ' was found in both "'.str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]).'" and "'.$path.'", the first will be used.' + ); + } } } @@ -229,23 +274,23 @@ EOF; file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile); file_put_contents($targetDir.'/autoload_psr4.php', $psr4File); file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile); - if ($includePathFile = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { - file_put_contents($targetDir.'/include_paths.php', $includePathFile); - } - if ($includeFilesFile = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { - file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile); + $includePathFilePath = $targetDir.'/include_paths.php'; + if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { + file_put_contents($includePathFilePath, $includePathFileContents); + } elseif (file_exists($includePathFilePath)) { + unlink($includePathFilePath); + } + $includeFilesFilePath = $targetDir.'/autoload_files.php'; + if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) { + file_put_contents($includeFilesFilePath, $includeFilesFileContents); + } elseif (file_exists($includeFilesFilePath)) { + unlink($includeFilesFilePath); } file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); - file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)); + file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)); - // use stream_copy_to_stream instead of copy - // to work around https://bugs.php.net/bug.php?id=64634 - $sourceLoader = fopen(__DIR__.'/ClassLoader.php', 'r'); - $targetLoader = fopen($targetDir.'/ClassLoader.php', 'w+'); - stream_copy_to_stream($sourceLoader, $targetLoader); - fclose($sourceLoader); - fclose($targetLoader); - unset($sourceLoader, $targetLoader); + $this->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); + $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array( 'optimize' => (bool) $scanPsr0Packages, @@ -310,7 +355,7 @@ EOF; $psr0 = $this->parseAutoloadsType($packageMap, 'psr-0', $mainPackage); $psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $mainPackage); - $classmap = $this->parseAutoloadsType($sortedPackageMap, 'classmap', $mainPackage); + $classmap = $this->parseAutoloadsType(array_reverse($sortedPackageMap), 'classmap', $mainPackage); $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $mainPackage); krsort($psr0); @@ -453,7 +498,7 @@ return ComposerAutoloaderInit$suffix::getLoader(); AUTOLOAD; } - protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative) + protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader) { // TODO the class ComposerAutoloaderInit should be revert to a closure // when APC has been fixed: @@ -530,7 +575,7 @@ PSR4; CLASSMAP; } - if ($classMapAuthoritative) { + if ($this->classMapAuthoritative) { $file .= <<<'CLASSMAPAUTHORITATIVE' $loader->setClassMapAuthoritative(true); @@ -727,4 +772,20 @@ FOOTER; return $sortedPackageMap; } + + /** + * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 + * + * @param string $source + * @param string $target + */ + protected function safeCopy($source, $target) + { + $source = fopen($source, 'r'); + $target = fopen($target, 'w+'); + + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + } } diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 4e05d3b15..5e1469e83 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -351,7 +351,7 @@ class ClassLoader foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { if (0 === strpos($class, $prefix)) { foreach ($this->prefixDirsPsr4[$prefix] as $dir) { - if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } @@ -361,7 +361,7 @@ class ClassLoader // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { - if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } @@ -380,7 +380,7 @@ class ClassLoader foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { - if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } @@ -390,7 +390,7 @@ class ClassLoader // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { - if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 02b21bd40..5484b7d41 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -49,9 +49,8 @@ class ClassMapGenerator * @param IOInterface $io IO object * @param string $namespace Optional namespace prefix to filter by * - * @return array A class map array - * * @throws \RuntimeException When the path is neither an existing file nor directory + * @return array A class map array */ public static function createMap($path, $whitelist = null, IOInterface $io = null, $namespace = null) { @@ -91,7 +90,7 @@ class ClassMapGenerator if (!isset($map[$class])) { $map[$class] = $filePath; - } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example)s?/}i', strtr($map[$class].' '.$filePath, '\\', '/'))) { + } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class].' '.$filePath, '\\', '/'))) { $io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.' diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index e49edf649..9f9c55ba6 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -43,10 +43,12 @@ class Cache $this->whitelist = $whitelist; $this->filesystem = $filesystem ?: new Filesystem(); - if (!is_dir($this->root)) { - if (!@mkdir($this->root, 0777, true)) { - $this->enabled = false; - } + if ( + (!is_dir($this->root) && !@mkdir($this->root, 0777, true)) + || !is_writable($this->root) + ) { + $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache'); + $this->enabled = false; } } @@ -86,6 +88,9 @@ class Cache try { return file_put_contents($this->root . $file, $contents); } catch (\ErrorException $e) { + if ($this->io->isDebug()) { + $this->io->writeError('Failed to write into cache: '.$e->getMessage().''); + } if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { // Remove partial file. unlink($this->root . $file); diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index b7c039510..b8c439678 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -19,7 +19,7 @@ use Composer\Repository\CompositeRepository; use Composer\Script\ScriptEvents; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; - +use Composer\Util\Filesystem; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -105,7 +105,12 @@ EOT } $io->writeError('Creating the archive into "'.$dest.'".'); - $archiveManager->archive($package, $format, $dest); + $packagePath = $archiveManager->archive($package, $format, $dest); + $fs = new Filesystem; + $shortPath = $fs->findShortestPath(getcwd(), $packagePath, true); + + $io->writeError('Created: ', false); + $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath); return 0; } diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index a833a5140..d0f504d9c 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -66,7 +66,7 @@ class ConfigCommand extends Command new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), - new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), @@ -129,7 +129,7 @@ EOT { parent::initialize($input, $output); - if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) { + if ($input->getOption('global') && null !== $input->getOption('file')) { throw new \RuntimeException('--file and --global can not be combined'); } @@ -139,7 +139,7 @@ EOT // passed in a file to use $configFile = $input->getOption('global') ? ($this->config->get('home') . '/config.json') - : $input->getOption('file'); + : ($input->getOption('file') ?: trim(getenv('COMPOSER')) ?: 'composer.json'); // create global composer.json if this was invoked using `composer global config` if ($configFile === 'composer.json' && !file_exists($configFile) && realpath(getcwd()) === realpath($this->config->get('home'))) { @@ -151,7 +151,7 @@ EOT $authConfigFile = $input->getOption('global') ? ($this->config->get('home') . '/auth.json') - : dirname(realpath($input->getOption('file'))) . '/auth.json'; + : dirname(realpath($configFile)) . '/auth.json'; $this->authConfigFile = new JsonFile($authConfigFile); $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); @@ -238,17 +238,19 @@ EOT } elseif (strpos($settingKey, '.')) { $bits = explode('.', $settingKey); $data = $data['config']; + $match = false; foreach ($bits as $bit) { - if (isset($data[$bit])) { - $data = $data[$bit]; - } elseif (isset($data[implode('.', $bits)])) { - // last bit can contain domain names and such so try to join whatever is left if it exists - $data = $data[implode('.', $bits)]; - break; - } else { - throw new \RuntimeException($settingKey.' is not defined'); + $key = isset($key) ? $key.'.'.$bit : $bit; + $match = false; + if (isset($data[$key])) { + $match = true; + $data = $data[$key]; + unset($key); } - array_shift($bits); + } + + if (!$match) { + throw new \RuntimeException($settingKey.' is not defined.'); } $value = $data; @@ -278,7 +280,7 @@ EOT 'use-include-path' => array($booleanValidator, $booleanNormalizer), 'preferred-install' => array( function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); }, - function ($val) { return $val; } + function ($val) { return $val; }, ), 'store-auths' => array( function ($val) { return in_array($val, array('true', 'false', 'prompt'), true); }, @@ -288,7 +290,7 @@ EOT } return $val !== 'false' && (bool) $val; - } + }, ), 'notify-on-install' => array($booleanValidator, $booleanNormalizer), 'vendor-dir' => array('is_string', function ($val) { return $val; }), @@ -301,7 +303,7 @@ EOT 'cache-files-ttl' => array('is_numeric', 'intval'), 'cache-files-maxsize' => array( function ($val) { return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0; }, - function ($val) { return $val; } + function ($val) { return $val; }, ), 'discard-changes' => array( function ($val) { return in_array($val, array('stash', 'true', 'false', '1', '0'), true); }, @@ -311,7 +313,7 @@ EOT } return $val !== 'false' && (bool) $val; - } + }, ), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), @@ -336,7 +338,7 @@ EOT }, function ($vals) { return $vals; - } + }, ), 'github-domains' => array( function ($vals) { @@ -348,7 +350,7 @@ EOT }, function ($vals) { return $vals; - } + }, ), ); @@ -464,6 +466,7 @@ EOT protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, $k = null) { $origK = $k; + $io = $this->getIO(); foreach ($contents as $key => $value) { if ($k === null && !in_array($key, array('config', 'repositories'))) { continue; @@ -474,13 +477,7 @@ EOT if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { $k .= preg_replace('{^config\.}', '', $key . '.'); $this->listConfiguration($value, $rawVal, $output, $k); - - if (substr_count($k, '.') > 1) { - $k = str_split($k, strrpos($k, '.', -2)); - $k = $k[0] . '.'; - } else { - $k = $origK; - } + $k = $origK; continue; } @@ -498,9 +495,9 @@ EOT } if (is_string($rawVal) && $rawVal != $value) { - $this->getIO()->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); + $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')'); } else { - $this->getIO()->write('[' . $k . $key . '] ' . $value . ''); + $io->write('[' . $k . $key . '] ' . $value . ''); } } } diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 02aaf6f1b..2cae1414a 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -100,16 +100,17 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $config = Factory::createConfig(); + $io = $this->getIO(); $this->updatePreferredOptions($config, $input, $preferSource, $preferDist, true); if ($input->getOption('no-custom-installers')) { - $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } return $this->installProject( - $this->getIO(), + $io, $config, $input->getArgument('package'), $input->getArgument('directory'), @@ -290,15 +291,15 @@ EOT // handler Ctrl+C for unix-like systems if (function_exists('pcntl_signal')) { - declare(ticks = 100); - pcntl_signal(SIGINT, function() use ($directory) { + declare (ticks = 100); + pcntl_signal(SIGINT, function () use ($directory) { $fs = new Filesystem(); $fs->removeDirectory($directory); exit(130); }); } - $io->writeError('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')'); + $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); if ($disablePlugins) { $io->writeError('Plugins have been disabled.'); @@ -346,8 +347,8 @@ EOT * Updated preferSource or preferDist based on the preferredInstall config option * @param Config $config * @param InputInterface $input - * @param boolean $preferSource - * @param boolean $preferDist + * @param bool $preferSource + * @param bool $preferDist */ protected function updatePreferredOptions(Config $config, InputInterface $input, &$preferSource, &$preferDist, $keepVcsRequiresPreferSource = false) { diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index 97b9d0f39..b4017c2fe 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -81,6 +81,7 @@ EOT $messages = array(); $outputPackages = array(); + $io = $this->getIO(); foreach ($repo->getPackages() as $package) { foreach ($types as $type) { foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) { @@ -96,9 +97,9 @@ EOT if ($messages) { sort($messages); - $this->getIO()->write($messages); + $io->write($messages); } else { - $this->getIO()->writeError('There is no installed package depending on "'.$needle.'".'); + $io->writeError('There is no installed package depending on "'.$needle.'".'); } } } diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index c279d182c..afca401b6 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -57,12 +57,13 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->getComposer(false); + $io = $this->getIO(); if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); - $this->getIO()->write('Checking composer.json: ', false); + $io->write('Checking composer.json: ', false); $this->outputResult($this->checkComposerSchema()); } @@ -72,44 +73,44 @@ EOT $config = Factory::createConfig(); } - $this->rfs = new RemoteFilesystem($this->getIO(), $config); - $this->process = new ProcessExecutor($this->getIO()); + $this->rfs = new RemoteFilesystem($io, $config); + $this->process = new ProcessExecutor($io); - $this->getIO()->write('Checking platform settings: ', false); + $io->write('Checking platform settings: ', false); $this->outputResult($this->checkPlatform()); - $this->getIO()->write('Checking git settings: ', false); + $io->write('Checking git settings: ', false); $this->outputResult($this->checkGit()); - $this->getIO()->write('Checking http connectivity to packagist: ', false); + $io->write('Checking http connectivity to packagist: ', false); $this->outputResult($this->checkHttp('http')); - $this->getIO()->write('Checking https connectivity to packagist: ', false); + $io->write('Checking https connectivity to packagist: ', false); $this->outputResult($this->checkHttp('https')); $opts = stream_context_get_options(StreamContextFactory::getContext('http://example.org')); if (!empty($opts['http']['proxy'])) { - $this->getIO()->write('Checking HTTP proxy: ', false); + $io->write('Checking HTTP proxy: ', false); $this->outputResult($this->checkHttpProxy()); - $this->getIO()->write('Checking HTTP proxy support for request_fulluri: ', false); + $io->write('Checking HTTP proxy support for request_fulluri: ', false); $this->outputResult($this->checkHttpProxyFullUriRequestParam()); - $this->getIO()->write('Checking HTTPS proxy support for request_fulluri: ', false); + $io->write('Checking HTTPS proxy support for request_fulluri: ', false); $this->outputResult($this->checkHttpsProxyFullUriRequestParam()); } if ($oauth = $config->get('github-oauth')) { foreach ($oauth as $domain => $token) { - $this->getIO()->write('Checking '.$domain.' oauth access: ', false); + $io->write('Checking '.$domain.' oauth access: ', false); $this->outputResult($this->checkGithubOauth($domain, $token)); } } else { - $this->getIO()->write('Checking github.com rate limit: ', false); + $io->write('Checking github.com rate limit: ', false); try { $rate = $this->getGithubRateLimit('github.com'); $this->outputResult(true); if (10 > $rate['remaining']) { - $this->getIO()->write('WARNING'); - $this->getIO()->write(sprintf( + $io->write('WARNING'); + $io->write(sprintf( 'Github has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL @@ -128,10 +129,10 @@ EOT } } - $this->getIO()->write('Checking disk free space: ', false); + $io->write('Checking disk free space: ', false); $this->outputResult($this->checkDiskSpace($config)); - $this->getIO()->write('Checking composer version: ', false); + $io->write('Checking composer version: ', false); $this->outputResult($this->checkVersion()); return $this->failures; @@ -263,7 +264,7 @@ EOT $url = $domain === 'github.com' ? 'https://api.'.$domain.'/user/repos' : 'https://'.$domain.'/api/v3/user/repos'; return $this->rfs->getContents($domain, $url, false, array( - 'retry-auth-failure' => false + 'retry-auth-failure' => false, )) ? true : 'Unexpected error'; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { @@ -275,10 +276,10 @@ EOT } /** - * @param string $domain - * @param string $token - * @return array + * @param string $domain + * @param string $token * @throws TransportException + * @return array */ private function getGithubRateLimit($domain, $token = null) { @@ -295,7 +296,7 @@ EOT private function checkDiskSpace($config) { - $minSpaceFree = 1024*1024; + $minSpaceFree = 1024 * 1024; if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) ) { @@ -322,15 +323,16 @@ EOT */ private function outputResult($result) { + $io = $this->getIO(); if (true === $result) { - $this->getIO()->write('OK'); + $io->write('OK'); } else { $this->failures++; - $this->getIO()->write('FAIL'); + $io->write('FAIL'); if ($result instanceof \Exception) { - $this->getIO()->write('['.get_class($result).'] '.$result->getMessage()); + $io->write('['.get_class($result).'] '.$result->getMessage()); } elseif ($result) { - $this->getIO()->write(trim($result)); + $io->write(trim($result)); } } } diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index f560953b8..c452d1fb4 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -31,6 +31,7 @@ class DumpAutoloadCommand extends Command ->setDescription('Dumps the autoloader') ->setDefinition(array( new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'), )) ->setHelp(<<getPackage(); $config = $composer->getConfig(); - $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); + $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); - if ($optimize) { + if ($optimize || $authoritative) { $this->getIO()->writeError('Generating optimized autoload files'); } else { $this->getIO()->writeError('Generating autoload files'); @@ -62,6 +64,7 @@ EOT $generator = $composer->getAutoloadGenerator(); $generator->setDevMode(!$input->getOption('no-dev')); + $generator->setClassMapAuthoritative($authoritative); $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); } } diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index b20e661f9..fff1f86eb 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -14,7 +14,6 @@ namespace Composer\Command; use Composer\Factory; use Composer\Package\CompletePackageInterface; -use Composer\Repository\CompositeRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\ArrayRepository; use Composer\Util\ProcessExecutor; @@ -58,6 +57,7 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $repos = $this->initializeRepos(); + $io = $this->getIO(); $return = 0; foreach ($input->getArgument('packages') as $packageName) { @@ -75,12 +75,12 @@ EOT if (!$packageExists) { $return = 1; - $this->getIO()->writeError('Package '.$packageName.' not found'); + $io->writeError('Package '.$packageName.' not found'); } if (!$handled) { $return = 1; - $this->getIO()->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); + $io->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); } } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 971477306..795d72958 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -16,10 +16,10 @@ use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Factory; use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionSelector; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; -use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -106,11 +106,12 @@ EOT $file = new JsonFile('composer.json'); $json = $file->encode($options); + $io = $this->getIO(); if ($input->isInteractive()) { - $this->getIO()->writeError(array('', $json, '')); - if (!$this->getIO()->askConfirmation('Do you confirm generation [yes]? ', true)) { - $this->getIO()->writeError('Command aborted'); + $io->writeError(array('', $json, '')); + if (!$io->askConfirmation('Do you confirm generation [yes]? ', true)) { + $io->writeError('Command aborted'); return 1; } @@ -128,7 +129,7 @@ EOT if (!$this->hasVendorIgnore($ignoreFile)) { $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; - if ($this->getIO()->askConfirmation($question, true)) { + if ($io->askConfirmation($question, true)) { $this->addVendorIgnore($ignoreFile); } } @@ -141,17 +142,17 @@ EOT protected function interact(InputInterface $input, OutputInterface $output) { $git = $this->getGitConfig(); - + $io = $this->getIO(); $formatter = $this->getHelperSet()->get('formatter'); - $this->getIO()->writeError(array( + $io->writeError(array( '', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), - '' + '', )); // namespace - $this->getIO()->writeError(array( + $io->writeError(array( '', 'This command will guide you through creating your composer.json config.', '', @@ -181,7 +182,7 @@ EOT } } - $name = $this->getIO()->askAndValidate( + $name = $io->askAndValidate( 'Package name (/) ['.$name.']: ', function ($value) use ($name) { if (null === $value) { @@ -202,7 +203,7 @@ EOT $input->setOption('name', $name); $description = $input->getOption('description') ?: false; - $description = $this->getIO()->ask( + $description = $io->ask( 'Description ['.$description.']: ', $description ); @@ -215,7 +216,7 @@ EOT } $self = $this; - $author = $this->getIO()->askAndValidate( + $author = $io->askAndValidate( 'Author ['.$author.']: ', function ($value) use ($self, $author) { $value = $value ?: $author; @@ -229,7 +230,7 @@ EOT $input->setOption('author', $author); $minimumStability = $input->getOption('stability') ?: null; - $minimumStability = $this->getIO()->askAndValidate( + $minimumStability = $io->askAndValidate( 'Minimum Stability ['.$minimumStability.']: ', function ($value) use ($self, $minimumStability) { if (null === $value) { @@ -251,31 +252,31 @@ EOT $input->setOption('stability', $minimumStability); $type = $input->getOption('type') ?: false; - $type = $this->getIO()->ask( + $type = $io->ask( 'Package Type ['.$type.']: ', $type ); $input->setOption('type', $type); $license = $input->getOption('license') ?: false; - $license = $this->getIO()->ask( + $license = $io->ask( 'License ['.$license.']: ', $license ); $input->setOption('license', $license); - $this->getIO()->writeError(array('', 'Define your dependencies.', '')); + $io->writeError(array('', 'Define your dependencies.', '')); $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $requirements = array(); - if ($this->getIO()->askConfirmation($question, true)) { + if ($io->askConfirmation($question, true)) { $requirements = $this->determineRequirements($input, $output, $input->getOption('require')); } $input->setOption('require', $requirements); $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; $devRequirements = array(); - if ($this->getIO()->askConfirmation($question, true)) { + if ($io->askConfirmation($question, true)) { $devRequirements = $this->determineRequirements($input, $output, $input->getOption('require-dev')); } $input->setOption('require-dev', $devRequirements); @@ -283,7 +284,7 @@ EOT /** * @private - * @param string $author + * @param string $author * @return array */ public function parseAuthorString($author) @@ -292,7 +293,7 @@ EOT if ($this->isValidEmail($match['email'])) { return array( 'name' => trim($match['name']), - 'email' => $match['email'] + 'email' => $match['email'], ); } } @@ -325,6 +326,7 @@ EOT if ($requires) { $requires = $this->normalizeRequirements($requires); $result = array(); + $io = $this->getIO(); foreach ($requires as $requirement) { if (!isset($requirement['version'])) { @@ -332,7 +334,7 @@ EOT $version = $this->findBestVersionForPackage($input, $requirement['name']); $requirement['version'] = $version; - $this->getIO()->writeError(sprintf( + $io->writeError(sprintf( 'Using version %s for %s', $requirement['version'], $requirement['name'] @@ -346,7 +348,8 @@ EOT } $versionParser = new VersionParser(); - while (null !== $package = $this->getIO()->ask('Search for a package: ')) { + $io = $this->getIO(); + while (null !== $package = $io->ask('Search for a package: ')) { $matches = $this->findPackages($package); if (count($matches)) { @@ -362,14 +365,14 @@ EOT // no match, prompt which to pick if (!$exactMatch) { - $this->getIO()->writeError(array( + $io->writeError(array( '', sprintf('Found %s packages matching %s', count($matches), $package), - '' + '', )); - $this->getIO()->writeError($choices); - $this->getIO()->writeError(''); + $io->writeError($choices); + $io->writeError(''); $validator = function ($selection) use ($matches, $versionParser) { if ('' === $selection) { @@ -399,7 +402,7 @@ EOT throw new \Exception('Not a valid selection'); }; - $package = $this->getIO()->askAndValidate( + $package = $io->askAndValidate( 'Enter package # to add, or the complete package name if it is not listed: ', $validator, 3, @@ -415,7 +418,7 @@ EOT return $input ?: false; }; - $constraint = $this->getIO()->askAndValidate( + $constraint = $io->askAndValidate( 'Enter the version constraint to require (or leave blank to use the latest version): ', $validator, 3, @@ -425,7 +428,7 @@ EOT if (false === $constraint) { $constraint = $this->findBestVersionForPackage($input, $package); - $this->getIO()->writeError(sprintf( + $io->writeError(sprintf( 'Using version %s for %s', $constraint, $package @@ -588,8 +591,8 @@ EOT * * @param InputInterface $input * @param string $name - * @return string * @throws \InvalidArgumentException + * @return string */ private function findBestVersionForPackage(InputInterface $input, $name) { diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index e548b8d69..3ae00c228 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -46,6 +46,7 @@ class InstallCommand extends Command new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), )) @@ -64,24 +65,24 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { + $io = $this->getIO(); if ($args = $input->getArgument('packages')) { - $this->getIO()->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); + $io->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')) { - $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { - $this->getIO()->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); + $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); - $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -110,7 +111,8 @@ EOT $preferDist = $input->getOption('prefer-dist'); } - $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $install ->setDryRun($input->getOption('dry-run')) @@ -121,6 +123,7 @@ EOT ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ; diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index ddeabf00f..b542c3fe6 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -13,7 +13,6 @@ namespace Composer\Command; use Composer\Json\JsonFile; -use Composer\Package\Version\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Package\PackageInterface; @@ -56,8 +55,6 @@ EOT $root = $composer->getPackage(); $repo = $composer->getRepositoryManager()->getLocalRepository(); - $versionParser = new VersionParser; - if ($input->getOption('no-dev')) { $packages = $this->filterRequiredPackages($repo, $root); } else { @@ -65,14 +62,15 @@ EOT } ksort($packages); + $io = $this->getIO(); switch ($format = $input->getOption('format')) { case 'text': - $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(''); + $io->write('Name: '.$root->getPrettyName().''); + $io->write('Version: '.$root->getFullPrettyVersion().''); + $io->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); + $io->write('Dependencies:'); + $io->write(''); $table = new Table($output); $table->setStyle('compact'); @@ -82,7 +80,7 @@ EOT foreach ($packages as $package) { $table->addRow(array( $package->getPrettyName(), - $versionParser->formatVersion($package), + $package->getFullPrettyVersion(), implode(', ', $package->getLicense()) ?: 'none', )); } @@ -92,14 +90,14 @@ EOT case 'json': foreach ($packages as $package) { $dependencies[$package->getPrettyName()] = array( - 'version' => $versionParser->formatVersion($package), + 'version' => $package->getFullPrettyVersion(), 'license' => $package->getLicense(), ); } - $this->getIO()->write(JsonFile::encode(array( + $io->write(JsonFile::encode(array( 'name' => $root->getPrettyName(), - 'version' => $versionParser->formatVersion($root), + 'version' => $root->getFullPrettyVersion(), 'license' => $root->getLicense(), 'dependencies' => $dependencies, ))); diff --git a/src/Composer/Command/RemoveCommand.php b/src/Composer/Command/RemoveCommand.php index 3779b09e9..869720718 100644 --- a/src/Composer/Command/RemoveCommand.php +++ b/src/Composer/Command/RemoveCommand.php @@ -42,6 +42,8 @@ class RemoveCommand extends Command new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), )) ->setHelp(<<remove command removes a package from the current @@ -68,19 +70,20 @@ EOT $type = $input->getOption('dev') ? 'require-dev' : 'require'; $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; + $io = $this->getIO(); foreach ($packages as $package) { if (isset($composer[$type][$package])) { $json->removeLink($type, $package); } elseif (isset($composer[$altType][$package])) { - $this->getIO()->writeError(''.$package.' could not be found in '.$type.' but it is present in '.$altType.''); - if ($this->getIO()->isInteractive()) { - if ($this->getIO()->askConfirmation('Do you want to remove it from '.$altType.' [yes]? ', true)) { + $io->writeError(''.$package.' could not be found in '.$type.' but it is present in '.$altType.''); + if ($io->isInteractive()) { + if ($io->askConfirmation('Do you want to remove it from '.$altType.' [yes]? ', true)) { $json->removeLink($altType, $package); } } } else { - $this->getIO()->writeError(''.$package.' is not required in your composer.json and has not been removed'); + $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); } } @@ -91,7 +94,6 @@ EOT // Update packages $composer = $this->getComposer(); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); - $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -99,9 +101,14 @@ EOT $install = Installer::create($io, $composer); $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); + $install ->setVerbose($input->getOption('verbose')) ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setUpdate(true) ->setUpdateWhitelist($packages) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) @@ -110,7 +117,7 @@ EOT $status = $install->run(); if ($status !== 0) { - $this->getIO()->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); + $io->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 82401ffb2..c7bcc8735 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -20,7 +20,7 @@ use Composer\Factory; use Composer\Installer; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; @@ -48,6 +48,8 @@ class RequireCommand extends InitCommand new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), + new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), )) ->setHelp(<<getIO(); $newlyCreated = !file_exists($file); if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) { - $this->getIO()->writeError(''.$file.' could not be created.'); + $io->writeError(''.$file.' could not be created.'); return 1; } if (!is_readable($file)) { - $this->getIO()->writeError(''.$file.' is not readable.'); + $io->writeError(''.$file.' is not readable.'); return 1; } if (!is_writable($file)) { - $this->getIO()->writeError(''.$file.' is not writable.'); + $io->writeError(''.$file.' is not writable.'); return 1; } @@ -126,18 +129,19 @@ EOT $json->write($composerDefinition); } - $this->getIO()->writeError(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); + $io->writeError(''.$file.' has been '.($newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; } $updateDevMode = !$input->getOption('update-no-dev'); + $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); // Update packages $this->resetComposer(); $composer = $this->getComposer(); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); - $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -149,6 +153,8 @@ EOT ->setPreferSource($input->getOption('prefer-source')) ->setPreferDist($input->getOption('prefer-dist')) ->setDevMode($updateDevMode) + ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setUpdate(true) ->setUpdateWhitelist(array_keys($requirements)) ->setWhitelistDependencies($input->getOption('update-with-dependencies')) @@ -158,10 +164,10 @@ EOT $status = $install->run(); if ($status !== 0) { if ($newlyCreated) { - $this->getIO()->writeError("\n".'Installation failed, deleting '.$file.'.'); + $io->writeError("\n".'Installation failed, deleting '.$file.'.'); unlink($json->getPath()); } else { - $this->getIO()->writeError("\n".'Installation failed, reverting '.$file.' to its original content.'); + $io->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 8b5b13d2a..5ad87bf40 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -104,9 +104,10 @@ EOT return 0; } - $this->getIO()->writeError('scripts:'); + $io = $this->getIO(); + $io->writeError('scripts:'); foreach ($scripts as $name => $script) { - $this->getIO()->write(' ' . $name); + $io->write(' ' . $name); } return 0; diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 54990f30d..e636fc4d0 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -56,13 +56,14 @@ EOT { // init repos $platformRepo = new PlatformRepository; + $io = $this->getIO(); if ($composer = $this->getComposer(false)) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } else { - $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $this->getIO()->writeError('No composer.json found in the current directory, showing packages from ' . implode(', ', array_keys($defaultRepos))); + $defaultRepos = Factory::createDefaultRepositories($io); + $io->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 +79,7 @@ EOT $results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags); foreach ($results as $result) { - $this->getIO()->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); + $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); } } } diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index eed811c13..6b85e000d 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -60,7 +60,8 @@ EOT { $baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE; $config = Factory::createConfig(); - $remoteFilesystem = new RemoteFilesystem($this->getIO(), $config); + $io = $this->getIO(); + $remoteFilesystem = new RemoteFilesystem($io, $config); $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; @@ -84,13 +85,13 @@ EOT $updateVersion = $input->getArgument('version') ?: $latestVersion; if (preg_match('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { - $this->getIO()->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); + $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); return 1; } if (Composer::VERSION === $updateVersion) { - $this->getIO()->writeError('You are already using composer version '.$updateVersion.'.'); + $io->writeError('You are already using composer version '.$updateVersion.'.'); return 0; } @@ -104,11 +105,11 @@ EOT self::OLD_INSTALL_EXT ); - $this->getIO()->writeError(sprintf("Updating to version %s.", $updateVersion)); + $io->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)) { - $this->getIO()->writeError('The download of the new composer version failed for an unexpected reason'); + $io->writeError('The download of the new composer version failed for an unexpected reason'); return 1; } @@ -120,22 +121,22 @@ EOT $fs = new Filesystem; foreach ($finder as $file) { $file = (string) $file; - $this->getIO()->writeError('Removing: '.$file.''); + $io->writeError('Removing: '.$file.''); $fs->remove($file); } } if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { - $this->getIO()->writeError('The file is corrupted ('.$err->getMessage().').'); - $this->getIO()->writeError('Please re-run the self-update command to try again.'); + $io->writeError('The file is corrupted ('.$err->getMessage().').'); + $io->writeError('Please re-run the self-update command to try again.'); return 1; } if (file_exists($backupFile)) { - $this->getIO()->writeError('Use composer self-update --rollback to return to version '.Composer::VERSION); + $io->writeError('Use composer self-update --rollback to return to version '.Composer::VERSION); } else { - $this->getIO()->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); + $io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); } } @@ -160,9 +161,10 @@ EOT } $oldFile = $rollbackDir . "/{$rollbackVersion}" . self::OLD_INSTALL_EXT; - $this->getIO()->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); + $io = $this->getIO(); + $io->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); if ($err = $this->setLocalPhar($localFilename, $oldFile)) { - $this->getIO()->writeError('The backup file was corrupted ('.$err->getMessage().') and has been removed.'); + $io->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 b4f5de49b..8aa6de5ca 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -16,7 +16,7 @@ use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\DefaultPolicy; use Composer\Factory; use Composer\Package\CompletePackageInterface; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Symfony\Component\Console\Input\InputInterface; @@ -28,7 +28,7 @@ use Composer\Repository\CompositeRepository; use Composer\Repository\ComposerRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; -use Composer\Util\SpdxLicense; +use Composer\Spdx\SpdxLicenses; /** * @author Robert Schönthal @@ -71,6 +71,7 @@ EOT $platformRepo = new PlatformRepository; $composer = $this->getComposer(false); + $io = $this->getIO(); if ($input->getOption('self')) { $package = $this->getComposer()->getPackage(); $repos = $installedRepo = new ArrayRepository(array($package)); @@ -83,17 +84,17 @@ EOT if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); } else { - $defaultRepos = Factory::createDefaultRepositories($this->getIO()); + $defaultRepos = Factory::createDefaultRepositories($io); $repos = new CompositeRepository($defaultRepos); - $this->getIO()->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } else { - $defaultRepos = Factory::createDefaultRepositories($this->getIO()); - $this->getIO()->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); + $defaultRepos = Factory::createDefaultRepositories($io); + $io->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)); } @@ -120,9 +121,9 @@ EOT $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); if ($package->getSuggests()) { - $this->getIO()->write("\nsuggests"); + $io->write("\nsuggests"); foreach ($package->getSuggests() as $suggested => $reason) { - $this->getIO()->write($suggested . ' ' . $reason . ''); + $io->write($suggested . ' ' . $reason . ''); } } $this->printLinks($package, 'provides'); @@ -173,7 +174,7 @@ EOT foreach (array('platform:' => true, 'available:' => false, 'installed:' => true) as $type => $showVersion) { if (isset($packages[$type])) { if ($tree) { - $this->getIO()->write($type); + $io->write($type); } ksort($packages[$type]); @@ -181,7 +182,7 @@ EOT foreach ($packages[$type] as $package) { if (is_object($package)) { $nameLength = max($nameLength, strlen($package->getPrettyName())); - $versionLength = max($versionLength, strlen($this->versionParser->formatVersion($package))); + $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); } else { $nameLength = max($nameLength, $package); } @@ -197,7 +198,7 @@ EOT } if ($input->getOption('path') && null === $composer) { - $this->getIO()->writeError('No composer.json found in the current directory, disabling "path" option'); + $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', false); } @@ -209,7 +210,7 @@ EOT $output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); if ($writeVersion) { - $output->write(' ' . str_pad($this->versionParser->formatVersion($package), $versionLength, ' '), false); + $output->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false); } if ($writeDescription) { @@ -228,10 +229,10 @@ EOT } else { $output->write($indent . $package); } - $this->getIO()->write(''); + $io->write(''); } if ($tree) { - $this->getIO()->write(''); + $io->write(''); } } } @@ -244,8 +245,8 @@ EOT * @param RepositoryInterface $repos * @param string $name * @param string $version - * @return array array(CompletePackageInterface, array of versions) * @throws \InvalidArgumentException + * @return array array(CompletePackageInterface, array of versions) */ protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null) { @@ -291,53 +292,54 @@ EOT */ protected function printMeta(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo) { - $this->getIO()->write('name : ' . $package->getPrettyName()); - $this->getIO()->write('descrip. : ' . $package->getDescription()); - $this->getIO()->write('keywords : ' . join(', ', $package->getKeywords() ?: array())); + $io = $this->getIO(); + $io->write('name : ' . $package->getPrettyName()); + $io->write('descrip. : ' . $package->getDescription()); + $io->write('keywords : ' . join(', ', $package->getKeywords() ?: array())); $this->printVersions($package, $versions, $installedRepo); - $this->getIO()->write('type : ' . $package->getType()); + $io->write('type : ' . $package->getType()); $this->printLicenses($package); - $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())); + $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); + $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); + $io->write('names : ' . implode(', ', $package->getNames())); if ($package->isAbandoned()) { $replacement = ($package->getReplacementPackage() !== null) ? ' The author suggests using the ' . $package->getReplacementPackage(). ' package instead.' : null; - $this->getIO()->writeError( + $io->writeError( sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) ); } if ($package->getSupport()) { - $this->getIO()->write("\nsupport"); + $io->write("\nsupport"); foreach ($package->getSupport() as $type => $value) { - $this->getIO()->write('' . $type . ' : '.$value); + $io->write('' . $type . ' : '.$value); } } if ($package->getAutoload()) { - $this->getIO()->write("\nautoload"); + $io->write("\nautoload"); foreach ($package->getAutoload() as $type => $autoloads) { - $this->getIO()->write('' . $type . ''); + $io->write('' . $type . ''); if ($type === 'psr-0') { foreach ($autoloads as $name => $path) { - $this->getIO()->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'psr-4') { foreach ($autoloads as $name => $path) { - $this->getIO()->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); + $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'classmap') { - $this->getIO()->write(implode(', ', $autoloads)); + $io->write(implode(', ', $autoloads)); } } if ($package->getIncludePaths()) { - $this->getIO()->write('include-path'); - $this->getIO()->write(implode(', ', $package->getIncludePaths())); + $io->write('include-path'); + $io->write(implode(', ', $package->getIncludePaths())); } } } @@ -374,11 +376,12 @@ EOT protected function printLinks(CompletePackageInterface $package, $linkType, $title = null) { $title = $title ?: $linkType; + $io = $this->getIO(); if ($links = $package->{'get'.ucfirst($linkType)}()) { - $this->getIO()->write("\n" . $title . ""); + $io->write("\n" . $title . ""); foreach ($links as $link) { - $this->getIO()->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); + $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); } } } @@ -390,12 +393,13 @@ EOT */ protected function printLicenses(CompletePackageInterface $package) { - $spdxLicense = new SpdxLicense; + $spdxLicenses = new SpdxLicenses(); $licenses = $package->getLicense(); + $io = $this->getIO(); foreach ($licenses as $licenseId) { - $license = $spdxLicense->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url + $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url if (!$license) { $out = $licenseId; @@ -408,7 +412,7 @@ EOT } } - $this->getIO()->write('license : ' . $out); + $io->write('license : ' . $out); } } } diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 220327cb6..7455e84b8 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -60,6 +60,7 @@ EOT $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); $errors = array(); + $io = $this->getIO(); // list packages foreach ($installedRepo->getPackages() as $package) { @@ -68,6 +69,10 @@ EOT if ($downloader instanceof ChangeReportInterface) { $targetDir = $im->getInstallPath($package); + if (is_link($targetDir)) { + $errors[$targetDir] = $targetDir . ' is a symbolic link.'; + } + if ($changes = $downloader->getLocalChanges($package, $targetDir)) { $errors[$targetDir] = $changes; } @@ -76,9 +81,9 @@ EOT // output errors/warnings if (!$errors) { - $this->getIO()->writeError('No local changes'); + $io->writeError('No local changes'); } else { - $this->getIO()->writeError('You have changes in the following dependencies:'); + $io->writeError('You have changes in the following dependencies:'); } foreach ($errors as $path => $changes) { @@ -86,15 +91,15 @@ EOT $indentedChanges = implode("\n", array_map(function ($line) { return ' ' . ltrim($line); }, explode("\n", $changes))); - $this->getIO()->write(''.$path.':'); - $this->getIO()->write($indentedChanges); + $io->write(''.$path.':'); + $io->write($indentedChanges); } else { - $this->getIO()->write($path); + $io->write($path); } } if ($errors && !$input->getOption('verbose')) { - $this->getIO()->writeError('Use --verbose (-v) to see modified files'); + $io->writeError('Use --verbose (-v) to see modified files'); } // Dispatch post-status-command diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php new file mode 100644 index 000000000..d59ef1dc6 --- /dev/null +++ b/src/Composer/Command/SuggestsCommand.php @@ -0,0 +1,98 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class SuggestsCommand extends Command +{ + protected function configure() + { + $this + ->setName('suggests') + ->setDescription('Show package suggestions') + ->setDefinition(array( + new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'), + )) + ->setHelp(<<%command.name% command shows suggested packages. + +With -v you also see which package suggested it and why. + +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $lock = $this->getComposer()->getLocker()->getLockData(); + + if (empty($lock)) { + throw new \RuntimeException('Lockfile seems to be empty?'); + } + + $packages = $lock['packages']; + + if (!$input->getOption('no-dev')) { + $packages += $lock['packages-dev']; + } + + $filter = $input->getArgument('packages'); + + foreach ($packages as $package) { + if (empty($package['suggest'])) { + continue; + } + + if (!empty($filter) && !in_array($package['name'], $filter)) { + continue; + } + + $this->printSuggestions($packages, $package['name'], $package['suggest']); + } + } + + protected function printSuggestions($installed, $source, $suggestions) + { + foreach ($suggestions as $suggestion => $reason) { + foreach ($installed as $package) { + if ($package['name'] === $suggestion) { + continue 2; + } + } + + if (empty($reason)) { + $reason = '*'; + } + + $this->printSuggestion($source, $suggestion, $reason); + } + } + + protected function printSuggestion($package, $suggestion, $reason) + { + $io = $this->getIO(); + + if ($io->isVerbose()) { + $io->write(sprintf('%s suggests %s: %s', $package, $suggestion, $reason)); + } else { + $io->write(sprintf('%s', $suggestion)); + } + } +} diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 579236143..b67ff16cf 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -47,6 +47,7 @@ class UpdateCommand extends Command new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), + new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'), @@ -74,18 +75,18 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { + $io = $this->getIO(); if ($input->getOption('no-custom-installers')) { - $this->getIO()->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); + $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->getOption('dev')) { - $this->getIO()->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); + $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); - $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -114,7 +115,8 @@ EOT $preferDist = $input->getOption('prefer-dist'); } - $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $install ->setDryRun($input->getOption('dry-run')) @@ -125,6 +127,7 @@ EOT ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setOptimizeAutoloader($optimize) + ->setClassMapAuthoritative($authoritative) ->setUpdate(true) ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ->setWhitelistDependencies($input->getOption('with-dependencies')) diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 320032984..66902cbb2 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\Factory; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Util\ConfigValidator; use Symfony\Component\Console\Input\InputArgument; @@ -34,14 +35,22 @@ class ValidateCommand extends Command { $this ->setName('validate') - ->setDescription('Validates a composer.json') + ->setDescription('Validates a composer.json and composer.lock') ->setDefinition(array( new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), + new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), - new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json') + new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), + new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), + new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json'), )) ->setHelp(<<getArgument('file'); + $io = $this->getIO(); if (!file_exists($file)) { - $this->getIO()->writeError('' . $file . ' not found.'); + $io->writeError('' . $file . ' not found.'); - return 1; + return 3; } if (!is_readable($file)) { - $this->getIO()->writeError('' . $file . ' is not readable.'); + $io->writeError('' . $file . ' is not readable.'); - return 1; + return 3; } - $validator = new ConfigValidator($this->getIO()); + $validator = new ConfigValidator($io); $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; $checkPublish = !$input->getOption('no-check-publish'); + $checkLock = !$input->getOption('no-check-lock'); + $isStrict = $input->getOption('strict'); list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); - // output errors/warnings + $lockErrors = array(); + $composer = Factory::create($io, $file); + $locker = $composer->getLocker(); + if ($locker->isLocked() && !$locker->isFresh()) { + $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json.'; + } + + $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); + + $exitCode = $errors || ($publishErrors && $checkPublish) || ($lockErrors && $checkLock) ? 2 : ($isStrict && $warnings ? 1 : 0); + + if ($input->getOption('with-dependencies')) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + foreach ($localRepo->getPackages() as $package) { + $path = $composer->getInstallationManager()->getInstallPath($package); + $file = $path . '/composer.json'; + if (is_dir($path) && file_exists($file)) { + list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); + $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); + + $depCode = $errors || ($publishErrors && $checkPublish) ? 2 : ($isStrict && $warnings ? 1 : 0); + $exitCode = max($depCode, $exitCode); + } + } + } + + return $exitCode; + } + + private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false) + { if (!$errors && !$publishErrors && !$warnings) { - $this->getIO()->write('' . $file . ' is valid'); + $io->write('' . $name . ' is valid'); } elseif (!$errors && !$publishErrors) { - $this->getIO()->writeError('' . $file . ' is valid, but with a few warnings'); - $this->getIO()->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); + $io->writeError('' . $name . ' is valid, but with a few warnings'); + if ($printSchemaUrl) { + $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); + } } elseif (!$errors) { - $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 https://getcomposer.org/doc/04-schema.md for details on the schema'); + $io->writeError('' . $name . ' is valid for simple usage with composer but has'); + $io->writeError('strict errors that make it unable to be published as a package:'); + if ($printSchemaUrl) { + $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); + } } else { - $this->getIO()->writeError('' . $file . ' is invalid, the following errors/warnings were found:'); + $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); + } + + // If checking publish errors, display them as errors, otherwise just show them as warnings + if ($checkPublish) { + $errors = array_merge($errors, $publishErrors); + } else { + $warnings = array_merge($warnings, $publishErrors); + } + + // If checking lock errors, display them as errors, otherwise just show them as warnings + if ($checkLock) { + $errors = array_merge($errors, $lockErrors); + } else { + $warnings = array_merge($warnings, $lockErrors); } $messages = array( @@ -92,19 +152,10 @@ EOT '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) { - $this->getIO()->writeError('<' . $style . '>' . $msg . ''); + $io->writeError('<' . $style . '>' . $msg . ''); } } - - return $errors || ($publishErrors && $checkPublish) ? 1 : 0; } } diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 749adf659..de977e8e2 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -13,6 +13,7 @@ namespace Composer; use Composer\Json\JsonFile; +use Composer\Spdx\SpdxLicenses; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\Process; use Seld\PharUtils\Timestamps; @@ -32,8 +33,8 @@ class Compiler /** * Compiles composer into a single phar file * - * @throws \RuntimeException * @param string $pharFile The full path to the file to create + * @throws \RuntimeException */ public function compile($pharFile = 'composer.phar') { @@ -95,7 +96,8 @@ class Compiler $finder = new Finder(); $finder->files() ->name('*.json') - ->in(__DIR__ . '/../../res') + ->in(__DIR__.'/../../res') + ->in(SpdxLicenses::getResourcesDir()) ->sort($finderSort) ; @@ -116,6 +118,8 @@ class Compiler ->in(__DIR__.'/../../vendor/seld/jsonlint/') ->in(__DIR__.'/../../vendor/seld/cli-prompt/') ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/') + ->in(__DIR__.'/../../vendor/composer/spdx-licenses/') + ->in(__DIR__.'/../../vendor/composer/semver/') ->sort($finderSort) ; diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 87f8f03ff..4c3669db9 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -13,7 +13,6 @@ namespace Composer; use Composer\Config\ConfigSourceInterface; -use Composer\Plugin\PluginInterface; /** * @author Jordi Boggiano @@ -59,7 +58,7 @@ class Config 'type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true, - ) + ), ); private $config; @@ -70,7 +69,7 @@ class Config private $useEnvironment; /** - * @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings + * @param bool $useEnvironment Use COMPOSER_ environment variables to replace config settings */ public function __construct($useEnvironment = true, $baseDir = null) { @@ -183,7 +182,7 @@ class Config return $val; } - return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val); + return ($flags & self::RELATIVE_PATHS == self::RELATIVE_PATHS) ? $val : $this->realpath($val); case 'cache-ttl': return (int) $this->config[$key]; @@ -334,8 +333,8 @@ class Config * This should be used to read COMPOSER_ environment variables * that overload config values. * - * @param string $var - * @return string|boolean + * @param string $var + * @return string|bool */ private function getComposerEnv($var) { diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index ee4f4e9c0..21bc22937 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -89,9 +89,10 @@ class Application extends BaseApplication { $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); ErrorHandler::register($this->io); + $io = $this->getIO(); if (PHP_VERSION_ID < 50302) { - $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.'); + $io->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) { - $this->getIO()->writeError(sprintf('Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); + $io->writeError(sprintf('Warning: This development build of composer is over 60 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 ($this->getIO()->isDebug() >= 4) { - $this->getIO()->writeError('Changed CWD to ' . getcwd()); + if ($io->isDebug() >= 4) { + $io->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)) { - $this->getIO()->writeError('A script named '.$script.' would override a native Composer function and has been skipped'); + $io->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)) { - $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'); + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MB), time: '.round(microtime(true) - $startTime, 2).'s'); } return $result; @@ -158,8 +159,8 @@ class Application extends BaseApplication /** * @param InputInterface $input - * @return string * @throws \RuntimeException + * @return string */ private function getNewWorkingDir(InputInterface $input) { @@ -176,30 +177,32 @@ class Application extends BaseApplication */ public function renderException($exception, $output) { + $io = $this->getIO(); + try { $composer = $this->getComposer(false, true); if ($composer) { $config = $composer->getConfig(); - $minSpaceFree = 1024*1024; + $minSpaceFree = 1024 * 1024; if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { - $this->getIO()->writeError('The disk hosting '.$dir.' is full, this may be the cause of the following exception'); + $io->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')) { - $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'); + $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun'); + $io->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')) { - $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'); + $io->writeError('The following exception is caused by a lack of memory and not having swap configured'); + $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details'); } if ($output instanceof ConsoleOutputInterface) { @@ -272,6 +275,7 @@ class Application extends BaseApplication $commands[] = new Command\SearchCommand(); $commands[] = new Command\ValidateCommand(); $commands[] = new Command\ShowCommand(); + $commands[] = new Command\SuggestsCommand(); $commands[] = new Command\RequireCommand(); $commands[] = new Command\DumpAutoloadCommand(); $commands[] = new Command\StatusCommand(); diff --git a/src/Composer/Console/HtmlOutputFormatter.php b/src/Composer/Console/HtmlOutputFormatter.php index cb42133c3..994fcbcd5 100644 --- a/src/Composer/Console/HtmlOutputFormatter.php +++ b/src/Composer/Console/HtmlOutputFormatter.php @@ -27,7 +27,7 @@ class HtmlOutputFormatter extends OutputFormatter 34 => 'blue', 35 => 'magenta', 36 => 'cyan', - 37 => 'white' + 37 => 'white', ); private static $availableBackgroundColors = array( 40 => 'black', @@ -37,7 +37,7 @@ class HtmlOutputFormatter extends OutputFormatter 44 => 'blue', 45 => 'magenta', 46 => 'cyan', - 47 => 'white' + 47 => 'white', ); private static $availableOptions = array( 1 => 'bold', @@ -60,6 +60,7 @@ class HtmlOutputFormatter extends OutputFormatter $formatted = parent::format($message); $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; + return preg_replace_callback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", array($this, 'formatHtml'), $formatted); } diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 440d6856c..684f11851 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -15,7 +15,7 @@ namespace Composer\DependencyResolver; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; /** * @author Nils Adermann @@ -38,8 +38,8 @@ class DefaultPolicy implements PolicyInterface return BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB]; } - $constraint = new VersionConstraint($operator, $b->getVersion()); - $version = new VersionConstraint('==', $a->getVersion()); + $constraint = new Constraint($operator, $b->getVersion()); + $version = new Constraint('==', $a->getVersion()); return $constraint->matchSpecific($version, true); } @@ -194,7 +194,7 @@ class DefaultPolicy implements PolicyInterface foreach ($source->getReplaces() as $link) { if ($link->getTarget() === $target->getName() // && (null === $link->getConstraint() || -// $link->getConstraint()->matches(new VersionConstraint('==', $target->getVersion())))) { +// $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) { ) { return true; } diff --git a/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php index c901bd190..920e54e67 100644 --- a/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php +++ b/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php @@ -13,6 +13,7 @@ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; /** * Solver install operation. diff --git a/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php b/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php index 56f7ac19b..77f3aef8c 100644 --- a/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php +++ b/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php @@ -13,6 +13,7 @@ namespace Composer\DependencyResolver\Operation; use Composer\Package\AliasPackage; +use Composer\Package\PackageInterface; /** * Solver install operation. diff --git a/src/Composer/DependencyResolver/Operation/SolverOperation.php b/src/Composer/DependencyResolver/Operation/SolverOperation.php index a4e8384d3..e1a68585e 100644 --- a/src/Composer/DependencyResolver/Operation/SolverOperation.php +++ b/src/Composer/DependencyResolver/Operation/SolverOperation.php @@ -12,7 +12,6 @@ namespace Composer\DependencyResolver\Operation; -use Composer\Package\Version\VersionParser; use Composer\Package\PackageInterface; /** @@ -46,6 +45,6 @@ abstract class SolverOperation implements OperationInterface protected function formatVersion(PackageInterface $package) { - return VersionParser::formatVersion($package); + return $package->getFullPrettyVersion(); } } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 000d63805..96e46c531 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -14,10 +14,10 @@ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; -use Composer\Package\Version\VersionParser; -use Composer\Package\LinkConstraint\LinkConstraintInterface; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\LinkConstraint\EmptyConstraint; +use Composer\Semver\VersionParser; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\EmptyConstraint; use Composer\Repository\RepositoryInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\ComposerRepository; @@ -31,7 +31,7 @@ use Composer\Package\PackageInterface; * @author Nils Adermann * @author Jordi Boggiano */ -class Pool +class Pool implements \Countable { const MATCH_NAME = -1; const MATCH_NONE = 0; @@ -150,27 +150,35 @@ class Pool } /** - * Retrieves the package object for a given package id. - * - * @param int $id - * @return PackageInterface - */ + * Retrieves the package object for a given package id. + * + * @param int $id + * @return PackageInterface + */ public function packageById($id) { return $this->packages[$id - 1]; } + /** + * Returns how many packages have been loaded into the pool + */ + public function count() + { + return count($this->packages); + } + /** * Searches all packages providing the given package name and match the constraint * - * @param string $name The package name to be searched for - * @param LinkConstraintInterface $constraint A constraint that all returned - * packages must match or null to return all - * @param bool $mustMatchName Whether the name of returned packages - * must match the given name - * @return PackageInterface[] A set of packages + * @param string $name The package name to be searched for + * @param ConstraintInterface $constraint A constraint that all returned + * packages must match or null to return all + * @param bool $mustMatchName Whether the name of returned packages + * must match the given name + * @return PackageInterface[] A set of packages */ - public function whatProvides($name, LinkConstraintInterface $constraint = null, $mustMatchName = false) + public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false) { $key = ((int) $mustMatchName).$constraint; if (isset($this->providerCache[$name][$key])) { @@ -309,12 +317,12 @@ class Pool * Checks if the package matches the given constraint directly or through * provided or replaced packages * - * @param array|PackageInterface $candidate - * @param string $name Name of the package to be matched - * @param LinkConstraintInterface $constraint The constraint to verify - * @return int One of the MATCH* constants of this class or 0 if there is no match + * @param array|PackageInterface $candidate + * @param string $name Name of the package to be matched + * @param ConstraintInterface $constraint The constraint to verify + * @return int One of the MATCH* constants of this class or 0 if there is no match */ - private function match($candidate, $name, LinkConstraintInterface $constraint = null) + private function match($candidate, $name, ConstraintInterface $constraint = null) { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); @@ -328,7 +336,7 @@ class Pool } if ($candidateName === $name) { - $pkgConstraint = new VersionConstraint('==', $candidateVersion); + $pkgConstraint = new Constraint('==', $candidateVersion); if ($constraint === null || $constraint->matches($pkgConstraint)) { return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED; diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index a1910c3ed..f4e5f1744 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -47,7 +47,7 @@ class Problem */ public function addRule(Rule $rule) { - $this->addReason($rule->getId(), array( + $this->addReason(spl_object_hash($rule), array( 'rule' => $rule, 'job' => $rule->getJob(), )); @@ -87,8 +87,12 @@ class Problem } if ($job && $job['cmd'] === 'install' && empty($packages)) { + // handle php/hhvm if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { + $available = $this->pool->whatProvides($job['packageName']); + $version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); + $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; if (defined('HHVM_VERSION')) { @@ -97,7 +101,7 @@ class Problem return $msg . 'you are running this with PHP and not HHVM.'; } - return $msg . 'your PHP version ('. phpversion().') does not satisfy that requirement.'; + return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.'; } // handle php extensions @@ -218,7 +222,7 @@ class Problem /** * Turns a constraint into text usable in a sentence describing a job * - * @param \Composer\Package\LinkConstraint\LinkConstraintInterface $constraint + * @param \Composer\Semver\Constraint\ConstraintInterface $constraint * @return string */ protected function constraintToText($constraint) diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index 0776f040e..7eacdf1f6 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -12,7 +12,7 @@ namespace Composer\DependencyResolver; -use Composer\Package\LinkConstraint\LinkConstraintInterface; +use Composer\Semver\Constraint\ConstraintInterface; /** * @author Nils Adermann @@ -26,17 +26,17 @@ class Request $this->jobs = array(); } - public function install($packageName, LinkConstraintInterface $constraint = null) + public function install($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'install', $constraint); } - public function update($packageName, LinkConstraintInterface $constraint = null) + public function update($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'update', $constraint); } - public function remove($packageName, LinkConstraintInterface $constraint = null) + public function remove($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'remove', $constraint); } @@ -46,12 +46,12 @@ class Request * * These jobs will not be tempered with by the solver */ - public function fix($packageName, LinkConstraintInterface $constraint = null) + public function fix($packageName, ConstraintInterface $constraint = null) { $this->addJob($packageName, 'install', $constraint, true); } - protected function addJob($packageName, $cmd, LinkConstraintInterface $constraint = null, $fixed = false) + protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false) { $packageName = strtolower($packageName); @@ -59,7 +59,7 @@ class Request 'cmd' => $cmd, 'packageName' => $packageName, 'constraint' => $constraint, - 'fixed' => $fixed + 'fixed' => $fixed, ); } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 37db43745..03de5ab84 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -29,63 +29,51 @@ class Rule const RULE_LEARNED = 12; const RULE_PACKAGE_ALIAS = 13; + const BITFIELD_TYPE = 0; + const BITFIELD_REASON = 8; + const BITFIELD_DISABLED = 16; + /** * READ-ONLY: The literals this rule consists of. * @var array */ public $literals; - protected $disabled; - protected $type; - protected $id; - protected $reason; + protected $bitfield; protected $reasonData; - protected $job; - - protected $ruleHash; - public function __construct(array $literals, $reason, $reasonData, $job = null) { // sort all packages ascending by id sort($literals); $this->literals = $literals; - $this->reason = $reason; $this->reasonData = $reasonData; - $this->disabled = false; - - $this->job = $job; - - $this->type = -1; + if ($job) { + $this->job = $job; + } - $this->ruleHash = substr(md5(implode(',', $this->literals)), 0, 5); + $this->bitfield = (0 << self::BITFIELD_DISABLED) | + ($reason << self::BITFIELD_REASON) | + (255 << self::BITFIELD_TYPE); } public function getHash() { - return $this->ruleHash; - } - - public function setId($id) - { - $this->id = $id; - } + $data = unpack('ihash', md5(implode(',', $this->literals), true)); - public function getId() - { - return $this->id; + return $data['hash']; } public function getJob() { - return $this->job; + return isset($this->job) ? $this->job : null; } public function getReason() { - return $this->reason; + return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON; } public function getReasonData() @@ -95,11 +83,11 @@ class Rule public function getRequiredPackage() { - if ($this->reason === self::RULE_JOB_INSTALL) { + if ($this->getReason() === self::RULE_JOB_INSTALL) { return $this->reasonData; } - if ($this->reason === self::RULE_PACKAGE_REQUIRES) { + if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { return $this->reasonData->getTarget(); } } @@ -114,10 +102,6 @@ class Rule */ public function equals(Rule $rule) { - if ($this->ruleHash !== $rule->ruleHash) { - return false; - } - if (count($this->literals) != count($rule->literals)) { return false; } @@ -133,32 +117,32 @@ class Rule public function setType($type) { - $this->type = $type; + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE); } public function getType() { - return $this->type; + return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE; } public function disable() { - $this->disabled = true; + $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED); } public function enable() { - $this->disabled = false; + $this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_DISABLED); } public function isDisabled() { - return $this->disabled; + return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } public function isEnabled() { - return !$this->disabled; + return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } /** @@ -184,7 +168,7 @@ class Rule $ruleText .= $pool->literalToPrettyString($literal, $installedMap); } - switch ($this->reason) { + switch ($this->getReason()) { case self::RULE_INTERNAL_ALLOW_UPDATE: return $ruleText; @@ -216,16 +200,17 @@ class Rule } else { $targetName = $this->reasonData->getTarget(); - // handle php extensions if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') { + // handle php/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 ('. phpversion().') does not satisfy that requirement.'; + $text .= ' -> your PHP version ('. phpversion() .') or "config.platform.php" value does not satisfy that requirement.'; } } elseif (0 === strpos($targetName, 'ext-')) { + // handle php extensions $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/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index b9545123f..26771cef6 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -30,7 +30,7 @@ class RuleSet implements \IteratorAggregate, \Countable public $ruleById; protected static $types = array( - -1 => 'UNKNOWN', + 255 => 'UNKNOWN', self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_JOB => 'JOB', self::TYPE_LEARNED => 'LEARNED', @@ -66,7 +66,6 @@ class RuleSet implements \IteratorAggregate, \Countable $this->ruleById[$this->nextRuleId] = $rule; $rule->setType($type); - $rule->setId($this->nextRuleId); $this->nextRuleId++; $hash = $rule->getHash(); @@ -131,7 +130,7 @@ class RuleSet implements \IteratorAggregate, \Countable public function getTypes() { $types = self::$types; - unset($types[-1]); + unset($types[255]); return array_keys($types); } diff --git a/src/Composer/DependencyResolver/RuleWatchNode.php b/src/Composer/DependencyResolver/RuleWatchNode.php index cdbf6a00b..907cb4cc0 100644 --- a/src/Composer/DependencyResolver/RuleWatchNode.php +++ b/src/Composer/DependencyResolver/RuleWatchNode.php @@ -83,7 +83,7 @@ class RuleWatchNode /** * Given one watched literal, this method returns the other watched literal * - * @param int The watched literal that should not be returned + * @param int $literal The watched literal that should not be returned * @return int A literal */ public function getOtherWatch($literal) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 6975df2cd..0568a4d2c 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -50,6 +50,11 @@ class Solver $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool); } + public function getRuleSetSize() + { + return count($this->rules); + } + // aka solver_makeruledecisions private function makeAssertionRuleDecisions() { @@ -212,7 +217,7 @@ class Solver * Evaluates each term affected by the decision (linked through watches) * If we find unit rules we make new decisions based on them * - * @param integer $level + * @param int $level * @return Rule|null A rule on conflict, otherwise null. */ protected function propagate($level) @@ -314,7 +319,7 @@ class Solver $this->rules->add($newRule, RuleSet::TYPE_LEARNED); - $this->learnedWhy[$newRule->getId()] = $why; + $this->learnedWhy[spl_object_hash($newRule)] = $why; $ruleNode = new RuleWatchNode($newRule); $ruleNode->watch2OnHighest($this->decisions); @@ -449,7 +454,7 @@ class Solver private function analyzeUnsolvableRule($problem, $conflictRule) { - $why = $conflictRule->getId(); + $why = spl_object_hash($conflictRule); if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { $learnedWhy = $this->learnedWhy[$why]; @@ -567,7 +572,7 @@ class Solver private function enableDisableLearnedRules() { foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) { - $why = $this->learnedWhy[$rule->getId()]; + $why = $this->learnedWhy[spl_object_hash($rule)]; $problemRules = $this->learnedPool[$why]; $foundDisabled = false; diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index 308172bcc..9973f9d39 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -32,7 +32,7 @@ class SolverProblemsException extends \RuntimeException { $text = "\n"; foreach ($this->problems as $i => $problem) { - $text .= " Problem ".($i+1).$problem->getPrettyString($this->installedMap)."\n"; + $text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n"; } if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) { diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index ff0b9e338..f42b82872 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -77,7 +77,11 @@ 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->writeError(' Invalid zip file, retrying...'); + if ($this->io->isDebug()) { + $this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...'); + } else { + $this->io->writeError(' Invalid zip file, retrying...'); + } usleep(500000); continue; } @@ -115,7 +119,7 @@ abstract class ArchiveDownloader extends FileDownloader // update api archives to the proper reference $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference(); } - } else if ($package->getDistReference() && strpos($url, 'bitbucket.org')) { + } elseif ($package->getDistReference() && strpos($url, 'bitbucket.org')) { if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) { // update Bitbucket archives to the proper reference $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $package->getDistReference() . '.' . $match[4]; diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 5c0980725..4d2e9c8f4 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -103,10 +103,9 @@ class DownloadManager /** * Returns downloader for a specific installation type. * - * @param string $type installation type - * @return DownloaderInterface - * + * @param string $type installation type * @throws \InvalidArgumentException if downloader for provided type is not registered + * @return DownloaderInterface */ public function getDownloader($type) { @@ -121,12 +120,11 @@ class DownloadManager /** * Returns downloader for already installed package. * - * @param PackageInterface $package package instance - * @return DownloaderInterface|null - * + * @param PackageInterface $package package instance * @throws \InvalidArgumentException if package has no installation source specified * @throws \LogicException if specific downloader used to load package with - * wrong type + * wrong type + * @return DownloaderInterface|null */ public function getDownloaderForInstalledPackage(PackageInterface $package) { diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 04f8fc4b5..77a08cbf4 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -16,7 +16,6 @@ use Composer\Config; use Composer\Cache; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; -use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; @@ -81,7 +80,7 @@ class FileDownloader implements DownloaderInterface throw new \InvalidArgumentException('The given package is missing url information'); } - $this->io->writeError(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); $urls = $package->getDistUrls(); while ($url = array_shift($urls)) { @@ -138,7 +137,7 @@ class FileDownloader implements DownloaderInterface break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort - if ((0 !== $e->getCode() && !in_array($e->getCode(),array(500, 502, 503, 504))) || !$retries) { + if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { throw $e; } if ($this->io->isVerbose()) { @@ -205,7 +204,7 @@ class FileDownloader implements DownloaderInterface */ public function remove(PackageInterface $package, $path) { - $this->io->writeError(" - Removing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } @@ -226,11 +225,10 @@ class FileDownloader implements DownloaderInterface /** * Process the download url * - * @param PackageInterface $package package the url is coming from - * @param string $url download url - * @return string url - * + * @param PackageInterface $package package the url is coming from + * @param string $url download url * @throws \RuntimeException If any problem with the url + * @return string url */ protected function processUrl(PackageInterface $package, $url) { diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index a869e5b5a..60a896256 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -13,7 +13,6 @@ namespace Composer\Downloader; use Composer\Package\PackageInterface; -use Composer\Util\GitHub; use Composer\Util\Git as GitUtil; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; @@ -82,7 +81,7 @@ class GitDownloader extends VcsDownloader $command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer'; $commandCallable = function ($url) use ($command) { - return sprintf($command, ProcessExecutor::escape ($url)); + return sprintf($command, ProcessExecutor::escape($url)); }; $this->gitUtil->runCommand($commandCallable, $url, $path); @@ -150,7 +149,7 @@ class GitDownloader extends VcsDownloader } while (true) { - switch ($this->io->ask(' Discard changes [y,n,v,'.($update ? 's,' : '').'?]? ', '?')) { + switch ($this->io->ask(' Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; @@ -170,6 +169,10 @@ class GitDownloader extends VcsDownloader $this->io->writeError($changes); break; + case 'd': + $this->viewDiff($path); + break; + case '?': default: help: @@ -177,6 +180,7 @@ class GitDownloader extends VcsDownloader ' 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', + ' d - view local modifications (diff)', )); if ($update) { $this->io->writeError(' s - stash changes and try to reapply them after the update'); @@ -205,13 +209,12 @@ class GitDownloader extends VcsDownloader /** * Updates the given path to the given commit ref * - * @param string $path - * @param string $reference - * @param string $branch - * @param \DateTime $date - * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found - * + * @param string $path + * @param string $reference + * @param string $branch + * @param \DateTime $date * @throws \RuntimeException + * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found */ protected function updateToCommit($path, $reference, $branch, $date) { @@ -327,6 +330,20 @@ class GitDownloader extends VcsDownloader $this->hasStashedChanges = true; } + /** + * @param $path + * @throws \RuntimeException + */ + protected function viewDiff($path) + { + $path = $this->normalizePath($path); + if (0 !== $this->process->execute('git diff HEAD', $output, $path)) { + throw new \RuntimeException("Could not view diff\n\n:".$this->process->getErrorOutput()); + } + + $this->io->writeError($output); + } + protected function normalizePath($path) { if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) { diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php new file mode 100644 index 000000000..3ff47da8e --- /dev/null +++ b/src/Composer/Downloader/PathDownloader.php @@ -0,0 +1,61 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * Download a package from a local path. + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathDownloader extends FileDownloader +{ + /** + * {@inheritdoc} + */ + public function download(PackageInterface $package, $path) + { + $fileSystem = new Filesystem(); + $this->filesystem->removeDirectory($path); + + $this->io->writeError(sprintf( + ' - Installing %s (%s)', + $package->getName(), + $package->getFullPrettyVersion() + )); + + $url = $package->getDistUrl(); + $realUrl = realpath($url); + if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { + throw new \RuntimeException(sprintf( + 'Path "%s" is not found', + $url + )); + } + + try { + $shortestPath = $this->filesystem->findShortestPath($path, $realUrl); + $fileSystem->symlink($shortestPath, $path); + $this->io->writeError(sprintf(' Symlinked from %s', $url)); + } catch (IOException $e) { + $fileSystem->mirror($realUrl, $path); + $this->io->writeError(sprintf(' Mirrored from %s', $url)); + } + + $this->io->writeError(''); + } +} diff --git a/src/Composer/Downloader/PearPackageExtractor.php b/src/Composer/Downloader/PearPackageExtractor.php index 1106d927f..44267d558 100644 --- a/src/Composer/Downloader/PearPackageExtractor.php +++ b/src/Composer/Downloader/PearPackageExtractor.php @@ -48,7 +48,6 @@ class PearPackageExtractor * @param array $vars used for replacement tasks * @throws \RuntimeException * @throws \UnexpectedValueException - * */ public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array()) { @@ -130,9 +129,9 @@ class PearPackageExtractor * @param string $source string path to extracted files * @param array $roles array [role => roleRoot] relative root for files having that role * @param array $vars list of values can be used for replacement tasks + * @throws \RuntimeException * @return array array of 'source' => 'target', where source is location of file in the tarball (relative to source * path, and target is destination of file (also relative to $source path) - * @throws \RuntimeException */ private function buildCopyActions($source, array $roles, $vars) { diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index ae2769999..d99fc7997 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -43,7 +43,7 @@ class PerforceDownloader extends VcsDownloader private function getLabelFromSourceReference($ref) { - $pos = strpos($ref,'@'); + $pos = strpos($ref, '@'); if (false !== $pos) { return substr($ref, $pos + 1); } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index a13d2310f..addf95d37 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -151,7 +151,7 @@ class SvnDownloader extends VcsDownloader */ protected function getCommitLogs($fromReference, $toReference, $path) { - if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference) ) { + if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) { // strip paths from references and only keep the actual revision $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 254f6143d..c77dd439b 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -14,7 +14,6 @@ namespace Composer\Downloader; use Composer\Config; use Composer\Package\PackageInterface; -use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; @@ -54,7 +53,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } - $this->io->writeError(" - Installing " . $package->getName() . " (" . VersionParser::formatVersion($package) . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); $this->filesystem->emptyDirectory($path); $urls = $package->getSourceUrls(); @@ -100,8 +99,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } $name .= ' '.$initial->getPrettyVersion(); } else { - $from = VersionParser::formatVersion($initial); - $to = VersionParser::formatVersion($target); + $from = $initial->getFullPrettyVersion(); + $to = $target->getFullPrettyVersion(); } $this->io->writeError(" - Updating " . $name . " (" . $from . " => " . $to . ")"); diff --git a/src/Composer/EventDispatcher/Event.php b/src/Composer/EventDispatcher/Event.php index 38ae4f440..2091f6be0 100644 --- a/src/Composer/EventDispatcher/Event.php +++ b/src/Composer/EventDispatcher/Event.php @@ -35,7 +35,7 @@ class Event protected $flags; /** - * @var boolean Whether the event should not be passed to more listeners + * @var bool Whether the event should not be passed to more listeners */ private $propagationStopped = false; @@ -86,7 +86,7 @@ class Event /** * Checks if stopPropagation has been called * - * @return boolean Whether propagation has been stopped + * @return bool Whether propagation has been stopped */ public function isPropagationStopped() { diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 3bb1c8496..721e26a3f 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -21,6 +21,7 @@ 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; @@ -135,10 +136,10 @@ class EventDispatcher * * @param Event $event The event object to pass to the event handlers/listeners. * @param string $additionalArgs - * @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 * @throws \RuntimeException * @throws \Exception + * @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 */ protected function doDispatch(Event $event) { @@ -170,8 +171,14 @@ class EventDispatcher throw $e; } } else { - $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor','escape'), $event->getArguments())); - if (0 !== ($exitCode = $this->process->execute($callable . ($args === '' ? '' : ' '.$args)))) { + $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments())); + $exec = $callable . ($args === '' ? '' : ' '.$args); + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); + } else { + $this->io->writeError(sprintf('> %s', $exec)); + } + if (0 !== ($exitCode = $this->process->execute($exec))) { $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); @@ -195,12 +202,18 @@ class EventDispatcher { $event = $this->checkListenerExpectedEvent(array($className, $methodName), $event); + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); + } else { + $this->io->writeError(sprintf('> %s::%s', $className, $methodName)); + } + return $className::$methodName($event); } /** - * @param mixed $target - * @param Event $event + * @param mixed $target + * @param Event $event * @return Event|CommandEvent */ protected function checkListenerExpectedEvent($target, Event $event) @@ -247,7 +260,7 @@ class EventDispatcher * * @param string $eventName The event name - typically a constant * @param Callable $listener A callable expecting an event argument - * @param integer $priority A higher value represents a higher priority + * @param int $priority A higher value represents a higher priority */ protected function addListener($eventName, $listener, $priority = 0) { @@ -300,8 +313,8 @@ class EventDispatcher /** * Checks if an event has listeners registered * - * @param Event $event - * @return boolean + * @param Event $event + * @return bool */ public function hasEventListeners(Event $event) { @@ -342,8 +355,8 @@ class EventDispatcher /** * Checks if string given references a class path and method * - * @param string $callable - * @return boolean + * @param string $callable + * @return bool */ protected function isPhpScript($callable) { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 19261739f..5f61a243d 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -16,6 +16,7 @@ use Composer\Config\JsonConfigSource; use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Package\Archiver; +use Composer\Package\Version\VersionGuesser; use Composer\Repository\RepositoryManager; use Composer\Repository\WritableRepositoryInterface; use Composer\Util\ProcessExecutor; @@ -23,7 +24,7 @@ use Composer\Util\RemoteFilesystem; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; /** * Creates a configured instance of composer. @@ -373,7 +374,8 @@ class Factory // load package $parser = new VersionParser; - $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io)); + $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser); + $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser); $package = $loader->load($localConfig); $composer->setPackage($package); @@ -415,7 +417,7 @@ class Factory $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; - $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile)); + $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, file_get_contents($composerFile)); $composer->setLocker($locker); } @@ -440,6 +442,7 @@ class Factory $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); + $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); return $rm; } @@ -512,6 +515,7 @@ class Factory $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache)); $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache)); $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache)); + $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache)); return $dm; } diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index 581680b7c..db3fb634b 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -23,8 +23,8 @@ 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( diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 622e73ccf..ae5b952ea 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -18,7 +18,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; -use Symfony\Component\Process\ExecutableFinder; /** * The Input/Output helper. @@ -112,8 +111,8 @@ class ConsoleIO extends BaseIO /** * @param array $messages - * @param boolean $newline - * @param boolean $stderr + * @param bool $newline + * @param bool $stderr */ private function doWrite($messages, $newline, $stderr) { @@ -154,9 +153,9 @@ class ConsoleIO extends BaseIO /** * @param array $messages - * @param boolean $newline - * @param integer $size - * @param boolean $stderr + * @param bool $newline + * @param int $size + * @param bool $stderr */ private function doOverwrite($messages, $newline, $size, $stderr) { diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 7dc9f5a09..b26700a8c 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -77,7 +77,7 @@ interface IOInterface * * @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 + * @param int $size The size of line */ public function overwrite($messages, $newline = true, $size = null); @@ -86,7 +86,7 @@ interface IOInterface * * @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 + * @param int $size The size of line */ public function overwriteError($messages, $newline = true, $size = null); @@ -96,9 +96,8 @@ interface IOInterface * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * - * @return string The user answer - * * @throws \RuntimeException If there is no data to read in the input stream + * @return string The user answer */ public function ask($question, $default = null); @@ -123,12 +122,11 @@ interface IOInterface * * @param string|array $question The question to ask * @param callback $validator A PHP callback - * @param null|integer $attempts Max number of times to ask before giving up (default of null means infinite) + * @param null|int $attempts Max number of times to ask before giving up (default of null means infinite) * @param string $default The default answer if none is given by the user * - * @return mixed - * * @throws \Exception When any of the validators return an error + * @return mixed */ public function askAndValidate($question, $validator, $attempts = null, $default = null); @@ -153,7 +151,7 @@ interface IOInterface * * @param string $repositoryName The unique name of repository * - * @return boolean + * @return bool */ public function hasAuthentication($repositoryName); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index e58381856..5f1299a97 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -18,6 +18,7 @@ use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Rule; @@ -33,7 +34,7 @@ use Composer\Json\JsonFile; use Composer\Package\AliasPackage; use Composer\Package\CompletePackage; use Composer\Package\Link; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; use Composer\Package\Locker; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; @@ -43,6 +44,7 @@ use Composer\Repository\InstalledFilesystemRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryManager; +use Composer\Repository\WritableRepositoryInterface; use Composer\Script\ScriptEvents; /** @@ -101,6 +103,7 @@ class Installer protected $preferSource = false; protected $preferDist = false; protected $optimizeAutoloader = false; + protected $classMapAuthoritative = false; protected $devMode = false; protected $dryRun = false; protected $verbose = false; @@ -157,9 +160,8 @@ class Installer /** * Run installation (or update) * - * @return int 0 on success or a positive error code on failure - * * @throws \Exception + * @return int 0 on success or a positive error code on failure */ public function run() { @@ -333,6 +335,7 @@ class Installer } $this->autoloadGenerator->setDevMode($this->devMode); + $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); } @@ -344,13 +347,23 @@ class Installer $vendorDir = $this->config->get('vendor-dir'); if (is_dir($vendorDir)) { - touch($vendorDir); + // suppress errors as this fails sometimes on OSX for no apparent reason + // see https://github.com/composer/composer/issues/4070#issuecomment-129792748 + @touch($vendorDir); } } return 0; } + /** + * @param RepositoryInterface $localRepo + * @param RepositoryInterface $installedRepo + * @param PlatformRepository $platformRepo + * @param array $aliases + * @param bool $withDevReqs + * @return int + */ protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $withDevReqs) { // init vars @@ -414,7 +427,7 @@ class Installer && $this->installationManager->isPackageInstalled($localRepo, $package) ) { $removedUnstablePackages[$package->getName()] = true; - $request->remove($package->getName(), new VersionConstraint('=', $package->getVersion())); + $request->remove($package->getName(), new Constraint('=', $package->getVersion())); } } } @@ -453,7 +466,7 @@ class Installer foreach ($currentPackages as $curPackage) { if ($curPackage->getName() === $candidate) { if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) { - $constraint = new VersionConstraint('=', $curPackage->getVersion()); + $constraint = new Constraint('=', $curPackage->getVersion()); $request->install($curPackage->getName(), $constraint); } break; @@ -473,7 +486,7 @@ class Installer if (isset($aliases[$package->getName()][$version])) { $version = $aliases[$package->getName()][$version]['alias_normalized']; } - $constraint = new VersionConstraint('=', $version); + $constraint = new Constraint('=', $version); $constraint->setPrettyString($package->getPrettyVersion()); $request->install($package->getName(), $constraint); } @@ -511,6 +524,11 @@ class Installer return max(1, $e->getCode()); } + if ($this->io->isVerbose()) { + $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies"); + $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies"); + } + // force dev packages to be updated if we update or install from a (potentially new) lock $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations); @@ -552,7 +570,8 @@ class Installer if ('update' === $operation->getJobType() && $operation->getTargetPackage()->isDev() && $operation->getTargetPackage()->getVersion() === $operation->getInitialPackage()->getVersion() - && $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference() + && (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference()) + && (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference()) ) { if ($this->io->isDebug()) { $this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version'); @@ -608,7 +627,7 @@ class Installer if (!$this->dryRun) { // force source/dist urls to be updated for all packages - $operations = $this->processPackageUrls($pool, $policy, $localRepo, $repositories); + $this->processPackageUrls($pool, $policy, $localRepo, $repositories); $localRepo->write(); } @@ -679,6 +698,11 @@ class Installer return array_merge($uninstOps, $operations); } + /** + * @param bool $withDevReqs + * @param RepositoryInterface|null $lockedRepository + * @return Pool + */ private function createPool($withDevReqs, RepositoryInterface $lockedRepository = null) { if (!$this->update && $this->locker->isLocked()) { // install from lock @@ -687,7 +711,7 @@ class Installer $requires = array(); foreach ($lockedRepository->getPackages() as $package) { - $constraint = new VersionConstraint('=', $package->getVersion()); + $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); $requires[$package->getName()] = $constraint; } @@ -717,6 +741,9 @@ class Installer return new Pool($minimumStability, $stabilityFlags, $rootConstraints); } + /** + * @return DefaultPolicy + */ private function createPolicy() { $preferStable = null; @@ -737,11 +764,16 @@ class Installer return new DefaultPolicy($preferStable, $preferLowest); } + /** + * @param RootPackageInterface $rootPackage + * @param PlatformRepository $platformRepo + * @return Request + */ private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo) { $request = new Request(); - $constraint = new VersionConstraint('=', $rootPackage->getVersion()); + $constraint = new Constraint('=', $rootPackage->getVersion()); $constraint->setPrettyString($rootPackage->getPrettyVersion()); $request->install($rootPackage->getName(), $constraint); @@ -755,7 +787,7 @@ class Installer // to prevent the solver trying to remove or update those $provided = $rootPackage->getProvides(); foreach ($fixedPackages as $package) { - $constraint = new VersionConstraint('=', $package->getVersion()); + $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); // skip platform packages that are provided by the root package @@ -770,6 +802,19 @@ class Installer return $request; } + /** + * @param WritableRepositoryInterface $localRepo + * @param Pool $pool + * @param PolicyInterface $policy + * @param array $repositories + * @param RepositoryInterface $installedRepo + * @param RepositoryInterface $lockedRepository + * @param bool $installFromLock + * @param bool $withDevReqs + * @param string $task + * @param array|null $operations + * @return array + */ private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, $task, array $operations = null) { if ($task === 'force-updates' && null === $operations) { @@ -847,7 +892,7 @@ class Installer } // find similar packages (name/version) in all repositories - $matches = $pool->whatProvides($package->getName(), new VersionConstraint('=', $package->getVersion())); + $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -902,6 +947,9 @@ class Installer /** * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback + * @param bool $withDevReqs + * @param RepositoryInterface $installedRepo + * @return array */ private function getCurrentPackages($withDevReqs, $installedRepo) { @@ -917,6 +965,9 @@ class Installer return $installedRepo->getPackages(); } + /** + * @return array + */ private function getRootAliases() { if (!$this->update && $this->locker->isLocked()) { @@ -930,13 +981,19 @@ class Installer foreach ($aliases as $alias) { $normalizedAliases[$alias['package']][$alias['version']] = array( 'alias' => $alias['alias'], - 'alias_normalized' => $alias['alias_normalized'] + 'alias_normalized' => $alias['alias_normalized'], ); } return $normalizedAliases; } + /** + * @param Pool $pool + * @param PolicyInterface $policy + * @param WritableRepositoryInterface $localRepo + * @param array $repositories + */ private function processPackageUrls($pool, $policy, $localRepo, $repositories) { if (!$this->update) { @@ -945,7 +1002,7 @@ class Installer foreach ($localRepo->getCanonicalPackages() as $package) { // find similar packages (name/version) in all repositories - $matches = $pool->whatProvides($package->getName(), new VersionConstraint('=', $package->getVersion())); + $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion())); foreach ($matches as $index => $match) { // skip local packages if (!in_array($match->getRepository(), $repositories, true)) { @@ -967,7 +1024,15 @@ class Installer $newPackage = $pool->literalToPackage($matches[0]); // update the dist and source URLs - $package->setSourceUrl($newPackage->getSourceUrl()); + $sourceUrl = $package->getSourceUrl(); + $newSourceUrl = $newPackage->getSourceUrl(); + + if ($sourceUrl !== $newSourceUrl) { + $package->setSourceType($newPackage->getSourceType()); + $package->setSourceUrl($newSourceUrl); + $package->setSourceReference($newPackage->getSourceReference()); + } + // only update dist url for github/bitbucket dists as they use a combination of dist url + dist reference to install // but for other urls this is ambiguous and could result in bad outcomes if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com)/}', $newPackage->getDistUrl())) { @@ -977,6 +1042,10 @@ class Installer } } + /** + * @param PlatformRepository $platformRepo + * @param array $aliases + */ private function aliasPlatformPackages(PlatformRepository $platformRepo, $aliases) { foreach ($aliases as $package => $versions) { @@ -991,6 +1060,10 @@ class Installer } } + /** + * @param PackageInterface $package + * @return bool + */ private function isUpdateable(PackageInterface $package) { if (!$this->updateWhitelist) { @@ -1020,6 +1093,10 @@ class Installer return "{^" . $cleanedWhiteListedPattern . "$}i"; } + /** + * @param array $links + * @return array + */ private function extractPlatformRequirements($links) { $platformReqs = array(); @@ -1040,7 +1117,7 @@ class Installer * update whitelist themselves. * * @param RepositoryInterface $localRepo - * @param boolean $devMode + * @param bool $devMode * @param array $rootRequires An array of links to packages in require of the root package * @param array $rootDevRequires An array of links to packages in require-dev of the root package */ @@ -1172,6 +1249,10 @@ class Installer ); } + /** + * @param RepositoryInterface $additionalInstalledRepository + * @return $this + */ public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository) { $this->additionalInstalledRepository = $additionalInstalledRepository; @@ -1182,7 +1263,7 @@ class Installer /** * Whether to run in drymode or not * - * @param boolean $dryRun + * @param bool $dryRun * @return Installer */ public function setDryRun($dryRun = true) @@ -1205,7 +1286,7 @@ class Installer /** * prefer source installation * - * @param boolean $preferSource + * @param bool $preferSource * @return Installer */ public function setPreferSource($preferSource = true) @@ -1218,7 +1299,7 @@ class Installer /** * prefer dist installation * - * @param boolean $preferDist + * @param bool $preferDist * @return Installer */ public function setPreferDist($preferDist = true) @@ -1237,6 +1318,29 @@ class Installer public function setOptimizeAutoloader($optimizeAutoloader = false) { $this->optimizeAutoloader = (boolean) $optimizeAutoloader; + if (!$this->optimizeAutoloader) { + // Force classMapAuthoritative off when not optimizing the + // autoloader + $this->setClassMapAuthoritative(false); + } + + return $this; + } + + /** + * Whether or not generated autoloader considers the class map + * authoritative. + * + * @param bool $classMapAuthoritative + * @return Installer + */ + public function setClassMapAuthoritative($classMapAuthoritative = false) + { + $this->classMapAuthoritative = (boolean) $classMapAuthoritative; + if ($this->classMapAuthoritative) { + // Force optimizeAutoloader when classmap is authoritative + $this->setOptimizeAutoloader(true); + } return $this; } @@ -1244,7 +1348,7 @@ class Installer /** * update packages * - * @param boolean $update + * @param bool $update * @return Installer */ public function setUpdate($update = true) @@ -1257,7 +1361,7 @@ class Installer /** * enables dev packages * - * @param boolean $devMode + * @param bool $devMode * @return Installer */ public function setDevMode($devMode = true) @@ -1270,7 +1374,7 @@ class Installer /** * set whether to run autoloader or not * - * @param boolean $dumpAutoloader + * @param bool $dumpAutoloader * @return Installer */ public function setDumpAutoloader($dumpAutoloader = true) @@ -1283,7 +1387,7 @@ class Installer /** * set whether to run scripts or not * - * @param boolean $runScripts + * @param bool $runScripts * @return Installer */ public function setRunScripts($runScripts = true) @@ -1309,7 +1413,7 @@ class Installer /** * run in verbose mode * - * @param boolean $verbose + * @param bool $verbose * @return Installer */ public function setVerbose($verbose = true) @@ -1332,7 +1436,7 @@ class Installer /** * set ignore Platform Package requirements * - * @param boolean $ignorePlatformReqs + * @param bool $ignorePlatformReqs * @return Installer */ public function setIgnorePlatformRequirements($ignorePlatformReqs = false) @@ -1359,7 +1463,7 @@ class Installer /** * Should dependencies of whitelisted packages be updated recursively? * - * @param boolean $updateDependencies + * @param bool $updateDependencies * @return Installer */ public function setWhitelistDependencies($updateDependencies = true) @@ -1372,7 +1476,7 @@ class Installer /** * Should packages be preferred in a stable version when updating? * - * @param boolean $preferStable + * @param bool $preferStable * @return Installer */ public function setPreferStable($preferStable = true) @@ -1385,7 +1489,7 @@ class Installer /** * Should packages be preferred in a lowest version when updating? * - * @param boolean $preferLowest + * @param bool $preferLowest * @return Installer */ public function setPreferLowest($preferLowest = true) diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index a43acbbda..b774f83c9 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -89,9 +89,8 @@ class InstallationManager * * @param string $type package type * - * @return InstallerInterface - * * @throws \InvalidArgumentException if installer for provided type is not registered + * @return InstallerInterface */ public function getInstaller($type) { @@ -249,7 +248,7 @@ class InstallationManager 'header' => array('Content-type: application/x-www-form-urlencoded'), 'content' => http_build_query($params, '', '&'), 'timeout' => 3, - ) + ), ); $context = StreamContextFactory::getContext($url, $opts); @@ -273,7 +272,7 @@ class InstallationManager 'header' => array('Content-Type: application/json'), 'content' => json_encode($postData), 'timeout' => 6, - ) + ), ); $context = StreamContextFactory::getContext($repoUrl, $opts); diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index 469b91ed4..e64dfadd2 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -14,6 +14,7 @@ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; +use InvalidArgumentException; /** * Interface for the package installation manager. diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index aa2d46062..75b3a0c6e 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -266,6 +266,11 @@ class LibraryInstaller implements InstallerInterface $this->filesystem->unlink($link.'.bat'); } } + + // attempt removing the bin dir in case it is left empty + if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) { + @rmdir($this->binDir); + } } protected function initializeVendorDir() diff --git a/src/Composer/Installer/PackageEvent.php b/src/Composer/Installer/PackageEvent.php index e1c5e6080..f5cf0ed6e 100644 --- a/src/Composer/Installer/PackageEvent.php +++ b/src/Composer/Installer/PackageEvent.php @@ -18,7 +18,6 @@ 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; /** diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index ddb8e5bdd..14aec4462 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -13,7 +13,6 @@ namespace Composer\Installer; use Composer\Composer; -use Composer\Package\Package; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; @@ -27,7 +26,6 @@ use Composer\Package\PackageInterface; class PluginInstaller extends LibraryInstaller { private $installationManager; - private static $classCounter = 0; /** * Initializes Plugin installer. diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 1c51a4251..ee4f04e84 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -137,8 +137,8 @@ class JsonFile * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant - * @return bool true on success * @throws JsonValidationException + * @return bool true on success */ public function validateSchema($schema = self::STRICT_SCHEMA) { @@ -218,7 +218,7 @@ class JsonFile /** * Throws an exception according to a given code with a customized message * - * @param int $code return code of json_last_error function + * @param int $code return code of json_last_error function * @throws \RuntimeException */ private static function throwEncodeError($code) @@ -269,10 +269,10 @@ class JsonFile * * @param string $json * @param string $file - * @return bool true on success * @throws \UnexpectedValueException * @throws JsonValidationException * @throws ParsingException + * @return bool true on success */ protected static function validateSyntax($json, $file = null) { diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php index d109acae8..8e2005715 100644 --- a/src/Composer/Json/JsonFormatter.php +++ b/src/Composer/Json/JsonFormatter.php @@ -23,7 +23,6 @@ namespace Composer\Json; class JsonFormatter { /** - * * This code is based on the function found at: * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ * diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index f6aa98a4c..8e1bb88fe 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -12,8 +12,8 @@ namespace Composer\Package; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\VersionParser; /** * @author Jordi Boggiano @@ -162,7 +162,7 @@ class AliasPackage extends BasePackage implements CompletePackageInterface } /** - * @param array $links + * @param array $links * @param string $linkType * @internal param string $prettyVersion * @return array @@ -174,14 +174,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface foreach ($links as $link) { // link is self.version, but must be replacing also the replaced version if ('self.version' === $link->getPrettyConstraint()) { - $newLinks[] = new Link($link->getSource(), $link->getTarget(), new VersionConstraint('=', $this->version), $linkType, $this->prettyVersion); + $newLinks[] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); } } $links = array_merge($links, $newLinks); } else { foreach ($links as $index => $link) { if ('self.version' === $link->getPrettyConstraint()) { - $links[$index] = new Link($link->getSource(), $link->getTarget(), new VersionConstraint('=', $this->version), $linkType, $this->prettyVersion); + $links[$index] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); } } } diff --git a/src/Composer/Package/Archiver/ArchivableFilesFinder.php b/src/Composer/Package/Archiver/ArchivableFilesFinder.php index 44c682616..1e94f1e15 100644 --- a/src/Composer/Package/Archiver/ArchivableFilesFinder.php +++ b/src/Composer/Package/Archiver/ArchivableFilesFinder.php @@ -13,8 +13,7 @@ namespace Composer\Package\Archiver; use Composer\Util\Filesystem; - -use Symfony\Component\Finder; +use Symfony\Component\Finder\Finder; /** * A Symfony Finder wrapper which locates files that should go into archives @@ -27,7 +26,7 @@ use Symfony\Component\Finder; class ArchivableFilesFinder extends \FilterIterator { /** - * @var Symfony\Component\Finder\Finder + * @var Finder */ protected $finder; @@ -49,7 +48,7 @@ class ArchivableFilesFinder extends \FilterIterator new ComposerExcludeFilter($sources, $excludes), ); - $this->finder = new Finder\Finder(); + $this->finder = new Finder(); $filter = function (\SplFileInfo $file) use ($sources, $filters, $fs) { if ($file->isLink() && strpos($file->getLinkTarget(), $sources) !== 0) { diff --git a/src/Composer/Package/Archiver/ArchiverInterface.php b/src/Composer/Package/Archiver/ArchiverInterface.php index 72a06c1c9..a625f5c07 100644 --- a/src/Composer/Package/Archiver/ArchiverInterface.php +++ b/src/Composer/Package/Archiver/ArchiverInterface.php @@ -37,7 +37,7 @@ interface ArchiverInterface * @param string $format The archive format * @param string $sourceType The source type (git, svn, hg, etc.) * - * @return boolean true if the format is supported by the archiver + * @return bool true if the format is supported by the archiver */ public function supports($format, $sourceType); } diff --git a/src/Composer/Package/Archiver/BaseExcludeFilter.php b/src/Composer/Package/Archiver/BaseExcludeFilter.php index d724f31e4..c94c52a6b 100644 --- a/src/Composer/Package/Archiver/BaseExcludeFilter.php +++ b/src/Composer/Package/Archiver/BaseExcludeFilter.php @@ -123,7 +123,7 @@ abstract class BaseExcludeFilter protected function generatePattern($rule) { $negate = false; - $pattern = '#'; + $pattern = '{'; if (strlen($rule) && $rule[0] === '!') { $negate = true; @@ -143,6 +143,6 @@ abstract class BaseExcludeFilter // remove delimiters as well as caret (^) and dollar sign ($) from the regex $pattern .= substr(Finder\Glob::toRegex($rule), 2, -2) . '(?=$|/)'; - return array($pattern . '#', $negate, false); + return array($pattern . '}', $negate, false); } } diff --git a/src/Composer/Package/Archiver/HgExcludeFilter.php b/src/Composer/Package/Archiver/HgExcludeFilter.php index fa2327c5f..b9c81cdc1 100644 --- a/src/Composer/Package/Archiver/HgExcludeFilter.php +++ b/src/Composer/Package/Archiver/HgExcludeFilter.php @@ -26,7 +26,7 @@ class HgExcludeFilter extends BaseExcludeFilter /** * Either HG_IGNORE_REGEX or HG_IGNORE_GLOB - * @var integer + * @var int */ protected $patternMode; diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 965e5ddc8..0369a36dd 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -161,7 +161,7 @@ abstract class BasePackage implements PackageInterface /** * checks if this package is a platform package * - * @return boolean + * @return bool */ public function isPlatform() { @@ -206,6 +206,23 @@ abstract class BasePackage implements PackageInterface return $this->getPrettyName().' '.$this->getPrettyVersion(); } + /** + * {@inheritDoc} + */ + public function getFullPrettyVersion($truncate = true) + { + if (!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git'))) { + return $this->getPrettyVersion(); + } + + // if source reference is a sha1 hash -- truncate + if ($truncate && strlen($this->getSourceReference()) === 40) { + return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7); + } + + return $this->getPrettyVersion() . ' ' . $this->getSourceReference(); + } + public function __clone() { $this->repository = null; diff --git a/src/Composer/Package/CompletePackage.php b/src/Composer/Package/CompletePackage.php index 0de1609d0..0a926a4ce 100644 --- a/src/Composer/Package/CompletePackage.php +++ b/src/Composer/Package/CompletePackage.php @@ -172,7 +172,7 @@ class CompletePackage extends Package implements CompletePackageInterface } /** - * @return boolean + * @return bool */ public function isAbandoned() { @@ -180,7 +180,7 @@ class CompletePackage extends Package implements CompletePackageInterface } /** - * @param boolean|string $abandoned + * @param bool|string $abandoned */ public function setAbandoned($abandoned) { diff --git a/src/Composer/Package/CompletePackageInterface.php b/src/Composer/Package/CompletePackageInterface.php index 8263a6535..4036b3cec 100644 --- a/src/Composer/Package/CompletePackageInterface.php +++ b/src/Composer/Package/CompletePackageInterface.php @@ -82,7 +82,7 @@ interface CompletePackageInterface extends PackageInterface /** * Returns if the package is abandoned or not * - * @return boolean + * @return bool */ public function isAbandoned(); diff --git a/src/Composer/Package/Link.php b/src/Composer/Package/Link.php index e09f163eb..e695822fd 100644 --- a/src/Composer/Package/Link.php +++ b/src/Composer/Package/Link.php @@ -12,7 +12,7 @@ namespace Composer\Package; -use Composer\Package\LinkConstraint\LinkConstraintInterface; +use Composer\Semver\Constraint\ConstraintInterface; /** * Represents a link between two packages, represented by their names @@ -32,7 +32,7 @@ class Link protected $target; /** - * @var LinkConstraintInterface|null + * @var ConstraintInterface|null */ protected $constraint; @@ -49,13 +49,13 @@ class Link /** * Creates a new package link. * - * @param string $source - * @param string $target - * @param LinkConstraintInterface|null $constraint Constraint applying to the target of this link - * @param string $description Used to create a descriptive string representation - * @param string|null $prettyConstraint + * @param string $source + * @param string $target + * @param ConstraintInterface|null $constraint Constraint applying to the target of this link + * @param string $description Used to create a descriptive string representation + * @param string|null $prettyConstraint */ - public function __construct($source, $target, LinkConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null) + public function __construct($source, $target, ConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null) { $this->source = strtolower($source); $this->target = strtolower($target); @@ -81,7 +81,7 @@ class Link } /** - * @return LinkConstraintInterface|null + * @return ConstraintInterface|null */ public function getConstraint() { @@ -110,7 +110,7 @@ class Link } /** - * @param PackageInterface $sourcePackage + * @param PackageInterface $sourcePackage * @return string */ public function getPrettyString(PackageInterface $sourcePackage) diff --git a/src/Composer/Package/LinkConstraint/EmptyConstraint.php b/src/Composer/Package/LinkConstraint/EmptyConstraint.php index ca1c75ff5..09eb15ada 100644 --- a/src/Composer/Package/LinkConstraint/EmptyConstraint.php +++ b/src/Composer/Package/LinkConstraint/EmptyConstraint.php @@ -12,36 +12,13 @@ namespace Composer\Package\LinkConstraint; +use Composer\Semver\Constraint\EmptyConstraint as SemverEmptyConstraint; + +@trigger_error('The ' . __NAMESPACE__ . '\EmptyConstraint class is deprecated, use Composer\Semver\Constraint\EmptyConstraint instead.', E_USER_DEPRECATED); + /** - * Defines an absence of constraints - * - * @author Jordi Boggiano + * @deprecated use Composer\Semver\Constraint\EmptyConstraint instead */ -class EmptyConstraint implements LinkConstraintInterface +class EmptyConstraint extends SemverEmptyConstraint implements LinkConstraintInterface { - protected $prettyString; - - public function matches(LinkConstraintInterface $provider) - { - return true; - } - - public function setPrettyString($prettyString) - { - $this->prettyString = $prettyString; - } - - public function getPrettyString() - { - if ($this->prettyString) { - return $this->prettyString; - } - - return $this->__toString(); - } - - public function __toString() - { - return '[]'; - } } diff --git a/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php b/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php index 199c9fc42..bac98c6f9 100644 --- a/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php +++ b/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php @@ -12,15 +12,13 @@ namespace Composer\Package\LinkConstraint; +use Composer\Semver\Constraint\ConstraintInterface; + +@trigger_error('The ' . __NAMESPACE__ . '\LinkConstraintInterface interface is deprecated, use Composer\Semver\Constraint\ConstraintInterface instead.', E_USER_DEPRECATED); + /** - * Defines a constraint on a link between two packages. - * - * @author Nils Adermann + * @deprecated use Composer\Semver\Constraint\ConstraintInterface instead */ -interface LinkConstraintInterface +interface LinkConstraintInterface extends ConstraintInterface { - public function matches(LinkConstraintInterface $provider); - public function setPrettyString($prettyString); - public function getPrettyString(); - public function __toString(); } diff --git a/src/Composer/Package/LinkConstraint/MultiConstraint.php b/src/Composer/Package/LinkConstraint/MultiConstraint.php index 3871aeb2f..6e091a430 100644 --- a/src/Composer/Package/LinkConstraint/MultiConstraint.php +++ b/src/Composer/Package/LinkConstraint/MultiConstraint.php @@ -12,72 +12,13 @@ namespace Composer\Package\LinkConstraint; +use Composer\Semver\Constraint\MultiConstraint as SemverMultiConstraint; + +@trigger_error('The ' . __NAMESPACE__ . '\MultiConstraint class is deprecated, use Composer\Semver\Constraint\MultiConstraint instead.', E_USER_DEPRECATED); + /** - * Defines a conjunctive or disjunctive set of constraints on the target of a package link - * - * @author Nils Adermann - * @author Jordi Boggiano + * @deprecated use Composer\Semver\Constraint\MultiConstraint instead */ -class MultiConstraint implements LinkConstraintInterface +class MultiConstraint extends SemverMultiConstraint implements LinkConstraintInterface { - protected $constraints; - protected $prettyString; - protected $conjunctive; - - /** - * Sets operator and version to compare a package with - * - * @param array $constraints A set of constraints - * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive - */ - public function __construct(array $constraints, $conjunctive = true) - { - $this->constraints = $constraints; - $this->conjunctive = $conjunctive; - } - - public function matches(LinkConstraintInterface $provider) - { - if (false === $this->conjunctive) { - foreach ($this->constraints as $constraint) { - if ($constraint->matches($provider)) { - return true; - } - } - - return false; - } - - foreach ($this->constraints as $constraint) { - if (!$constraint->matches($provider)) { - return false; - } - } - - return true; - } - - public function setPrettyString($prettyString) - { - $this->prettyString = $prettyString; - } - - public function getPrettyString() - { - if ($this->prettyString) { - return $this->prettyString; - } - - return $this->__toString(); - } - - public function __toString() - { - $constraints = array(); - foreach ($this->constraints as $constraint) { - $constraints[] = $constraint->__toString(); - } - - return '['.implode($this->conjunctive ? ' ' : ' || ', $constraints).']'; - } } diff --git a/src/Composer/Package/LinkConstraint/SpecificConstraint.php b/src/Composer/Package/LinkConstraint/SpecificConstraint.php index e0904dbbf..880873887 100644 --- a/src/Composer/Package/LinkConstraint/SpecificConstraint.php +++ b/src/Composer/Package/LinkConstraint/SpecificConstraint.php @@ -12,42 +12,13 @@ namespace Composer\Package\LinkConstraint; +use Composer\Semver\Constraint\AbstractConstraint; + +@trigger_error('The ' . __NAMESPACE__ . '\SpecificConstraint abstract class is deprecated, use Composer\Semver\Constraint\AbstractConstraint instead.', E_USER_DEPRECATED); + /** - * Provides a common basis for specific package link constraints - * - * @author Nils Adermann + * @deprecated use Composer\Semver\Constraint\AbstractConstraint instead */ -abstract class SpecificConstraint implements LinkConstraintInterface +abstract class SpecificConstraint extends AbstractConstraint implements LinkConstraintInterface { - protected $prettyString; - - public function matches(LinkConstraintInterface $provider) - { - if ($provider instanceof MultiConstraint) { - // turn matching around to find a match - return $provider->matches($this); - } elseif ($provider instanceof $this) { - return $this->matchSpecific($provider); - } - - return true; - } - - public function setPrettyString($prettyString) - { - $this->prettyString = $prettyString; - } - - public function getPrettyString() - { - if ($this->prettyString) { - return $this->prettyString; - } - - return $this->__toString(); - } - - // implementations must implement a method of this format: - // not declared abstract here because type hinting violates parameter coherence (TODO right word?) - // public function matchSpecific( $provider); } diff --git a/src/Composer/Package/LinkConstraint/VersionConstraint.php b/src/Composer/Package/LinkConstraint/VersionConstraint.php index cd2336227..580016795 100644 --- a/src/Composer/Package/LinkConstraint/VersionConstraint.php +++ b/src/Composer/Package/LinkConstraint/VersionConstraint.php @@ -12,113 +12,13 @@ namespace Composer\Package\LinkConstraint; +use Composer\Semver\Constraint\Constraint; + +@trigger_error('The ' . __NAMESPACE__ . '\VersionConstraint class is deprecated, use Composer\Semver\Constraint\Constraint instead.', E_USER_DEPRECATED); + /** - * Constrains a package link based on package version - * - * Version numbers must be compatible with version_compare - * - * @author Nils Adermann + * @deprecated use Composer\Semver\Constraint\Constraint instead */ -class VersionConstraint extends SpecificConstraint +class VersionConstraint extends Constraint implements LinkConstraintInterface { - private $operator; - private $version; - - /** - * Sets operator and version to compare a package with - * - * @param string $operator A comparison operator - * @param string $version A version to compare to - */ - public function __construct($operator, $version) - { - if ('=' === $operator) { - $operator = '=='; - } - - if ('<>' === $operator) { - $operator = '!='; - } - - $this->operator = $operator; - $this->version = $version; - } - - public function versionCompare($a, $b, $operator, $compareBranches = false) - { - $aIsBranch = 'dev-' === substr($a, 0, 4); - $bIsBranch = 'dev-' === substr($b, 0, 4); - if ($aIsBranch && $bIsBranch) { - return $operator == '==' && $a === $b; - } - - // when branches are not comparable, we make sure dev branches never match anything - if (!$compareBranches && ($aIsBranch || $bIsBranch)) { - return false; - } - - return version_compare($a, $b, $operator); - } - - /** - * @param VersionConstraint $provider - * @param bool $compareBranches - * @return bool - */ - public function matchSpecific(VersionConstraint $provider, $compareBranches = false) - { - static $cache = array(); - if (isset($cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches])) { - return $cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches]; - } - - return $cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches] = - $this->doMatchSpecific($provider, $compareBranches); - } - - /** - * @param VersionConstraint $provider - * @param bool $compareBranches - * @return bool - */ - private function doMatchSpecific(VersionConstraint $provider, $compareBranches = false) - { - $noEqualOp = str_replace('=', '', $this->operator); - $providerNoEqualOp = str_replace('=', '', $provider->operator); - - $isEqualOp = '==' === $this->operator; - $isNonEqualOp = '!=' === $this->operator; - $isProviderEqualOp = '==' === $provider->operator; - $isProviderNonEqualOp = '!=' === $provider->operator; - - // '!=' operator is match when other operator is not '==' operator or version is not match - // these kinds of comparisons always have a solution - if ($isNonEqualOp || $isProviderNonEqualOp) { - return !$isEqualOp && !$isProviderEqualOp - || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); - } - - // an example for the condition is <= 2.0 & < 1.0 - // these kinds of comparisons always have a solution - if ($this->operator != '==' && $noEqualOp == $providerNoEqualOp) { - return true; - } - - if ($this->versionCompare($provider->version, $this->version, $this->operator, $compareBranches)) { - // special case, e.g. require >= 1.0 and provide < 1.0 - // 1.0 >= 1.0 but 1.0 is outside of the provided interval - if ($provider->version == $this->version && $provider->operator == $providerNoEqualOp && $this->operator != $noEqualOp) { - return false; - } - - return true; - } - - return false; - } - - public function __toString() - { - return $this->operator.' '.$this->version; - } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 60cff3b33..42398e41f 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -14,9 +14,10 @@ namespace Composer\Package\Loader; use Composer\Package; use Composer\Package\AliasPackage; +use Composer\Package\Link; use Composer\Package\RootAliasPackage; use Composer\Package\RootPackageInterface; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; /** * @author Konstantin Kudryashiv @@ -115,7 +116,7 @@ class ArrayLoader implements LoaderInterface if (isset($config[$type])) { $method = 'set'.ucfirst($opts['method']); $package->{$method}( - $this->versionParser->parseLinks( + $this->parseLinks( $package->getName(), $package->getPrettyVersion(), $opts['description'], @@ -147,7 +148,7 @@ class ArrayLoader implements LoaderInterface } if (!empty($config['time'])) { - $time = preg_match('/^\d+$/D', $config['time']) ? '@'.$config['time'] : $config['time']; + $time = preg_match('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; try { $date = new \DateTime($time, new \DateTimeZone('UTC')); @@ -216,6 +217,29 @@ class ArrayLoader implements LoaderInterface return $package; } + /** + * @param string $source source package name + * @param string $sourceVersion source package version (pretty version ideally) + * @param string $description link description (e.g. requires, replaces, ..) + * @param array $links array of package name => constraint mappings + * @return Link[] + */ + public function parseLinks($source, $sourceVersion, $description, $links) + { + $res = array(); + foreach ($links as $target => $constraint) { + if ('self.version' === $constraint) { + $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion); + } else { + $parsedConstraint = $this->versionParser->parseConstraints($constraint); + } + + $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); + } + + return $res; + } + /** * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists * diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 9c7c436bc..1266346db 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -16,13 +16,10 @@ use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Config; use Composer\Factory; -use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionGuesser; +use Composer\Semver\VersionParser; use Composer\Repository\RepositoryManager; -use Composer\Repository\Vcs\HgDriver; -use Composer\IO\NullIO; use Composer\Util\ProcessExecutor; -use Composer\Util\Git as GitUtil; -use Composer\Util\Svn as SvnUtil; /** * ArrayLoader built for the sole purpose of loading the root package @@ -33,16 +30,28 @@ use Composer\Util\Svn as SvnUtil; */ class RootPackageLoader extends ArrayLoader { + /** + * @var RepositoryManager + */ private $manager; + + /** + * @var Config + */ private $config; - private $process; - public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, ProcessExecutor $process = null) + /** + * @var VersionGuesser + */ + private $versionGuesser; + + public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null) { + parent::__construct($parser); + $this->manager = $manager; $this->config = $config; - $this->process = $process ?: new ProcessExecutor(); - parent::__construct($parser); + $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser); } public function load(array $config, $class = 'Composer\Package\RootPackage') @@ -56,7 +65,7 @@ class RootPackageLoader extends ArrayLoader if (getenv('COMPOSER_ROOT_VERSION')) { $version = getenv('COMPOSER_ROOT_VERSION'); } else { - $version = $this->guessVersion($config); + $version = $this->versionGuesser->guessVersion($config, getcwd()); } if (!$version) { @@ -176,172 +185,4 @@ class RootPackageLoader extends ArrayLoader return $references; } - - private function guessVersion(array $config) - { - if (function_exists('proc_open')) { - $version = $this->guessGitVersion($config); - if (null !== $version) { - return $version; - } - - $version = $this->guessHgVersion($config); - if (null !== $version) { - return $version; - } - - return $this->guessSvnVersion($config); - } - } - - private function guessGitVersion(array $config) - { - GitUtil::cleanEnv(); - - // try to fetch current version from git tags - if (0 === $this->process->execute('git describe --exact-match --tags', $output)) { - try { - return $this->versionParser->normalize(trim($output)); - } catch (\Exception $e) { - } - } - - // try to fetch current version from git branch - if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output)) { - $branches = array(); - $isFeatureBranch = false; - $version = null; - - // find current branch and collect all branch names - foreach ($this->process->splitLines($output) as $branch) { - if ($branch && preg_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { - if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ') { - $version = 'dev-'.$match[2]; - $isFeatureBranch = true; - } else { - $version = $this->versionParser->normalizeBranch($match[1]); - $isFeatureBranch = 0 === strpos($version, 'dev-'); - if ('9999999-dev' === $version) { - $version = 'dev-'.$match[1]; - } - } - } - - if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { - if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) { - $branches[] = $match[1]; - } - } - } - - if (!$isFeatureBranch) { - return $version; - } - - // try to find the best (nearest) version branch to assume this feature's version - $version = $this->guessFeatureVersion($config, $version, $branches, 'git rev-list %candidate%..%branch%'); - - return $version; - } - } - - private function guessHgVersion(array $config) - { - // try to fetch current version from hg branch - if (0 === $this->process->execute('hg branch', $output)) { - $branch = trim($output); - $version = $this->versionParser->normalizeBranch($branch); - $isFeatureBranch = 0 === strpos($version, 'dev-'); - - if ('9999999-dev' === $version) { - $version = 'dev-'.$branch; - } - - if (!$isFeatureBranch) { - return $version; - } - - // re-use the HgDriver to fetch branches (this properly includes bookmarks) - $config = array('url' => getcwd()); - $driver = new HgDriver($config, new NullIO(), $this->config, $this->process); - $branches = array_keys($driver->getBranches()); - - // try to find the best (nearest) version branch to assume this feature's version - $version = $this->guessFeatureVersion($config, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"'); - - return $version; - } - } - - private function guessFeatureVersion(array $config, $version, array $branches, $scmCmdline) - { - // ignore feature branches if they have no branch-alias or self.version is used - // and find the branch they came from to use as a version instead - if ((isset($config['extra']['branch-alias']) && !isset($config['extra']['branch-alias'][$version])) - || strpos(json_encode($config), '"self.version"') - ) { - $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; - } - - $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline); - if (0 !== $this->process->execute($cmdLine, $output)) { - continue; - } - - if (strlen($output) < $length) { - $length = strlen($output); - $version = $this->versionParser->normalizeBranch($candidate); - if ('9999999-dev' === $version) { - $version = 'dev-'.$match[1]; - } - } - } - } - - return $version; - } - - private function guessSvnVersion(array $config) - { - SvnUtil::cleanEnv(); - - // try to fetch current version from svn - if (0 === $this->process->execute('svn info --xml', $output)) { - $trunkPath = isset($config['trunk-path']) ? preg_quote($config['trunk-path'], '#') : 'trunk'; - $branchesPath = isset($config['branches-path']) ? preg_quote($config['branches-path'], '#') : 'branches'; - $tagsPath = isset($config['tags-path']) ? preg_quote($config['tags-path'], '#') : 'tags'; - - $urlPattern = '#.*/('.$trunkPath.'|('.$branchesPath.'|'. $tagsPath .')/(.*))#'; - - if (preg_match($urlPattern, $output, $matches)) { - if (isset($matches[2]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { - // we are in a branches path - $version = $this->versionParser->normalizeBranch($matches[3]); - if ('9999999-dev' === $version) { - $version = 'dev-'.$matches[3]; - } - - return $version; - } - - return $this->versionParser->normalize(trim($matches[1])); - } - } - } } diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 86386989c..4f01a680d 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -14,8 +14,8 @@ namespace Composer\Package\Loader; use Composer\Package; use Composer\Package\BasePackage; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\Version\VersionParser; +use Composer\Semver\Constraint\Constraint; +use Composer\Semver\VersionParser; use Composer\Repository\PlatformRepository; /** @@ -149,7 +149,7 @@ class ValidatingArrayLoader implements LoaderInterface } } - $unboundConstraint = new VersionConstraint('=', $this->versionParser->normalize('dev-master')); + $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master')); foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 9f9a0abf8..dba5541be 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -19,9 +19,9 @@ use Composer\Util\ProcessExecutor; use Composer\Repository\ArrayRepository; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Loader\ArrayLoader; -use Composer\Package\Version\VersionParser; use Composer\Util\Git as GitUtil; use Composer\IO\IOInterface; +use Seld\JsonLint\ParsingException; /** * Reads/writes project lockfile (composer.lock). @@ -35,6 +35,7 @@ class Locker private $repositoryManager; private $installationManager; private $hash; + private $contentHash; private $loader; private $dumper; private $process; @@ -44,17 +45,18 @@ class Locker * Initializes packages locker. * * @param IOInterface $io - * @param JsonFile $lockFile lockfile loader - * @param RepositoryManager $repositoryManager repository manager instance - * @param InstallationManager $installationManager installation manager instance - * @param string $hash unique hash of the current composer configuration + * @param JsonFile $lockFile lockfile loader + * @param RepositoryManager $repositoryManager repository manager instance + * @param InstallationManager $installationManager installation manager instance + * @param string $composerFileContents The contents of the composer file */ - public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $hash) + public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents) { $this->lockFile = $lockFile; $this->repositoryManager = $repositoryManager; $this->installationManager = $installationManager; - $this->hash = $hash; + $this->hash = md5($composerFileContents); + $this->contentHash = $this->getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, true); $this->dumper = new ArrayDumper(); $this->process = new ProcessExecutor($io); @@ -85,6 +87,11 @@ class Locker { $lock = $this->lockFile->read(); + if (!empty($lock['content-hash'])) { + // There is a content hash key, use that instead of the file hash + return $this->contentHash === $lock['content-hash']; + } + return $this->hash === $lock['hash']; } @@ -133,11 +140,10 @@ class Locker public function getPlatformRequirements($withDevReqs = false) { $lockData = $this->getLockData(); - $versionParser = new VersionParser(); $requirements = array(); if (!empty($lockData['platform'])) { - $requirements = $versionParser->parseLinks( + $requirements = $this->loader->parseLinks( '__ROOT__', '1.0.0', 'requires', @@ -146,7 +152,7 @@ class Locker } if ($withDevReqs && !empty($lockData['platform-dev'])) { - $devRequirements = $versionParser->parseLinks( + $devRequirements = $this->loader->parseLinks( '__ROOT__', '1.0.0', 'requires', @@ -239,8 +245,9 @@ class Locker $lock = array( '_readme' => array('This file locks the dependencies of your project to a known state', 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file', - 'This file is @gener'.'ated automatically'), + 'This file is @gener'.'ated automatically', ), 'hash' => $this->hash, + 'content-hash' => $this->contentHash, 'packages' => null, 'packages-dev' => null, 'aliases' => array(), @@ -280,7 +287,12 @@ class Locker return false; } - if (!$this->isLocked() || $lock !== $this->getLockData()) { + try { + $isLocked = $this->isLocked(); + } catch (ParsingException $e) { + $isLocked = false; + } + if (!$isLocked || $lock !== $this->getLockData()) { $this->lockFile->write($lock); $this->lockDataCache = null; @@ -378,4 +390,43 @@ class Locker return $datetime ? $datetime->format('Y-m-d H:i:s') : null; } + + /** + * Returns the md5 hash of the sorted content of the composer file. + * + * @param string $composerFileContents The contents of the composer file. + * + * @return string + */ + private function getContentHash($composerFileContents) + { + $content = json_decode($composerFileContents, true); + + $relevantKeys = array( + 'name', + 'version', + 'require', + 'require-dev', + 'conflict', + 'replace', + 'provide', + 'minimum-stability', + 'prefer-stable', + 'repositories', + 'extra', + ); + + $relevantContent = array(); + + foreach (array_intersect($relevantKeys, array_keys($content)) as $key) { + $relevantContent[$key] = $content[$key]; + } + if (isset($content['config']['platform'])) { + $relevantContent['config']['platform'] = $content['config']['platform']; + } + + ksort($relevantContent); + + return md5(json_encode($relevantContent)); + } } diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 0c98a0e56..7f813887e 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -12,7 +12,7 @@ namespace Composer\Package; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Util\ComposerMirror; /** diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index a51274d5b..c88771cfd 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -192,6 +192,16 @@ interface PackageInterface */ public function getPrettyVersion(); + /** + * Returns the pretty version string plus a git or hg commit hash of this package + * + * @see getPrettyVersion + * + * @param bool $truncate If the source reference is a sha1 hash, truncate it + * @return string version + */ + public function getFullPrettyVersion($truncate = true); + /** * Returns the release date of the package * diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php new file mode 100644 index 000000000..4dd0be511 --- /dev/null +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -0,0 +1,228 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\Config; +use Composer\Repository\Vcs\HgDriver; +use Composer\IO\NullIO; +use Composer\Semver\VersionParser as SemverVersionParser; +use Composer\Util\Git as GitUtil; +use Composer\Util\ProcessExecutor; +use Composer\Util\Svn as SvnUtil; + +/** + * Try to guess the current version number based on different VCS configuration. + * + * @author Jordi Boggiano + * @author Samuel Roze + */ +class VersionGuesser +{ + /** + * @var Config + */ + private $config; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * @var SemverVersionParser + */ + private $versionParser; + + /** + * @param Config $config + * @param ProcessExecutor $process + * @param VersionParser $versionParser + */ + public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser) + { + $this->config = $config; + $this->process = $process; + $this->versionParser = $versionParser; + } + + /** + * @param array $packageConfig + * @param string $path Path to guess into + */ + public function guessVersion(array $packageConfig, $path) + { + if (function_exists('proc_open')) { + $version = $this->guessGitVersion($packageConfig, $path); + if (null !== $version) { + return $version; + } + + $version = $this->guessHgVersion($packageConfig, $path); + if (null !== $version) { + return $version; + } + + return $this->guessSvnVersion($packageConfig, $path); + } + } + + private function guessGitVersion(array $packageConfig, $path) + { + GitUtil::cleanEnv(); + + // try to fetch current version from git tags + if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) { + try { + return $this->versionParser->normalize(trim($output)); + } catch (\Exception $e) { + } + } + + // try to fetch current version from git branch + if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output, $path)) { + $branches = array(); + $isFeatureBranch = false; + $version = null; + + // find current branch and collect all branch names + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && preg_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { + if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ') { + $version = 'dev-'.$match[2]; + $isFeatureBranch = true; + } else { + $version = $this->versionParser->normalizeBranch($match[1]); + $isFeatureBranch = 0 === strpos($version, 'dev-'); + if ('9999999-dev' === $version) { + $version = 'dev-'.$match[1]; + } + } + } + + if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { + if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) { + $branches[] = $match[1]; + } + } + } + + if (!$isFeatureBranch) { + return $version; + } + + // try to find the best (nearest) version branch to assume this feature's version + $version = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path); + + return $version; + } + } + + private function guessHgVersion(array $packageConfig, $path) + { + // try to fetch current version from hg branch + if (0 === $this->process->execute('hg branch', $output, $path)) { + $branch = trim($output); + $version = $this->versionParser->normalizeBranch($branch); + $isFeatureBranch = 0 === strpos($version, 'dev-'); + + if ('9999999-dev' === $version) { + $version = 'dev-'.$branch; + } + + if (!$isFeatureBranch) { + return $version; + } + + // re-use the HgDriver to fetch branches (this properly includes bookmarks) + $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process); + $branches = array_keys($driver->getBranches()); + + // try to find the best (nearest) version branch to assume this feature's version + $version = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path); + + return $version; + } + } + + private function guessFeatureVersion(array $packageConfig, $version, array $branches, $scmCmdline, $path) + { + // ignore feature branches if they have no branch-alias or self.version is used + // and find the branch they came from to use as a version instead + if ((isset($packageConfig['extra']['branch-alias']) && !isset($packageConfig['extra']['branch-alias'][$version])) + || strpos(json_encode($packageConfig), '"self.version"') + ) { + $branch = preg_replace('{^dev-}', '', $version); + $length = PHP_INT_MAX; + + $nonFeatureBranches = ''; + if (!empty($packageConfig['non-feature-branches'])) { + $nonFeatureBranches = implode('|', $packageConfig['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; + } + + $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline); + if (0 !== $this->process->execute($cmdLine, $output, $path)) { + continue; + } + + if (strlen($output) < $length) { + $length = strlen($output); + $version = $this->versionParser->normalizeBranch($candidate); + if ('9999999-dev' === $version) { + $version = 'dev-'.$match[1]; + } + } + } + } + + return $version; + } + + private function guessSvnVersion(array $packageConfig, $path) + { + SvnUtil::cleanEnv(); + + // try to fetch current version from svn + if (0 === $this->process->execute('svn info --xml', $output, $path)) { + $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; + $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; + $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; + + $urlPattern = '#.*/('.$trunkPath.'|('.$branchesPath.'|'. $tagsPath .')/(.*))#'; + + if (preg_match($urlPattern, $output, $matches)) { + if (isset($matches[2]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { + // we are in a branches path + $version = $this->versionParser->normalizeBranch($matches[3]); + if ('9999999-dev' === $version) { + $version = 'dev-'.$matches[3]; + } + + return $version; + } + + return $this->versionParser->normalize(trim($matches[1])); + } + } + } +} diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index b5a32a448..00d1e669a 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -12,513 +12,18 @@ namespace Composer\Package\Version; -use Composer\Package\BasePackage; -use Composer\Package\PackageInterface; -use Composer\Package\Link; -use Composer\Package\LinkConstraint\EmptyConstraint; -use Composer\Package\LinkConstraint\MultiConstraint; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\VersionParser as SemverVersionParser; -/** - * Version parser - * - * @author Jordi Boggiano - */ -class VersionParser +class VersionParser extends SemverVersionParser { - private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'; - - /** - * Returns the stability of a version - * - * @param string $version - * @return string - */ - public static function parseStability($version) - { - $version = preg_replace('{#.+$}i', '', $version); - - if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) { - return 'dev'; - } - - preg_match('{'.self::$modifierRegex.'$}i', strtolower($version), $match); - if (!empty($match[3])) { - return 'dev'; - } - - if (!empty($match[1])) { - if ('beta' === $match[1] || 'b' === $match[1]) { - return 'beta'; - } - if ('alpha' === $match[1] || 'a' === $match[1]) { - return 'alpha'; - } - if ('rc' === $match[1]) { - return 'RC'; - } - } - - return 'stable'; - } - - public static function normalizeStability($stability) - { - $stability = strtolower($stability); - - return $stability === 'rc' ? 'RC' : $stability; - } - - public static function formatVersion(PackageInterface $package, $truncate = true) - { - if (!$package->isDev() || !in_array($package->getSourceType(), array('hg', 'git'))) { - return $package->getPrettyVersion(); - } - - // if source reference is a sha1 hash -- truncate - if ($truncate && strlen($package->getSourceReference()) === 40) { - return $package->getPrettyVersion() . ' ' . substr($package->getSourceReference(), 0, 7); - } - - return $package->getPrettyVersion() . ' ' . $package->getSourceReference(); - } - - /** - * Normalizes a version string to be able to perform comparisons on it - * - * @param string $version - * @param string $fullVersion optional complete version string to give more context - * @throws \UnexpectedValueException - * @return string - */ - public function normalize($version, $fullVersion = null) - { - $version = trim($version); - if (null === $fullVersion) { - $fullVersion = $version; - } - - // ignore aliases and just assume the alias is required instead of the source - if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) { - $version = $match[1]; - } - - // ignore build metadata - if (preg_match('{^([^,\s+]+)\+[^\s]+$}', $version, $match)) { - $version = $match[1]; - } - - // match master-like branches - if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { - return '9999999-dev'; - } - - if ('dev-' === strtolower(substr($version, 0, 4))) { - return 'dev-'.substr($version, 4); - } - - // match classical versioning - if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.self::$modifierRegex.'$}i', $version, $matches)) { - $version = $matches[1] - .(!empty($matches[2]) ? $matches[2] : '.0') - .(!empty($matches[3]) ? $matches[3] : '.0') - .(!empty($matches[4]) ? $matches[4] : '.0'); - $index = 5; - } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.self::$modifierRegex.'$}i', $version, $matches)) { // match date-based versioning - $version = preg_replace('{\D}', '-', $matches[1]); - $index = 2; - } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?'.self::$modifierRegex.'$}i', $version, $matches)) { - $version = $matches[1] - .(!empty($matches[2]) ? $matches[2] : '.0') - .(!empty($matches[3]) ? $matches[3] : '.0') - .(!empty($matches[4]) ? $matches[4] : '.0'); - $index = 5; - } - - // add version modifiers if a version was matched - if (isset($index)) { - if (!empty($matches[$index])) { - if ('stable' === $matches[$index]) { - return $version; - } - $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index+1]) ? $matches[$index+1] : ''); - } - - if (!empty($matches[$index+2])) { - $version .= '-dev'; - } - - return $version; - } - - // match dev branches - if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { - try { - return $this->normalizeBranch($match[1]); - } catch (\Exception $e) { - } - } - - $extraMessage = ''; - if (preg_match('{ +as +'.preg_quote($version).'$}', $fullVersion)) { - $extraMessage = ' in "'.$fullVersion.'", the alias must be an exact version'; - } elseif (preg_match('{^'.preg_quote($version).' +as +}', $fullVersion)) { - $extraMessage = ' in "'.$fullVersion.'", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; - } - - throw new \UnexpectedValueException('Invalid version string "'.$version.'"'.$extraMessage); - } - - /** - * Extract numeric prefix from alias, if it is in numeric format, suitable for - * version comparison - * - * @param string $branch Branch name (e.g. 2.1.x-dev) - * @return string|false Numeric prefix if present (e.g. 2.1.) or false - */ - public function parseNumericAliasPrefix($branch) - { - if (preg_match('/^(?P(\d+\\.)*\d+)(?:\.x)?-dev$/i', $branch, $matches)) { - return $matches['version']."."; - } - - return false; - } - - /** - * Normalizes a branch name to be able to perform comparisons on it - * - * @param string $name - * @return string - */ - public function normalizeBranch($name) - { - $name = trim($name); - - if (in_array($name, array('master', 'trunk', 'default'))) { - return $this->normalize($name); - } - - if (preg_match('#^v?(\d+)(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?$#i', $name, $matches)) { - $version = ''; - for ($i = 1; $i < 5; $i++) { - $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; - } - - return str_replace('x', '9999999', $version).'-dev'; - } - - return 'dev-'.$name; - } - - /** - * @param string $source source package name - * @param string $sourceVersion source package version (pretty version ideally) - * @param string $description link description (e.g. requires, replaces, ..) - * @param array $links array of package name => constraint mappings - * @return Link[] - */ - public function parseLinks($source, $sourceVersion, $description, $links) - { - $res = array(); - foreach ($links as $target => $constraint) { - if ('self.version' === $constraint) { - $parsedConstraint = $this->parseConstraints($sourceVersion); - } else { - $parsedConstraint = $this->parseConstraints($constraint); - } - - $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint); - } - - return $res; - } - - /** - * Parses as constraint string into LinkConstraint objects - * - * @param string $constraints - * @return \Composer\Package\LinkConstraint\LinkConstraintInterface - */ - public function parseConstraints($constraints) - { - $prettyConstraint = $constraints; - - if (preg_match('{^([^,\s]*?)@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $constraints, $match)) { - $constraints = empty($match[1]) ? '*' : $match[1]; - } - - if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) { - $constraints = $match[1]; - } - - $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); - $orGroups = array(); - foreach ($orConstraints as $constraints) { - $andConstraints = preg_split('{(?< ,]) *(? 1) { - $constraintObjects = array(); - foreach ($andConstraints as $constraint) { - $constraintObjects = array_merge($constraintObjects, $this->parseConstraint($constraint)); - } - } else { - $constraintObjects = $this->parseConstraint($andConstraints[0]); - } - - if (1 === count($constraintObjects)) { - $constraint = $constraintObjects[0]; - } else { - $constraint = new MultiConstraint($constraintObjects); - } - - $orGroups[] = $constraint; - } - - if (1 === count($orGroups)) { - $constraint = $orGroups[0]; - } else { - $constraint = new MultiConstraint($orGroups, false); - } - - $constraint->setPrettyString($prettyConstraint); - - return $constraint; - } - - private function parseConstraint($constraint) - { - if (preg_match('{^([^,\s]+?)@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $constraint, $match)) { - $constraint = $match[1]; - if ($match[2] !== 'stable') { - $stabilityModifier = $match[2]; - } - } - - if (preg_match('{^[xX*](\.[xX*])*$}i', $constraint)) { - return array(new EmptyConstraint); - } - - $versionRegex = '(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex; - - // match tilde constraints - // like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous - // version, to ensure that unstable instances of the current version are allowed. - // however, if a stability suffix is added to the constraint, then a >= match on the current version is - // used instead - if (preg_match('{^~>?'.$versionRegex.'$}i', $constraint, $matches)) { - if (substr($constraint, 0, 2) === '~>') { - throw new \UnexpectedValueException( - 'Could not parse version constraint '.$constraint.': '. - 'Invalid operator "~>", you probably meant to use the "~" operator' - ); - } - - // Work out which position in the version we are operating at - if (isset($matches[4]) && '' !== $matches[4]) { - $position = 4; - } elseif (isset($matches[3]) && '' !== $matches[3]) { - $position = 3; - } elseif (isset($matches[2]) && '' !== $matches[2]) { - $position = 2; - } else { - $position = 1; - } - - // Calculate the stability suffix - $stabilitySuffix = ''; - if (!empty($matches[5])) { - $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : ''); - } - - if (!empty($matches[7])) { - $stabilitySuffix .= '-dev'; - } - - if (!$stabilitySuffix) { - $stabilitySuffix = "-dev"; - } - $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix; - $lowerBound = new VersionConstraint('>=', $lowVersion); - - // For upper bound, we increment the position of one more significance, - // but highPosition = 0 would be illegal - $highPosition = max(1, $position - 1); - $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; - $upperBound = new VersionConstraint('<', $highVersion); - - return array( - $lowerBound, - $upperBound - ); - } - - // match caret constraints - if (preg_match('{^\^'.$versionRegex.'($)}i', $constraint, $matches)) { - // Work out which position in the version we are operating at - if ('0' !== $matches[1] || '' === $matches[2]) { - $position = 1; - } elseif ('0' !== $matches[2] || '' === $matches[3]) { - $position = 2; - } else { - $position = 3; - } - - // Calculate the stability suffix - $stabilitySuffix = ''; - if (empty($matches[5]) && empty($matches[7])) { - $stabilitySuffix .= '-dev'; - } - - $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); - $lowerBound = new VersionConstraint('>=', $lowVersion); - - // For upper bound, we increment the position of one more significance, - // but highPosition = 0 would be illegal - $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; - $upperBound = new VersionConstraint('<', $highVersion); - - return array( - $lowerBound, - $upperBound - ); - } - - // match wildcard constraints - if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[xX*]$}', $constraint, $matches)) { - if (isset($matches[3]) && '' !== $matches[3]) { - $position = 3; - } elseif (isset($matches[2]) && '' !== $matches[2]) { - $position = 2; - } else { - $position = 1; - } - - $lowVersion = $this->manipulateVersionString($matches, $position) . "-dev"; - $highVersion = $this->manipulateVersionString($matches, $position, 1) . "-dev"; - - if ($lowVersion === "0.0.0.0-dev") { - return array(new VersionConstraint('<', $highVersion)); - } - - return array( - new VersionConstraint('>=', $lowVersion), - new VersionConstraint('<', $highVersion), - ); - } - - // match hyphen constraints - if (preg_match('{^(?P'.$versionRegex.') +- +(?P'.$versionRegex.')($)}i', $constraint, $matches)) { - // Calculate the stability suffix - $lowStabilitySuffix = ''; - if (empty($matches[6]) && empty($matches[8])) { - $lowStabilitySuffix = '-dev'; - } - - $lowVersion = $this->normalize($matches['from']); - $lowerBound = new VersionConstraint('>=', $lowVersion . $lowStabilitySuffix); - - $empty = function ($x) { - return ($x === 0 || $x === "0") ? false : empty($x); - }; - - if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) { - $highVersion = $this->normalize($matches['to']); - $upperBound = new VersionConstraint('<=', $highVersion); - } else { - $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]); - $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev'; - $upperBound = new VersionConstraint('<', $highVersion); - } - - return array( - $lowerBound, - $upperBound - ); - } - - // match operators constraints - if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { - try { - $version = $this->normalize($matches[2]); - - if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') { - $version .= '-' . $stabilityModifier; - } elseif ('<' === $matches[1]) { - if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { - $version .= '-dev'; - } - } - - return array(new VersionConstraint($matches[1] ?: '=', $version)); - } catch (\Exception $e) { - } - } - - $message = 'Could not parse version constraint '.$constraint; - if (isset($e)) { - $message .= ': '. $e->getMessage(); - } - - throw new \UnexpectedValueException($message); - } - /** - * Increment, decrement, or simply pad a version number. + * Parses an array of strings representing package/version pairs. * - * Support function for {@link parseConstraint()} + * The parsing results in an array of arrays, each of which + * contain a 'name' key with value and optionally a 'version' key with value. * - * @param array $matches Array with version parts in array indexes 1,2,3,4 - * @param int $position 1,2,3,4 - which segment of the version to decrement - * @param int $increment - * @param string $pad The string to pad version parts after $position - * @return string The new version - */ - private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') - { - for ($i = 4; $i > 0; $i--) { - if ($i > $position) { - $matches[$i] = $pad; - } elseif ($i == $position && $increment) { - $matches[$i] += $increment; - // If $matches[$i] was 0, carry the decrement - if ($matches[$i] < 0) { - $matches[$i] = $pad; - $position--; - - // Return null on a carry overflow - if ($i == 1) { - return; - } - } - } - } - - return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; - } - - private function expandStability($stability) - { - $stability = strtolower($stability); - - switch ($stability) { - case 'a': - return 'alpha'; - case 'b': - return 'beta'; - case 'p': - case 'pl': - return 'patch'; - case 'rc': - return 'RC'; - default: - return $stability; - } - } - - /** - * Parses a name/version pairs and returns an array of pairs + the + * @param array $pairs a set of package/version pairs separated by ":", "=" or " " * - * @param array $pairs a set of package/version pairs separated by ":", "=" or " " * @return array[] array of arrays containing a name and (if provided) a version */ public function parseNameVersionPairs(array $pairs) @@ -528,8 +33,8 @@ class VersionParser for ($i = 0, $count = count($pairs); $i < $count; $i++) { $pair = preg_replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i])); - if (false === strpos($pair, ' ') && isset($pairs[$i+1]) && false === strpos($pairs[$i+1], '/')) { - $pair .= ' '.$pairs[$i+1]; + if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/')) { + $pair .= ' '.$pairs[$i + 1]; $i++; } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 563ba056a..a22cf2c70 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool; use Composer\Package\PackageInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; +use Composer\Semver\VersionParser as SemverVersionParser; /** * Selects the best possible version for a package @@ -130,7 +131,7 @@ class VersionSelector private function getParser() { if ($this->parser === null) { - $this->parser = new VersionParser(); + $this->parser = new SemverVersionParser(); } return $this->parser; diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 833b3e29b..174055f4e 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -16,12 +16,12 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Package\Package; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Repository\RepositoryInterface; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Link; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; use Composer\DependencyResolver\Pool; /** @@ -45,9 +45,9 @@ class PluginManager /** * Initializes plugin manager * - * @param IOInterface $io - * @param Composer $composer - * @param Composer $globalComposer + * @param IOInterface $io + * @param Composer $composer + * @param Composer $globalComposer */ public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null) { @@ -131,7 +131,7 @@ class PluginManager } $currentPluginApiVersion = $this->getPluginApiVersion(); - $currentPluginApiConstraint = new VersionConstraint('==', $this->versionParser->normalize($currentPluginApiVersion)); + $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); if (!$requiresComposer->matches($currentPluginApiConstraint)) { $this->io->writeError('The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 4f6e2daa7..5f9eaf406 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -15,9 +15,9 @@ namespace Composer\Repository; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; -use Composer\Package\Version\VersionParser; -use Composer\Package\LinkConstraint\LinkConstraintInterface; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\VersionParser; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; /** * A repository implementation that simply stores packages in an array @@ -26,6 +26,7 @@ use Composer\Package\LinkConstraint\VersionConstraint; */ class ArrayRepository implements RepositoryInterface { + /** @var PackageInterface[] */ protected $packages; public function __construct(array $packages = array()) @@ -42,14 +43,14 @@ class ArrayRepository implements RepositoryInterface { $name = strtolower($name); - if (!$constraint instanceof LinkConstraintInterface) { + if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { - $pkgConstraint = new VersionConstraint('==', $package->getVersion()); + $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } @@ -66,14 +67,14 @@ class ArrayRepository implements RepositoryInterface $name = strtolower($name); $packages = array(); - if (null !== $constraint && !$constraint instanceof LinkConstraintInterface) { + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { - $pkgConstraint = new VersionConstraint('==', $package->getVersion()); + $pkgConstraint = new Constraint('==', $package->getVersion()); if (null === $constraint || $constraint->matches($pkgConstraint)) { $packages[] = $package; } diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 2bb518b8f..01d662212 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -15,6 +15,7 @@ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Loader\LoaderInterface; /** * @author Serge Smertin @@ -48,7 +49,7 @@ class ArtifactRepository extends ArrayRepository { $io = $this->io; - $directory = new \RecursiveDirectoryIterator($path); + $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $iterator = new \RecursiveIteratorIterator($directory); $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i'); foreach ($regex as $file) { @@ -77,7 +78,7 @@ class ArtifactRepository extends ArrayRepository /** * Find a file by name, returning the one that has the shortest path. * - * @param \ZipArchive $zip + * @param \ZipArchive $zip * @param $filename * @return bool|int */ @@ -140,7 +141,7 @@ class ArtifactRepository extends ArrayRepository $package['dist'] = array( 'type' => 'zip', 'url' => $file->getPathname(), - 'shasum' => sha1_file($file->getRealPath()) + 'shasum' => sha1_file($file->getRealPath()), ); $package = $this->loader->load($package); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 1d3c2cd92..ce73037a8 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -13,10 +13,9 @@ namespace Composer\Repository; use Composer\Package\Loader\ArrayLoader; -use Composer\Package\Package; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Cache; @@ -26,8 +25,8 @@ use Composer\Util\RemoteFilesystem; use Composer\Plugin\PluginEvents; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; -use Composer\Package\LinkConstraint\LinkConstraintInterface; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\Constraint; /** * @author Jordi Boggiano @@ -108,7 +107,7 @@ class ComposerRepository extends ArrayRepository } $name = strtolower($name); - if (!$constraint instanceof LinkConstraintInterface) { + if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } @@ -118,7 +117,7 @@ class ComposerRepository extends ArrayRepository $packages = $this->whatProvides(new Pool('dev'), $providerName); foreach ($packages as $package) { if ($name === $package->getName()) { - $pkgConstraint = new VersionConstraint('==', $package->getVersion()); + $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } @@ -139,7 +138,7 @@ class ComposerRepository extends ArrayRepository // normalize name $name = strtolower($name); - if (null !== $constraint && !$constraint instanceof LinkConstraintInterface) { + if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } @@ -151,7 +150,7 @@ class ComposerRepository extends ArrayRepository $candidates = $this->whatProvides(new Pool('dev'), $providerName); foreach ($candidates as $package) { if ($name === $package->getName()) { - $pkgConstraint = new VersionConstraint('==', $package->getVersion()); + $pkgConstraint = new Constraint('==', $package->getVersion()); if (null === $constraint || $constraint->matches($pkgConstraint)) { $packages[] = $package; } @@ -585,6 +584,11 @@ class ComposerRepository extends ArrayRepository $filename = $this->baseUrl.'/'.$filename; } + // url-encode $ signs in URLs as bad proxies choke on them + if (($pos = strpos($filename, '$')) && preg_match('{^https?://.*}i', $filename)) { + $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1); + } + $retries = 3; while ($retries--) { try { diff --git a/src/Composer/Repository/CompositeRepository.php b/src/Composer/Repository/CompositeRepository.php index d05259f88..7f29385bf 100644 --- a/src/Composer/Repository/CompositeRepository.php +++ b/src/Composer/Repository/CompositeRepository.php @@ -108,20 +108,6 @@ class CompositeRepository implements RepositoryInterface return $matches ? call_user_func_array('array_merge', $matches) : array(); } - /** - * {@inheritDoc} - */ - public function filterPackages($callback, $class = 'Composer\Package\Package') - { - foreach ($this->repositories as $repository) { - if (false === $repository->filterPackages($callback, $class)) { - return false; - } - } - - return true; - } - /** * {@inheritdoc} */ diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php new file mode 100644 index 000000000..294a7a187 --- /dev/null +++ b/src/Composer/Repository/PathRepository.php @@ -0,0 +1,144 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Config; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Version\VersionGuesser; +use Composer\Semver\VersionParser; +use Composer\Util\ProcessExecutor; + +/** + * This repository allows installing local packages that are not necessarily under their own VCS. + * + * The local packages will be symlinked when possible, else they will be copied. + * + * @code + * "require": { + * "/": "*" + * }, + * "repositories": [ + * { + * "type": "path", + * "url": "../../relative/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/package/" + * }, + * { + * "type": "path", + * "url": "/absolute/path/to/several/packages/*" + * } + * ] + * @endcode + * + * @author Samuel Roze + * @author Johann Reinke + */ +class PathRepository extends ArrayRepository +{ + /** + * @var ArrayLoader + */ + private $loader; + + /** + * @var VersionGuesser + */ + private $versionGuesser; + + /** + * @var string + */ + private $url; + + /** + * @var ProcessExecutor + */ + private $process; + + /** + * Initializes path repository. + * + * @param array $repoConfig + * @param IOInterface $io + * @param Config $config + */ + public function __construct(array $repoConfig, IOInterface $io, Config $config) + { + if (!isset($repoConfig['url'])) { + throw new \RuntimeException('You must specify the `url` configuration for the path repository'); + } + + $this->loader = new ArrayLoader(); + $this->url = $repoConfig['url']; + $this->process = new ProcessExecutor($io); + $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); + + parent::__construct(); + } + + /** + * Initializes path repository. + * + * This method will basically read the folder and add the found package. + */ + protected function initialize() + { + parent::initialize(); + + foreach ($this->getUrlMatches() as $url) { + $path = realpath($url) . '/'; + $composerFilePath = $path.'composer.json'; + + if (!file_exists($composerFilePath)) { + continue; + } + + $json = file_get_contents($composerFilePath); + $package = JsonFile::parseJson($json, $composerFilePath); + $package['dist'] = array( + 'type' => 'path', + 'url' => $url, + 'reference' => '', + ); + + if (!isset($package['version'])) { + $package['version'] = $this->versionGuesser->guessVersion($package, $path) ?: 'dev-master'; + } + if (is_dir($path.'/.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) { + $package['dist']['reference'] = trim($output); + } + + $package = $this->loader->load($package); + $this->addPackage($package); + } + + if (count($this->getPackages()) == 0) { + throw new \RuntimeException(sprintf('No `composer.json` file found in any path repository in "%s"', $this->url)); + } + } + + /** + * Get a list of all (possibly relative) path names matching given url (supports globbing). + * + * @return string[] + */ + private function getUrlMatches() + { + return glob($this->url, GLOB_MARK | GLOB_ONLYDIR); + } +} diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 1f882eb80..e2519d9b9 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,13 +13,13 @@ namespace Composer\Repository; use Composer\IO\IOInterface; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Repository\Pear\ChannelReader; use Composer\Package\CompletePackage; use Composer\Repository\Pear\ChannelInfo; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\Link; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; use Composer\Util\RemoteFilesystem; use Composer\Config; @@ -120,7 +120,7 @@ class PearRepository extends ArrayRepository // cause we've know only repository channel alias if ($channelInfo->getName() == $packageDefinition->getChannelName()) { $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName()); - $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $aliasConstraint = new Constraint('==', $normalizedVersion); $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); } @@ -129,7 +129,7 @@ class PearRepository extends ArrayRepository && ($this->vendorAlias != 'pear-'.$channelInfo->getAlias() || $channelInfo->getName() != $packageDefinition->getChannelName()) ) { $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}"; - $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $aliasConstraint = new Constraint('==', $normalizedVersion); $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index d7be746a2..44a70b83b 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -15,7 +15,7 @@ namespace Composer\Repository; use Composer\Config; use Composer\Package\PackageInterface; use Composer\Package\CompletePackage; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Plugin\PluginInterface; /** diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index aaba31a9e..94e52af5f 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -38,8 +38,8 @@ interface RepositoryInterface extends \Countable /** * Searches for the first match of a package by name and version. * - * @param string $name package name - * @param string|\Composer\Package\LinkConstraint\LinkConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface|null */ @@ -48,17 +48,17 @@ interface RepositoryInterface extends \Countable /** * Searches for all packages matching a name and optionally a version. * - * @param string $name package name - * @param string|\Composer\Package\LinkConstraint\LinkConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * - * @return array + * @return PackageInterface[] */ public function findPackages($name, $constraint = null); /** * Returns list of registered packages. * - * @return array + * @return PackageInterface[] */ public function getPackages(); diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 47c152398..372dc8979 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -15,6 +15,7 @@ namespace Composer\Repository; use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; +use Composer\Package\PackageInterface; /** * Repositories manager. @@ -42,8 +43,8 @@ class RepositoryManager /** * Searches for a package by it's name and version in managed repositories. * - * @param string $name package name - * @param string|\Composer\Package\LinkConstraint\LinkConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return PackageInterface|null */ @@ -59,8 +60,8 @@ class RepositoryManager /** * Searches for all packages matching a name and optionally a version in managed repositories. * - * @param string $name package name - * @param string|\Composer\Package\LinkConstraint\LinkConstraintInterface $constraint package version or version constraint to match against + * @param string $name package name + * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against * * @return array */ @@ -90,8 +91,8 @@ class RepositoryManager * * @param string $type repository type * @param array $config repository configuration - * @return RepositoryInterface * @throws \InvalidArgumentException if repository for provided type is not registered + * @return RepositoryInterface */ public function createRepository($type, $config) { diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index fd2e71545..517012751 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -50,6 +50,9 @@ class GitHubDriver extends VcsDriver $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = !empty($match[1]) ? $match[1] : $match[2]; + if ($this->originUrl === 'www.github.com') { + $this->originUrl = 'github.com'; + } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) { @@ -260,7 +263,7 @@ class GitHubDriver extends VcsDriver } $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3]; - if (!in_array($originUrl, $config->get('github-domains'))) { + if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) { return false; } diff --git a/src/Composer/Repository/Vcs/PerforceDriver.php b/src/Composer/Repository/Vcs/PerforceDriver.php index c7c225b27..01d4a52d8 100644 --- a/src/Composer/Repository/Vcs/PerforceDriver.php +++ b/src/Composer/Repository/Vcs/PerforceDriver.php @@ -119,7 +119,7 @@ class PerforceDriver extends VcsDriver 'type' => 'perforce', 'url' => $this->repoConfig['url'], 'reference' => $identifier, - 'p4user' => $this->perforce->getUser() + 'p4user' => $this->perforce->getUser(), ); return $source; diff --git a/src/Composer/Repository/Vcs/VcsDriverInterface.php b/src/Composer/Repository/Vcs/VcsDriverInterface.php index dd30baacd..307d63ef6 100644 --- a/src/Composer/Repository/Vcs/VcsDriverInterface.php +++ b/src/Composer/Repository/Vcs/VcsDriverInterface.php @@ -77,14 +77,13 @@ interface VcsDriverInterface * Return true if the repository has a composer file for a given identifier, * false otherwise. * - * @param string $identifier Any identifier to a specific branch/tag/commit - * @return boolean Whether the repository has a composer file for a given identifier. + * @param string $identifier Any identifier to a specific branch/tag/commit + * @return bool Whether the repository has a composer file for a given identifier. */ public function hasComposerFile($identifier); /** * Performs any cleanup necessary as the driver is not longer needed - * */ public function cleanup(); diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 034260d0c..b6ebd3b35 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -14,7 +14,7 @@ namespace Composer\Repository; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\VcsDriverInterface; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; @@ -55,7 +55,7 @@ class VcsRepository extends ArrayRepository $this->url = $repoConfig['url']; $this->io = $io; $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; - $this->verbose = $io->isVerbose(); + $this->verbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; } diff --git a/src/Composer/Repository/WritableArrayRepository.php b/src/Composer/Repository/WritableArrayRepository.php index 756f24137..041e40562 100644 --- a/src/Composer/Repository/WritableArrayRepository.php +++ b/src/Composer/Repository/WritableArrayRepository.php @@ -42,7 +42,7 @@ class WritableArrayRepository extends ArrayRepository implements WritableReposit { $packages = $this->getPackages(); - // get at most one package of each name, prefering non-aliased ones + // get at most one package of each name, preferring non-aliased ones $packagesByName = array(); foreach ($packages as $package) { if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { diff --git a/src/Composer/Repository/WritableRepositoryInterface.php b/src/Composer/Repository/WritableRepositoryInterface.php index c046c377c..4500005d9 100644 --- a/src/Composer/Repository/WritableRepositoryInterface.php +++ b/src/Composer/Repository/WritableRepositoryInterface.php @@ -41,14 +41,14 @@ interface WritableRepositoryInterface extends RepositoryInterface public function removePackage(PackageInterface $package); /** - * Get unique packages, with aliases resolved and removed + * Get unique packages (at most one package of each name), with aliases resolved and removed. * * @return PackageInterface[] */ public function getCanonicalPackages(); /** - * Forces a reload of all packages + * Forces a reload of all packages. */ public function reload(); } diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index bde7e3b6f..138f43c1a 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -35,7 +35,7 @@ class Event extends BaseEvent private $io; /** - * @var boolean Dev mode flag + * @var bool Dev mode flag */ private $devMode; @@ -45,7 +45,7 @@ class Event extends BaseEvent * @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 bool $devMode Whether or not we are in dev mode * @param array $args Arguments passed by the user * @param array $flags Optional flags to pass data not as argument */ @@ -80,7 +80,7 @@ class Event extends BaseEvent /** * Return the dev mode flag * - * @return boolean + * @return bool */ public function isDevMode() { diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php index eac5619e7..a7ddaca9b 100644 --- a/src/Composer/Util/ConfigValidator.php +++ b/src/Composer/Util/ConfigValidator.php @@ -18,6 +18,7 @@ use Composer\Package\Loader\InvalidPackageException; use Composer\Json\JsonValidationException; use Composer\IO\IOInterface; use Composer\Json\JsonFile; +use Composer\Spdx\SpdxLicenses; /** * Validates a composer configuration. @@ -37,8 +38,8 @@ class ConfigValidator /** * Validates the config, and returns the result. * - * @param string $file The path to the file - * @param integer $arrayLoaderValidationFlags Flags for ArrayLoader validation + * @param string $file The path to the file + * @param int $arrayLoaderValidationFlags Flags for ArrayLoader validation * * @return array a triple containing the errors, publishable errors, and warnings */ @@ -82,7 +83,7 @@ class ConfigValidator } } - $licenseValidator = new SpdxLicense(); + $licenseValidator = new SpdxLicenses(); if ('proprietary' !== $manifest['license'] && array() !== $manifest['license'] && !$licenseValidator->validate($manifest['license'])) { $warnings[] = sprintf( 'License %s is not a valid SPDX license identifier, see http://www.spdx.org/licenses/ if you use an open license.' @@ -123,6 +124,14 @@ class ConfigValidator } } + // check for empty psr-0/psr-4 namespace prefixes + if (isset($manifest['autoload']['psr-0'][''])) { + $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; + } + if (isset($manifest['autoload']['psr-4'][''])) { + $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; + } + try { $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); if (!isset($manifest['version'])) { diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index b10cb15d5..399491f8c 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -58,6 +58,7 @@ class ErrorHandler if (isset($a['line'], $a['file'])) { return ' '.$a['file'].':'.$a['line'].''; } + return null; }, array_slice(debug_backtrace(), 2)))); } diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 92e5e0623..93b1a4010 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -88,10 +88,9 @@ class Filesystem * Uses the process component if proc_open is enabled on the PHP * installation. * - * @param string $directory - * @return bool - * + * @param string $directory * @throws \RuntimeException + * @return bool */ public function removeDirectory($directory) { @@ -174,10 +173,9 @@ class Filesystem /** * Attempts to unlink a file and in case of failure retries after 350ms on windows * - * @param string $path - * @return bool - * + * @param string $path * @throws \RuntimeException + * @return bool */ public function unlink($path) { @@ -200,10 +198,9 @@ class Filesystem /** * Attempts to rmdir a file and in case of failure retries after 350ms on windows * - * @param string $path - * @return bool - * + * @param string $path * @throws \RuntimeException + * @return bool */ public function rmdir($path) { @@ -508,7 +505,14 @@ class Filesystem return unlink($path); } - private function isSymlinkedDirectory($directory) + /** + * return true if that directory is a symlink. + * + * @param string $directory + * + * @return bool + */ + public function isSymlinkedDirectory($directory) { if (!is_dir($directory)) { return false; diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 5d84ebbf3..1d4a98294 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -105,9 +105,7 @@ class Git return; } } - } elseif ( // private non-github repo that failed to authenticate - $this->isAuthenticationFailure($url, $match) - ) { + } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate if (strpos($match[2], '@')) { list($authParts, $match[2]) = explode('@', $match[2], 2); } @@ -119,7 +117,7 @@ class Git $defaultUsername = null; if (isset($authParts) && $authParts) { if (false !== strpos($authParts, ':')) { - list($defaultUsername,) = explode(':', $authParts, 2); + list($defaultUsername, ) = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; } diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php index 22c67c61b..8aaa7db65 100644 --- a/src/Composer/Util/GitHub.php +++ b/src/Composer/Util/GitHub.php @@ -15,7 +15,6 @@ namespace Composer\Util; use Composer\IO\IOInterface; use Composer\Config; use Composer\Downloader\TransportException; -use Composer\Json\JsonFile; /** * @author Jordi Boggiano @@ -95,6 +94,7 @@ class GitHub if (!$token) { $this->io->writeError('No token given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config github-oauth.github.com "'); + return false; } diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php index 4e5c0c76b..994149268 100644 --- a/src/Composer/Util/NoProxyPattern.php +++ b/src/Composer/Util/NoProxyPattern.php @@ -114,7 +114,7 @@ class NoProxyPattern * @param string $cidr IPv4 block in CIDR notation * @param string $ip IPv4 address * - * @return boolean + * @return bool */ private static function inCIDRBlock($cidr, $ip) { diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index 8e79f87b3..c1eaeebe9 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -297,7 +297,7 @@ class Perforce } throw new \Exception('p4 command not found in path: ' . $errorOutput); } - throw new \Exception('Invalid user name: ' . $this->getUser() ); + throw new \Exception('Invalid user name: ' . $this->getUser()); } return true; @@ -305,7 +305,7 @@ class Perforce public function connectClient() { - $p4CreateClientCommand = $this->generateP4Command('client -i < ' . str_replace( " ", "\\ ", $this->getP4ClientSpec() )); + $p4CreateClientCommand = $this->generateP4Command('client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec())); $this->executeCommand($p4CreateClientCommand); } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 65367dad1..f3ad94a82 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -37,6 +37,7 @@ class RemoteFilesystem private $retryAuthFailure; private $lastHeaders; private $storeAuth; + private $degradedMode = false; /** * Constructor. @@ -55,11 +56,11 @@ class RemoteFilesystem /** * Copy the remote file in local. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param string $fileName the local filename - * @param boolean $progress Display the progression - * @param array $options Additional context options + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param bool $progress Display the progression + * @param array $options Additional context options * * @return bool true */ @@ -71,10 +72,10 @@ class RemoteFilesystem /** * Get the content. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param boolean $progress Display the progression - * @param array $options Additional context options + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param bool $progress Display the progression + * @param array $options Additional context options * * @return bool|string The content */ @@ -106,11 +107,11 @@ class RemoteFilesystem /** * Get file content or copy action. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param array $additionalOptions context options - * @param string $fileName the local filename - * @param boolean $progress Display the progression + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param array $additionalOptions context options + * @param string $fileName the local filename + * @param bool $progress Display the progression * * @throws TransportException|\Exception * @throws TransportException When the file could not be downloaded @@ -155,6 +156,10 @@ class RemoteFilesystem if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } + if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') { + // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol + $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20); + } $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); if ($this->progress) { @@ -186,6 +191,16 @@ class RemoteFilesystem } restore_error_handler(); if (isset($e) && !$this->retry) { + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(array( + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + throw $e; } @@ -201,36 +216,51 @@ class RemoteFilesystem $result = false; } + if ($this->progress && !$this->retry) { + $this->io->overwriteError(" Downloading: 100%"); + } + // decode gzip if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http') { $decode = false; foreach ($http_response_header as $header) { if (preg_match('{^content-encoding: *gzip *$}i', $header)) { $decode = true; - continue; } elseif (preg_match('{^HTTP/}i', $header)) { + // In case of redirects, http_response_headers contains the headers of all responses + // so we reset the flag when a new response is being parsed as we are only interested in the last response $decode = false; } } if ($decode) { - if (PHP_VERSION_ID >= 50400) { - $result = zlib_decode($result); - } else { - // work around issue with gzuncompress & co that do not work with all gzip checksums - $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); - } + try { + if (PHP_VERSION_ID >= 50400) { + $result = zlib_decode($result); + } else { + // work around issue with gzuncompress & co that do not work with all gzip checksums + $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); + } + + if (!$result) { + throw new TransportException('Failed to decode zlib stream'); + } + } catch (\Exception $e) { + if ($this->degradedMode) { + throw $e; + } - if (!$result) { - throw new TransportException('Failed to decode zlib stream'); + $this->degradedMode = true; + $this->io->writeError(array( + 'Failed to decode response: '.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } } - if ($this->progress && !$this->retry) { - $this->io->overwriteError(" Downloading: 100%"); - } - // handle copy command if download was successful if (false !== $result && null !== $fileName) { if ('' === $result) { @@ -269,6 +299,16 @@ class RemoteFilesystem $e->setHeaders($http_response_header); } + if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { + $this->degradedMode = true; + $this->io->writeError(array( + ''.$e->getMessage().'', + 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', + )); + + return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); + } + throw $e; } @@ -282,12 +322,12 @@ class RemoteFilesystem /** * Get notification action. * - * @param integer $notificationCode The notification code - * @param integer $severity The severity level + * @param int $notificationCode The notification code + * @param int $severity The severity level * @param string $message The message - * @param integer $messageCode The message code - * @param integer $bytesTransferred The loaded size - * @param integer $bytesMax The total size + * @param int $messageCode The message code + * @param int $bytesTransferred The loaded size + * @param int $bytesMax The total size * @throws TransportException */ protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) @@ -302,7 +342,6 @@ class RemoteFilesystem } $this->promptAuthAndRetry($messageCode); - break; } break; @@ -314,7 +353,6 @@ class RemoteFilesystem } $this->promptAuthAndRetry($messageCode, $message); - break; } break; @@ -326,11 +364,7 @@ class RemoteFilesystem case STREAM_NOTIFY_PROGRESS: if ($this->bytesMax > 0 && $this->progress) { - $progression = 0; - - if ($this->bytesMax > 0) { - $progression = round($bytesTransferred / $this->bytesMax * 100); - } + $progression = round($bytesTransferred / $this->bytesMax * 100); if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; @@ -402,7 +436,7 @@ class RemoteFilesystem php_uname('s'), php_uname('r'), $phpVersion - ) + ), ); if (extension_loaded('zlib')) { @@ -410,8 +444,12 @@ class RemoteFilesystem } $options = array_replace_recursive($this->options, $additionalOptions); - $options['http']['protocol_version'] = 1.1; - $headers[] = 'Connection: close'; + if (!$this->degradedMode) { + // degraded mode disables HTTP/1.1 which causes issues with some bad + // proxies/software due to the use of chunked encoding + $options['http']['protocol_version'] = 1.1; + $headers[] = 'Connection: close'; + } if ($this->io->hasAuthentication($originUrl)) { $auth = $this->io->getAuthentication($originUrl); diff --git a/src/Composer/Util/SpdxLicense.php b/src/Composer/Util/SpdxLicense.php index 650b918e4..8934de679 100644 --- a/src/Composer/Util/SpdxLicense.php +++ b/src/Composer/Util/SpdxLicense.php @@ -12,222 +12,13 @@ namespace Composer\Util; -use Composer\Json\JsonFile; +use Composer\Spdx\SpdxLicenses; + +@trigger_error('The ' . __NAMESPACE__ . '\SpdxLicense class is deprecated, use Composer\Spdx\SpdxLicenses instead.', E_USER_DEPRECATED); /** - * Supports composer array and SPDX tag notation for disjunctive/conjunctive - * licenses. - * - * @author Tom Klingenberg + * @deprecated use Composer\Spdx\SpdxLicenses instead */ -class SpdxLicense +class SpdxLicense extends SpdxLicenses { - /** - * @var array - */ - private $licenses; - - public function __construct() - { - $this->loadLicenses(); - } - - private function loadLicenses() - { - if (is_array($this->licenses)) { - return $this->licenses; - } - - $jsonFile = new JsonFile(__DIR__ . '/../../../res/spdx-licenses.json'); - $this->licenses = $jsonFile->read(); - - return $this->licenses; - } - - /** - * Returns license metadata by license identifier. - * - * @param string $identifier - * - * @return array|null - */ - public function getLicenseByIdentifier($identifier) - { - if (!isset($this->licenses[$identifier])) { - return; - } - - $license = $this->licenses[$identifier]; - - // add URL for the license text (it's not included in the json) - $license[2] = 'http://spdx.org/licenses/' . $identifier . '#licenseText'; - - return $license; - } - - /** - * Returns the short identifier of a license by full name. - * - * @param string $identifier - * - * @return string - */ - public function getIdentifierByName($name) - { - foreach ($this->licenses as $identifier => $licenseData) { - if ($licenseData[0] === $name) { // key 0 = fullname - return $identifier; - } - } - } - - /** - * Returns the OSI Approved status for a license by identifier. - * - * @return bool - */ - public function isOsiApprovedByIdentifier($identifier) - { - return $this->licenses[$identifier][1]; // key 1 = osi approved - } - - /** - * Check, if the identifier for a license is valid. - * - * @param string $identifier - * - * @return bool - */ - private function isValidLicenseIdentifier($identifier) - { - $identifiers = array_keys($this->licenses); - - return in_array($identifier, $identifiers); - } - - /** - * @param array|string $license - * - * @return bool - * @throws \InvalidArgumentException - */ - public function validate($license) - { - if (is_array($license)) { - $count = count($license); - if ($count !== count(array_filter($license, 'is_string'))) { - throw new \InvalidArgumentException('Array of strings expected.'); - } - $license = $count > 1 ? '('.implode(' or ', $license).')' : (string) reset($license); - } - - if (!is_string($license)) { - throw new \InvalidArgumentException(sprintf( - 'Array or String expected, %s given.', gettype($license) - )); - } - - return $this->isValidLicenseString($license); - } - - /** - * @param string $license - * - * @return bool - * @throws \RuntimeException - */ - private function isValidLicenseString($license) - { - $tokens = array( - 'po' => '\(', - 'pc' => '\)', - 'op' => '(?:or|and)', - 'lix' => '(?:NONE|NOASSERTION)', - 'lir' => 'LicenseRef-\d+', - 'lic' => '[-+_.a-zA-Z0-9]{3,}', - 'ws' => '\s+', - '_' => '.', - ); - - $next = function () use ($license, $tokens) { - static $offset = 0; - - if ($offset >= strlen($license)) { - return null; - } - - foreach ($tokens as $name => $token) { - if (false === $r = preg_match('{' . $token . '}', $license, $matches, PREG_OFFSET_CAPTURE, $offset)) { - throw new \RuntimeException('Pattern for token %s failed (regex error).', $name); - } - if ($r === 0) { - continue; - } - if ($matches[0][1] !== $offset) { - continue; - } - $offset += strlen($matches[0][0]); - - return array($name, $matches[0][0]); - } - - throw new \RuntimeException('At least the last pattern needs to match, but it did not (dot-match-all is missing?).'); - }; - - $open = 0; - $require = 1; - $lastop = null; - - while (list($token, $string) = $next()) { - switch ($token) { - case 'po': - if ($open || !$require) { - return false; - } - $open = 1; - break; - case 'pc': - if ($open !== 1 || $require || !$lastop) { - return false; - } - $open = 2; - break; - case 'op': - if ($require || !$open) { - return false; - } - $lastop || $lastop = $string; - if ($lastop !== $string) { - return false; - } - $require = 1; - break; - case 'lix': - if ($open) { - return false; - } - goto lir; - case 'lic': - if (!$this->isValidLicenseIdentifier($string)) { - return false; - } - // Fall-through intended - case 'lir': - lir: - if (!$require) { - return false; - } - $require = 0; - break; - case 'ws': - break; - case '_': - return false; - default: - throw new \RuntimeException(sprintf('Unparsed token: %s.', print_r($token, true))); - } - } - - return !($open % 2 || $require); - } } diff --git a/src/Composer/Util/SpdxLicensesUpdater.php b/src/Composer/Util/SpdxLicensesUpdater.php deleted file mode 100644 index efcc30065..000000000 --- a/src/Composer/Util/SpdxLicensesUpdater.php +++ /dev/null @@ -1,67 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Util; - -use Composer\Json\JsonFormatter; - -/** - * The SPDX Licenses Updater scrapes licenses from the spdx website - * and updates the "res/spdx-licenses.json" file accordingly. - * - * The class is used by the update script "bin/update-spdx-licenses". - */ -class SpdxLicensesUpdater -{ - private $licensesUrl = 'http://www.spdx.org/licenses/'; - - public function update() - { - $json = json_encode($this->getLicenses(), true); - $prettyJson = JsonFormatter::format($json, true, true); - file_put_contents(__DIR__ . '/../../../res/spdx-licenses.json', $prettyJson); - } - - private function getLicenses() - { - $licenses = array(); - - $dom = new \DOMDocument; - $dom->loadHTMLFile($this->licensesUrl); - - $xPath = new \DOMXPath($dom); - $trs = $xPath->query('//table//tbody//tr'); - - // iterate over each row in the table - foreach ($trs as $tr) { - $tds = $tr->getElementsByTagName('td'); // get the columns in this row - - if ($tds->length < 4) { - throw new \Exception('Obtaining the license table failed. Wrong table format. Found less than 4 cells in a row.'); - } - - if (trim($tds->item(3)->nodeValue) == 'License Text') { - $fullname = trim($tds->item(0)->nodeValue); - $identifier = trim($tds->item(1)->nodeValue); - $osiApproved = ((isset($tds->item(2)->nodeValue) && $tds->item(2)->nodeValue === 'Y')) ? true : false; - - // The license URL is not scraped intentionally to keep json file size low. - // It's build when requested, see SpdxLicense->getLicenseByIdentifier(). - //$licenseURL = = $tds->item(3)->getAttribute('href'); - - $licenses += array($identifier => array($fullname, $osiApproved)); - } - } - - return $licenses; - } -} diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index ffeaf3f83..cd8c05d92 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -26,8 +26,8 @@ final class StreamContextFactory * @param string $url URL the context is to be used for * @param array $defaultOptions Options to merge with the default * @param array $defaultParams Parameters to specify on the context - * @return resource Default context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled + * @return resource Default context */ public static function getContext($url, array $defaultOptions = array(), array $defaultParams = array()) { diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index d5694b3be..4117499c8 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -54,7 +54,7 @@ class Svn protected $process; /** - * @var integer + * @var int */ protected $qtyAuthTries = 0; @@ -94,9 +94,8 @@ class Svn * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * - * @return string - * * @throws \RuntimeException + * @return string */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { @@ -147,7 +146,7 @@ class Svn } /** - * @param boolean $cacheCredentials + * @param bool $cacheCredentials */ public function setCacheCredentials($cacheCredentials) { @@ -157,8 +156,8 @@ class Svn /** * Repositories requests credentials, let's put them in. * - * @return \Composer\Util\Svn * @throws \RuntimeException + * @return \Composer\Util\Svn */ protected function doAuthDance() { @@ -229,8 +228,8 @@ class Svn /** * Get the password for the svn command. Can be empty. * - * @return string * @throws \LogicException + * @return string */ protected function getPassword() { @@ -244,8 +243,8 @@ class Svn /** * Get the username for the svn command. * - * @return string * @throws \LogicException + * @return string */ protected function getUsername() { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index dbb7d2fad..8c7727058 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -200,7 +200,7 @@ class AutoloadGeneratorTest extends TestCase $package->setDevAutoload(array( 'files' => array('devfiles/foo.php'), 'psr-0' => array( - 'Main' => 'tests/' + 'Main' => 'tests/', ), )); @@ -381,7 +381,7 @@ class AutoloadGeneratorTest extends TestCase $package->setAutoload(array( 'psr-0' => array('Prefix' => 'foo/bar/non/existing/'), - 'psr-4' => array('Prefix\\' => 'foo/bar/non/existing2/') + 'psr-4' => array('Prefix\\' => 'foo/bar/non/existing2/'), )); $this->repository->expects($this->once()) @@ -512,35 +512,35 @@ class AutoloadGeneratorTest extends TestCase $packages[] = $a = new Package('a/a', '1.0', '1.0'); $packages[] = $b = new Package('b/b', '1.0', '1.0'); $packages[] = $c = new Package('c/c', '1.0', '1.0'); - $a->setAutoload(array('classmap' => array(''))); - $b->setAutoload(array('classmap' => array('test.php'))); - $c->setAutoload(array('classmap' => array('./'))); + $a->setAutoload(array('psr-4' => array('' => 'src/'))); + $b->setAutoload(array('psr-4' => array('' => './'))); + $c->setAutoload(array('psr-4' => array('' => 'foo/'))); $this->repository->expects($this->once()) ->method('getCanonicalPackages') ->will($this->returnValue($packages)); - $this->configValueMap['classmap-authoritative'] = true; - $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b'); $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo'); - file_put_contents($this->vendorDir.'/a/a/src/a.php', 'vendorDir.'/b/b/test.php', 'vendorDir.'/c/c/foo/test.php', 'vendorDir.'/a/a/src/ClassMapFoo.php', 'vendorDir.'/b/b/ClassMapBar.php', 'vendorDir.'/c/c/foo/ClassMapBaz.php', 'generator->setClassMapAuthoritative(true); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); + $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated."); $this->assertEquals( array( - 'ClassMapBar' => $this->vendorDir.'/b/b/test.php', - 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php', - 'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php', + 'ClassMapBar' => $this->vendorDir.'/b/b/ClassMapBar.php', + 'ClassMapBaz' => $this->vendorDir.'/c/c/foo/ClassMapBaz.php', + 'ClassMapFoo' => $this->vendorDir.'/a/a/src/ClassMapFoo.php', ), include $this->vendorDir.'/composer/autoload_classmap.php' ); - $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); + $this->assertAutoloadFiles('classmap8', $this->vendorDir.'/composer', 'classmap'); $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php')); } @@ -585,6 +585,71 @@ class AutoloadGeneratorTest extends TestCase $this->assertTrue(function_exists('testFilesAutoloadGenerationRoot')); } + public function testFilesAutoloadGenerationRemoveExtraEntitiesFromAutoloadFiles() + { + $autoloadPackage = new Package('a', '1.0', '1.0'); + $autoloadPackage->setAutoload(array('files' => array('root.php'))); + $autoloadPackage->setIncludePaths(array('/lib', '/src')); + + $notAutoloadPackage = new Package('a', '1.0', '1.0'); + + $autoloadPackages = array(); + $autoloadPackages[] = $a = new Package('a/a', '1.0', '1.0'); + $autoloadPackages[] = $b = new Package('b/b', '1.0', '1.0'); + $autoloadPackages[] = $c = new Package('c/c', '1.0', '1.0'); + $a->setAutoload(array('files' => array('test.php'))); + $a->setIncludePaths(array('lib1', 'src1')); + $b->setAutoload(array('files' => array('test2.php'))); + $b->setIncludePaths(array('lib2')); + $c->setAutoload(array('files' => array('test3.php', 'foo/bar/test4.php'))); + $c->setIncludePaths(array('lib3')); + $c->setTargetDir('foo/bar'); + + $notAutoloadPackages = array(); + $notAutoloadPackages[] = $a = new Package('a/a', '1.0', '1.0'); + $notAutoloadPackages[] = $b = new Package('b/b', '1.0', '1.0'); + $notAutoloadPackages[] = $c = new Package('c/c', '1.0', '1.0'); + + $this->repository->expects($this->at(0)) + ->method('getCanonicalPackages') + ->will($this->returnValue($autoloadPackages)); + + $this->repository->expects($this->at(1)) + ->method('getCanonicalPackages') + ->will($this->returnValue($notAutoloadPackages)); + + $this->repository->expects($this->at(2)) + ->method('getCanonicalPackages') + ->will($this->returnValue($notAutoloadPackages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo/bar'); + file_put_contents($this->vendorDir.'/a/a/test.php', 'vendorDir.'/b/b/test2.php', 'vendorDir.'/c/c/foo/bar/test3.php', 'vendorDir.'/c/c/foo/bar/test4.php', 'workingDir.'/root.php', 'generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/include_paths_functions.php', $this->vendorDir.'/composer/include_paths.php'); + + $this->generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_files_functions_with_removed_extra.php', $this->vendorDir.'/composer/autoload_files.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/include_paths_functions_with_removed_extra.php', $this->vendorDir.'/composer/include_paths.php'); + + $this->generator->dump($this->config, $this->repository, $notAutoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_real.php'); + $this->assertFileNotExists($this->vendorDir.'/composer/autoload_files.php'); + $this->assertFileNotExists($this->vendorDir.'/composer/include_paths.php'); + } + public function testFilesAutoloadOrderByDependencies() { $package = new Package('a', '1.0', '1.0'); @@ -655,7 +720,7 @@ class AutoloadGeneratorTest extends TestCase $mainPackage = new Package('z', '1.0', '1.0'); $mainPackage->setAutoload(array( 'psr-0' => array('A\\B' => $this->workingDir.'/lib'), - 'classmap' => array($this->workingDir.'/src') + 'classmap' => array($this->workingDir.'/src'), )); $mainPackage->setRequires(array(new Link('z', 'a/a'))); diff --git a/tests/Composer/Test/Autoload/ClassLoaderTest.php b/tests/Composer/Test/Autoload/ClassLoaderTest.php index ec4156a15..50772101e 100644 --- a/tests/Composer/Test/Autoload/ClassLoaderTest.php +++ b/tests/Composer/Test/Autoload/ClassLoaderTest.php @@ -9,17 +9,6 @@ use Composer\Autoload\ClassLoader; */ class ClassLoaderTest extends \PHPUnit_Framework_TestCase { - public function testLoadClassDotPhp() - { - $loader = new ClassLoader(); - $loader->add('DirDotPhp\\', __DIR__ . '/Fixtures'); - $loader->addPsr4('DirDotPhp\\', __DIR__ . '/Fixtures/DirDotPhp/psr4'); - - $class = 'DirDotPhp\\Dir'; - $loader->loadClass($class); - $this->assertTrue(class_exists($class, false), "->loadClass() loads '$class'."); - } - /** * Tests regular PSR-0 and PSR-4 class loading. * diff --git a/tests/Composer/Test/Autoload/Fixtures/DirDotPhp/Dir.php b/tests/Composer/Test/Autoload/Fixtures/DirDotPhp/Dir.php deleted file mode 100644 index 9d9d62d8c..000000000 --- a/tests/Composer/Test/Autoload/Fixtures/DirDotPhp/Dir.php +++ /dev/null @@ -1,6 +0,0 @@ - $vendorDir . '/b/b/ClassMapBar.php', + 'ClassMapBaz' => $vendorDir . '/c/c/foo/ClassMapBaz.php', + 'ClassMapFoo' => $vendorDir . '/a/a/src/ClassMapFoo.php', +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_files_functions_with_removed_extra.php b/tests/Composer/Test/Autoload/Fixtures/autoload_files_functions_with_removed_extra.php new file mode 100644 index 000000000..8d124df37 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_files_functions_with_removed_extra.php @@ -0,0 +1,10 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequireFilesAutoload($file); + } + + return $loader; + } +} + +function composerRequireFilesAutoload($file) +{ + require $file; +} diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php new file mode 100644 index 000000000..205cd7158 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php @@ -0,0 +1,50 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} + +function composerRequireFilesAutoload($file) +{ + require $file; +} diff --git a/tests/Composer/Test/Autoload/Fixtures/include_paths_functions.php b/tests/Composer/Test/Autoload/Fixtures/include_paths_functions.php new file mode 100644 index 000000000..f1095cf67 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/include_paths_functions.php @@ -0,0 +1,15 @@ + array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true) + 'packagist' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), ), array(), ); @@ -44,7 +44,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase array(), array( array('packagist' => false), - ) + ), ); $data['local config adds above defaults'] = array( @@ -62,7 +62,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $data['system config adds above core defaults'] = array( array( 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), - 'packagist' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true) + 'packagist' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true), ), array(), array( @@ -73,11 +73,11 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $data['local config can disable repos by name and re-add them anonymously to bring them above system config'] = array( array( 0 => array('type' => 'composer', 'url' => 'http://packagist.org'), - 'example.com' => array('type' => 'composer', 'url' => 'http://example.com') + 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), ), array( array('packagist' => false), - array('type' => 'composer', 'url' => 'http://packagist.org') + array('type' => 'composer', 'url' => 'http://packagist.org'), ), array( 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), @@ -87,10 +87,10 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $data['local config can override by name to bring a repo above system config'] = array( array( 'packagist' => array('type' => 'composer', 'url' => 'http://packagistnew.org'), - 'example.com' => array('type' => 'composer', 'url' => 'http://example.com') + 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), ), array( - 'packagist' => array('type' => 'composer', 'url' => 'http://packagistnew.org') + 'packagist' => array('type' => 'composer', 'url' => 'http://packagistnew.org'), ), array( 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), @@ -139,7 +139,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $config->merge(array('config' => array( 'bin-dir' => '$HOME/foo', 'cache-dir' => '/baz/', - 'vendor-dir' => 'vendor' + 'vendor-dir' => 'vendor', ))); $home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/'); @@ -153,7 +153,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $config = new Config(false, '/foo/bar'); $config->merge(array('config' => array( 'bin-dir' => '{$vendor-dir}/foo', - 'vendor-dir' => 'vendor' + 'vendor-dir' => 'vendor', ))); $this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir')); diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 993cbffe8..d694813c6 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -18,7 +18,7 @@ use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Pool; use Composer\Package\Link; use Composer\Package\AliasPackage; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; use Composer\TestCase; class DefaultPolicyTest extends TestCase @@ -158,7 +158,7 @@ class DefaultPolicyTest extends TestCase $this->pool->addRepository($this->repoImportant); $this->pool->addRepository($this->repo); - $packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev')); + $packages = $this->pool->whatProvides('a', new Constraint('=', '2.1.9999999.9999999-dev')); $literals = array(); foreach ($packages as $package) { $literals[] = $package->getId(); @@ -176,8 +176,8 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '2.0')); - $packageA->setProvides(array(new Link('A', 'X', new VersionConstraint('==', '1.0'), 'provides'))); - $packageB->setProvides(array(new Link('B', 'X', new VersionConstraint('==', '1.0'), 'provides'))); + $packageA->setProvides(array(new Link('A', 'X', new Constraint('==', '1.0'), 'provides'))); + $packageB->setProvides(array(new Link('B', 'X', new Constraint('==', '1.0'), 'provides'))); $this->pool->addRepository($this->repo); @@ -194,7 +194,7 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); $this->repo->addPackage($packageB = $this->getPackage('B', '2.0')); - $packageB->setReplaces(array(new Link('B', 'A', new VersionConstraint('==', '1.0'), 'replaces'))); + $packageB->setReplaces(array(new Link('B', 'A', new Constraint('==', '1.0'), 'replaces'))); $this->pool->addRepository($this->repo); @@ -212,8 +212,8 @@ class DefaultPolicyTest extends TestCase $this->repo->addPackage($packageB = $this->getPackage('vendor-b/replacer', '1.0')); $this->repo->addPackage($packageA = $this->getPackage('vendor-a/replacer', '1.0')); - $packageA->setReplaces(array(new Link('vendor-a/replacer', 'vendor-a/package', new VersionConstraint('==', '1.0'), 'replaces'))); - $packageB->setReplaces(array(new Link('vendor-b/replacer', 'vendor-a/package', new VersionConstraint('==', '1.0'), 'replaces'))); + $packageA->setReplaces(array(new Link('vendor-a/replacer', 'vendor-a/package', new Constraint('==', '1.0'), 'replaces'))); + $packageB->setReplaces(array(new Link('vendor-b/replacer', 'vendor-a/package', new Constraint('==', '1.0'), 'replaces'))); $this->pool->addRepository($this->repo); diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index 6688b24aa..f71af15bd 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Rule; +use Composer\DependencyResolver\RuleSet; use Composer\DependencyResolver\Pool; use Composer\Repository\ArrayRepository; use Composer\TestCase; @@ -30,15 +31,8 @@ class RuleTest extends TestCase { $rule = new Rule(array(123), 'job1', null); - $this->assertEquals(substr(md5('123'), 0, 5), $rule->getHash()); - } - - public function testSetAndGetId() - { - $rule = new Rule(array(), 'job1', null); - $rule->setId(666); - - $this->assertEquals(666, $rule->getId()); + $hash = unpack('ihash', md5('123', true)); + $this->assertEquals($hash['hash'], $rule->getHash()); } public function testEqualsForRulesWithDifferentHashes() @@ -68,9 +62,9 @@ class RuleTest extends TestCase public function testSetAndGetType() { $rule = new Rule(array(), 'job1', null); - $rule->setType('someType'); + $rule->setType(RuleSet::TYPE_JOB); - $this->assertEquals('someType', $rule->getType()); + $this->assertEquals(RuleSet::TYPE_JOB, $rule->getType()); } public function testEnable() diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index e50b38b93..2c67d7464 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -19,7 +19,7 @@ use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; use Composer\Package\Link; use Composer\TestCase; -use Composer\Package\LinkConstraint\MultiConstraint; +use Composer\Semver\Constraint\MultiConstraint; class SolverTest extends TestCase { @@ -503,7 +503,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageX = $this->getPackage('X', '1.0')); $packageX->setRequires(array( 'a' => new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'), - 'b' => new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires') + 'b' => new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'), )); $this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0')); @@ -522,7 +522,7 @@ class SolverTest extends TestCase $this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0')); $packageS->setReplaces(array( 'a' => new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), - 'b' => new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces') + 'b' => new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), )); $this->reposComplete(); @@ -850,13 +850,13 @@ class SolverTest extends TestCase $result[] = array( 'job' => 'update', 'from' => $operation->getInitialPackage(), - 'to' => $operation->getTargetPackage() + 'to' => $operation->getTargetPackage(), ); } else { $job = ('uninstall' === $operation->getJobType() ? 'remove' : 'install'); $result[] = array( 'job' => $job, - 'package' => $operation->getPackage() + 'package' => $operation->getPackage(), ); } } diff --git a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php index 5429d109d..10ac27955 100644 --- a/tests/Composer/Test/Downloader/PearPackageExtractorTest.php +++ b/tests/Composer/Test/Downloader/PearPackageExtractorTest.php @@ -46,7 +46,7 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase 'to' => 'PEAR/Frontend/Gtk/xpm/black_close_icon.xpm', 'role' => 'php', 'tasks' => array(), - ) + ), ); $this->assertSame($expectedFileActions, $fileActions); } @@ -69,7 +69,7 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase 'to' => 'Net/URL.php', 'role' => 'php', 'tasks' => array(), - ) + ), ); $this->assertSame($expectedFileActions, $fileActions); } @@ -99,16 +99,16 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase 'role' => 'php', 'tasks' => array(), ), - 'php/Test.php' => array ( + 'php/Test.php' => array( 'from' => 'Zend_Authentication-2.0.0beta4/php/Test.php', 'to' => '/php/Test.php', 'role' => 'script', - 'tasks' => array ( - array ( + 'tasks' => array( + array( 'from' => '@version@', 'to' => 'version', - ) - ) + ), + ), ), 'renamedFile.php' => array( 'from' => 'Zend_Authentication-2.0.0beta4/renamedFile.php', diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 06d9c652d..925772d42 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -13,7 +13,6 @@ namespace Composer\Test\EventDispatcher; use Composer\EventDispatcher\Event; -use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallerEvents; use Composer\TestCase; use Composer\Script\ScriptEvents; @@ -29,10 +28,18 @@ class EventDispatcherTest extends TestCase { $io = $this->getMock('Composer\IO\IOInterface'); $dispatcher = $this->getDispatcherStubForListenersTest(array( - 'Composer\Test\EventDispatcher\EventDispatcherTest::call' + 'Composer\Test\EventDispatcher\EventDispatcherTest::call', ), $io); - $io->expects($this->once()) + $io->expects($this->at(0)) + ->method('isVerbose') + ->willReturn(0); + + $io->expects($this->at(1)) + ->method('writeError') + ->with('> Composer\Test\EventDispatcher\EventDispatcherTest::call'); + + $io->expects($this->at(2)) ->method('writeError') ->with('Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception'); @@ -43,7 +50,7 @@ class EventDispatcherTest extends TestCase { $io = $this->getMock('Composer\IO\IOInterface'); $dispatcher = $this->getDispatcherStubForListenersTest(array( - 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent' + 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent', ), $io); $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false)); @@ -53,7 +60,7 @@ class EventDispatcherTest extends TestCase { $io = $this->getMock('Composer\IO\IOInterface'); $dispatcher = $this->getDispatcherStubForListenersTest(array( - 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent' + 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent', ), $io); $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false)); @@ -94,12 +101,11 @@ class EventDispatcherTest extends TestCase $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), - $this->getMock('Composer\IO\IOInterface'), + $io = $this->getMock('Composer\IO\IOInterface'), $process, )) ->setMethods(array( 'getListeners', - 'executeEventPhpScript', )) ->getMock(); @@ -112,14 +118,26 @@ class EventDispatcherTest extends TestCase 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod', 'echo -n bar', ); + $dispatcher->expects($this->atLeastOnce()) ->method('getListeners') ->will($this->returnValue($listeners)); - $dispatcher->expects($this->once()) - ->method('executeEventPhpScript') - ->with('Composer\Test\EventDispatcher\EventDispatcherTest', 'someMethod') - ->will($this->returnValue(true)); + $io->expects($this->any()) + ->method('isVerbose') + ->willReturn(1); + + $io->expects($this->at(1)) + ->method('writeError') + ->with($this->equalTo('> post-install-cmd: echo -n foo')); + + $io->expects($this->at(3)) + ->method('writeError') + ->with($this->equalTo('> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod')); + + $io->expects($this->at(5)) + ->method('writeError') + ->with($this->equalTo('> post-install-cmd: echo -n bar')); $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } @@ -150,12 +168,12 @@ class EventDispatcherTest extends TestCase ); } - public function testDispatcherOutputsCommands() + public function testDispatcherOutputsCommand() { $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') ->setConstructorArgs(array( $this->getMock('Composer\Composer'), - $this->getMock('Composer\IO\IOInterface'), + $io = $this->getMock('Composer\IO\IOInterface'), new ProcessExecutor, )) ->setMethods(array('getListeners')) @@ -166,6 +184,10 @@ class EventDispatcherTest extends TestCase ->method('getListeners') ->will($this->returnValue($listener)); + $io->expects($this->once()) + ->method('writeError') + ->with($this->equalTo('> echo foo')); + ob_start(); $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); $this->assertEquals('foo', trim(ob_get_clean())); @@ -188,7 +210,15 @@ class EventDispatcherTest extends TestCase ->method('getListeners') ->will($this->returnValue($listener)); - $io->expects($this->once()) + $io->expects($this->at(0)) + ->method('isVerbose') + ->willReturn(0); + + $io->expects($this->at(1)) + ->method('writeError') + ->willReturn('> exit 1'); + + $io->expects($this->at(2)) ->method('writeError') ->with($this->equalTo('Script '.$code.' handling the post-install-cmd event returned with an error')); diff --git a/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test new file mode 100644 index 000000000..1e528d047 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test @@ -0,0 +1,57 @@ +--TEST-- +Converting from one VCS type to another (including an URL change) should update the lock file. +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "a/a", "version": "1.0.0", + "source": { "reference": "new-git-ref", "type": "git", "url": "new-git-url" } + } + ] + } + ], + "require": { + "a/a": "1.0.0" + } +} +--INSTALLED-- +[ + { + "name": "a/a", "version": "1.0.0", + "source": { "reference": "old-hg-ref", "type": "hg", "url": "old-hg-url" } + } +] +--LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "1.0.0", + "source": { "reference": "old-hg-ref", "type": "hg", "url": "old-hg-url" } + } + ] +} +--RUN-- +update +--EXPECT-LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "1.0.0", + "source": { "reference": "new-git-ref", "type": "git", "url": "new-git-url" }, + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT-- + diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 7fa85d76d..5339b8ff3 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -14,7 +14,6 @@ namespace Composer\Test; use Composer\Installer; use Composer\Console\Application; -use Composer\Config; use Composer\Json\JsonFile; use Composer\Repository\ArrayRepository; use Composer\Repository\RepositoryManager; @@ -108,7 +107,7 @@ class InstallerTest extends TestCase $a, new ArrayRepository(array($b)), array( - 'install' => array($b) + 'install' => array($b), ), ); @@ -128,7 +127,7 @@ class InstallerTest extends TestCase $a, new ArrayRepository(array($a, $b)), array( - 'install' => array($b) + 'install' => array($b), ), ); @@ -191,7 +190,8 @@ class InstallerTest extends TestCase })); } - $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), md5(json_encode($composerConfig))); + $contents = json_encode($composerConfig); + $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); $composer->setLocker($locker); $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); @@ -237,6 +237,7 @@ class InstallerTest extends TestCase if ($expectLock) { unset($actualLock['hash']); + unset($actualLock['content-hash']); unset($actualLock['_readme']); $this->assertEquals($expectLock, $actualLock); } diff --git a/tests/Composer/Test/Json/ComposerSchemaTest.php b/tests/Composer/Test/Json/ComposerSchemaTest.php index 1b8805f48..51949797c 100644 --- a/tests/Composer/Test/Json/ComposerSchemaTest.php +++ b/tests/Composer/Test/Json/ComposerSchemaTest.php @@ -23,18 +23,18 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase { $json = '{ }'; $this->assertEquals(array( - array('property' => '', 'message' => 'the property name is required'), - array('property' => '', 'message' => 'the property description is required'), + array('property' => 'name', 'message' => 'The property name is required'), + array('property' => 'description', 'message' => 'The property description is required'), ), $this->check($json)); $json = '{ "name": "vendor/package" }'; $this->assertEquals(array( - array('property' => '', 'message' => 'the property description is required'), + array('property' => 'description', 'message' => 'The property description is required'), ), $this->check($json)); $json = '{ "description": "generic description" }'; $this->assertEquals(array( - array('property' => '', 'message' => 'the property name is required'), + array('property' => 'name', 'message' => 'The property name is required'), ), $this->check($json)); } @@ -44,7 +44,7 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( array( 'property' => 'minimum-stability', - 'message' => 'does not match the regex pattern ^dev|alpha|beta|rc|RC|stable$' + 'message' => 'Does not match the regex pattern ^dev|alpha|beta|rc|RC|stable$', ), ), $this->check($json), 'empty string'); @@ -52,7 +52,7 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( array( 'property' => 'minimum-stability', - 'message' => 'does not match the regex pattern ^dev|alpha|beta|rc|RC|stable$' + 'message' => 'Does not match the regex pattern ^dev|alpha|beta|rc|RC|stable$', ), ), $this->check($json), 'dummy'); diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 279ccd939..867c69307 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -54,6 +54,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testParseErrorDetectSingleQuotes() { + if (defined('JSON_PARSER_NOTSTRICT')) { + $this->markTestSkipped('jsonc issue, see https://github.com/remicollet/pecl-json-c/issues/23'); + } $json = '{ \'foo\': "bar" }'; diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index 0e974ad35..fd6d49584 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -38,7 +38,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase " \"require\": {\n". " \"vendor/baz\": \"qux\"\n". " }\n". -"}\n" +"}\n", ), array( '{ @@ -53,7 +53,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -68,7 +68,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -87,7 +87,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -107,7 +107,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -127,7 +127,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -159,7 +159,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } }] } -' +', ), array( '{ @@ -188,7 +188,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "foo": "qux" } } -' +', ), array( '{ @@ -207,7 +207,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "foo": "qux" } } -' +', ), array( '{ @@ -229,7 +229,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "foo": "qux" } } -' +', ), array( '{ @@ -277,7 +277,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "foo": "qux" } } -' +', ), ); } @@ -311,7 +311,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "vendor/baz": "qux" } } -' +', ), array( '{ @@ -329,7 +329,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "foo": "bar" } } -' +', ), array( '{ @@ -401,7 +401,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on simple ones last' => array( '{ @@ -426,7 +426,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on simple ones unique' => array( '{ @@ -443,7 +443,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "repositories": { } } -' +', ), 'works on simple ones middle' => array( '{ @@ -476,7 +476,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on undefined ones' => array( '{ @@ -497,7 +497,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on child having unmatched name' => array( '{ @@ -518,7 +518,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on child having duplicate name' => array( '{ @@ -541,7 +541,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } } } -' +', ), 'works on empty repos' => array( '{ @@ -549,19 +549,19 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } }', 'bar', - true + true, ), 'works on empty repos2' => array( '{ "repositories": {} }', 'bar', - true + true, ), 'works on missing repos' => array( "{\n}", 'bar', - true + true, ), 'works on deep repos' => array( '{ @@ -577,7 +577,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase "repositories": { } } -' +', ), 'fails on deep repos with borked texts' => array( '{ @@ -588,7 +588,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } }', 'bar', - false + false, ), 'fails on deep repos with borked texts2' => array( '{ @@ -599,7 +599,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase } }', 'bar', - false + false, ), 'fails on deep arrays with borked texts' => array( '{ @@ -610,7 +610,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase ] }', 'bar', - false + false, ), 'fails on deep arrays with borked texts2' => array( '{ @@ -621,7 +621,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase ] }', 'bar', - false + false, ), ); } diff --git a/tests/Composer/Test/Json/JsonValidationExceptionTest.php b/tests/Composer/Test/Json/JsonValidationExceptionTest.php index 76959d688..31ba9b7bf 100644 --- a/tests/Composer/Test/Json/JsonValidationExceptionTest.php +++ b/tests/Composer/Test/Json/JsonValidationExceptionTest.php @@ -36,7 +36,7 @@ class JsonValidationExceptionTest extends \PHPUnit_Framework_TestCase { return array( array('test message', array()), - array(null, null) + array(null, null), ); } } diff --git a/tests/Composer/Test/Mock/ProcessExecutorMock.php b/tests/Composer/Test/Mock/ProcessExecutorMock.php deleted file mode 100644 index 13ea9792b..000000000 --- a/tests/Composer/Test/Mock/ProcessExecutorMock.php +++ /dev/null @@ -1,31 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Mock; - -use Composer\Util\ProcessExecutor; - -class ProcessExecutorMock extends ProcessExecutor -{ - private $execute; - - public function __construct(\Closure $execute) - { - $this->execute = $execute; - } - - public function execute($command, &$output = null, $cwd = null) - { - $execute = $this->execute; - - return $execute($command, $output, $cwd); - } -} diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index e9233f3cb..f395eba6e 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -14,7 +14,6 @@ namespace Composer\Test\Package\Archiver; use Composer\Package\Archiver\ArchivableFilesFinder; use Composer\Util\Filesystem; - use Symfony\Component\Process\Process; use Symfony\Component\Process\ExecutableFinder; @@ -75,7 +74,8 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase 'parameters.yml', 'parameters.yml.dist', '!important!.txt', - '!important_too!.txt' + '!important_too!.txt', + '#weirdfile', ); foreach ($fileTree as $relativePath) { @@ -98,7 +98,7 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase '!/prefixB.foo', '/prefixA.foo', 'prefixC.*', - '!*/*/*/prefixC.foo' + '!*/*/*/prefixC.foo', ); $this->finder = new ArchivableFilesFinder($this->sources, $excludes); @@ -106,6 +106,7 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase $this->assertArchivableFiles(array( '/!important!.txt', '/!important_too!.txt', + '/#weirdfile', '/A/prefixA.foo', '/A/prefixD.foo', '/A/prefixE.foo', @@ -170,7 +171,8 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase 'H/**', 'J/', 'parameters.yml', - '\!important!.txt' + '\!important!.txt', + '\#*', ))); // git does not currently support negative git attributes @@ -181,7 +183,7 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase //'!/prefixB.foo export-ignore', '/prefixA.foo export-ignore', 'prefixC.* export-ignore', - //'!/*/*/prefixC.foo export-ignore' + //'!/*/*/prefixC.foo export-ignore', ))); $this->finder = new ArchivableFilesFinder($this->sources, array()); @@ -284,7 +286,7 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase * * @param string $process The name of the binary to test. * - * @return boolean True if the process is available, false otherwise. + * @return bool True if the process is available, false otherwise. */ protected function isProcessAvailable($process) { diff --git a/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php b/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php index 97c02c8e6..0a19a1327 100644 --- a/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php +++ b/tests/Composer/Test/Package/Archiver/GitExcludeFilterTest.php @@ -29,8 +29,8 @@ class GitExcludeFilterTest extends \PHPUnit_Framework_TestCase public function patterns() { return array( - array('app/config/parameters.yml', array('#(?=[^\.])app/(?=[^\.])config/(?=[^\.])parameters\.yml(?=$|/)#', false, false)), - array('!app/config/parameters.yml', array('#(?=[^\.])app/(?=[^\.])config/(?=[^\.])parameters\.yml(?=$|/)#', true, false)), + array('app/config/parameters.yml', array('{(?=[^\.])app/(?=[^\.])config/(?=[^\.])parameters\.yml(?=$|/)}', false, false)), + array('!app/config/parameters.yml', array('{(?=[^\.])app/(?=[^\.])config/(?=[^\.])parameters\.yml(?=$|/)}', true, false)), ); } } diff --git a/tests/Composer/Test/Package/BasePackageTest.php b/tests/Composer/Test/Package/BasePackageTest.php index 1fe0ece84..c54762fca 100644 --- a/tests/Composer/Test/Package/BasePackageTest.php +++ b/tests/Composer/Test/Package/BasePackageTest.php @@ -39,4 +39,51 @@ class BasePackageTest extends \PHPUnit_Framework_TestCase $package->setRepository($this->getMock('Composer\Repository\RepositoryInterface')); $package->setRepository($this->getMock('Composer\Repository\RepositoryInterface')); } + + /** + * @dataProvider formattedVersions + */ + public function testFormatVersionForDevPackage(BasePackage $package, $truncate, $expected) + { + $this->assertSame($expected, $package->getFullPrettyVersion($truncate)); + } + + public function formattedVersions() + { + $data = array( + array( + 'sourceReference' => 'v2.1.0-RC2', + 'truncate' => true, + 'expected' => 'PrettyVersion v2.1.0-RC2', + ), + array( + 'sourceReference' => 'bbf527a27356414bfa9bf520f018c5cb7af67c77', + 'truncate' => true, + 'expected' => 'PrettyVersion bbf527a', + ), + array( + 'sourceReference' => 'v1.0.0', + 'truncate' => false, + 'expected' => 'PrettyVersion v1.0.0', + ), + array( + 'sourceReference' => 'bbf527a27356414bfa9bf520f018c5cb7af67c77', + 'truncate' => false, + 'expected' => 'PrettyVersion bbf527a27356414bfa9bf520f018c5cb7af67c77', + ), + ); + + $self = $this; + $createPackage = function ($arr) use ($self) { + $package = $self->getMockForAbstractClass('\Composer\Package\BasePackage', array(), '', false); + $package->expects($self->once())->method('isDev')->will($self->returnValue(true)); + $package->expects($self->once())->method('getSourceType')->will($self->returnValue('git')); + $package->expects($self->once())->method('getPrettyVersion')->will($self->returnValue('PrettyVersion')); + $package->expects($self->any())->method('getSourceReference')->will($self->returnValue($arr['sourceReference'])); + + return array($package, $arr['truncate'], $arr['expected']); + }; + + return array_map($createPackage, $data); + } } diff --git a/tests/Composer/Test/Package/CompletePackageTest.php b/tests/Composer/Test/Package/CompletePackageTest.php index de119ebaa..aa3127071 100644 --- a/tests/Composer/Test/Package/CompletePackageTest.php +++ b/tests/Composer/Test/Package/CompletePackageTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Package; use Composer\Package\Package; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; use Composer\TestCase; class CompletePackageTest extends TestCase diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index c9aa2b8a0..c2c820fb1 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -14,7 +14,7 @@ namespace Composer\Test\Package\Dumper; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Link; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; class ArrayDumperTest extends \PHPUnit_Framework_TestCase { @@ -45,7 +45,7 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase array( 'name' => 'foo', 'version' => '1.0', - 'version_normalized' => '1.0.0.0' + 'version_normalized' => '1.0.0.0', ), $config ); @@ -100,7 +100,7 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase return array( array( 'type', - 'library' + 'library', ), array( 'time', @@ -110,46 +110,46 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase ), array( 'authors', - array('Nils Adermann ', 'Jordi Boggiano ') + array('Nils Adermann ', 'Jordi Boggiano '), ), array( 'homepage', - 'https://getcomposer.org' + 'https://getcomposer.org', ), array( 'description', - 'Dependency Manager' + 'Dependency Manager', ), array( 'keywords', array('package', 'dependency', 'autoload'), null, - array('autoload', 'dependency', 'package') + array('autoload', 'dependency', 'package'), ), array( 'bin', array('bin/composer'), - 'binaries' + 'binaries', ), array( 'license', - array('MIT') + array('MIT'), ), array( 'autoload', - array('psr-0' => array('Composer' => 'src/')) + array('psr-0' => array('Composer' => 'src/')), ), array( 'repositories', - array('packagist' => false) + array('packagist' => false), ), array( 'scripts', - array('post-update-cmd' => 'MyVendor\\MyClass::postUpdate') + array('post-update-cmd' => 'MyVendor\\MyClass::postUpdate'), ), array( 'extra', - array('class' => 'MyVendor\\Installer') + array('class' => 'MyVendor\\Installer'), ), array( 'archive', @@ -161,20 +161,20 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase ), array( 'require', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'requires', array('foo/bar' => '1.0.0'), ), array( 'require-dev', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires (for development)', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires (for development)', '1.0.0')), 'devRequires', array('foo/bar' => '1.0.0'), ), array( 'suggest', array('foo/bar' => 'very useful package'), - 'suggests' + 'suggests', ), array( 'support', @@ -182,45 +182,45 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase ), array( 'require', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'requires', - array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0') + array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0'), ), array( 'require-dev', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'devRequires', - array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0') + array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0'), ), array( 'suggest', array('foo/bar' => 'very useful package', 'bar/baz' => 'another useful package'), 'suggests', - array('bar/baz' => 'another useful package', 'foo/bar' => 'very useful package') + array('bar/baz' => 'another useful package', 'foo/bar' => 'very useful package'), ), array( 'provide', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'provides', - array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0') + array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0'), ), array( 'replace', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'replaces', - array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0') + array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0'), ), array( 'conflict', - array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')), + array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), 'conflicts', - array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0') + array('bar/baz' => '1.0.0', 'foo/bar' => '1.0.0'), ), array( 'transport-options', array('ssl' => array('local_cert' => '/opt/certs/test.pem')), - 'transportOptions' - ) + 'transportOptions', + ), ); } diff --git a/tests/Composer/Test/Package/LinkConstraint/MultiConstraintTest.php b/tests/Composer/Test/Package/LinkConstraint/MultiConstraintTest.php deleted file mode 100644 index 892b7fef7..000000000 --- a/tests/Composer/Test/Package/LinkConstraint/MultiConstraintTest.php +++ /dev/null @@ -1,54 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Package\LinkConstraint; - -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\LinkConstraint\MultiConstraint; - -class MultiConstraintTest extends \PHPUnit_Framework_TestCase -{ - public function testMultiVersionMatchSucceeds() - { - $versionRequireStart = new VersionConstraint('>', '1.0'); - $versionRequireEnd = new VersionConstraint('<', '1.2'); - $versionProvide = new VersionConstraint('==', '1.1'); - - $multiRequire = new MultiConstraint(array($versionRequireStart, $versionRequireEnd)); - - $this->assertTrue($multiRequire->matches($versionProvide)); - } - - public function testMultiVersionProvidedMatchSucceeds() - { - $versionRequireStart = new VersionConstraint('>', '1.0'); - $versionRequireEnd = new VersionConstraint('<', '1.2'); - $versionProvideStart = new VersionConstraint('>=', '1.1'); - $versionProvideEnd = new VersionConstraint('<', '2.0'); - - $multiRequire = new MultiConstraint(array($versionRequireStart, $versionRequireEnd)); - $multiProvide = new MultiConstraint(array($versionProvideStart, $versionProvideEnd)); - - $this->assertTrue($multiRequire->matches($multiProvide)); - } - - public function testMultiVersionMatchFails() - { - $versionRequireStart = new VersionConstraint('>', '1.0'); - $versionRequireEnd = new VersionConstraint('<', '1.2'); - $versionProvide = new VersionConstraint('==', '1.2'); - - $multiRequire = new MultiConstraint(array($versionRequireStart, $versionRequireEnd)); - - $this->assertFalse($multiRequire->matches($versionProvide)); - } -} diff --git a/tests/Composer/Test/Package/LinkConstraint/VersionConstraintTest.php b/tests/Composer/Test/Package/LinkConstraint/VersionConstraintTest.php deleted file mode 100644 index e2adda282..000000000 --- a/tests/Composer/Test/Package/LinkConstraint/VersionConstraintTest.php +++ /dev/null @@ -1,105 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Package\LinkConstraint; - -use Composer\Package\LinkConstraint\VersionConstraint; - -class VersionConstraintTest extends \PHPUnit_Framework_TestCase -{ - public static function successfulVersionMatches() - { - return array( - // require provide - array('==', '1', '==', '1'), - array('>=', '1', '>=', '2'), - array('>=', '2', '>=', '1'), - array('>=', '2', '>', '1'), - array('<=', '2', '>=', '1'), - array('>=', '1', '<=', '2'), - array('==', '2', '>=', '2'), - array('!=', '1', '!=', '1'), - array('!=', '1', '==', '2'), - array('!=', '1', '<', '1'), - array('!=', '1', '<=', '1'), - array('!=', '1', '>', '1'), - array('!=', '1', '>=', '1'), - array('==', 'dev-foo-bar', '==', 'dev-foo-bar'), - array('==', 'dev-foo-xyz', '==', 'dev-foo-xyz'), - array('>=', 'dev-foo-bar', '>=', 'dev-foo-xyz'), - array('<=', 'dev-foo-bar', '<', 'dev-foo-xyz'), - array('!=', 'dev-foo-bar', '<', 'dev-foo-xyz'), - array('>=', 'dev-foo-bar', '!=', 'dev-foo-bar'), - array('!=', 'dev-foo-bar', '!=', 'dev-foo-xyz'), - ); - } - - /** - * @dataProvider successfulVersionMatches - */ - public function testVersionMatchSucceeds($requireOperator, $requireVersion, $provideOperator, $provideVersion) - { - $versionRequire = new VersionConstraint($requireOperator, $requireVersion); - $versionProvide = new VersionConstraint($provideOperator, $provideVersion); - - $this->assertTrue($versionRequire->matches($versionProvide)); - } - - public static function failingVersionMatches() - { - return array( - // require provide - array('==', '1', '==', '2'), - array('>=', '2', '<=', '1'), - array('>=', '2', '<', '2'), - array('<=', '2', '>', '2'), - array('>', '2', '<=', '2'), - array('<=', '1', '>=', '2'), - array('>=', '2', '<=', '1'), - array('==', '2', '<', '2'), - array('!=', '1', '==', '1'), - array('==', '1', '!=', '1'), - array('==', 'dev-foo-dist', '==', 'dev-foo-zist'), - array('==', 'dev-foo-bist', '==', 'dev-foo-aist'), - array('<=', 'dev-foo-bist', '>=', 'dev-foo-aist'), - array('>=', 'dev-foo-bist', '<', 'dev-foo-aist'), - array('<', '0.12', '==', 'dev-foo'), // branches are not comparable - array('>', '0.12', '==', 'dev-foo'), // branches are not comparable - ); - } - - /** - * @dataProvider failingVersionMatches - */ - public function testVersionMatchFails($requireOperator, $requireVersion, $provideOperator, $provideVersion) - { - $versionRequire = new VersionConstraint($requireOperator, $requireVersion); - $versionProvide = new VersionConstraint($provideOperator, $provideVersion); - - $this->assertFalse($versionRequire->matches($versionProvide)); - } - - public function testComparableBranches() - { - $versionRequire = new VersionConstraint('>', '0.12'); - $versionProvide = new VersionConstraint('==', 'dev-foo'); - - $this->assertFalse($versionRequire->matches($versionProvide)); - $this->assertFalse($versionRequire->matchSpecific($versionProvide, true)); - - $versionRequire = new VersionConstraint('<', '0.12'); - $versionProvide = new VersionConstraint('==', 'dev-foo'); - - $this->assertFalse($versionRequire->matches($versionProvide)); - $this->assertTrue($versionRequire->matchSpecific($versionProvide, true)); - } -} diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 1491571a1..10963b55b 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -17,6 +17,11 @@ use Composer\Package\Dumper\ArrayDumper; class ArrayLoaderTest extends \PHPUnit_Framework_TestCase { + /** + * @var ArrayLoader + */ + private $loader; + public function setUp() { $this->loader = new ArrayLoader(null, true); @@ -118,7 +123,7 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase 'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'), ), 'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')), - 'abandoned' => 'foo/bar' + 'abandoned' => 'foo/bar', ); $package = $this->loader->load($config); @@ -189,7 +194,7 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase $config = array( 'name' => 'A', 'version' => '1.2.3.4', - 'abandoned' => 'foo/bar' + 'abandoned' => 'foo/bar', ); $package = $this->loader->load($config); @@ -201,10 +206,50 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase { $config = array( 'name' => 'A', - 'version' => '1.2.3.4' + 'version' => '1.2.3.4', ); $package = $this->loader->load($config); $this->assertFalse($package->isAbandoned()); } + + public function pluginApiVersions() + { + return array( + array('1.0'), + array('1.0.0'), + array('1.0.0.0'), + array('1'), + array('=1.0.0'), + array('==1.0'), + array('~1.0.0'), + array('*'), + array('3.0.*'), + array('@stable'), + array('1.0.0@stable'), + array('^5.1'), + array('>=1.0.0 <2.5'), + array('x'), + array('1.0.0-dev'), + ); + } + + /** + * @dataProvider pluginApiVersions + */ + public function testPluginApiVersionAreKeptAsDeclared($apiVersion) + { + $links = $this->loader->parseLinks('Plugin', '9.9.9', '', array('composer-plugin-api' => $apiVersion)); + + $this->assertArrayHasKey('composer-plugin-api', $links); + $this->assertSame($apiVersion, $links['composer-plugin-api']->getConstraint()->getPrettyString()); + } + + public function testPluginApiVersionDoesSupportSelfVersion() + { + $links = $this->loader->parseLinks('Plugin', '6.6.6', '', array('composer-plugin-api' => 'self.version')); + + $this->assertArrayHasKey('composer-plugin-api', $links); + $this->assertSame('6.6.6', $links['composer-plugin-api']->getConstraint()->getPrettyString()); + } } diff --git a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php index 37c259676..218d6e5ff 100644 --- a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php @@ -15,144 +15,17 @@ namespace Composer\Test\Package\Loader; use Composer\Config; use Composer\Package\Loader\RootPackageLoader; use Composer\Package\BasePackage; -use Composer\Test\Mock\ProcessExecutorMock; -use Composer\Repository\RepositoryManager; +use Composer\Package\Version\VersionGuesser; +use Composer\Semver\VersionParser; class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase { - public function testDetachedHeadBecomesDevHash() - { - if (!function_exists('proc_open')) { - $this->markTestSkipped('proc_open() is not available'); - } - - $commitHash = '03a15d220da53c52eddd5f32ffca64a7b3801bea'; - - $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, $commitHash) { - if (0 === strpos($command, 'git describe')) { - // simulate not being on a tag - return 1; - } - - $self->assertStringStartsWith('git branch', $command); - - $output = "* (no branch) $commitHash 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()); - - $this->assertEquals("dev-$commitHash", $package->getVersion()); - } - - public function testTagBecomesVersion() - { - 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) { - $self->assertEquals('git describe --exact-match --tags', $command); - - $output = "v2.0.5-alpha2"; - - return 0; - }); - - $config = new Config; - $config->merge(array('repositories' => array('packagist' => false))); - $loader = new RootPackageLoader($manager, $config, null, $processExecutor); - $package = $loader->load(array()); - - $this->assertEquals("2.0.5.0-alpha2", $package->getVersion()); - } - - public function testInvalidTagBecomesVersion() - { - 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 ('git describe --exact-match --tags' === $command) { - $output = "foo-bar"; - - return 0; - } - - $output = "* foo 03a15d220da53c52eddd5f32ffca64a7b3801bea 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()); - - $this->assertEquals("dev-foo", $package->getVersion()); - } - - public function testNoVersionIsVisibleInPrettyVersion() - { - $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 = $this->getMockBuilder('Composer\Util\ProcessExecutor') - ->disableOriginalConstructor() - ->getMock(); - $processExecutor->expects($this->any()) - ->method('execute') - ->willReturn(null); - - $config = new Config; - $config->merge(array('repositories' => array('packagist' => false))); - $loader = new RootPackageLoader($manager, $config, null, $processExecutor); - $package = $loader->load(array()); - - $this->assertEquals("1.0.0.0", $package->getVersion()); - $this->assertEquals("No version set (parsed as 1.0.0)", $package->getPrettyVersion()); - } - protected function loadPackage($data) { $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') ->disableOriginalConstructor() ->getMock(); - $processExecutor = new ProcessExecutorMock(function ($command, &$output = null, $cwd = null) { - return 1; - }); - $config = new Config; $config->merge(array('repositories' => array('packagist' => false))); @@ -168,7 +41,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase 'foo/bar' => '~2.1.0-beta2', 'bar/baz' => '1.0.x-dev as 1.2.0', 'qux/quux' => '1.0.*@rc', - 'zux/complex' => '~1.0,>=1.0.2@dev' + 'zux/complex' => '~1.0,>=1.0.2@dev', ), 'minimum-stability' => 'alpha', )); @@ -181,6 +54,35 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase ), $package->getStabilityFlags()); } + public function testNoVersionIsVisibleInPrettyVersion() + { + $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') + ->disableOriginalConstructor() + ->getMock() + ; + + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->disableOriginalConstructor() + ->getMock() + ; + + $executor + ->expects($this->any()) + ->method('execute') + ->willReturn(null) + ; + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $executor, new VersionParser())); + $package = $loader->load(array()); + + $this->assertEquals("1.0.0.0", $package->getVersion()); + $this->assertEquals("No version set (parsed as 1.0.0)", $package->getPrettyVersion()); + } + public function testFeatureBranchPrettyVersion() { if (!function_exists('proc_open')) { @@ -189,32 +91,53 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; + + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->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 = ""; + $executor + ->expects($this->at(0)) + ->method('execute') + ->willReturnCallback(function ($command) use ($self) { + $self->assertEquals('git describe --exact-match --tags', $command); - return 0; - } + return 1; + }) + ; - if ('git branch --no-color --no-abbrev -v' !== $command) { - return 1; //0; - } + $executor + ->expects($this->at(1)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; - $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + return 0; + }) + ; - $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; + $executor + ->expects($this->at(2)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git rev-list master..latest-production', $command); + $output = ""; - return 0; - }); + return 0; + }) + ; $config = new Config; $config->merge(array('repositories' => array('packagist' => false))); - $loader = new RootPackageLoader($manager, $config, null, $processExecutor); + $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $executor, new VersionParser())); $package = $loader->load(array('require' => array('foo/bar' => 'self.version'))); $this->assertEquals("dev-master", $package->getPrettyVersion()); @@ -228,32 +151,42 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase $manager = $this->getMockBuilder('\\Composer\\Repository\\RepositoryManager') ->disableOriginalConstructor() - ->getMock(); - - $self = $this; + ->getMock() + ; - /* 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 = ""; + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->disableOriginalConstructor() + ->getMock() + ; - return 0; - } + $self = $this; - if ('git branch --no-color --no-abbrev -v' !== $command) { - return 1; //0; - } + $executor + ->expects($this->at(0)) + ->method('execute') + ->willReturnCallback(function ($command) use ($self) { + $self->assertEquals('git describe --exact-match --tags', $command); - $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + return 1; + }) + ; - $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; + $executor + ->expects($this->at(1)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + $output = "* latest-production 38137d2f6c70e775e137b2d8a7a7d3eaebf7c7e5 Commit message\n master 4f6ed96b0bc363d2aa4404c3412de1c011f67c66 Commit message\n"; - return 0; - }); + return 0; + }) + ; $config = new Config; $config->merge(array('repositories' => array('packagist' => false))); - $loader = new RootPackageLoader($manager, $config, null, $processExecutor); + $loader = new RootPackageLoader($manager, $config, null, new VersionGuesser($config, $executor, new VersionParser())); $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/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 075ee8253..4668099bf 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -12,7 +12,6 @@ namespace Composer\Test\Package\Loader; -use Composer\Package; use Composer\Package\Loader\ValidatingArrayLoader; use Composer\Package\Loader\InvalidPackageException; @@ -119,7 +118,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase array( 'type' => 'composer', 'url' => 'https://packagist.org/', - ) + ), ), 'config' => array( 'bin-dir' => 'bin', @@ -140,14 +139,14 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'branch-alias' => array( 'dev-master' => '2.0-dev', 'dev-old' => '1.0.x-dev', - '3.x-dev' => '3.1.x-dev' + '3.x-dev' => '3.1.x-dev', ), ), 'bin' => array( 'bin/foo', 'bin/bar', ), - 'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')) + 'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')), ), ), array( // test as array @@ -221,8 +220,8 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'name' => 'foo', ), array( - 'name : invalid value (foo), must match [A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*' - ) + 'name : invalid value (foo), must match [A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', + ), ), array( array( @@ -231,7 +230,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase ), array( 'homepage : should be a string, integer given', - ) + ), ), array( array( @@ -242,7 +241,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase ), array( 'support.source : invalid value, must be a string', - ) + ), ), array( array( @@ -250,8 +249,8 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'autoload' => 'strings', ), array( - 'autoload : should be an array, string given' - ) + 'autoload : should be an array, string given', + ), ), array( array( @@ -263,8 +262,8 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase ), ), array( - 'autoload : invalid value (psr0), must be one of psr-0, psr-4, classmap, files' - ) + 'autoload : invalid value (psr0), must be one of psr-0, psr-4, classmap, files', + ), ), array( array( @@ -272,8 +271,8 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'transport-options' => 'test', ), array( - 'transport-options : should be an array, string given' - ) + 'transport-options : should be an array, string given', + ), ), ); } @@ -287,8 +286,8 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'homepage' => 'foo:bar', ), array( - 'homepage : invalid value (foo:bar), must be an http/https URL' - ) + 'homepage : invalid value (foo:bar), must be an http/https URL', + ), ), array( array( @@ -305,7 +304,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'support.forum : invalid value (foo:bar), must be an http/https URL', 'support.issues : invalid value (foo:bar), must be an http/https URL', 'support.wiki : invalid value (foo:bar), must be an http/https URL', - ) + ), ), array( array( @@ -323,35 +322,35 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'require.bar/foo : unbound version constraints (dev-master) should be avoided', 'require.bar/hacked : unbound version constraints (@stable) should be avoided', ), - false + false, ), array( array( 'name' => 'foo/bar', 'extra' => array( 'branch-alias' => array( - '5.x-dev' => '3.1.x-dev' + '5.x-dev' => '3.1.x-dev', ), - ) + ), ), array( - 'extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version' + 'extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version', ), - false + false, ), array( array( 'name' => 'foo/bar', 'extra' => array( 'branch-alias' => array( - '5.x-dev' => '3.1-dev' + '5.x-dev' => '3.1-dev', ), - ) + ), ), array( - 'extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version' + 'extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version', ), - false + false, ), ); } diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 914bbe2f9..79f82e74d 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -20,7 +20,8 @@ class LockerTest extends \PHPUnit_Framework_TestCase public function testIsLocked() { $json = $this->createJsonFileMock(); - $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), 'md5'); + $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), + $this->getJsonContent()); $json ->expects($this->any()) @@ -40,7 +41,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); $json ->expects($this->once()) @@ -58,7 +59,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); $json ->expects($this->once()) @@ -70,8 +71,8 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue(array( 'packages' => array( array('name' => 'pkg1', 'version' => '1.0.0-beta'), - array('name' => 'pkg2', 'version' => '0.1.10') - ) + array('name' => 'pkg2', 'version' => '0.1.10'), + ), ))); $repo = $locker->getLockedRepository(); @@ -85,7 +86,8 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $jsonContent = $this->getJsonContent() . ' '; + $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); $package1 = $this->createPackageMock(); $package2 = $this->createPackageMock(); @@ -116,17 +118,21 @@ class LockerTest extends \PHPUnit_Framework_TestCase ->method('getVersion') ->will($this->returnValue('0.1.10.0')); + $hash = md5($jsonContent); + $contentHash = md5(trim($jsonContent)); + $json ->expects($this->once()) ->method('write') ->with(array( '_readme' => array('This file locks the dependencies of your project to a known state', 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file', - 'This file is @gener'.'ated automatically'), - 'hash' => 'md5', + 'This file is @gener'.'ated automatically', ), + 'hash' => $hash, + 'content-hash' => $contentHash, 'packages' => array( array('name' => 'pkg1', 'version' => '1.0.0-beta'), - array('name' => 'pkg2', 'version' => '0.1.10') + array('name' => 'pkg2', 'version' => '0.1.10'), ), 'packages-dev' => array(), 'aliases' => array(), @@ -148,7 +154,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); $package1 = $this->createPackageMock(); $package1 @@ -167,12 +173,13 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $jsonContent = $this->getJsonContent(); + $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); $json ->expects($this->once()) ->method('read') - ->will($this->returnValue(array('hash' => 'md5'))); + ->will($this->returnValue(array('hash' => md5($jsonContent)))); $this->assertTrue($locker->isFresh()); } @@ -183,12 +190,47 @@ class LockerTest extends \PHPUnit_Framework_TestCase $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5'); + $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + + $json + ->expects($this->once()) + ->method('read') + ->will($this->returnValue(array('hash' => $this->getJsonContent(array('name' => 'test2'))))); + + $this->assertFalse($locker->isFresh()); + } + + public function testIsFreshWithContentHash() + { + $json = $this->createJsonFileMock(); + $repo = $this->createRepositoryManagerMock(); + $inst = $this->createInstallationManagerMock(); + + $jsonContent = $this->getJsonContent(); + $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); + + $json + ->expects($this->once()) + ->method('read') + ->will($this->returnValue(array('hash' => md5($jsonContent . ' '), 'content-hash' => md5($jsonContent)))); + + $this->assertTrue($locker->isFresh()); + } + + public function testIsFreshFalseWithContentHash() + { + $json = $this->createJsonFileMock(); + $repo = $this->createRepositoryManagerMock(); + $inst = $this->createInstallationManagerMock(); + + $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + + $differentHash = md5($this->getJsonContent(array('name' => 'test2'))); $json ->expects($this->once()) ->method('read') - ->will($this->returnValue(array('hash' => 'oldmd5'))); + ->will($this->returnValue(array('hash' => $differentHash, 'content-hash' => $differentHash))); $this->assertFalse($locker->isFresh()); } @@ -227,4 +269,16 @@ class LockerTest extends \PHPUnit_Framework_TestCase return $this->getMockBuilder('Composer\Package\PackageInterface') ->getMock(); } + + private function getJsonContent(array $customData = array()) + { + $data = array_merge(array( + 'minimum-stability' => 'beta', + 'name' => 'test', + ), $customData); + + ksort($data); + + return json_encode($data); + } } diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php new file mode 100644 index 000000000..84da8d2e9 --- /dev/null +++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php @@ -0,0 +1,137 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Version; + +use Composer\Config; +use Composer\Package\Version\VersionGuesser; +use Composer\Semver\VersionParser; + +class VersionGuesserTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!function_exists('proc_open')) { + $this->markTestSkipped('proc_open() is not available'); + } + } + + public function testDetachedHeadBecomesDevHash() + { + $commitHash = '03a15d220da53c52eddd5f32ffca64a7b3801bea'; + + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->disableOriginalConstructor() + ->getMock() + ; + + $executor + ->expects($this->at(0)) + ->method('execute') + ->with('git describe --exact-match --tags') + ->willReturn(1) + ; + + $self = $this; + + $executor + ->expects($this->at(1)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self, $commitHash) { + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + $output = "* (no branch) $commitHash Commit message\n"; + + return 0; + }) + ; + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $guesser = new VersionGuesser($config, $executor, new VersionParser()); + $version = $guesser->guessVersion(array(), 'dummy/path'); + + $this->assertEquals("dev-$commitHash", $version); + } + + public function testTagBecomesVersion() + { + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->disableOriginalConstructor() + ->getMock() + ; + + $self = $this; + + $executor + ->expects($this->at(0)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git describe --exact-match --tags', $command); + $output = "v2.0.5-alpha2"; + + return 0; + }) + ; + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $guesser = new VersionGuesser($config, $executor, new VersionParser()); + $version = $guesser->guessVersion(array(), 'dummy/path'); + + $this->assertEquals("2.0.5.0-alpha2", $version); + } + + public function testInvalidTagBecomesVersion() + { + $executor = $this->getMockBuilder('\\Composer\\Util\\ProcessExecutor') + ->setMethods(array('execute')) + ->disableArgumentCloning() + ->disableOriginalConstructor() + ->getMock() + ; + + $self = $this; + + $executor + ->expects($this->at(0)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git describe --exact-match --tags', $command); + $output = "foo-bar"; + + return 0; + }) + ; + + $executor + ->expects($this->at(1)) + ->method('execute') + ->willReturnCallback(function ($command, &$output) use ($self) { + $self->assertEquals('git branch --no-color --no-abbrev -v', $command); + $output = "* foo 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n"; + + return 0; + }) + ; + + $config = new Config; + $config->merge(array('repositories' => array('packagist' => false))); + $guesser = new VersionGuesser($config, $executor, new VersionParser()); + $version = $guesser->guessVersion(array(), 'dummy/path'); + + $this->assertEquals("dev-foo", $version); + } +} diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php deleted file mode 100644 index 565451811..000000000 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ /dev/null @@ -1,563 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Test\Package\Version; - -use Composer\Package\Link; -use Composer\Package\Version\VersionParser; -use Composer\Package\LinkConstraint\MultiConstraint; -use Composer\Package\LinkConstraint\VersionConstraint; -use Composer\Package\LinkConstraint\EmptyConstraint; -use Composer\Package\PackageInterface; - -class VersionParserTest extends \PHPUnit_Framework_TestCase -{ - /** - * @dataProvider formattedVersions - */ - public function testFormatVersionForDevPackage(PackageInterface $package, $truncate, $expected) - { - $this->assertSame($expected, VersionParser::formatVersion($package, $truncate)); - } - - public function formattedVersions() - { - $data = array( - array( - 'sourceReference' => 'v2.1.0-RC2', - 'truncate' => true, - 'expected' => 'PrettyVersion v2.1.0-RC2' - ), - array( - 'sourceReference' => 'bbf527a27356414bfa9bf520f018c5cb7af67c77', - 'truncate' => true, - 'expected' => 'PrettyVersion bbf527a' - ), - array( - 'sourceReference' => 'v1.0.0', - 'truncate' => false, - 'expected' => 'PrettyVersion v1.0.0' - ), - array( - 'sourceReference' => 'bbf527a27356414bfa9bf520f018c5cb7af67c77', - 'truncate' => false, - 'expected' => 'PrettyVersion bbf527a27356414bfa9bf520f018c5cb7af67c77' - ), - ); - - $self = $this; - $createPackage = function ($arr) use ($self) { - $package = $self->getMock('\Composer\Package\PackageInterface'); - $package->expects($self->once())->method('isDev')->will($self->returnValue(true)); - $package->expects($self->once())->method('getSourceType')->will($self->returnValue('git')); - $package->expects($self->once())->method('getPrettyVersion')->will($self->returnValue('PrettyVersion')); - $package->expects($self->any())->method('getSourceReference')->will($self->returnValue($arr['sourceReference'])); - - return array($package, $arr['truncate'], $arr['expected']); - }; - - return array_map($createPackage, $data); - } - - /** - * @dataProvider numericAliasVersions - */ - public function testParseNumericAliasPrefix($input, $expected) - { - $parser = new VersionParser; - $this->assertSame($expected, $parser->parseNumericAliasPrefix($input)); - } - - public function numericAliasVersions() - { - return array( - array('0.x-dev', '0.'), - array('1.0.x-dev', '1.0.'), - array('1.x-dev', '1.'), - array('1.2.x-dev', '1.2.'), - array('1.2-dev', '1.2.'), - array('1-dev', '1.'), - array('dev-develop', false), - array('dev-master', false), - ); - } - - /** - * @dataProvider successfulNormalizedVersions - */ - public function testNormalizeSucceeds($input, $expected) - { - $parser = new VersionParser; - $this->assertSame($expected, $parser->normalize($input)); - } - - public function successfulNormalizedVersions() - { - return array( - 'none' => array('1.0.0', '1.0.0.0'), - 'none/2' => array('1.2.3.4', '1.2.3.4'), - 'parses state' => array('1.0.0RC1dev', '1.0.0.0-RC1-dev'), - 'CI parsing' => array('1.0.0-rC15-dev', '1.0.0.0-RC15-dev'), - 'delimiters' => array('1.0.0.RC.15-dev', '1.0.0.0-RC15-dev'), - 'RC uppercase' => array('1.0.0-rc1', '1.0.0.0-RC1'), - 'patch replace' => array('1.0.0.pl3-dev', '1.0.0.0-patch3-dev'), - 'forces w.x.y.z' => array('1.0-dev', '1.0.0.0-dev'), - 'forces w.x.y.z/2' => array('0', '0.0.0.0'), - 'parses long' => array('10.4.13-beta', '10.4.13.0-beta'), - 'parses long/2' => array('10.4.13beta2', '10.4.13.0-beta2'), - 'parses long/semver' => array('10.4.13beta.2', '10.4.13.0-beta2'), - 'expand shorthand' => array('10.4.13-b', '10.4.13.0-beta'), - 'expand shorthand2' => array('10.4.13-b5', '10.4.13.0-beta5'), - 'strips leading v' => array('v1.0.0', '1.0.0.0'), - 'strips v/datetime' => array('v20100102', '20100102'), - 'parses dates y-m' => array('2010.01', '2010-01'), - 'parses dates w/ .' => array('2010.01.02', '2010-01-02'), - 'parses dates w/ -' => array('2010-01-02', '2010-01-02'), - 'parses numbers' => array('2010-01-02.5', '2010-01-02-5'), - 'parses dates y.m.Y' => array('2010.1.555', '2010.1.555.0'), - 'parses datetime' => array('20100102-203040', '20100102-203040'), - 'parses dt+number' => array('20100102203040-10', '20100102203040-10'), - 'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'), - 'parses master' => array('dev-master', '9999999-dev'), - 'parses trunk' => array('dev-trunk', '9999999-dev'), - 'parses branches' => array('1.x-dev', '1.9999999.9999999.9999999-dev'), - 'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'), - 'parses arbitrary2' => array('DEV-FOOBAR', 'dev-FOOBAR'), - 'parses arbitrary3' => array('dev-feature/foo', 'dev-feature/foo'), - 'ignores aliases' => array('dev-master as 1.0.0', '9999999-dev'), - 'semver metadata' => array('dev-master+foo.bar', '9999999-dev'), - 'semver metadata/2' => array('1.0.0-beta.5+foo', '1.0.0.0-beta5'), - 'semver metadata/3' => array('1.0.0+foo', '1.0.0.0'), - 'metadata w/ alias' => array('1.0.0+foo as 2.0', '1.0.0.0'), - ); - } - - /** - * @dataProvider failingNormalizedVersions - * @expectedException UnexpectedValueException - */ - public function testNormalizeFails($input) - { - $parser = new VersionParser; - $parser->normalize($input); - } - - public function failingNormalizedVersions() - { - return array( - 'empty ' => array(''), - 'invalid chars' => array('a'), - 'invalid type' => array('1.0.0-meh'), - 'too many bits' => array('1.0.0.0.0'), - 'non-dev arbitrary' => array('feature-foo'), - 'metadata w/ space' => array('1.0.0+foo bar'), - ); - } - - /** - * @dataProvider successfulNormalizedBranches - */ - public function testNormalizeBranch($input, $expected) - { - $parser = new VersionParser; - $this->assertSame((string) $expected, (string) $parser->normalizeBranch($input)); - } - - public function successfulNormalizedBranches() - { - return array( - 'parses x' => array('v1.x', '1.9999999.9999999.9999999-dev'), - 'parses *' => array('v1.*', '1.9999999.9999999.9999999-dev'), - 'parses digits' => array('v1.0', '1.0.9999999.9999999-dev'), - 'parses digits/2' => array('2.0', '2.0.9999999.9999999-dev'), - 'parses long x' => array('v1.0.x', '1.0.9999999.9999999-dev'), - 'parses long *' => array('v1.0.3.*', '1.0.3.9999999-dev'), - 'parses long digits' => array('v2.4.0', '2.4.0.9999999-dev'), - 'parses long digits/2' => array('2.4.4', '2.4.4.9999999-dev'), - 'parses master' => array('master', '9999999-dev'), - 'parses trunk' => array('trunk', '9999999-dev'), - 'parses arbitrary' => array('feature-a', 'dev-feature-a'), - 'parses arbitrary/2' => array('FOOBAR', 'dev-FOOBAR'), - ); - } - - public function testParseConstraintsIgnoresStabilityFlag() - { - $parser = new VersionParser; - $this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0@dev')); - } - - public function testParseConstraintsIgnoresReferenceOnDevVersion() - { - $parser = new VersionParser; - $this->assertSame((string) new VersionConstraint('=', '1.0.9999999.9999999-dev'), (string) $parser->parseConstraints('1.0.x-dev#abcd123')); - $this->assertSame((string) new VersionConstraint('=', '1.0.9999999.9999999-dev'), (string) $parser->parseConstraints('1.0.x-dev#trunk/@123')); - } - - /** - * @expectedException UnexpectedValueException - */ - public function testParseConstraintsFailsOnBadReference() - { - $parser = new VersionParser; - $this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0#abcd123')); - $this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0#trunk/@123')); - } - - /** - * @expectedException UnexpectedValueException - * @expectedExceptionMessage Invalid operator "~>", you probably meant to use the "~" operator - */ - public function testParseConstraintsNudgesRubyDevsTowardsThePathOfRighteousness() - { - $parser = new VersionParser; - $parser->parseConstraints('~>1.2'); - } - - /** - * @dataProvider simpleConstraints - */ - public function testParseConstraintsSimple($input, $expected) - { - $parser = new VersionParser; - $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); - } - - public function simpleConstraints() - { - return array( - 'match any' => array('*', new EmptyConstraint()), - 'match any/2' => array('*.*', new EmptyConstraint()), - 'match any/3' => array('*.x.*', new EmptyConstraint()), - 'match any/4' => array('x.X.x.*', new EmptyConstraint()), - 'not equal' => array('<>1.0.0', new VersionConstraint('<>', '1.0.0.0')), - 'not equal/2' => array('!=1.0.0', new VersionConstraint('!=', '1.0.0.0')), - 'greater than' => array('>1.0.0', new VersionConstraint('>', '1.0.0.0')), - 'lesser than' => array('<1.2.3.4', new VersionConstraint('<', '1.2.3.4-dev')), - 'less/eq than' => array('<=1.2.3', new VersionConstraint('<=', '1.2.3.0')), - 'great/eq than' => array('>=1.2.3', new VersionConstraint('>=', '1.2.3.0')), - 'equals' => array('=1.2.3', new VersionConstraint('=', '1.2.3.0')), - 'double equals' => array('==1.2.3', new VersionConstraint('=', '1.2.3.0')), - 'no op means eq' => array('1.2.3', new VersionConstraint('=', '1.2.3.0')), - 'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')), - 'shorthand beta' => array('1.2.3b5', new VersionConstraint('=', '1.2.3.0-beta5')), - 'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')), - 'accepts spaces/2' => array('< 1.2.3', new VersionConstraint('<', '1.2.3.0-dev')), - 'accepts spaces/3' => array('> 1.2.3', new VersionConstraint('>', '1.2.3.0')), - 'accepts master' => array('>=dev-master', new VersionConstraint('>=', '9999999-dev')), - 'accepts master/2' => array('dev-master', new VersionConstraint('=', '9999999-dev')), - 'accepts arbitrary' => array('dev-feature-a', new VersionConstraint('=', 'dev-feature-a')), - 'regression #550' => array('dev-some-fix', new VersionConstraint('=', 'dev-some-fix')), - 'regression #935' => array('dev-CAPS', new VersionConstraint('=', 'dev-CAPS')), - 'ignores aliases' => array('dev-master as 1.0.0', new VersionConstraint('=', '9999999-dev')), - 'lesser than override' => array('<1.2.3.4-stable', new VersionConstraint('<', '1.2.3.4')), - ); - } - - /** - * @dataProvider wildcardConstraints - */ - public function testParseConstraintsWildcard($input, $min, $max) - { - $parser = new VersionParser; - if ($min) { - $expected = new MultiConstraint(array($min, $max)); - } else { - $expected = $max; - } - - $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); - } - - public function wildcardConstraints() - { - return array( - array('2.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '3.0.0.0-dev')), - array('20.*', new VersionConstraint('>=', '20.0.0.0-dev'), new VersionConstraint('<', '21.0.0.0-dev')), - array('2.0.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.1.0.0-dev')), - array('2.2.x', new VersionConstraint('>=', '2.2.0.0-dev'), new VersionConstraint('<', '2.3.0.0-dev')), - array('2.10.X', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.11.0.0-dev')), - array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0-dev'), new VersionConstraint('<', '2.1.4.0-dev')), - array('0.*', null, new VersionConstraint('<', '1.0.0.0-dev')), - ); - } - - /** - * @dataProvider tildeConstraints - */ - public function testParseTildeWildcard($input, $min, $max) - { - $parser = new VersionParser; - if ($min) { - $expected = new MultiConstraint(array($min, $max)); - } else { - $expected = $max; - } - - $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); - } - - public function tildeConstraints() - { - return array( - array('~1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.0', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.0.0', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '1.1.0.0-dev')), - array('~1.2', new VersionConstraint('>=', '1.2.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2.3', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<', '1.3.0.0-dev')), - array('~1.2.3.4', new VersionConstraint('>=', '1.2.3.4-dev'), new VersionConstraint('<', '1.2.4.0-dev')), - array('~1.2-beta',new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2-b2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2-BETA2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2.2-dev', new VersionConstraint('>=', '1.2.2.0-dev'), new VersionConstraint('<', '1.3.0.0-dev')), - array('~1.2.2-stable', new VersionConstraint('>=', '1.2.2.0-stable'), new VersionConstraint('<', '1.3.0.0-dev')), - ); - } - - /** - * @dataProvider caretConstraints - */ - public function testParseCaretWildcard($input, $min, $max) - { - $parser = new VersionParser; - if ($min) { - $expected = new MultiConstraint(array($min, $max)); - } else { - $expected = $max; - } - - $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); - } - - public function caretConstraints() - { - return array( - array('^1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('^0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '1.0.0.0-dev')), - array('^0.0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '0.1.0.0-dev')), - array('^1.2', new VersionConstraint('>=', '1.2.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('^1.2.3-beta.2', new VersionConstraint('>=', '1.2.3.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), - array('^1.2.3.4', new VersionConstraint('>=', '1.2.3.4-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('^1.2.3', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), - array('^0.2.3', new VersionConstraint('>=', '0.2.3.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')), - array('^0.2', new VersionConstraint('>=', '0.2.0.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')), - array('^0.2.0', new VersionConstraint('>=', '0.2.0.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')), - array('^0.0.3', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')), - array('^0.0.3-alpha', new VersionConstraint('>=', '0.0.3.0-alpha'), new VersionConstraint('<', '0.0.4.0-dev')), - array('^0.0.3-dev', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')), - ); - } - - /** - * @dataProvider hyphenConstraints - */ - public function testParseHyphen($input, $min, $max) - { - $parser = new VersionParser; - if ($min) { - $expected = new MultiConstraint(array($min, $max)); - } else { - $expected = $max; - } - - $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); - } - - public function hyphenConstraints() - { - return array( - array('1 - 2', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '3.0.0.0-dev')), - array('1.2.3 - 2.3.4.5', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<=', '2.3.4.5')), - array('1.2-beta - 2.3', new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.4.0.0-dev')), - array('1.2-beta - 2.3-dev', new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<=', '2.3.0.0-dev')), - array('1.2-RC - 2.3.1', new VersionConstraint('>=', '1.2.0.0-RC'), new VersionConstraint('<=', '2.3.1.0')), - array('1.2.3-alpha - 2.3-RC', new VersionConstraint('>=', '1.2.3.0-alpha'), new VersionConstraint('<=', '2.3.0.0-RC')), - array('1 - 2.0', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.1.0.0-dev')), - array('1 - 2.1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.2.0.0-dev')), - array('1.2 - 2.1.0', new VersionConstraint('>=', '1.2.0.0-dev'), new VersionConstraint('<=', '2.1.0.0')), - array('1.3 - 2.1.3', new VersionConstraint('>=', '1.3.0.0-dev'), new VersionConstraint('<=', '2.1.3.0')), - ); - } - - /** - * @dataProvider multiConstraintProvider - */ - public function testParseConstraintsMulti($constraint) - { - $parser = new VersionParser; - $first = new VersionConstraint('>', '2.0.0.0'); - $second = new VersionConstraint('<=', '3.0.0.0'); - $multi = new MultiConstraint(array($first, $second)); - $this->assertSame((string) $multi, (string) $parser->parseConstraints($constraint)); - } - - public function multiConstraintProvider() - { - return array( - array('>2.0,<=3.0'), - array('>2.0 <=3.0'), - array('>2.0 <=3.0'), - array('>2.0, <=3.0'), - array('>2.0 ,<=3.0'), - array('>2.0 , <=3.0'), - array('>2.0 , <=3.0'), - array('> 2.0 <= 3.0'), - array('> 2.0 , <= 3.0'), - array(' > 2.0 , <= 3.0 '), - ); - } - - public function testParseConstraintsMultiWithStabilitySuffix() - { - $parser = new VersionParser; - $first = new VersionConstraint('>=', '1.1.0.0-alpha4'); - $second = new VersionConstraint('<', '1.2.9999999.9999999-dev'); - $multi = new MultiConstraint(array($first, $second)); - $this->assertSame((string) $multi, (string) $parser->parseConstraints('>=1.1.0-alpha4,<1.2.x-dev')); - - $first = new VersionConstraint('>=', '1.1.0.0-alpha4'); - $second = new VersionConstraint('<', '1.2.0.0-beta2'); - $multi = new MultiConstraint(array($first, $second)); - $this->assertSame((string) $multi, (string) $parser->parseConstraints('>=1.1.0-alpha4,<1.2-beta2')); - } - - /** - * @dataProvider multiConstraintProvider2 - */ - public function testParseConstraintsMultiDisjunctiveHasPrioOverConjuctive($constraint) - { - $parser = new VersionParser; - $first = new VersionConstraint('>', '2.0.0.0'); - $second = new VersionConstraint('<', '2.0.5.0-dev'); - $third = new VersionConstraint('>', '2.0.6.0'); - $multi1 = new MultiConstraint(array($first, $second)); - $multi2 = new MultiConstraint(array($multi1, $third), false); - $this->assertSame((string) $multi2, (string) $parser->parseConstraints($constraint)); - } - - public function multiConstraintProvider2() - { - return array( - array('>2.0,<2.0.5 | >2.0.6'), - array('>2.0,<2.0.5 || >2.0.6'), - array('> 2.0 , <2.0.5 | > 2.0.6'), - ); - } - - public function testParseConstraintsMultiWithStabilities() - { - $parser = new VersionParser; - $first = new VersionConstraint('>', '2.0.0.0'); - $second = new VersionConstraint('<=', '3.0.0.0-dev'); - $multi = new MultiConstraint(array($first, $second)); - $this->assertSame((string) $multi, (string) $parser->parseConstraints('>2.0@stable,<=3.0@dev')); - } - - /** - * @dataProvider failingConstraints - * @expectedException UnexpectedValueException - */ - public function testParseConstraintsFails($input) - { - $parser = new VersionParser; - $parser->parseConstraints($input); - } - - public function failingConstraints() - { - return array( - 'empty ' => array(''), - 'invalid version' => array('1.0.0-meh'), - 'operator abuse' => array('>2.0,,<=3.0'), - 'operator abuse/2' => array('>2.0 ,, <=3.0'), - 'operator abuse/3' => array('>2.0 ||| <=3.0'), - ); - } - - /** - * @dataProvider stabilityProvider - */ - public function testParseStability($expected, $version) - { - $this->assertSame($expected, VersionParser::parseStability($version)); - } - - public function stabilityProvider() - { - return array( - array('stable', '1'), - array('stable', '1.0'), - array('stable', '3.2.1'), - array('stable', 'v3.2.1'), - array('dev', 'v2.0.x-dev'), - array('dev', 'v2.0.x-dev#abc123'), - array('dev', 'v2.0.x-dev#trunk/@123'), - array('RC', '3.0-RC2'), - array('dev', 'dev-master'), - array('dev', '3.1.2-dev'), - array('stable', '3.1.2-pl2'), - array('stable', '3.1.2-patch'), - array('alpha', '3.1.2-alpha5'), - array('beta', '3.1.2-beta'), - array('beta', '2.0B1'), - array('alpha', '1.2.0a1'), - array('alpha', '1.2_a1'), - array('RC', '2.0.0rc1') - ); - } - - public function pluginApiVersions() - { - return array( - array('1.0'), - array('1.0.0'), - array('1.0.0.0'), - array('1'), - array('=1.0.0'), - array('==1.0'), - array('~1.0.0'), - array('*'), - array('3.0.*'), - array('@stable'), - array('1.0.0@stable'), - array('^5.1'), - array('>=1.0.0 <2.5'), - array('x'), - array('1.0.0-dev'), - ); - } - - /** - * @dataProvider pluginApiVersions - */ - public function testPluginApiVersionAreKeptAsDeclared($apiVersion) - { - $parser = new VersionParser; - - /** @var Link[] $links */ - $links = $parser->parseLinks('Plugin', '9.9.9', '', array('composer-plugin-api' => $apiVersion)); - - $this->assertArrayHasKey('composer-plugin-api', $links); - $this->assertSame($apiVersion, $links['composer-plugin-api']->getConstraint()->getPrettyString()); - } - - public function testPluginApiVersionDoesSupportSelfVersion() - { - $parser = new VersionParser; - - /** @var Link[] $links */ - $links = $parser->parseLinks('Plugin', '6.6.6', '', array('composer-plugin-api' => 'self.version')); - - $this->assertArrayHasKey('composer-plugin-api', $links); - $this->assertSame('6.6.6', $links['composer-plugin-api']->getConstraint()->getPrettyString()); - } -} diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index 6547e7056..a48cc805d 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -13,7 +13,7 @@ namespace Composer\Test\Package\Version; use Composer\Package\Version\VersionSelector; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; class VersionSelectorTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index cd05b922f..b449d7e90 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -216,7 +216,7 @@ class PluginInstallerTest extends TestCase } /** - * @param string $newPluginApiVersion + * @param string $newPluginApiVersion * @param CompletePackage[] $plugins */ private function setPluginApiVersionWithPlugins($newPluginApiVersion, array $plugins = array()) @@ -242,7 +242,7 @@ class PluginInstallerTest extends TestCase $this->repository ->expects($this->any()) ->method('getPackages') - ->will($this->returnCallback(function() use($plugApiInternalPackage, $plugins) { + ->will($this->returnCallback(function () use ($plugApiInternalPackage, $plugins) { return array_merge(array($plugApiInternalPackage), $plugins); })); diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 5109ee41f..3b35714df 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -12,12 +12,11 @@ namespace Composer\Test\Repository; -use Composer\Repository\ComposerRepository; use Composer\IO\NullIO; use Composer\Test\Mock\FactoryMock; use Composer\TestCase; use Composer\Package\Loader\ArrayLoader; -use Composer\Package\Version\VersionParser; +use Composer\Semver\VersionParser; class ComposerRepositoryTest extends TestCase { @@ -76,8 +75,8 @@ class ComposerRepositoryTest extends TestCase array('foo/bar' => array( 'name' => 'foo/bar', 'versions' => array( - '1.0.0' => array('name' => 'foo/bar', 'version' => '1.0.0') - ) + '1.0.0' => array('name' => 'foo/bar', 'version' => '1.0.0'), + ), )), ), // New repository format @@ -111,7 +110,7 @@ class ComposerRepositoryTest extends TestCase $properties = array( 'cache' => $cache, 'loader' => new ArrayLoader(), - 'providerListing' => array('p/a.json' => array('sha256' => 'xxx')) + 'providerListing' => array('p/a.json' => array('sha256' => 'xxx')), ); foreach ($properties as $property => $value) { @@ -141,7 +140,7 @@ class ComposerRepositoryTest extends TestCase 'name' => 'a', 'version' => '0.6', )), - ) + ), ))); $pool = $this->getMock('Composer\DependencyResolver\Pool'); diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index fa1ec6d5b..6f8b71d20 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -26,7 +26,7 @@ class FilesystemRepositoryTest extends TestCase ->expects($this->once()) ->method('read') ->will($this->returnValue(array( - array('name' => 'package1', 'version' => '1.0.0-beta', 'type' => 'vendor') + array('name' => 'package1', 'version' => '1.0.0-beta', 'type' => 'vendor'), ))); $json ->expects($this->once()) @@ -94,7 +94,7 @@ class FilesystemRepositoryTest extends TestCase ->expects($this->once()) ->method('write') ->with(array( - array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0') + array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0'), )); $repository->addPackage($this->getPackage('mypkg', '0.1.10')); diff --git a/tests/Composer/Test/Repository/Fixtures/path/with-version/composer.json b/tests/Composer/Test/Repository/Fixtures/path/with-version/composer.json new file mode 100644 index 000000000..985b89947 --- /dev/null +++ b/tests/Composer/Test/Repository/Fixtures/path/with-version/composer.json @@ -0,0 +1,4 @@ +{ + "name": "test/path-versioned", + "version": "0.0.2" +} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Fixtures/path/without-version/composer.json b/tests/Composer/Test/Repository/Fixtures/path/without-version/composer.json new file mode 100644 index 000000000..cddefceb2 --- /dev/null +++ b/tests/Composer/Test/Repository/Fixtures/path/without-version/composer.json @@ -0,0 +1,3 @@ +{ + "name": "test/path-unversioned" +} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/PathRepositoryTest.php b/tests/Composer/Test/Repository/PathRepositoryTest.php new file mode 100644 index 000000000..47b7ac24f --- /dev/null +++ b/tests/Composer/Test/Repository/PathRepositoryTest.php @@ -0,0 +1,106 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\Loader\ArrayLoader; +use Composer\Semver\VersionParser; +use Composer\TestCase; + +class PathRepositoryTest extends TestCase +{ + public function testLoadPackageFromFileSystemWithVersion() + { + $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') + ->getMock(); + + $config = new \Composer\Config(); + $loader = new ArrayLoader(new VersionParser()); + $versionGuesser = null; + + $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-version')); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $repository->getPackages(); + + $this->assertEquals(1, $repository->count()); + $this->assertTrue($repository->hasPackage($this->getPackage('test/path-versioned', '0.0.2'))); + } + + public function testLoadPackageFromFileSystemWithoutVersion() + { + $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') + ->getMock(); + + $config = new \Composer\Config(); + $loader = new ArrayLoader(new VersionParser()); + $versionGuesser = null; + + $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'without-version')); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $packages = $repository->getPackages(); + + $this->assertEquals(1, $repository->count()); + + $package = $packages[0]; + $this->assertEquals('test/path-unversioned', $package->getName()); + + $packageVersion = $package->getVersion(); + $this->assertTrue(!empty($packageVersion)); + } + + public function testLoadPackageFromFileSystemWithWildcard() + { + $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') + ->getMock(); + + $config = new \Composer\Config(); + $loader = new ArrayLoader(new VersionParser()); + $versionGuesser = null; + + $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*')); + $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config, $loader); + $packages = $repository->getPackages(); + + $this->assertEquals(2, $repository->count()); + + $package = $packages[0]; + $this->assertEquals('test/path-versioned', $package->getName()); + + $package = $packages[1]; + $this->assertEquals('test/path-unversioned', $package->getName()); + } + + /** + * Verify relative repository URLs remain relative, see #4439 + */ + public function testUrlRemainsRelative() + { + $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') + ->getMock(); + + $config = new \Composer\Config(); + $loader = new ArrayLoader(new VersionParser()); + $versionGuesser = null; + + $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-version')); + $relativeUrl = ltrim(substr($repositoryUrl, strlen(getcwd())), DIRECTORY_SEPARATOR); + + $repository = new PathRepository(array('url' => $relativeUrl), $ioInterface, $config, $loader); + $packages = $repository->getPackages(); + + $this->assertEquals(1, $repository->count()); + + $package = $packages[0]; + $this->assertEquals('test/path-versioned', $package->getName()); + $this->assertEquals(rtrim($relativeUrl, DIRECTORY_SEPARATOR), rtrim($package->getDistUrl(), DIRECTORY_SEPARATOR)); + } +} diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index c22250a04..3e68282f4 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -13,8 +13,8 @@ namespace Composer\Repository\Pear; use Composer\TestCase; -use Composer\Package\Version\VersionParser; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\VersionParser; +use Composer\Semver\Constraint\Constraint; use Composer\Package\Link; use Composer\Package\CompletePackage; use Composer\Test\Mock\RemoteFilesystemMock; @@ -106,18 +106,18 @@ class ChannelReaderTest extends TestCase 'ext', 'xml' ), - ) + ), ) ) - ) + ), ) - ) + ), ) ); $packages = $ref->invoke($reader, $channelInfo, new VersionParser()); - $expectedPackage = new CompletePackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1'); + $expectedPackage = new CompletePackage('pear-test.loc/sample', '1.0.0.1', '1.0.0.1'); $expectedPackage->setType('pear-library'); $expectedPackage->setDistType('file'); $expectedPackage->setDescription('description'); @@ -135,7 +135,7 @@ class ChannelReaderTest extends TestCase '*-ext-xml' => '*', )); $expectedPackage->setReplaces(array( - new Link('pear-test.loc/sample', 'pear-test/sample', new VersionConstraint('==', '1.0.0.1'), 'replaces', '== 1.0.0.1'), + new Link('pear-test.loc/sample', 'pear-test/sample', new Constraint('==', '1.0.0.1'), 'replaces', '== 1.0.0.1'), )); $this->assertCount(1, $packages); @@ -144,7 +144,7 @@ class ChannelReaderTest extends TestCase private function createConstraint($operator, $version) { - $constraint = new VersionConstraint($operator, $version); + $constraint = new Constraint($operator, $version); $constraint->setPrettyString($operator.' '.$version); return $constraint; diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index 7eaad13e7..f076d72cc 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -40,7 +40,7 @@ class PearRepositoryTest extends TestCase ); $repoConfig = array( - 'url' => $url + 'url' => $url, ); $this->createRepository($repoConfig); @@ -64,7 +64,7 @@ class PearRepositoryTest extends TestCase public function testRepositoryRead($url, array $expectedPackages) { $repoConfig = array( - 'url' => $url + 'url' => $url, ); if (!@file_get_contents('http://'.$url)) { @@ -88,37 +88,37 @@ class PearRepositoryTest extends TestCase 'pear.php.net', array( array('name' => 'pear-pear.php.net/PEAR', 'version' => '1.9.4'), - ) + ), ), array( 'pear.pdepend.org', array( array('name' => 'pear-pear.pdepend.org/PHP_Depend', 'version' => '1.0.5'), - ) + ), ), array( 'pear.phpmd.org', array( array('name' => 'pear-pear.phpmd.org/PHP_PMD', 'version' => '1.3.3'), - ) + ), ), array( 'pear.doctrine-project.org', array( array('name' => 'pear-pear.doctrine-project.org/DoctrineORM', 'version' => '2.2.2'), - ) + ), ), array( 'pear.symfony-project.com', array( array('name' => 'pear-pear.symfony-project.com/YAML', 'version' => '1.0.6'), - ) + ), ), array( 'pear.pirum-project.org', array( array('name' => 'pear-pear.pirum-project.org/Pirum', 'version' => '1.1.4'), - ) + ), ), ); } diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 1363afd15..83d18dbe4 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -18,8 +18,8 @@ use RecursiveArrayIterator; use RecursiveIteratorIterator; /** -* @author Rob Bast -*/ + * @author Rob Bast + */ class GitHubTest extends \PHPUnit_Framework_TestCase { private $username = 'username'; diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php index dc727096d..f4d12d1be 100644 --- a/tests/Composer/Test/Util/PerforceTest.php +++ b/tests/Composer/Test/Util/PerforceTest.php @@ -53,7 +53,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'depot' => self::TEST_DEPOT, 'branch' => self::TEST_BRANCH, 'p4user' => self::TEST_P4USER, - 'unique_perforce_client_name' => self::TEST_CLIENT_NAME + 'unique_perforce_client_name' => self::TEST_CLIENT_NAME, ); } @@ -222,9 +222,9 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'depot' => 'depot', 'branch' => 'branch', 'p4user' => 'user', - 'p4password' => 'TEST_PASSWORD' + 'p4password' => 'TEST_PASSWORD', ); - $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, $this->getMockIOInterface(), 'TEST'); + $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, $this->getMockIOInterface(), 'TEST'); $password = $this->perforce->queryP4Password(); $this->assertEquals('TEST_PASSWORD', $password); } @@ -476,7 +476,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'name' => 'test/perforce', 'description' => 'Basic project for testing', 'minimum-stability' => 'dev', - 'autoload' => array('psr-0' => array()) + 'autoload' => array('psr-0' => array()), ); $this->assertEquals($expected, $result); } @@ -517,7 +517,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'name' => 'test/perforce', 'description' => 'Basic project for testing', 'minimum-stability' => 'dev', - 'autoload' => array('psr-0' => array()) + 'autoload' => array('psr-0' => array()), ); $this->assertEquals($expected, $result); } @@ -546,7 +546,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'name' => 'test/perforce', 'description' => 'Basic project for testing', 'minimum-stability' => 'dev', - 'autoload' => array('psr-0' => array()) + 'autoload' => array('psr-0' => array()), ); $this->assertEquals($expected, $result); } @@ -588,7 +588,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'name' => 'test/perforce', 'description' => 'Basic project for testing', 'minimum-stability' => 'dev', - 'autoload' => array('psr-0' => array()) + 'autoload' => array('psr-0' => array()), ); $this->assertEquals($expected, $result); } @@ -662,7 +662,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase '"psr-0" : {', '}', '}', - '}' + '}', ); return implode($composer_json); @@ -688,7 +688,7 @@ class PerforceTest extends \PHPUnit_Framework_TestCase 'SubmitOptions: revertunchanged', PHP_EOL, 'LineEnd: local', - PHP_EOL + PHP_EOL, ); if ($withStream) { $expectedArray[] = 'Stream:'; diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 04502ebdc..d09993130 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -13,7 +13,6 @@ namespace Composer\Test\Util; use Composer\Util\RemoteFilesystem; -use Installer\Exception; class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase { @@ -137,6 +136,9 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase $this->assertNull($this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0)); } + /** + * @group slow + */ public function testCaptureAuthenticationParamsFromUrl() { $io = $this->getMock('Composer\IO\IOInterface'); diff --git a/tests/Composer/Test/Util/SpdxLicenseTest.php b/tests/Composer/Test/Util/SpdxLicenseTest.php deleted file mode 100644 index d4665954e..000000000 --- a/tests/Composer/Test/Util/SpdxLicenseTest.php +++ /dev/null @@ -1,132 +0,0 @@ -license = new SpdxLicense; - } - - public static function provideValidLicenses() - { - $json = file_get_contents(__DIR__ . '/../../../../res/spdx-licenses.json'); - - $licenses = json_decode($json, true); - - $identifiers = array_keys($licenses); - - $valid = array_merge( - array( - "MIT", - "NONE", - "NOASSERTION", - "LicenseRef-3", - array("LGPL-2.0", "GPL-3.0+"), - "(LGPL-2.0 or GPL-3.0+)", - "(EUDatagrid and GPL-3.0+)", - ), - $identifiers - ); - - foreach ($valid as &$r) { - $r = array($r); - } - - return $valid; - } - - public static function provideInvalidLicenses() - { - return array( - array(""), - array(array()), - array("The system pwns you"), - array("()"), - array("(MIT)"), - array("MIT NONE"), - array("MIT (MIT and MIT)"), - array("(MIT and MIT) MIT"), - array(array("LGPL-2.0", "The system pwns you")), - array("and GPL-3.0+"), - array("EUDatagrid and GPL-3.0+"), - array("(GPL-3.0 and GPL-2.0 or GPL-3.0+)"), - array("(EUDatagrid and GPL-3.0+ and )"), - array("(EUDatagrid xor GPL-3.0+)"), - array("(MIT Or MIT)"), - array("(NONE or MIT)"), - array("(NOASSERTION or MIT)"), - ); - } - - public static function provideInvalidArgument() - { - return array( - array(null), - array(new \stdClass), - array(array(new \stdClass)), - array(array("mixed", new \stdClass)), - array(array(new \stdClass, new \stdClass)), - ); - } - - /** - * @dataProvider provideValidLicenses - * @param $license - */ - public function testValidate($license) - { - $this->assertTrue($this->license->validate($license)); - } - - /** - * @dataProvider provideInvalidLicenses - * @param string|array $invalidLicense - */ - public function testInvalidLicenses($invalidLicense) - { - $this->assertFalse($this->license->validate($invalidLicense)); - } - - /** - * @dataProvider provideInvalidArgument - * @expectedException InvalidArgumentException - */ - public function testInvalidArgument($invalidArgument) - { - $this->license->validate($invalidArgument); - } - - public function testGetLicenseByIdentifier() - { - $license = $this->license->getLicenseByIdentifier('AGPL-1.0'); - $this->assertEquals($license[0], 'Affero General Public License v1.0'); // fullname - $this->assertFalse($license[1]); // osi approved - } - - public function testGetIdentifierByName() - { - $identifier = $this->license->getIdentifierByName('Affero General Public License v1.0'); - $this->assertEquals($identifier, 'AGPL-1.0'); - - $identifier = $this->license->getIdentifierByName('BSD 2-clause "Simplified" License'); - $this->assertEquals($identifier, 'BSD-2-Clause'); - } - - public function testIsOsiApprovedByIdentifier() - { - $osiApproved = $this->license->isOsiApprovedByIdentifier('MIT'); - $this->assertTrue($osiApproved); - - $osiApproved = $this->license->isOsiApprovedByIdentifier('AGPL-1.0'); - $this->assertFalse($osiApproved); - } -} diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index 2cc419670..590a868fd 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -52,11 +52,11 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase return array( array( $a = array('http' => array('follow_location' => 1, 'max_redirects' => 20)), array(), - array('options' => $a), array() + array('options' => $a), array(), ), array( $a = array('http' => array('method' => 'GET', 'max_redirects' => 20, 'follow_location' => 1)), array('http' => array('method' => 'GET')), - array('options' => $a, 'notification' => $f = function () {}), array('notification' => $f) + array('options' => $a, 'notification' => $f = function () {}), array('notification' => $f), ), ); } @@ -143,8 +143,8 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase 'follow_location' => 1, ), 'ssl' => array( 'SNI_enabled' => true, - 'SNI_server_name' => 'example.org' - ) + 'SNI_server_name' => 'example.org', + ), ); if (PHP_VERSION_ID >= 50600) { unset($expected['ssl']['SNI_server_name']); @@ -173,8 +173,8 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase 'follow_location' => 1, ), 'ssl' => array( 'SNI_enabled' => true, - 'SNI_server_name' => 'example.org' - ) + 'SNI_server_name' => 'example.org', + ), ); if (PHP_VERSION_ID >= 50600) { unset($expected['ssl']['SNI_server_name']); @@ -221,17 +221,17 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase { $options = array( 'http' => array( - 'header' => "X-Foo: bar\r\nContent-Type: application/json\r\nAuthorization: Basic aW52YWxpZA==" - ) + 'header' => "X-Foo: bar\r\nContent-Type: application/json\r\nAuthorization: Basic aW52YWxpZA==", + ), ); $expectedOptions = array( 'http' => array( 'header' => array( "X-Foo: bar", "Authorization: Basic aW52YWxpZA==", - "Content-Type: application/json" - ) - ) + "Content-Type: application/json", + ), + ), ); $context = StreamContextFactory::getContext('http://example.org', $options); $ctxoptions = stream_context_get_options($context); diff --git a/tests/Composer/Test/Util/SvnTest.php b/tests/Composer/Test/Util/SvnTest.php index b1f19ca1a..c54c9c504 100644 --- a/tests/Composer/Test/Util/SvnTest.php +++ b/tests/Composer/Test/Util/SvnTest.php @@ -60,9 +60,9 @@ class SvnTest extends \PHPUnit_Framework_TestCase $config->merge(array( 'config' => array( 'http-basic' => array( - 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar') - ) - ) + 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar'), + ), + ), )); $svn = new Svn($url, new NullIO, $config); @@ -81,9 +81,9 @@ class SvnTest extends \PHPUnit_Framework_TestCase array( 'config' => array( 'http-basic' => array( - 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar') - ) - ) + 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar'), + ), + ), ) ); @@ -104,9 +104,9 @@ class SvnTest extends \PHPUnit_Framework_TestCase array( 'config' => array( 'http-basic' => array( - 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar') - ) - ) + 'svn.apache.org' => array('username' => 'foo', 'password' => 'bar'), + ), + ), ) ); diff --git a/tests/Composer/TestCase.php b/tests/Composer/TestCase.php index 760b57291..2057c09b8 100644 --- a/tests/Composer/TestCase.php +++ b/tests/Composer/TestCase.php @@ -12,10 +12,9 @@ namespace Composer; -use Composer\Package\Version\VersionParser; -use Composer\Package\Package; +use Composer\Semver\VersionParser; use Composer\Package\AliasPackage; -use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; abstract class TestCase extends \PHPUnit_Framework_TestCase @@ -33,7 +32,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase protected function getVersionConstraint($operator, $version) { - $constraint = new VersionConstraint( + $constraint = new Constraint( $operator, self::getVersionParser()->normalize($version) );