1
0
Fork 0

Merge branch '2.0' into fix/unused

pull/6942/head
Jordi Boggiano 2019-08-02 16:38:53 +02:00 committed by GitHub
commit 8dfadd99d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
372 changed files with 11176 additions and 5313 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
indent_size = 2

5
.gitattributes vendored
View File

@ -10,3 +10,8 @@
# Exclude non-essential files from dist # Exclude non-essential files from dist
/tests export-ignore /tests export-ignore
.github export-ignore
.php_cs export-ignore
.travis.yml export-ignore
appveyor.yml export-ignore
phpunit.xml.dist export-ignore

View File

@ -21,6 +21,11 @@ If your issue involves installing, updating or resolving dependencies, the
chance of us being able to reproduce your issue will be much higher if you chance of us being able to reproduce your issue will be much higher if you
share your `composer.json` with us. share your `composer.json` with us.
Coding Style Fixes
------------------
We do not accept CS fixes pull requests. Fixes are done by the project maintainers when appropriate to avoid causing too many unnecessary conflicts between branches and pull requests.
Security Reports Security Reports
---------------- ----------------

11
.php_cs
View File

@ -20,17 +20,16 @@ $finder = PhpCsFixer\Finder::create()
return PhpCsFixer\Config::create() return PhpCsFixer\Config::create()
->setUsingCache(true) ->setUsingCache(true)
//->setUsingLinter(false)
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setRules(array( ->setRules(array(
'@PSR2' => true, '@PSR2' => true,
'array_syntax' => array('syntax' => 'long'),
'binary_operator_spaces' => true, 'binary_operator_spaces' => true,
'blank_line_before_return' => true, 'blank_line_before_statement' => array('statements' => array('declare', 'return')),
'cast_spaces' => true, 'cast_spaces' => array('space' => 'single'),
'header_comment' => array('header' => $header), 'header_comment' => array('header' => $header),
'include' => true, 'include' => true,
'array_syntax' => array('syntax' => 'long'), 'class_attributes_separation' => array('elements' => array('method')),
'method_separation' => true,
'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true, 'no_blank_lines_after_phpdoc' => true,
'no_empty_statement' => true, 'no_empty_statement' => true,
@ -39,7 +38,6 @@ return PhpCsFixer\Config::create()
'no_leading_namespace_whitespace' => true, 'no_leading_namespace_whitespace' => true,
'no_trailing_comma_in_singleline_array' => true, 'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true, 'no_unused_imports' => true,
'no_useless_else' => true,
'no_whitespace_in_blank_line' => true, 'no_whitespace_in_blank_line' => true,
'object_operator_without_whitespace' => true, 'object_operator_without_whitespace' => true,
'phpdoc_align' => true, 'phpdoc_align' => true,
@ -52,7 +50,6 @@ return PhpCsFixer\Config::create()
'phpdoc_types' => true, 'phpdoc_types' => true,
'psr0' => true, 'psr0' => true,
'single_blank_line_before_namespace' => true, 'single_blank_line_before_namespace' => true,
'short_scalar_cast' => true,
'standardize_not_equals' => true, 'standardize_not_equals' => true,
'ternary_operator_spaces' => true, 'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true, 'trailing_comma_in_multiline_array' => true,

View File

@ -1,7 +1,5 @@
language: php language: php
sudo: false
dist: trusty dist: trusty
git: git:
@ -16,50 +14,62 @@ addons:
packages: packages:
- parallel - parallel
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- nightly
matrix: matrix:
include: include:
- dist: precise - php: 5.3
php: 5.3 dist: precise
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
- php: 7.3
env: PHPSTAN=1
- php: 7.3
env:
- deps=high
- php: nightly
- php: 7.4snapshot
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- php: nightly - php: nightly
- php: 7.4snapshot
before_install: before_install:
# determine INI file # disable xdebug if available
- if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; then export INI=/etc/hhvm/php.ini; else export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi - phpenv config-rm xdebug.ini || echo "xdebug not available"
# disable xdebug if available # disable default memory limit
- phpenv config-rm xdebug.ini || echo "xdebug not available" - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
# disable default memory limit - echo memory_limit = -1 >> $INI
- echo memory_limit = -1 >> $INI - composer validate
install: install:
# flags to pass to install # flags to pass to install
- flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress"
# install dependencies using system provided composer binary # update deps to latest in case of high deps build
- composer install $flags - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi
# install dependencies using composer from source # install dependencies using system provided composer binary
- bin/composer install $flags - composer install $flags
# install dependencies using composer from source
- bin/composer install $flags
before_script: before_script:
# make sure git tests do not complain about user/email not being set # make sure git tests do not complain about user/email not being set
- git config --global user.name travis-ci - git config --global user.name travis-ci
- git config --global user.email travis@example.com - git config --global user.email travis@example.com
script: script:
# run test suite directories in parallel using GNU parallel # run test suite directories in parallel using GNU parallel
- ls -d tests/Composer/Test/* | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
# Run PHPStan
- if [[ $PHPSTAN == "1" ]]; then
bin/composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs &&
vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php;
fi
before_deploy: before_deploy:
- php -d phar.readonly=0 bin/compile - php -d phar.readonly=0 bin/compile
deploy: deploy:
provider: releases provider: releases
@ -69,4 +79,4 @@ deploy:
on: on:
tags: true tags: true
repo: composer/composer repo: composer/composer
php: '7.1' php: '7.2'

View File

@ -1,3 +1,152 @@
### [1.8.6] 2019-06-11
* Fixed handling of backslash-escapes handling in composer.json when using the require command
* Fixed create-project not following classmap-authoritative and apcu-autoloader config values
* Fixed HHVM version warning showing up in some cases when it was not in use
### [1.8.5] 2019-04-09
* HHVM 4.0 is no longer compatible with Composer. Please use PHP instead going forward.
* Added forward compatibility with upcoming 2.0 changes
* Fixed support for PHP 7.3-style heredoc/nowdoc syntax changes in autoload generation
* Fixed require command usage when combined with --ignore-platform-reqs
* Fixed and cleaned up various Windows junctions handling issues
### [1.8.4] 2019-02-11
* Fixed long standing solver bug leading to odd solving issues in edge cases, see #7946
* Fixed HHVM support for upcoming releases
* Fixed unix proxy for binaries to be POSIX compatible instead of breaking some shells
* Fixed invalid deprecation warning for composer-plugin-api
* Fixed edge case issues with Windows junctions when working with path repositories
### [1.8.3] 2019-01-30
* Fixed regression when executing partial updates
### [1.8.2] 2019-01-29
* Fixed invalid deprecation warning for ext-pdo_mysql and similar
* Updated to latest xdebug-handler
### [1.8.1] 2019-01-29
* Deprecated support for non-standard package names (anything with uppercase, or no / in it). Make sure to follow the warnings if you see any to avoid problems in 2.0.
* Fixed some packages missing from the autoloader config when installing with --no-dev
* Fixed support for cloning GitLab repos using OAuth tokens instead of SSH keys
* Fixed metapackage installs/updates missing from output
* Fixed --with-dependencies / --with-all-dependencies not updating some packages in some edge cases
* Fixed compatibility with Symfony 4.2 deprecations
* Fixed temp dir not being cleaned up on download error while archiving packages
* Updated to latest ca-bundle
### [1.8.0] 2018-12-03
* Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before
* Added support for removing packages using a wildcard with the `remove` command, e.g. `composer remove foo/*`
* Added `chat` to the list of `support` channels you can list in composer.json
* Added signal handling on require command to restore the composer.json in case of abort
* Added `--ignore` to `outdated` command to pass one or more packages that you do not want to be listed
* Added `--no-dev` to `check-platform-reqs` command to skip dev requirements even if they are installed
* Added support for running plugin commands from sub-directories within a project much like other Composer commands
* Added support for running Composer via phpdbg
* Added `lib-imagick` platform package
* Fixed validate command always checking for disabled checks when used with `--strict`
### [1.7.3] 2018-11-01
* Fixed handling of replace/conflict rules. This may affect dependency resolution in some edge cases.
* Fixed Bitbucket API support and migrated all calls to API v2 as v1 is deprecated
* Fixed support for lib-openssl 1.1.1 having only lowercase algorithm names
* Fixed escaping of URLs in Perforce and Svn drivers
* Fixed `show` command not respecting `--path` when a single package name was given
* Fixed regression in 1.7.2's handling of metapackages
### [1.7.2] 2018-08-16
* Fixed reporting of authentication/rate limiting issues for GitHub API access
* Fixed `create-project` not checking the checking the latest commit out when a cache was already present
* Fixed reporting of errors when `global` command can not switch the working directory
* Fixed PHP 5.3 JSON encoding issues with complex unicode character sequences
* Updated to latest ca-bundle and xdebug-handler projects, see related changelogs
### [1.7.1] 2018-08-07
* Fixed issue autoloading plugins in require-dev in some conditions
* Fixed handling of SSL to repo.packagist.org on very old PHP versions
### [1.7.0] 2018-08-03
* Added the overridden platform config's PHP version in the `diagnose` command output
* Fixed --no-plugins not being respected in a few commands
* Fixed 1.7.0-RC regression in output showing <warn> instead of proper colors
* Fixed 1.7.0-RC regression in output missing "Loading from cache" output on package install
### [1.7.0-RC] 2018-07-24
* Changed default repository URL from packagist.org to repo.packagist.org, this might affect people with strict firewall rules
* Changed output from Updating to Downgrading when performing package downgrades, this might affect anything parsing output
* Several minor performance improvements
* Added basic authentication support for mercurial repos
* Added explicit `i` and `u` aliases for the `install` and `update` commands
* Added support for `show` command to output json format with --tree
* Added support for {glob,braces} support in the path repository's path argument
* Added support in `status` command for showing diffs in vendor dir even for packages installed as dist/zip archives
* Added `--remove-vcs` flag to `create-project` command to avoid prompting for keeping VCS files
* Added `--no-secure-http` flag to `create-project` command to bypass https (use at your own risk)
* Added `pre-command-run` event that lets plugins modify arguments
* Added RemoteFilesystem::getRemoteContents extension point
* Fixed setting scripts via `config` command
### [1.6.5] 2018-05-04
* Fixed regression in 1.6.4 causing strange update behaviors with dev packages
* Fixed regression in 1.6.4 color support detection for Windows
* Fixed issues dealing with broken symlinks when switching branches and using path repositories
* Fixed JSON schema for package repositories
* Fixed issues on computers set to Turkish locale
* Fixed classmap parsing of files using short-open-tags when they are disabled in php
### [1.6.4] 2018-04-13
* Security fixes in some edge case scenarios, recommended update for all users
* Fixed regression in version guessing of path repositories
* Fixed removing aliased packages from the repository, which might resolve some odd update bugs
* Fixed updating of package URLs for GitLab
* Fixed run-script --list failing when script handlers were defined
* Fixed init command not respecting the current php version when selecting package versions
* Fixed handling of uppercase package names in why/why-not commands
* Fixed exclude-from-classmap symlink handling
* Fixed filesystem permissions of PEAR binaries
* Improved performance of subversion repos
* Other minor fixes
### [1.6.3] 2018-01-31
* Fixed GitLab downloads failing in some edge cases
* Fixed ctrl-C handling during create-project
* Fixed GitHub VCS repositories not prompting for a token in some conditions
* Fixed SPDX license identifiers being case sensitive
* Fixed and clarified a few dependency resolution error reporting strings
* Fixed SVN commit log fetching in verbose mode when using private repositories
### [1.6.2] 2018-01-05
* Fixed more autoloader regressions
* Fixed support for updating dist refs in gitlab URLs
### [1.6.1] 2018-01-04
* Fixed upgrade regression due to some autoloader cleanups
* Fixed some overly loose version constraints
### [1.6.0] 2018-01-04
* Added support for SPDX license identifiers v3.0, deprecates GPL/LGPL/AGPL identifiers, which should now have a `-only` or `-or-later` suffix added.
* Added support for COMPOSER_MEMORY_LIMIT env var to make Composer set the PHP memory limit explicitly
* Added support for simple strings for the `bin`
* Fixed `check-platform-reqs` bug in version checking
### [1.6.0-RC] 2017-12-19 ### [1.6.0-RC] 2017-12-19
* Improved performance of installs and updates from git clones when checking out known commits * Improved performance of installs and updates from git clones when checking out known commits
@ -14,7 +163,7 @@
### [1.5.6] - 2017-12-18 ### [1.5.6] - 2017-12-18
* Fixed root package version guessed when a tag is checked out * Fixed root package version guessed when a tag is checked out
* Fixed support for GitLab reposhosted on non-standard ports * Fixed support for GitLab repos hosted on non-standard ports
* Fixed regression in require command when requiring unstable packages, part 3 * Fixed regression in require command when requiring unstable packages, part 3
### [1.5.5] - 2017-12-01 ### [1.5.5] - 2017-12-01
@ -143,7 +292,7 @@
* Added `COMPOSER_MIRROR_PATH_REPOS` env var to force mirroring of path repositories vs symlinking * Added `COMPOSER_MIRROR_PATH_REPOS` env var to force mirroring of path repositories vs symlinking
* Added `COMPOSER_DEV_MODE` env var that is set by Composer to forward the dev mode to script handlers * Added `COMPOSER_DEV_MODE` env var that is set by Composer to forward the dev mode to script handlers
* Fixed support for git 2.11 * Fixed support for git 2.11
* Fixed output from zip and rar leaking out when an error occured * Fixed output from zip and rar leaking out when an error occurred
* Removed `hash` from composer.lock, only `content-hash` is now used which should reduce conflicts * Removed `hash` from composer.lock, only `content-hash` is now used which should reduce conflicts
* Minor fixes and performance improvements * Minor fixes and performance improvements
@ -578,7 +727,7 @@
* Added autoloading support for root packages that use target-dir * Added autoloading support for root packages that use target-dir
* Added awareness of the root package presence and support for it's provide/replace/conflict keys * Added awareness of the root package presence and support for it's provide/replace/conflict keys
* Added IOInterface::isDecorated to test for colored output support * Added IOInterface::isDecorated to test for colored output support
* Added validation of licenses based on the [SPDX registry](http://www.spdx.org/licenses/) * Added validation of licenses based on the [SPDX registry](https://spdx.org/licenses/)
* Improved repository protocol to have large cacheable parts * Improved repository protocol to have large cacheable parts
* Fixed various bugs relating to package aliasing, proxy configuration, binaries * Fixed various bugs relating to package aliasing, proxy configuration, binaries
* Various bug fixes and docs improvements * Various bug fixes and docs improvements
@ -602,6 +751,24 @@
* Initial release * Initial release
[1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6
[1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5
[1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4
[1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3
[1.8.2]: https://github.com/composer/composer/compare/1.8.1...1.8.2
[1.8.1]: https://github.com/composer/composer/compare/1.8.0...1.8.1
[1.8.0]: https://github.com/composer/composer/compare/1.7.3...1.8.0
[1.7.3]: https://github.com/composer/composer/compare/1.7.2...1.7.3
[1.7.2]: https://github.com/composer/composer/compare/1.7.1...1.7.2
[1.7.1]: https://github.com/composer/composer/compare/1.7.0...1.7.1
[1.7.0]: https://github.com/composer/composer/compare/1.7.0-RC...1.7.0
[1.7.0-RC]: https://github.com/composer/composer/compare/1.6.5...1.7.0-RC
[1.6.5]: https://github.com/composer/composer/compare/1.6.4...1.6.5
[1.6.4]: https://github.com/composer/composer/compare/1.6.3...1.6.4
[1.6.3]: https://github.com/composer/composer/compare/1.6.2...1.6.3
[1.6.2]: https://github.com/composer/composer/compare/1.6.1...1.6.2
[1.6.1]: https://github.com/composer/composer/compare/1.6.0...1.6.1
[1.6.0]: https://github.com/composer/composer/compare/1.6.0-RC...1.6.0
[1.6.0-RC]: https://github.com/composer/composer/compare/1.5.6...1.6.0-RC [1.6.0-RC]: https://github.com/composer/composer/compare/1.5.6...1.6.0-RC
[1.5.6]: https://github.com/composer/composer/compare/1.5.5...1.5.6 [1.5.6]: https://github.com/composer/composer/compare/1.5.5...1.5.6
[1.5.5]: https://github.com/composer/composer/compare/1.5.4...1.5.5 [1.5.5]: https://github.com/composer/composer/compare/1.5.4...1.5.5

View File

@ -6,8 +6,6 @@ Composer helps you declare, manage, and install dependencies of PHP projects.
See [https://getcomposer.org/](https://getcomposer.org/) for more information and documentation. 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) [![Build Status](https://travis-ci.org/composer/composer.svg?branch=master)](https://travis-ci.org/composer/composer)
[![Dependency Status](https://www.versioneye.com/php/composer:composer/dev-master/badge.svg)](https://www.versioneye.com/php/composer:composer/dev-master)
[![Reference Status](https://www.versioneye.com/php/composer:composer/reference_badge.svg?style=flat)](https://www.versioneye.com/php/composer:composer/references)
Installation / Usage Installation / Usage
-------------------- --------------------
@ -62,5 +60,3 @@ Acknowledgments
- This project's Solver started out as a PHP port of openSUSE's - This project's Solver started out as a PHP port of openSUSE's
[Libzypp satsolver](https://en.opensuse.org/openSUSE:Libzypp_satsolver). [Libzypp satsolver](https://en.opensuse.org/openSUSE:Libzypp_satsolver).
- This project uses hiddeninput.exe to prompt for passwords on windows, sources
and details can be found on the [github page of the project](https://github.com/Seldaek/hidden-input).

View File

@ -2,23 +2,27 @@ build: false
clone_depth: 5 clone_depth: 5
environment: environment:
PHP_CHOCO_VERSION: 7.2.0 # This sets the PHP version (from Chocolatey)
PHP_CACHE_DIR: C:\tools\php PHPCI_CHOCO_VERSION: 7.3.1
PHPCI_CACHE: C:\tools\phpci
PHPCI_PHP: C:\tools\phpci\php
PHPCI_COMPOSER: C:\tools\phpci\composer
cache: cache:
- '%PHP_CACHE_DIR% -> appveyor.yml' - '%PHPCI_CACHE% -> appveyor.yml'
init: init:
- SET PATH=%PHP_CACHE_DIR%;%PATH% - SET PATH=%PHPCI_PHP%;%PHPCI_COMPOSER%;%PATH%
- SET COMPOSER_CACHE_DIR=%PHP_CACHE_DIR% - SET COMPOSER_HOME=%PHPCI_COMPOSER%\home
- SET COMPOSER_CACHE_DIR=%PHPCI_COMPOSER%\cache
- SET COMPOSER_NO_INTERACTION=1 - SET COMPOSER_NO_INTERACTION=1
- SET PHP=0 - SET PHP=0
- SET ANSICON=121x90 (121x90) - SET ANSICON=121x90 (121x90)
install: install:
- IF EXIST %PHP_CACHE_DIR% (SET PHP=1) - IF EXIST %PHPCI_CACHE% (SET PHP=1)
- IF %PHP%==0 cinst php -y --version %PHP_CHOCO_VERSION% --params "/InstallDir:%PHP_CACHE_DIR%" - IF %PHP%==0 cinst php -i -y --version %PHPCI_CHOCO_VERSION% --params "/InstallDir:%PHPCI_PHP%"
- IF %PHP%==0 cinst composer -y --ia "/DEV=%PHP_CACHE_DIR%" - IF %PHP%==0 cinst composer -i -y --ia "/DEV=%PHPCI_COMPOSER%"
- php -v - php -v
- IF %PHP%==0 (composer --version) ELSE (composer self-update) - IF %PHP%==0 (composer --version) ELSE (composer self-update)
- cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%

View File

@ -1,25 +1,28 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
if (PHP_SAPI !== 'cli') { if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
echo 'Warning: Composer should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL; echo 'Warning: Composer should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
} }
setlocale(LC_ALL, 'C');
require __DIR__.'/../src/bootstrap.php'; require __DIR__.'/../src/bootstrap.php';
use Composer\Factory;
use Composer\XdebugHandler;
use Composer\Console\Application; use Composer\Console\Application;
use Composer\XdebugHandler\XdebugHandler;
error_reporting(-1); error_reporting(-1);
// Create output for XdebugHandler and Application // Restart without xdebug
$output = Factory::createOutput(); $xdebug = new XdebugHandler('Composer', '--ansi');
$xdebug = new XdebugHandler($output);
$xdebug->check(); $xdebug->check();
unset($xdebug); unset($xdebug);
if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) {
echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL;
exit(1);
}
if (function_exists('ini_set')) { if (function_exists('ini_set')) {
@ini_set('display_errors', 1); @ini_set('display_errors', 1);
@ -45,6 +48,10 @@ if (function_exists('ini_set')) {
if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) {
@ini_set('memory_limit', '1536M'); @ini_set('memory_limit', '1536M');
} }
// Set user defined memory limit
if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) {
@ini_set('memory_limit', $memoryLimit);
}
unset($memoryInBytes, $memoryLimit); unset($memoryInBytes, $memoryLimit);
} }
@ -52,4 +59,4 @@ putenv('COMPOSER_BINARY='.realpath($_SERVER['argv'][0]));
// run the command application // run the command application
$application = new Application(); $application = new Application();
$application->run(null, $output); $application->run();

View File

@ -1,9 +1,13 @@
{ {
"name": "composer/composer", "name": "composer/composer",
"description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
"keywords": ["package", "dependency", "autoload"],
"homepage": "https://getcomposer.org/",
"type": "library", "type": "library",
"description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
"keywords": [
"package",
"dependency",
"autoload"
],
"homepage": "https://getcomposer.org/",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
@ -17,52 +21,67 @@
"homepage": "http://seld.be" "homepage": "http://seld.be"
} }
], ],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/composer/issues"
},
"require": { "require": {
"php": "^5.3.2 || ^7.0", "php": "^5.3.2 || ^7.0",
"justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
"composer/ca-bundle": "^1.0", "composer/ca-bundle": "^1.0",
"composer/semver": "^1.0", "composer/semver": "^1.0",
"composer/spdx-licenses": "^1.0", "composer/spdx-licenses": "^1.2",
"composer/xdebug-handler": "^1.1",
"justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
"psr/log": "^1.0",
"seld/jsonlint": "^1.4", "seld/jsonlint": "^1.4",
"seld/phar-utils": "^1.0",
"symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/console": "^2.7 || ^3.0 || ^4.0",
"symfony/filesystem": "^2.7 || ^3.0 || ^4.0",
"symfony/finder": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0",
"symfony/process": "^2.7 || ^3.0 || ^4.0", "symfony/process": "^2.7 || ^3.0 || ^4.0",
"symfony/filesystem": "^2.7 || ^3.0 || ^4.0", "react/promise": "^1.2 || ^2.7"
"seld/phar-utils": "^1.0", },
"seld/cli-prompt": "^1.0", "conflict": {
"psr/log": "^1.0" "symfony/console": "2.8.38"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7", "phpunit/phpunit": "^4.8.35 || ^5.7",
"phpunit/phpunit-mock-objects": "^2.3 || ^3.0" "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"
}, },
"suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
"ext-zip": "Enabling the zip extension allows you to unzip archives",
"ext-zlib": "Allow gzip compression of HTTP requests"
},
"config": { "config": {
"platform": { "platform": {
"php": "5.3.9" "php": "5.3.9"
} }
}, },
"suggest": {
"ext-zip": "Enabling the zip extension allows you to unzip archives",
"ext-zlib": "Allow gzip compression of HTTP requests",
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"
},
"autoload": {
"psr-4": { "Composer\\": "src/Composer" }
},
"autoload-dev": {
"psr-4": { "Composer\\Test\\": "tests/Composer/Test" }
},
"bin": ["bin/composer"],
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.6-dev" "dev-master": "2.0-dev"
} }
}, },
"autoload": {
"psr-4": {
"Composer\\": "src/Composer"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\Test\\": "tests/Composer/Test"
}
},
"bin": [
"bin/composer"
],
"scripts": { "scripts": {
"compile": "@php -dphar.readonly=0 bin/compile",
"test": "phpunit" "test": "phpunit"
},
"scripts-descriptions": {
"compile": "Compile composer.phar",
"test": "Run all tests"
},
"support": {
"issues": "https://github.com/composer/composer/issues",
"irc": "irc://irc.freenode.org/composer"
} }
} }

323
composer.lock generated
View File

@ -1,23 +1,23 @@
{ {
"_readme": [ "_readme": [
"This file locks the dependencies of your project to a known state", "This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d3c8dbadf8d41e2c7933e274b2fe1327", "content-hash": "280f5d5184039085b5f22236d267ae82",
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.1.0", "version": "1.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288" "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/943b2c4fcad1ef178d16a713c2468bf7e579c288", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
"reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288", "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -26,7 +26,7 @@
"php": "^5.3.2 || ^7.0" "php": "^5.3.2 || ^7.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0" "symfony/process": "^2.5 || ^3.0 || ^4.0"
}, },
@ -60,20 +60,20 @@
"ssl", "ssl",
"tls" "tls"
], ],
"time": "2017-11-29T09:37:33+00:00" "time": "2019-01-28T09:30:10+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
"version": "1.4.2", "version": "1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/semver.git", "url": "https://github.com/composer/semver.git",
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -122,28 +122,27 @@
"validation", "validation",
"versioning" "versioning"
], ],
"time": "2016-08-30T16:08:34+00:00" "time": "2019-03-19T17:25:45+00:00"
}, },
{ {
"name": "composer/spdx-licenses", "name": "composer/spdx-licenses",
"version": "1.1.6", "version": "1.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/spdx-licenses.git", "url": "https://github.com/composer/spdx-licenses.git",
"reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c" "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2603a0d7ddc00a015deb576fa5297ca43dee6b1c", "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
"reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c", "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.3.2 || ^7.0" "php": "^5.3.2 || ^7.0 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.5 || ^5.0.5", "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7"
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -183,29 +182,73 @@
"spdx", "spdx",
"validator" "validator"
], ],
"time": "2017-04-03T19:08:52+00:00" "time": "2019-03-26T10:23:26+00:00"
}, },
{ {
"name": "justinrainbow/json-schema", "name": "composer/xdebug-handler",
"version": "5.2.6", "version": "1.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/justinrainbow/json-schema.git", "url": "https://github.com/composer/xdebug-handler.git",
"reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd" "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/d283e11b6e14c6f4664cf080415c4341293e5bbd", "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f",
"reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd", "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0",
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Composer\\XdebugHandler\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "John Stevenson",
"email": "john-stevenson@blueyonder.co.uk"
}
],
"description": "Restarts a process without xdebug.",
"keywords": [
"Xdebug",
"performance"
],
"time": "2019-05-27T17:52:04+00:00"
},
{
"name": "justinrainbow/json-schema",
"version": "5.2.8",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^2.1", "friendsofphp/php-cs-fixer": "~2.2.20",
"json-schema/json-schema-test-suite": "1.2.0", "json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.22" "phpunit/phpunit": "^4.8.35"
}, },
"bin": [ "bin": [
"bin/validate-json" "bin/validate-json"
@ -249,20 +292,20 @@
"json", "json",
"schema" "schema"
], ],
"time": "2017-10-21T13:15:38+00:00" "time": "2019-01-14T23:55:14+00:00"
}, },
{ {
"name": "psr/log", "name": "psr/log",
"version": "1.0.2", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/log.git", "url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -296,35 +339,38 @@
"psr", "psr",
"psr-3" "psr-3"
], ],
"time": "2016-10-10T12:19:37+00:00" "time": "2018-11-20T15:27:04+00:00"
}, },
{ {
"name": "seld/cli-prompt", "name": "react/promise",
"version": "1.0.3", "version": "v1.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/cli-prompt.git", "url": "https://github.com/reactphp/promise.git",
"reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd" "reference": "eefff597e67ff66b719f8171480add3c91474a1e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/a19a7376a4689d4d94cab66ab4f3c816019ba8dd", "url": "https://api.github.com/repos/reactphp/promise/zipball/eefff597e67ff66b719f8171480add3c91474a1e",
"reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd", "reference": "eefff597e67ff66b719f8171480add3c91474a1e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3" "php": ">=5.3.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.x-dev" "dev-master": "1.1-dev"
} }
}, },
"autoload": { "autoload": {
"psr-4": { "psr-0": {
"Seld\\CliPrompt\\": "src/" "React\\Promise": "src/"
} },
"files": [
"src/React/Promise/functions_include.php"
]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
@ -332,39 +378,32 @@
], ],
"authors": [ "authors": [
{ {
"name": "Jordi Boggiano", "name": "Jan Sorgalla",
"email": "j.boggiano@seld.be" "email": "jsorgalla@gmail.com"
} }
], ],
"description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", "description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [ "time": "2016-03-07T13:46:50+00:00"
"cli",
"console",
"hidden",
"input",
"prompt"
],
"time": "2017-03-18T11:32:45+00:00"
}, },
{ {
"name": "seld/jsonlint", "name": "seld/jsonlint",
"version": "1.6.2", "version": "1.7.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/jsonlint.git", "url": "https://github.com/Seldaek/jsonlint.git",
"reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19" "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38",
"reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.3 || ^7.0" "php": "^5.3 || ^7.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.5" "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
}, },
"bin": [ "bin": [
"bin/jsonlint" "bin/jsonlint"
@ -393,7 +432,7 @@
"parser", "parser",
"validator" "validator"
], ],
"time": "2017-11-30T15:34:22+00:00" "time": "2018-01-24T12:46:19+00:00"
}, },
{ {
"name": "seld/phar-utils", "name": "seld/phar-utils",
@ -441,16 +480,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de" "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/46270f1ca44f08ebc134ce120fd2c2baf5fd63de", "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12",
"reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de", "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -464,7 +503,7 @@
"symfony/process": "~2.1|~3.0.0" "symfony/process": "~2.1|~3.0.0"
}, },
"suggest": { "suggest": {
"psr/log": "For using the console logger", "psr/log-implementation": "For using the console logger",
"symfony/event-dispatcher": "", "symfony/event-dispatcher": "",
"symfony/process": "" "symfony/process": ""
}, },
@ -498,20 +537,20 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-29T09:33:18+00:00" "time": "2018-11-20T15:55:20+00:00"
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95" "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/e72a0340dc2e273b3c4398d8eef9157ba51d8b95", "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0",
"reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95", "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -555,24 +594,25 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-19T19:05:05+00:00" "time": "2018-11-11T11:18:13+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b" "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080",
"reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9" "php": ">=5.3.9",
"symfony/polyfill-ctype": "~1.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -604,20 +644,20 @@
], ],
"description": "Symfony Filesystem Component", "description": "Symfony Filesystem Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-19T18:39:05+00:00" "time": "2018-11-11T11:18:13+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8" "reference": "1444eac52273e345d9b95129bf914639305a9ba4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4",
"reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", "reference": "1444eac52273e345d9b95129bf914639305a9ba4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -653,20 +693,78 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-05T15:25:56+00:00" "time": "2018-11-11T11:18:13+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-ctype",
"version": "v1.6.0", "version": "v1.11.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" "reference": "82ebae02209c21113908c229e9883c419720738a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
"reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "reference": "82ebae02209c21113908c229e9883c419720738a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.11-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -678,7 +776,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.6-dev" "dev-master": "1.11-dev"
} }
}, },
"autoload": { "autoload": {
@ -712,20 +810,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-10-11T12:05:26+00:00" "time": "2019-02-06T07:57:58+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "d25449e031f600807949aab7cadbf267712f4eee" "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d25449e031f600807949aab7cadbf267712f4eee", "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
"reference": "d25449e031f600807949aab7cadbf267712f4eee", "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -761,7 +859,7 @@
], ],
"description": "Symfony Process Component", "description": "Symfony Process Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-05T15:25:56+00:00" "time": "2018-11-11T11:18:13+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -870,33 +968,33 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "1.7.3", "version": "1.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0", "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0", "sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0" "sebastian/recursion-context": "^1.0|^2.0|^3.0"
}, },
"require-dev": { "require-dev": {
"phpspec/phpspec": "^2.5|^3.2", "phpspec/phpspec": "^2.5|^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7" "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.7.x-dev" "dev-master": "1.8.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -929,7 +1027,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2017-11-24T13:59:53+00:00" "time": "2018-08-05T17:53:17+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -1681,20 +1779,21 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v2.8.32", "version": "v2.8.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "968ef42161e4bc04200119da473077f9e7015128" "reference": "02c1859112aa779d9ab394ae4f3381911d84052b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/968ef42161e4bc04200119da473077f9e7015128", "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b",
"reference": "968ef42161e4bc04200119da473077f9e7015128", "reference": "02c1859112aa779d9ab394ae4f3381911d84052b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9" "php": ">=5.3.9",
"symfony/polyfill-ctype": "~1.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -1726,7 +1825,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-11-29T09:33:18+00:00" "time": "2018-11-11T11:18:13+00:00"
} }
], ],
"aliases": [], "aliases": [],

View File

@ -14,7 +14,7 @@ manager. It does however support a "global" project for convenience via the
[global](03-cli.md#global) command. [global](03-cli.md#global) command.
This idea is not new and Composer is strongly inspired by node's This idea is not new and Composer is strongly inspired by node's
[npm](https://npmjs.org/) and ruby's [bundler](http://bundler.io/). [npm](https://www.npmjs.com/) and ruby's [bundler](https://bundler.io/).
Suppose: Suppose:
@ -40,14 +40,14 @@ To install packages from sources instead of simple zip archives, you will need
git, svn, fossil or hg depending on how the package is version-controlled. git, svn, fossil or hg depending on how the package is version-controlled.
Composer is multi-platform and we strive to make it run equally well on Windows, Composer is multi-platform and we strive to make it run equally well on Windows,
Linux and OSX. Linux and macOS.
## Installation - Linux / Unix / OSX ## Installation - Linux / Unix / macOS
### Downloading the Composer Executable ### Downloading the Composer Executable
Composer offers a convenient installer that you can execute directly from the Composer offers a convenient installer that you can execute directly from the
commandline. Feel free to [download this file](https://getcomposer.org/installer) command line. Feel free to [download this file](https://getcomposer.org/installer)
or review it on [GitHub](https://github.com/composer/getcomposer.org/blob/master/web/installer) 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 if you wish to know more about the inner workings of the installer. The source
is plain PHP. is plain PHP.
@ -57,16 +57,15 @@ project, or globally as a system wide executable.
#### Locally #### Locally
Installing Composer locally is a matter of just running the installer in your To install Composer locally, run the installer in your project directory. See
project directory. See [the Download page](https://getcomposer.org/download/) [the Download page](https://getcomposer.org/download/) for instructions.
for instructions.
The installer will just check a few PHP settings and then download The installer will check a few PHP settings and then download `composer.phar`
`composer.phar` to your working directory. This file is the Composer binary. It to your working directory. This file is the Composer binary. It is a PHAR
is a PHAR (PHP archive), which is an archive format for PHP which can be run on (PHP archive), which is an archive format for PHP which can be run on
the command line, amongst other things. the command line, amongst other things.
Now just run `php composer.phar` in order to run Composer. Now run `php composer.phar` in order to run Composer.
You can install Composer to a specific directory by using the `--install-dir` You can install Composer to a specific directory by using the `--install-dir`
option and additionally (re)name it as well using the `--filename` option. When option and additionally (re)name it as well using the `--filename` option. When
@ -78,12 +77,12 @@ following parameters:
php composer-setup.php --install-dir=bin --filename=composer php composer-setup.php --install-dir=bin --filename=composer
``` ```
Now just run `php bin/composer` in order to run Composer. Now run `php bin/composer` in order to run Composer.
#### Globally #### Globally
You can place the Composer PHAR anywhere you wish. If you put it in a directory You can place the Composer PHAR anywhere you wish. If you put it in a directory
that is part of your `PATH`, you can access it globally. On unixy systems you that is part of your `PATH`, you can access it globally. On Unix systems you
can even make it executable and invoke it without directly using the `php` can even make it executable and invoke it without directly using the `php`
interpreter. interpreter.
@ -94,10 +93,14 @@ you can run this to move composer.phar to a directory that is in your path:
mv composer.phar /usr/local/bin/composer mv composer.phar /usr/local/bin/composer
``` ```
If you like to install it only for your user and avoid requiring root permissions,
you can use `~/.local/bin` instead which is available by default on some
Linux distributions.
> **Note:** If the above fails due to permissions, you may need to run it again > **Note:** If the above fails due to permissions, you may need to run it again
> with sudo. > with sudo.
> **Note:** On some versions of OSX the `/usr` directory does not exist by > **Note:** On some versions of macOS the `/usr` directory does not exist by
> default. If you receive the error "/usr/local/bin/composer: No such file or > default. If you receive the error "/usr/local/bin/composer: No such file or
> directory" then you must create the directory manually before proceeding: > directory" then you must create the directory manually before proceeding:
> `mkdir -p /usr/local/bin`. > `mkdir -p /usr/local/bin`.
@ -105,7 +108,7 @@ mv composer.phar /usr/local/bin/composer
> **Note:** For information on changing your PATH, please read the > **Note:** For information on changing your PATH, please read the
> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google. > [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google.
Now just run `composer` in order to run Composer instead of `php composer.phar`. Now run `composer` in order to run Composer instead of `php composer.phar`.
## Installation - Windows ## Installation - Windows
@ -115,7 +118,7 @@ This is the easiest way to get Composer set up on your machine.
Download and run Download and run
[Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). It will [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). It will
install the latest Composer version and set up your PATH so that you can just install the latest Composer version and set up your PATH so that you can
call `composer` from any directory in your command line. call `composer` from any directory in your command line.
> **Note:** Close your current terminal. Test usage with a new terminal: This is > **Note:** Close your current terminal. Test usage with a new terminal: This is
@ -135,7 +138,7 @@ C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
Add the directory to your PATH environment variable if it isn't already. Add the directory to your PATH environment variable if it isn't already.
For information on changing your PATH variable, please see For information on changing your PATH variable, please see
[this article](http://www.computerhope.com/issues/ch000549.htm) and/or [this article](https://www.computerhope.com/issues/ch000549.htm) and/or
use Google. use Google.
Close your current terminal. Test usage with a new terminal: Close your current terminal. Test usage with a new terminal:

View File

@ -9,13 +9,13 @@ a logging library. If you have not yet installed Composer, refer to the
> **Note:** for the sake of simplicity, this introduction will assume you > **Note:** for the sake of simplicity, this introduction will assume you
> have performed a [local](00-intro.md#locally) install of Composer. > have performed a [local](00-intro.md#locally) install of Composer.
## `composer.json`: Project Setup ## `composer.json`: Project setup
To start using Composer in your project, all you need is a `composer.json` 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 file. This file describes the dependencies of your project and may contain
other metadata as well. other metadata as well.
### The `require` Key ### The `require` key
The first (and often only) thing you specify in `composer.json` is the The first (and often only) thing you specify in `composer.json` is the
[`require`](04-schema.md#require) key. You are simply telling Composer which [`require`](04-schema.md#require) key. You are simply telling Composer which
@ -41,10 +41,10 @@ assumed that the `monolog/monolog` package is registered on Packagist. (See more
about Packagist [below](#packagist), or read more about repositories about Packagist [below](#packagist), or read more about repositories
[here](05-repositories.md)). [here](05-repositories.md)).
### Package Names ### Package names
The package name consists of a vendor name and the project's name. Often these The package name consists of a vendor name and the project's name. Often these
will be identical - the vendor name just exists to prevent naming clashes. For will be identical - the vendor name only exists to prevent naming clashes. For
example, it would allow two different people to create a library named `json`. example, it would allow two different people to create a library named `json`.
One might be named `igorw/json` while the other might be `seldaek/json`. One might be named `igorw/json` while the other might be `seldaek/json`.
@ -53,10 +53,10 @@ Read more about publishing packages and package naming [here](02-libraries.md).
you to require certain versions of server software. See you to require certain versions of server software. See
[platform packages](#platform-packages) below.) [platform packages](#platform-packages) below.)
### Package Version Constraints ### Package version constraints
In our example, we are requesting the Monolog package with the version constraint In our example, we are requesting the Monolog package with the version constraint
[`1.0.*`](http://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*). [`1.0.*`](https://semver.mwl.be/#?package=monolog%2Fmonolog&version=1.0.*).
This means any version in the `1.0` development branch, or any version that is This means any version in the `1.0` development branch, or any version that is
greater than or equal to 1.0 and less than 1.1 (`>=1.0 <1.1`). greater than or equal to 1.0 and less than 1.1 (`>=1.0 <1.1`).
@ -84,9 +84,9 @@ versions, how versions relate to each other, and on version constraints.
> versions of a package. Read more about stability flags and the `minimum-stability` > versions of a package. Read more about stability flags and the `minimum-stability`
> key on the [schema page](04-schema.md). > key on the [schema page](04-schema.md).
## Installing Dependencies ## Installing dependencies
To install the defined dependencies for your project, just run the To install the defined dependencies for your project, run the
[`install`](03-cli.md#install) command. [`install`](03-cli.md#install) command.
```sh ```sh
@ -95,7 +95,7 @@ php composer.phar install
When you run this command, one of two things may happen: When you run this command, one of two things may happen:
### Installing Without `composer.lock` ### Installing without `composer.lock`
If you have never run the command before and there is also no `composer.lock` file present, If you have never run the command before and there is also no `composer.lock` file present,
Composer simply resolves all dependencies listed in your `composer.json` file and downloads Composer simply resolves all dependencies listed in your `composer.json` file and downloads
@ -114,7 +114,7 @@ of them that it downloaded to the `composer.lock` file, locking the project to t
versions. You should commit the `composer.lock` file to your project repo so that all people versions. You should commit the `composer.lock` file to your project repo so that all people
working on the project are locked to the same versions of dependencies (more below). working on the project are locked to the same versions of dependencies (more below).
### Installing With `composer.lock` ### Installing with `composer.lock`
This brings us to the second scenario. If there is already a `composer.lock` file as well as a This brings us to the second scenario. If there is already a `composer.lock` file as well as a
`composer.json` file when you run `composer install`, it means either you ran the `composer.json` file when you run `composer install`, it means either you ran the
@ -130,7 +130,7 @@ working on your project. As a result you will have all dependencies requested by
the file was created). This is by design, it ensures that your project does not break because of the file was created). This is by design, it ensures that your project does not break because of
unexpected changes in dependencies. unexpected changes in dependencies.
### Commit Your `composer.lock` File to Version Control ### Commit your `composer.lock` file to version control
Committing this file to VC is important because it will cause anyone who sets Committing this file to VC is important because it will cause anyone who sets
up the project to use the exact same up the project to use the exact same
@ -142,7 +142,7 @@ reinstalling the project you can feel confident the dependencies installed are
still working even if your dependencies released many new versions since then. still working even if your dependencies released many new versions since then.
(See note below about using the `update` command.) (See note below about using the `update` command.)
## Updating Dependencies to their Latest Versions ## Updating dependencies to their latest versions
As mentioned above, the `composer.lock` file prevents you from automatically getting As mentioned above, the `composer.lock` file prevents you from automatically getting
the latest versions of your dependencies. To update to the latest versions, use the the latest versions of your dependencies. To update to the latest versions, use the
@ -154,8 +154,10 @@ and running `install` again.)
```sh ```sh
php composer.phar update php composer.phar update
``` ```
> **Note:** Composer will display a Warning when executing an `install` command > **Note:** Composer will display a Warning when executing an `install` command
> if `composer.lock` and `composer.json` are not synchronized. > if the `composer.lock` has not been updated since changes were made to the
> `composer.json` that might affect dependency resolution.
If you only want to install or update one dependency, you can whitelist them: If you only want to install or update one dependency, you can whitelist them:
@ -188,15 +190,15 @@ installed on the system but are not actually installable by Composer. This
includes PHP itself, PHP extensions and some system libraries. includes PHP itself, PHP extensions and some system libraries.
* `php` represents the PHP version of the user, allowing you to apply * `php` represents the PHP version of the user, allowing you to apply
constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can constraints, e.g. `^7.1`. To require a 64bit version of php, you can
require the `php-64bit` package. require the `php-64bit` package.
* `hhvm` represents the version of the HHVM runtime and allows you to apply * `hhvm` represents the version of the HHVM runtime and allows you to apply
a constraint, e.g., '>=2.3.3'. a constraint, e.g., `^2.3`.
* `ext-<name>` allows you to require PHP extensions (includes core * `ext-<name>` allows you to require PHP extensions (includes core
extensions). Versioning can be quite inconsistent here, so it's often extensions). Versioning can be quite inconsistent here, so it's often
a good idea to just set the constraint to `*`. An example of an extension a good idea to set the constraint to `*`. An example of an extension
package name is `ext-gd`. package name is `ext-gd`.
* `lib-<name>` allows constraints to be made on versions of libraries used by * `lib-<name>` allows constraints to be made on versions of libraries used by
@ -255,10 +257,10 @@ In addition to PSR-4 autoloading, Composer also supports PSR-0, classmap and
files autoloading. See the [`autoload`](04-schema.md#autoload) reference for files autoloading. See the [`autoload`](04-schema.md#autoload) reference for
more information. more information.
See also the docs on [`optimizing the autoloader`](articles/autoloader-optimization.md). See also the docs on [optimizing the autoloader](articles/autoloader-optimization.md).
> **Note:** Composer provides its own autoloader. If you don't want to use that > **Note:** Composer provides its own autoloader. If you don't want to use that
> one, you can just include `vendor/composer/autoload_*.php` files, which return > one, you can include `vendor/composer/autoload_*.php` files, which return
> associative arrays allowing you to configure your own autoloader. > associative arrays allowing you to configure your own autoloader.
&larr; [Intro](00-intro.md) | [Libraries](02-libraries.md) &rarr; &larr; [Intro](00-intro.md) | [Libraries](02-libraries.md) &rarr;

View File

@ -22,6 +22,8 @@ The following options are available with every command:
* **--quiet (-q):** Do not output any message. * **--quiet (-q):** Do not output any message.
* **--no-interaction (-n):** Do not ask any interactive question. * **--no-interaction (-n):** Do not ask any interactive question.
* **--no-plugins:** Disables plugins. * **--no-plugins:** Disables plugins.
* **--no-cache:** Disables the use of the cache directory. Same as setting the COMPOSER_CACHE_DIR
env var to /dev/null (or NUL on Windows).
* **--working-dir (-d):** If specified, use the given directory as working directory. * **--working-dir (-d):** If specified, use the given directory as working directory.
* **--profile:** Display timing and memory usage information * **--profile:** Display timing and memory usage information
* **--ansi:** Force ANSI output. * **--ansi:** Force ANSI output.
@ -65,7 +67,7 @@ php composer.phar init
to a `composer` repository or a JSON string which similar to what the to a `composer` repository or a JSON string which similar to what the
[repositories](04-schema.md#repositories) key accepts. [repositories](04-schema.md#repositories) key accepts.
## install ## install / i
The `install` command reads the `composer.json` file from the current The `install` command reads the `composer.json` file from the current
directory, resolves the dependencies, and installs them into `vendor`. directory, resolves the dependencies, and installs them into `vendor`.
@ -115,7 +117,7 @@ resolution.
requirements and force the installation even if the local machine does not requirements and force the installation even if the local machine does not
fulfill these. See also the [`platform`](06-config.md#platform) config option. fulfill these. See also the [`platform`](06-config.md#platform) config option.
## update ## update / u
In order to get the latest versions of the dependencies and to update the In order to get the latest versions of the dependencies and to update the
`composer.lock` file, you should use the `update` command. This command is also `composer.lock` file, you should use the `update` command. This command is also
@ -129,7 +131,7 @@ php composer.phar update
This will resolve all dependencies of the project and write the exact versions This will resolve all dependencies of the project and write the exact versions
into `composer.lock`. into `composer.lock`.
If you just want to update a few packages and not all, you can list them as such: If you only want to update a few packages and not all, you can list them as such:
```sh ```sh
php composer.phar update vendor/package vendor/package2 php composer.phar update vendor/package vendor/package2
@ -138,7 +140,7 @@ php composer.phar update vendor/package vendor/package2
You can also use wildcards to update a bunch of packages at once: You can also use wildcards to update a bunch of packages at once:
```sh ```sh
php composer.phar update vendor/* php composer.phar update "vendor/*"
``` ```
### Options ### Options
@ -184,7 +186,7 @@ php composer.phar require
After adding/changing the requirements, the modified requirements will be After adding/changing the requirements, the modified requirements will be
installed or updated. installed or updated.
If you do not want to choose requirements interactively, you can just pass them If you do not want to choose requirements interactively, you can pass them
to the command. to the command.
```sh ```sh
@ -220,7 +222,6 @@ If you do not specify a package, composer will prompt you to search for a packag
Implicitly enables `--optimize-autoloader`. Implicitly enables `--optimize-autoloader`.
* **--apcu-autoloader:** Use APCu to cache found/not-found classes. * **--apcu-autoloader:** Use APCu to cache found/not-found classes.
## remove ## remove
The `remove` command removes packages from the `composer.json` file from The `remove` command removes packages from the `composer.json` file from
@ -258,6 +259,10 @@ match the platform requirements of the installed packages. This can be used
to verify that a production server has all the extensions needed to run a to verify that a production server has all the extensions needed to run a
project after installing it for example. project after installing it for example.
Unlike update/install, this command will ignore config.platform settings and
check the real platform packages so you can be certain you have the required
platform dependencies.
## global ## global
The global command allows you to run other commands like `install`, `remove`, `require` The global command allows you to run other commands like `install`, `remove`, `require`
@ -273,7 +278,7 @@ This can be used to install CLI utilities globally. Here is an example:
php composer.phar global require friendsofphp/php-cs-fixer php composer.phar global require friendsofphp/php-cs-fixer
``` ```
Now the `php-cs-fixer` binary is available globally. Just make sure your global Now the `php-cs-fixer` binary is available globally. Make sure your global
[vendor binaries](articles/vendor-binaries.md) directory is in your `$PATH` [vendor binaries](articles/vendor-binaries.md) directory is in your `$PATH`
environment variable, you can get its location with the following command : environment variable, you can get its location with the following command :
@ -281,7 +286,7 @@ environment variable, you can get its location with the following command :
php composer.phar global config bin-dir --absolute php composer.phar global config bin-dir --absolute
``` ```
If you wish to update the binary later on you can just run a global update: If you wish to update the binary later on you can run a global update:
```sh ```sh
php composer.phar global update php composer.phar global update
@ -290,7 +295,7 @@ php composer.phar global update
## search ## search
The search command allows you to search through the current project's package The search command allows you to search through the current project's package
repositories. Usually this will be just packagist. You simply pass it the repositories. Usually this will be packagist. You simply pass it the
terms you want to search for. terms you want to search for.
```sh ```sh
@ -492,7 +497,7 @@ php composer.phar validate
### Options ### Options
* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound version constraints. * **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound or overly strict version constraints.
* **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. * **--no-check-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. * **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid.
* **--with-dependencies:** Also validate the composer.json of all installed dependencies. * **--with-dependencies:** Also validate the composer.json of all installed dependencies.
@ -521,7 +526,7 @@ vendor/seld/jsonlint:
## self-update (selfupdate) ## self-update (selfupdate)
To update Composer itself to the latest version, just run the `self-update` To update Composer itself to the latest version, run the `self-update`
command. It will replace your `composer.phar` with the latest version. command. It will replace your `composer.phar` with the latest version.
```sh ```sh
@ -625,7 +630,7 @@ would set `"extra": { "foo": { "bar": "value" } }`.
## create-project ## create-project
You can use Composer to create new projects from an existing package. This is You can use Composer to create new projects from an existing package. This is
the equivalent of doing a git clone/svn checkout followed by a "composer install" the equivalent of doing a git clone/svn checkout followed by a `composer install`
of the vendors. of the vendors.
There are several applications for this: There are several applications for this:
@ -635,7 +640,7 @@ There are several applications for this:
3. Projects with multiple developers can use this feature to bootstrap the 3. Projects with multiple developers can use this feature to bootstrap the
initial application for development. initial application for development.
To create a new project using Composer you can use the "create-project" command. To create a new project using Composer you can use the `create-project` command.
Pass it a package name, and the directory to create the project in. You can also Pass it a package name, and the directory to create the project in. You can also
provide a version as third argument, otherwise the latest version is used. provide a version as third argument, otherwise the latest version is used.
@ -666,9 +671,13 @@ By default the command checks for the packages on packagist.org.
package. package.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters. terminals or scripts which don't handle backspace characters.
* **--no-secure-http:** Disable the secure-http config option temporarily while
installing the root package. Use at your own risk. Using this flag is a bad
idea.
* **--keep-vcs:** Skip the deletion of the VCS metadata for the created * **--keep-vcs:** Skip the deletion of the VCS metadata for the created
project. This is mostly useful if you run the command in non-interactive project. This is mostly useful if you run the command in non-interactive
mode. mode.
* **--remove-vcs:** Force-remove the VCS metadata without prompting.
* **--no-install:** Disables installation of the vendors. * **--no-install:** Disables installation of the vendors.
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` * **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not requirements and force the installation even if the local machine does not
@ -677,7 +686,7 @@ By default the command checks for the packages on packagist.org.
## dump-autoload (dumpautoload) ## dump-autoload (dumpautoload)
If you need to update the autoloader because of new classes in a classmap If you need to update the autoloader because of new classes in a classmap
package for example, you can use "dump-autoload" to do that without having to package for example, you can use `dump-autoload` to do that without having to
go through an install or update. go through an install or update.
Additionally, it can dump an optimized autoloader that converts PSR-0/4 packages Additionally, it can dump an optimized autoloader that converts PSR-0/4 packages
@ -688,7 +697,7 @@ using this option you can still use PSR-0/4 for convenience and classmaps for
performance. performance.
### Options ### Options
* **--no-scripts:** Skips the execution of all scripts defined in composer.json file. * **--no-scripts:** Skips the execution of all scripts defined in `composer.json` file.
* **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster * **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
autoloader. This is recommended especially for production, but can take autoloader. This is recommended especially for production, but can take
a bit of time to run so it is currently not done by default. a bit of time to run so it is currently not done by default.
@ -721,7 +730,7 @@ Lists the name, version and license of every package installed. Use
* **--list (-l):** List user defined scripts. * **--list (-l):** List user defined scripts.
To run [scripts](articles/scripts.md) manually you can use this command, To run [scripts](articles/scripts.md) manually you can use this command,
just give it the script name and optionally any required arguments. give it the script name and optionally any required arguments.
## exec ## exec
@ -762,7 +771,7 @@ php composer.phar archive vendor/package 2.0.21 --format=zip
## help ## help
To get more information about a certain command, just use `help`. To get more information about a certain command, you can use `help`.
```sh ```sh
php composer.phar help install php composer.phar help install
@ -793,6 +802,90 @@ COMPOSER=composer-other.json php composer.phar install
The generated lock file will use the same name: `composer-other.lock` in this example. The generated lock file will use the same name: `composer-other.lock` in this example.
### COMPOSER_ALLOW_SUPERUSER
If set to 1, this env disables the warning about running commands as root/super user.
It also disables automatic clearing of sudo sessions, so you should really only set this
if you use Composer as super user at all times like in docker containers.
### COMPOSER_AUTH
The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable.
The contents of the variable should be a JSON formatted object containing http-basic,
github-oauth, bitbucket-oauth, ... objects as needed, and following the
[spec from the config](06-config.md#gitlab-oauth).
### COMPOSER_BIN_DIR
By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md))
directory to something other than `vendor/bin`.
### COMPOSER_CACHE_DIR
The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory,
which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option.
By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and
`C:\Users\<user>\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows.
### COMPOSER_CAFILE
By setting this environmental value, you can set a path to a certificate bundle
file to be used during SSL/TLS peer verification.
### COMPOSER_DISCARD_CHANGES
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
### COMPOSER_HOME
The `COMPOSER_HOME` var allows you to change the Composer home directory. This
is a hidden, global (per-user on the machine) directory that is shared between
all projects.
By default it points to `C:\Users\<user>\AppData\Roaming\Composer` on Windows
and `/Users/<user>/.composer` on macOS. On \*nix systems that follow the [XDG Base
Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html),
it points to `$XDG_CONFIG_HOME/composer`. On other \*nix systems, it points to
`/home/<user>/.composer`.
#### COMPOSER_HOME/config.json
You may put a `config.json` file into the location which `COMPOSER_HOME` points
to. Composer will merge this configuration with your project's `composer.json`
when you run the `install` and `update` commands.
This file allows you to set [repositories](05-repositories.md) and
[configuration](06-config.md) for the user's projects.
In case global configuration matches _local_ configuration, the _local_
configuration in the project's `composer.json` always wins.
### COMPOSER_HTACCESS_PROTECT
Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the
composer home, cache, and data directories.
### COMPOSER_MEMORY_LIMIT
If set, the value is used as php's memory_limit.
### COMPOSER_MIRROR_PATH_REPOS
If set to 1, this env changes the default path repository strategy to `mirror` instead
of `symlink`. As it is the default strategy being set it can still be overwritten by
repository options.
### COMPOSER_NO_INTERACTION
If set to 1, this env var will make Composer behave as if you passed the
`--no-interaction` flag to every command. This can be set on build boxes/CI.
### COMPOSER_PROCESS_TIMEOUT
This env var controls the time Composer waits for commands (such as git
commands) to finish executing. The default value is 300 seconds (5 minutes).
### COMPOSER_ROOT_VERSION ### COMPOSER_ROOT_VERSION
By setting this var you can specify the version of the root package, if it can By setting this var you can specify the version of the root package, if it can
@ -803,11 +896,6 @@ not be guessed from VCS info and is not present in `composer.json`.
By setting this var you can make Composer install the dependencies into a By setting this var you can make Composer install the dependencies into a
directory other than `vendor`. directory other than `vendor`.
### COMPOSER_BIN_DIR
By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md))
directory to something other than `vendor/bin`.
### http_proxy or HTTP_PROXY ### http_proxy or HTTP_PROXY
If you are using Composer from behind an HTTP proxy, you can use the standard If you are using Composer from behind an HTTP proxy, you can use the standard
@ -824,16 +912,6 @@ similar use case), and need to support proxies, please provide the `CGI_HTTP_PRO
environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further
details. details.
### no_proxy or NO_PROXY
If you are behind a proxy and would like to disable it for certain domains, you
can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of
domains the proxy should *not* be used for.
The env var accepts domains, IP addresses, and IP address blocks in CIDR
notation. You can restrict the filter to a particular port (e.g. `:80`). You
can also set it to `*` to ignore the proxy for all HTTP requests.
### HTTP_PROXY_REQUEST_FULLURI ### HTTP_PROXY_REQUEST_FULLURI
If you use a proxy but it does not support the request_fulluri flag, then you If you use a proxy but it does not support the request_fulluri flag, then you
@ -846,79 +924,23 @@ If you use a proxy but it does not support the request_fulluri flag for HTTPS
requests, then you should set this env var to `false` or `0` to prevent Composer requests, then you should set this env var to `false` or `0` to prevent Composer
from setting the request_fulluri option. from setting the request_fulluri option.
### COMPOSER_HOME ### COMPOSER_SELF_UPDATE_TARGET
The `COMPOSER_HOME` var allows you to change the Composer home directory. This If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem.
is a hidden, global (per-user on the machine) directory that is shared between
all projects.
By default it points to `C:\Users\<user>\AppData\Roaming\Composer` on Windows ### no_proxy or NO_PROXY
and `/Users/<user>/.composer` on OSX. On *nix systems that follow the [XDG Base
Directory Specifications](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html),
it points to `$XDG_CONFIG_HOME/composer`. On other *nix systems, it points to
`/home/<user>/.composer`.
#### COMPOSER_HOME/config.json If you are behind a proxy and would like to disable it for certain domains, you
can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of
domains the proxy should *not* be used for.
You may put a `config.json` file into the location which `COMPOSER_HOME` points The env var accepts domains, IP addresses, and IP address blocks in CIDR
to. Composer will merge this configuration with your project's `composer.json` notation. You can restrict the filter to a particular port (e.g. `:80`). You
when you run the `install` and `update` commands. can also set it to `*` to ignore the proxy for all HTTP requests.
This file allows you to set [repositories](05-repositories.md) and ### COMPOSER_DISABLE_NETWORK
[configuration](06-config.md) for the user's projects.
In case global configuration matches _local_ configuration, the _local_ If set to `1`, disables network access (best effort). This can be used for debugging or
configuration in the project's `composer.json` always wins. to run Composer on a plane or a starship with poor connectivity.
### COMPOSER_CACHE_DIR
The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory,
which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option.
By default it points to `$COMPOSER_HOME/cache` on \*nix and OSX, and
`C:\Users\<user>\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows.
### COMPOSER_PROCESS_TIMEOUT
This env var controls the time Composer waits for commands (such as git
commands) to finish executing. The default value is 300 seconds (5 minutes).
### COMPOSER_CAFILE
By setting this environmental value, you can set a path to a certificate bundle
file to be used during SSL/TLS peer verification.
### COMPOSER_AUTH
The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable.
The contents of the variable should be a JSON formatted object containing http-basic,
github-oauth, bitbucket-oauth, ... objects as needed, and following the
[spec from the config](06-config.md#gitlab-oauth).
### COMPOSER_DISCARD_CHANGES
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
### COMPOSER_NO_INTERACTION
If set to 1, this env var will make Composer behave as if you passed the
`--no-interaction` flag to every command. This can be set on build boxes/CI.
### COMPOSER_ALLOW_SUPERUSER
If set to 1, this env disables the warning about running commands as root/super user.
It also disables automatic clearing of sudo sessions, so you should really only set this
if you use Composer as super user at all times like in docker containers.
### COMPOSER_MIRROR_PATH_REPOS
If set to 1, this env changes the default path repository strategy to `mirror` instead
of `symlink`. As it is the default strategy being set it can still be overwritten by
repository options.
### COMPOSER_HTACCESS_PROTECT
Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the
composer home, cache, and data directories.
&larr; [Libraries](02-libraries.md) | [Schema](04-schema.md) &rarr; &larr; [Libraries](02-libraries.md) | [Schema](04-schema.md) &rarr;

View File

@ -43,7 +43,7 @@ Required for published packages (libraries).
### description ### description
A short description of the package. Usually this is just one line long. A short description of the package. Usually this is one line long.
Required for published packages (libraries). Required for published packages (libraries).
@ -104,7 +104,7 @@ Out of the box, Composer supports four types:
[dedicated article](articles/custom-installers.md). [dedicated article](articles/custom-installers.md).
Only use a custom type if you need custom logic during installation. It is Only use a custom type if you need custom logic during installation. It is
recommended to omit this field and have it just default to `library`. recommended to omit this field and have it default to `library`.
### keywords ### keywords
@ -127,6 +127,12 @@ An URL to the website of the project.
Optional. Optional.
### readme
A relative path to the readme document.
Optional.
### time ### time
Release date of the version. Release date of the version.
@ -145,14 +151,14 @@ The recommended notation for the most common licenses is (alphabetical):
- BSD-2-Clause - BSD-2-Clause
- BSD-3-Clause - BSD-3-Clause
- BSD-4-Clause - BSD-4-Clause
- GPL-2.0 - GPL-2.0-only / GPL-2.0-or-later
- GPL-3.0 - GPL-3.0-only / GPL-3.0-or-later
- LGPL-2.1 - LGPL-2.1-only / LGPL-2.1-or-later
- LGPL-3.0 - LGPL-3.0-only / LGPL-3.0-or-later
- MIT - MIT
Optional, but it is highly recommended to supply this. More identifiers are Optional, but it is highly recommended to supply this. More identifiers are
listed at the [SPDX Open Source License Registry](https://www.spdx.org/licenses/). listed at the [SPDX Open Source License Registry](https://spdx.org/licenses/).
For closed-source software, you may use `"proprietary"` as the license identifier. For closed-source software, you may use `"proprietary"` as the license identifier.
@ -172,8 +178,8 @@ An Example for disjunctive licenses:
```json ```json
{ {
"license": [ "license": [
"LGPL-2.1", "LGPL-2.1-only",
"GPL-3.0+" "GPL-3.0-or-later"
] ]
} }
``` ```
@ -182,7 +188,7 @@ Alternatively they can be separated with "or" and enclosed in parenthesis;
```json ```json
{ {
"license": "(LGPL-2.1 or GPL-3.0+)" "license": "(LGPL-2.1-only or GPL-3.0-or-later)"
} }
``` ```
@ -214,7 +220,7 @@ An example:
{ {
"name": "Jordi Boggiano", "name": "Jordi Boggiano",
"email": "j.boggiano@seld.be", "email": "j.boggiano@seld.be",
"homepage": "http://seld.be", "homepage": "https://seld.be",
"role": "Developer" "role": "Developer"
} }
] ]
@ -237,6 +243,7 @@ Support information includes the following:
* **source:** URL to browse or download the sources. * **source:** URL to browse or download the sources.
* **docs:** URL to the documentation. * **docs:** URL to the documentation.
* **rss:** URL to the RSS feed. * **rss:** URL to the RSS feed.
* **chat:** URL to the chat channel.
An example: An example:
@ -272,7 +279,7 @@ All links are optional fields.
`require` and `require-dev` additionally support stability flags ([root-only](04-schema.md#root-package)). `require` and `require-dev` additionally support stability flags ([root-only](04-schema.md#root-package)).
These allow you to further restrict or expand the stability of a package beyond These allow you to further restrict or expand the stability of a package beyond
the scope of the [minimum-stability](#minimum-stability) setting. You can apply the scope of the [minimum-stability](#minimum-stability) setting. You can apply
them to a constraint, or just apply them to an empty constraint if you want to them to a constraint, or apply them to an empty constraint if you want to
allow unstable packages of a dependency for example. allow unstable packages of a dependency for example.
Example: Example:
@ -307,7 +314,9 @@ releases for the `doctrine/data-fixtures` package :
`require` and `require-dev` additionally support explicit references (i.e. `require` and `require-dev` additionally support explicit references (i.e.
commit) for dev versions to make sure they are locked to a given state, even commit) for dev versions to make sure they are locked to a given state, even
when you run update. These only work if you explicitly require a dev version when you run update. These only work if you explicitly require a dev version
and append the reference with `#<ref>`. and append the reference with `#<ref>`. This is also a
[root-only](04-schema.md#root-package) feature and will be ignored in
dependencies.
Example: Example:
@ -356,7 +365,6 @@ Example:
> use and require. Alternatively you may use third party tools to analyze > use and require. Alternatively you may use third party tools to analyze
> your project for the list of extensions used. > your project for the list of extensions used.
#### require #### require
Lists packages required by this package. The package will not be installed Lists packages required by this package. The package will not be installed
@ -407,7 +415,7 @@ simply list it in `provide`.
#### suggest #### suggest
Suggested packages that can enhance or work well with this package. These are Suggested packages that can enhance or work well with this package. These are
just informational and are displayed after the package is installed, to give informational and are displayed after the package is installed, to give
your users a hint that they could add more packages, even though they are not your users a hint that they could add more packages, even though they are not
strictly required. strictly required.
@ -571,7 +579,7 @@ Example:
#### Files #### Files
If you want to require certain files explicitly on every request then you can use If you want to require certain files explicitly on every request then you can use
the 'files' autoloading mechanism. This is useful if your package includes PHP functions the `files` autoloading mechanism. This is useful if your package includes PHP functions
that cannot be autoloaded by PHP. that cannot be autoloaded by PHP.
Example: Example:
@ -586,7 +594,7 @@ Example:
#### Exclude files from classmaps #### Exclude files from classmaps
If you want to exclude some files or folders from the classmap you can use the 'exclude-from-classmap' property. If you want to exclude some files or folders from the classmap you can use the `exclude-from-classmap` property.
This might be useful to exclude test classes in your live environment, for example, as those will be skipped This might be useful to exclude test classes in your live environment, for example, as those will be skipped
from the classmap even when building an optimized autoloader. from the classmap even when building an optimized autoloader.
@ -608,7 +616,7 @@ Example:
The autoloader can have quite a substantial impact on your request time The autoloader can have quite a substantial impact on your request time
(50-100ms per request in large frameworks using a lot of classes). See the (50-100ms per request in large frameworks using a lot of classes). See the
[`article about optimizing the autoloader`](articles/autoloader-optimization.md) [article about optimizing the autoloader](articles/autoloader-optimization.md)
for more details on how to reduce this impact. for more details on how to reduce this impact.
### autoload-dev <span>([root-only](04-schema.md#root-package))</span> ### autoload-dev <span>([root-only](04-schema.md#root-package))</span>
@ -712,7 +720,7 @@ Use `"prefer-stable": true` to enable.
Custom package repositories to use. Custom package repositories to use.
By default Composer just uses the packagist repository. By specifying By default Composer only uses the packagist repository. By specifying
repositories you can get packages from elsewhere. repositories you can get packages from elsewhere.
Repositories are not resolved recursively. You can only add them to your main Repositories are not resolved recursively. You can only add them to your main
@ -732,7 +740,7 @@ The following repository types are supported:
project. project.
* **package:** If you depend on a project that does not have any support for * **package:** If you depend on a project that does not have any support for
composer whatsoever you can define the package inline using a `package` composer whatsoever you can define the package inline using a `package`
repository. You basically just inline the `composer.json` object. repository. You basically inline the `composer.json` object.
For more information on any of these, see [Repositories](05-repositories.md). For more information on any of these, see [Repositories](05-repositories.md).
@ -768,7 +776,7 @@ Example:
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "3.1.7", "version": "3.1.7",
"dist": { "dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip", "url": "https://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip" "type": "zip"
}, },
"source": { "source": {
@ -862,6 +870,22 @@ The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`,
Optional. Optional.
### abandoned
Indicates whether this package has been abandoned.
It can be boolean or a package name/URL pointing to a recommended alternative.
Examples:
Use `"abandoned": true` to indicates this package is abandoned.
Use `"abandoned": "monolog/monolog"` to indicates this package is abandoned and the
recommended alternative is `monolog/monolog`.
Defaults to false.
Optional.
### non-feature-branches ### non-feature-branches
A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something), A list of regex patterns of branch names that are non-numeric (e.g. "latest" or something),
@ -883,7 +907,7 @@ but the same branch is installed (in the example: latest-testing).
An example: An example:
If you have a testing branch, that is heavily maintained during a testing phase and is If you have a testing branch, that is heavily maintained during a testing phase and is
deployed to your staging environment, normally "composer show -s" will give you `versions : * dev-master`. deployed to your staging environment, normally `composer show -s` will give you `versions : * dev-master`.
If you configure `latest-.*` as a pattern for non-feature-branches like this: If you configure `latest-.*` as a pattern for non-feature-branches like this:
@ -893,7 +917,7 @@ If you configure `latest-.*` as a pattern for non-feature-branches like this:
} }
``` ```
Then "composer show -s" will give you `versions : * dev-latest-testing`. Then `composer show -s` will give you `versions : * dev-latest-testing`.
Optional. Optional.

View File

@ -11,7 +11,7 @@ understand some of the basic concepts that Composer is built on.
### Package ### Package
Composer is a dependency manager. It installs packages locally. A package is Composer is a dependency manager. It installs packages locally. A package is
essentially just a directory containing something. In this case it is PHP essentially a directory containing something. In this case it is PHP
code, but in theory it could be anything. And it contains a package code, but in theory it could be anything. And it contains a package
description which has a name and a version. The name and the version are used description which has a name and a version. The name and the version are used
to identify the package. to identify the package.
@ -57,9 +57,9 @@ The main repository type is the `composer` repository. It uses a single
`packages.json` file that contains all of the package metadata. `packages.json` file that contains all of the package metadata.
This is also the repository type that packagist uses. To reference a This is also the repository type that packagist uses. To reference a
`composer` repository, just supply the path before the `packages.json` file. `composer` repository, supply the path before the `packages.json` file.
In the case of packagist, that file is located at `/packages.json`, so the URL of In the case of packagist, that file is located at `/packages.json`, so the URL of
the repository would be `packagist.org`. For `example.org/packages.json` the the repository would be `repo.packagist.org`. For `example.org/packages.json` the
repository URL would be `example.org`. repository URL would be `example.org`.
#### packages #### packages
@ -93,7 +93,7 @@ Here is a minimal package definition:
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "3.1.7", "version": "3.1.7",
"dist": { "dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip", "url": "https://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip" "type": "zip"
} }
} }
@ -177,7 +177,7 @@ integrity, for example:
The file above declares that acme/foo and acme/bar can be found in this The file above declares that acme/foo and acme/bar can be found in this
repository, by loading the file referenced by `providers-url`, replacing repository, by loading the file referenced by `providers-url`, replacing
`%package%` by the vendor namespaced package name and `%hash%` by the `%package%` by the vendor namespaced package name and `%hash%` by the
sha256 field. Those files themselves just contain package definitions as sha256 field. Those files themselves contain package definitions as
described [above](#packages). described [above](#packages).
These fields are optional. You probably don't need them for your own custom These fields are optional. You probably don't need them for your own custom
@ -284,8 +284,9 @@ VCS repository provides `dist`s for them that fetch the packages as zips.
* **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial) * **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial)
The VCS driver to be used is detected automatically based on the URL. However, The VCS driver to be used is detected automatically based on the URL. However,
should you need to specify one for whatever reason, you can use `fossil`, `git`, should you need to specify one for whatever reason, you can use `git-bitbucket`,
`svn` or `hg` as the repository type instead of `vcs`. `hg-bitbucket`, `github`, `gitlab`, `perforce`, `fossil`, `git`, `svn` or `hg`
as the repository type instead of `vcs`.
If you set the `no-api` key to `true` on a github repository it will clone the If you set the `no-api` key to `true` on a github repository it will clone the
repository as it would with any other git repository instead of using the repository as it would with any other git repository instead of using the
@ -304,14 +305,11 @@ After creating an OAuth consumer in the BitBucket control panel, you need to set
the credentials like this (more info [here](https://getcomposer.org/doc/06-config.md#bitbucket-oauth)): the credentials like this (more info [here](https://getcomposer.org/doc/06-config.md#bitbucket-oauth)):
```json ```json
{ {
"config": { "bitbucket-oauth": {
"bitbucket-oauth": { "bitbucket.org": {
"bitbucket.org": { "consumer-key": "myKey",
"consumer-key": "myKey", "consumer-secret": "mySecret"
"consumer-secret": "mySecret"
}
} }
} }
} }
``` ```
@ -488,7 +486,7 @@ Here is an example for the smarty template engine:
"name": "smarty/smarty", "name": "smarty/smarty",
"version": "3.1.7", "version": "3.1.7",
"dist": { "dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip", "url": "https://www.smarty.net/files/Smarty-3.1.7.zip",
"type": "zip" "type": "zip"
}, },
"source": { "source": {
@ -606,7 +604,7 @@ private packages:
} }
``` ```
Each zip artifact is just a ZIP archive with `composer.json` in root folder: Each zip artifact is a ZIP archive with `composer.json` in root folder:
```sh ```sh
unzip -l acme-corp-parser-10.3.5.zip unzip -l acme-corp-parser-10.3.5.zip
@ -659,7 +657,7 @@ be explicitly defined in the package's `composer.json` file. If the version
cannot be resolved by these means, it is assumed to be `dev-master`. cannot be resolved by these means, it is assumed to be `dev-master`.
The local package will be symlinked if possible, in which case the output in The local package will be symlinked if possible, in which case the output in
the console will read `Symlinked from ../../packages/my-package`. If symlinking the console will read `Symlinking from ../../packages/my-package`. If symlinking
is _not_ possible the package will be copied. In that case, the console will is _not_ possible the package will be copied. In that case, the console will
output `Mirrored from ../../packages/my-package`. output `Mirrored from ../../packages/my-package`.
@ -668,6 +666,10 @@ Instead of default fallback strategy you can force to use symlink with
mirroring can be useful when deploying or generating package from a mirroring can be useful when deploying or generating package from a
monolithic repository. monolithic repository.
> **Note:** On Windows, directory symlinks are implemented using NTFS junctions
> because they can be created by non-admin users. Mirroring will always be used
> on versions below Windows 7 or if `proc_open` has been disabled.
```json ```json
{ {
"repositories": [ "repositories": [
@ -708,7 +710,7 @@ You can disable the default Packagist.org repository by adding this to your
You can disable Packagist.org globally by using the global config flag: You can disable Packagist.org globally by using the global config flag:
``` ```bash
composer config -g repo.packagist false composer config -g repo.packagist false
``` ```

View File

@ -9,6 +9,20 @@ Defaults to `300`. The duration processes like git clones can run before
Composer assumes they died out. You may need to make this higher if you have a Composer assumes they died out. You may need to make this higher if you have a
slow connection or huge vendors. slow connection or huge vendors.
To disable the process timeout on a custom command under `scripts`, a static
helper is available:
```json
{
"scripts": {
"test": [
"Composer\\Config::disableProcessTimeout",
"phpunit"
]
}
}
```
## use-include-path ## use-include-path
Defaults to `false`. If `true`, the Composer autoloader will also look for classes Defaults to `false`. If `true`, the Composer autoloader will also look for classes
@ -65,13 +79,17 @@ an OAuth token for GitHub.
A list of domain names and oauth keys. For example using `{"gitlab.com": A list of domain names and oauth keys. For example using `{"gitlab.com":
"oauthtoken"}` as the value of this option will use `oauthtoken` to access "oauthtoken"}` as the value of this option will use `oauthtoken` to access
private repositories on gitlab. private repositories on gitlab. Please note: If the package is not hosted at
gitlab.com the domain names must be also specified with the
[`gitlab-domains`](06-config.md#gitlab-domains) option.
## gitlab-token ## gitlab-token
A list of domain names and private tokens. For example using `{"gitlab.com": A list of domain names and private tokens. For example using `{"gitlab.com":
"privatetoken"}` as the value of this option will use `privatetoken` to access "privatetoken"}` as the value of this option will use `privatetoken` to access
private repositories on gitlab. private repositories on gitlab. Please note: If the package is not hosted at
gitlab.com the domain names must be also specified with the
[`gitlab-domains`](06-config.md#gitlab-domains) option.
## disable-tls ## disable-tls
@ -120,7 +138,7 @@ value of this option will let Composer authenticate against example.org.
Lets you fake platform packages (PHP and extensions) so that you can emulate a Lets you fake platform packages (PHP and extensions) so that you can emulate a
production env or define your target platform in the config. Example: `{"php": production env or define your target platform in the config. Example: `{"php":
"5.4", "ext-something": "4.0"}`. "7.0.3", "ext-something": "4.0.3"}`.
## vendor-dir ## vendor-dir
@ -230,6 +248,14 @@ github API will have a date instead of the machine hostname.
Defaults to `["gitlab.com"]`. A list of domains of GitLab servers. Defaults to `["gitlab.com"]`. A list of domains of GitLab servers.
This is used if you use the `gitlab` repository type. This is used if you use the `gitlab` repository type.
## use-github-api
Defaults to `true`. Similar to the `no-api` key on a specific repository,
setting `use-github-api` to `false` will define the global behavior for all
GitHub repositories to clone the repository as it would with any other git
repository instead of using the GitHub API. But unlike using the `git`
driver directly, Composer will still attempt to use GitHub's zip files.
## notify-on-install ## notify-on-install
Defaults to `true`. Composer allows repositories to define a notification URL, Defaults to `true`. Composer allows repositories to define a notification URL,

View File

@ -7,7 +7,7 @@ contributing.
If you would like to contribute to Composer, please read the If you would like to contribute to Composer, please read the
[README](https://github.com/composer/composer) and [README](https://github.com/composer/composer) and
[CONTRIBUTING](https://github.com//composer/composer/blob/master/.github/CONTRIBUTING.md) [CONTRIBUTING](https://github.com/composer/composer/blob/master/.github/CONTRIBUTING.md)
documents. documents.
The most important guidelines are described as follows: The most important guidelines are described as follows:

View File

@ -60,7 +60,7 @@ Branch aliases are great for aliasing main development lines. But in order to
use them you need to have control over the source repository, and you need to use them you need to have control over the source repository, and you need to
commit changes to version control. commit changes to version control.
This is not really fun when you just want to try a bugfix of some library that This is not really fun when you want to try a bugfix of some library that
is a dependency of your local project. is a dependency of your local project.
For this reason, you can alias packages in your `require` and `require-dev` For this reason, you can alias packages in your `require` and `require-dev`
@ -72,7 +72,7 @@ local project.
You are using `symfony/monolog-bundle` which requires `monolog/monolog` version You are using `symfony/monolog-bundle` which requires `monolog/monolog` version
`1.*`. So you need your `dev-bugfix` to match that constraint. `1.*`. So you need your `dev-bugfix` to match that constraint.
Just add this to your project's root `composer.json`: Add this to your project's root `composer.json`:
```json ```json
{ {
@ -89,16 +89,23 @@ Just add this to your project's root `composer.json`:
} }
``` ```
Or let composer add it for you with:
```
php composer.phar require monolog/monolog:"dev-bugfix as 1.0.x-dev"
```
That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub
and alias it to `1.0.x-dev`. and alias it to `1.0.x-dev`.
> **Note:** If a package with inline aliases is required, the alias (right of > **Note:** Inline aliasing is a root-only feature. If a package with inline
> the `as`) is used as the version constraint. The part left of the `as` is > aliases is required, the alias (right of the `as`) is used as the version
> discarded. As a consequence, if A requires B and B requires `monolog/monolog` > constraint. The part left of the `as` is discarded. As a consequence, if
> version `dev-bugfix as 1.0.x-dev`, installing A will make B require > A requires B and B requires `monolog/monolog` version `dev-bugfix as 1.0.x-dev`,
> `1.0.x-dev`, which may exist as a branch alias or an actual `1.0` branch. If > installing A will make B require `1.0.x-dev`, which may exist as a branch
> it does not, it must be re-inline-aliased in A's `composer.json`. > alias or an actual `1.0` branch. If it does not, it must be
> inline-aliased again in A's `composer.json`.
> **Note:** Inline aliasing should be avoided, especially for published > **Note:** Inline aliasing should be avoided, especially for published
> packages. If you found a bug, try and get your fix merged upstream. This > packages/libraries. If you found a bug, try and get your fix merged upstream.
> helps to avoid issues for users of your package. > This helps to avoid issues for users of your package.

View File

@ -53,7 +53,6 @@ result in slow filesystem checks. To solve this issue two Level 2 optimization
options exist, and you can decide to enable either if you have a lot of options exist, and you can decide to enable either if you have a lot of
class_exists checks that are done for classes that do not exist in your project. class_exists checks that are done for classes that do not exist in your project.
## Optimization Level 2/A: Authoritative class maps ## Optimization Level 2/A: Authoritative class maps
### How to run it? ### How to run it?
@ -82,7 +81,6 @@ then you might experience "class not found" issues in production. Enable this wi
> Note: This can not be combined with Level 2/B optimizations. You have to choose one as > Note: This can not be combined with Level 2/B optimizations. You have to choose one as
> they address the same issue in different ways. > they address the same issue in different ways.
## Optimization Level 2/B: APCu cache ## Optimization Level 2/B: APCu cache
### How to run it? ### How to run it?

View File

@ -77,7 +77,7 @@ or another constraint if you want really specific versions.
} }
``` ```
Once you've done this, you just run: Once you've done this, you run:
php bin/satis build <configuration file> <build dir> php bin/satis build <configuration file> <build dir>
@ -112,6 +112,19 @@ Note that this will still need to pull and scan all of your VCS repositories
because any VCS repository might contain (on any branch) one of the selected because any VCS repository might contain (on any branch) one of the selected
packages. packages.
If you want to scan only the selected package and not all VCS repositories you need
to declare a *name* for all your package (this only work on VCS repositories type) :
```json
{
"repositories": [
{ "name": "company/privaterepo", "type": "vcs", "url": "https://github.com/mycompany/privaterepo" },
{ "name": "private/repo", "type": "vcs", "url": "http://svn.example.org/private/repo" },
{ "name": "mycompany/privaterepo2", "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" }
]
}
```
If you want to scan only a single repository and update all packages found in If you want to scan only a single repository and update all packages found in
it, pass the VCS repository URL as an optional argument: it, pass the VCS repository URL as an optional argument:
@ -306,7 +319,7 @@ be marked abandoned as well.
It is possible to make satis automatically resolve and add all dependencies for It is possible to make satis automatically resolve and add all dependencies for
your projects. This can be used with the Downloads functionality to have a your projects. This can be used with the Downloads functionality to have a
complete local mirror of packages. Just add the following to your `satis.json`: complete local mirror of packages. Add the following to your `satis.json`:
```json ```json
{ {
@ -336,9 +349,8 @@ is set to true.
* `notify-batch`: optional, specify a URL that will be called every time a * `notify-batch`: optional, specify a URL that will be called every time a
user installs a package. See [notify-batch]. user installs a package. See [notify-batch].
[ssh2 context options]: https://secure.php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-options [ssh2 context options]: https://secure.php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-options
[ssl context options]: https://secure.php.net/manual/en/context.ssl.php [ssl context options]: https://secure.php.net/manual/en/context.ssl.php
[Twig]: http://twig.sensiolabs.org/ [Twig]: https://twig.sensiolabs.org/
[config schema]: https://getcomposer.org/doc/04-schema.md#config [config schema]: https://getcomposer.org/doc/04-schema.md#config
[notify-batch]: https://getcomposer.org/doc/05-repositories.md#notify-batch [notify-batch]: https://getcomposer.org/doc/05-repositories.md#notify-batch

View File

@ -176,8 +176,8 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface
if ($protocol === 's3') { if ($protocol === 's3') {
$awsClient = new AwsClient($this->io, $this->composer->getConfig()); $awsClient = new AwsClient($this->io, $this->composer->getConfig());
$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); $s3Downloader = new S3Downloader($this->io, $event->getHttpDownloader()->getOptions(), $awsClient);
$event->setRemoteFilesystem($s3RemoteFilesystem); $event->setHttpdownloader($s3Downloader);
} }
} }
} }
@ -261,6 +261,11 @@ Now the `custom-plugin-command` is available alongside Composer commands.
> _Composer commands are based on the [Symfony Console Component][10]._ > _Composer commands are based on the [Symfony Console Component][10]._
## Running plugins manually
Plugins for an event can be run manually by the `run-script` command. This works the same way as
[running scripts manually](scripts.md#running-scripts-manually).
## Using Plugins ## Using Plugins
Plugin packages are automatically loaded as soon as they are installed and will Plugin packages are automatically loaded as soon as they are installed and will
@ -282,4 +287,4 @@ local project plugins are loaded.
[7]: ../01-basic-usage.md#package-versions [7]: ../01-basic-usage.md#package-versions
[8]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capable.php [8]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capable.php
[9]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capability/CommandProvider.php [9]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/Capability/CommandProvider.php
[10]: http://symfony.com/doc/current/components/console/introduction.html [10]: https://symfony.com/doc/current/components/console.html

View File

@ -15,7 +15,6 @@ the Composer execution process.
> executed. If a dependency of the root package specifies its own scripts, > executed. If a dependency of the root package specifies its own scripts,
> Composer does not execute those additional scripts. > Composer does not execute those additional scripts.
## Event names ## Event names
Composer fires the following named events during its execution process: Composer fires the following named events during its execution process:
@ -62,8 +61,11 @@ Composer fires the following named events during its execution process:
- **command**: occurs before any Composer Command is executed on the CLI. It - **command**: occurs before any Composer Command is executed on the CLI. It
provides you with access to the input and output objects of the program. provides you with access to the input and output objects of the program.
- **pre-file-download**: occurs before files are downloaded and allows - **pre-file-download**: occurs before files are downloaded and allows
you to manipulate the `RemoteFilesystem` object prior to downloading files you to manipulate the `HttpDownloader` object prior to downloading files
based on the URL to be downloaded. based on the URL to be downloaded.
- **pre-command-run**: occurs before a command is executed and allows you to
manipulate the `InputInterface` object's options and arguments to tweak
a command's behavior.
> **Note:** Composer makes no assumptions about the state of your dependencies > **Note:** Composer makes no assumptions about the state of your dependencies
> prior to `install` or `update`. Therefore, you should not specify scripts > prior to `install` or `update`. Therefore, you should not specify scripts
@ -187,7 +189,7 @@ composer run-script [--dev] [--no-dev] script
``` ```
For example `composer run-script post-install-cmd` will run any For example `composer run-script post-install-cmd` will run any
**post-install-cmd** scripts that have been defined. **post-install-cmd** scripts and [plugins](plugins.md) that have been defined.
You can also give additional arguments to the script handler by appending `--` You can also give additional arguments to the script handler by appending `--`
followed by the handler arguments. e.g. followed by the handler arguments. e.g.
@ -219,6 +221,56 @@ to the `phpunit` script.
> are easily accessible. In this example no matter if the `phpunit` binary is > are easily accessible. In this example no matter if the `phpunit` binary is
> actually in `vendor/bin/phpunit` or `bin/phpunit` it will be found and executed. > actually in `vendor/bin/phpunit` or `bin/phpunit` it will be found and executed.
Although Composer is not intended to manage long-running processes and other
such aspects of PHP projects, it can sometimes be handy to disable the process
timeout on custom commands. This timeout defaults to 300 seconds and can be
overridden in a variety of ways depending on the desired effect:
- disable it for all commands using the config key `process-timeout`,
- disable it for the current or future invocations of composer using the
environment variable `COMPOSER_PROCESS_TIMEOUT`,
- for a specific invocation using the `--timeout` flag of the `run-script` command,
- using a static helper for specific scripts.
To disable the timeout for specific scripts with the static helper directly in
composer.json:
```json
{
"scripts": {
"test": [
"Composer\\Config::disableProcessTimeout",
"phpunit"
]
}
}
```
To disable the timeout for every script on a given project, you can use the
composer.json configuration:
```json
{
"config": {
"process-timeout": 0
}
}
```
It's also possible to set the global environment variable to disable the timeout
of all following scripts in the current terminal environment:
```
export COMPOSER_PROCESS_TIMEOUT=0
```
To disable the timeout of a single script call, you must use the `run-script` composer
command and specify the `--timeout` parameter:
```
composer run-script --timeout=0 test
```
## Referencing scripts ## Referencing scripts
To enable script re-use and avoid duplicates, you can call a script from another To enable script re-use and avoid duplicates, you can call a script from another
@ -236,6 +288,17 @@ one by prefixing the command name with `@`:
} }
``` ```
You can also refer a script and pass it new arguments:
```json
{
"scripts": {
"tests": "phpunit",
"testsVerbose": "@tests -vvv"
}
}
```
## Calling Composer commands ## Calling Composer commands
To call Composer commands, you can use `@composer` which will automatically To call Composer commands, you can use `@composer` which will automatically

View File

@ -140,13 +140,19 @@ Debian-like systems):
memory_limit = -1 memory_limit = -1
``` ```
Composer also respects a memory limit defined by the `COMPOSER_MEMORY_LIMIT` environment variable:
```sh
COMPOSER_MEMORY_LIMIT=-1 composer.phar <...>
```
Or, you can increase the limit with a command-line argument: Or, you can increase the limit with a command-line argument:
```sh ```sh
php -d memory_limit=-1 composer.phar <...> php -d memory_limit=-1 composer.phar <...>
``` ```
This issue can also happen on cPanel instances, when the shell fork bomb protection is activated. For more information, see the [documentation](https://documentation.cpanel.net/display/ALD/Shell+Fork+Bomb+Protection) of the fork bomb feature on the cPanel site. This issue can also happen on cPanel instances, when the shell fork bomb protection is activated. For more information, see the [documentation](https://documentation.cpanel.net/display/68Docs/Shell+Fork+Bomb+Protection) of the fork bomb feature on the cPanel site.
## Xdebug impact on Composer ## Xdebug impact on Composer
@ -163,7 +169,7 @@ please report this [issue](https://github.com/composer/composer/issues).
2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`, 2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`,
`HKEY_CURRENT_USER\Software\Microsoft\Command Processor` `HKEY_CURRENT_USER\Software\Microsoft\Command Processor`
or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`. or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`.
3. Check if it contains any path to non-existent file, if it's the case, just remove them. 3. Check if it contains any path to non-existent file, if it's the case, remove them.
## API rate limit and OAuth tokens ## API rate limit and OAuth tokens
@ -244,7 +250,7 @@ following workarounds:
On linux, it seems that running this command helps to make ipv4 traffic have a On linux, it seems that running this command helps to make ipv4 traffic have a
higher prio than ipv6, which is a better alternative than disabling ipv6 entirely: higher prio than ipv6, which is a better alternative than disabling ipv6 entirely:
```Bash ```bash
sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf" sudo sh -c "echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf"
``` ```
@ -256,13 +262,13 @@ On windows the only way is to disable ipv6 entirely I am afraid (either in windo
Get name of your network device: Get name of your network device:
``` ```bash
networksetup -listallnetworkservices networksetup -listallnetworkservices
``` ```
Disable IPv6 on that device (in this case "Wi-Fi"): Disable IPv6 on that device (in this case "Wi-Fi"):
``` ```bash
networksetup -setv6off Wi-Fi networksetup -setv6off Wi-Fi
``` ```
@ -270,7 +276,7 @@ Run composer ...
You can enable IPv6 again with: You can enable IPv6 again with:
``` ```bash
networksetup -setv6automatic Wi-Fi networksetup -setv6automatic Wi-Fi
``` ```
@ -281,14 +287,14 @@ for everyone.
## Composer hangs with SSH ControlMaster ## Composer hangs with SSH ControlMaster
When you try to install packages from a Git repository and you use the `ControlMaster` When you try to install packages from a Git repository and you use the `ControlMaster`
setting for your SSH connection, Composer might just hang endlessly and you see a `sh` setting for your SSH connection, Composer might hang endlessly and you see a `sh`
process in the `defunct` state in your process list. process in the `defunct` state in your process list.
The reason for this is a SSH Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1988 The reason for this is a SSH Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1988
As a workaround, open a SSH connection to your Git host before running Composer: As a workaround, open a SSH connection to your Git host before running Composer:
``` ```bash
ssh -t git@mygitserver.tld ssh -t git@mygitserver.tld
composer update composer update
``` ```

View File

@ -13,7 +13,6 @@ If a package contains other scripts that are not needed by the package
users (like build or compile scripts) that code should not be listed users (like build or compile scripts) that code should not be listed
as a vendor binary. as a vendor binary.
## How is it defined? ## How is it defined?
It is defined by adding the `bin` key to a project's `composer.json`. It is defined by adding the `bin` key to a project's `composer.json`.
@ -34,12 +33,10 @@ for any project that **depends** on that project.
This is a convenient way to expose useful scripts that would This is a convenient way to expose useful scripts that would
otherwise be hidden deep in the `vendor/` directory. otherwise be hidden deep in the `vendor/` directory.
## What happens when Composer is run on a composer.json that defines vendor binaries? ## What happens when Composer is run on a composer.json that defines vendor binaries?
For the binaries that a package defines directly, nothing happens. For the binaries that a package defines directly, nothing happens.
## What happens when Composer is run on a composer.json that has dependencies with vendor binaries listed? ## What happens when Composer is run on a composer.json that has dependencies with vendor binaries listed?
Composer looks for the binaries defined in all of the dependencies. A Composer looks for the binaries defined in all of the dependencies. A
@ -69,13 +66,12 @@ Say project `my-vendor/project-b` has requirements setup like this:
``` ```
Running `composer install` for this `composer.json` will look at Running `composer install` for this `composer.json` will look at
all of project-b's dependencies and install them to `vendor/bin`. all of project-a's binaries and install them to `vendor/bin`.
In this case, Composer will make `vendor/my-vendor/project-a/bin/project-a-bin` In this case, Composer will make `vendor/my-vendor/project-a/bin/project-a-bin`
available as `vendor/bin/project-a-bin`. On a Unix-like platform available as `vendor/bin/project-a-bin`. On a Unix-like platform
this is accomplished by creating a symlink. this is accomplished by creating a symlink.
## What about Windows and .bat files? ## What about Windows and .bat files?
Packages managed entirely by Composer do not *need* to contain any Packages managed entirely by Composer do not *need* to contain any
@ -90,7 +86,6 @@ Packages that need to support workflows that may not include Composer
are welcome to maintain custom `.bat` files. In this case, the package are welcome to maintain custom `.bat` files. In this case, the package
should **not** list the `.bat` file as a binary as it is not needed. should **not** list the `.bat` file as a binary as it is not needed.
## Can vendor binaries be installed somewhere other than vendor/bin? ## Can vendor binaries be installed somewhere other than vendor/bin?
Yes, there are two ways an alternate vendor binary location can be specified: Yes, there are two ways an alternate vendor binary location can be specified:

View File

@ -32,7 +32,7 @@ repository:*
v1 v1
v2 v2
my-feature my-feature
nother-feature another-feature
~/my-library$ git tag ~/my-library$ git tag
v1.0 v1.0
@ -74,11 +74,11 @@ correct location in your `vendor` directory.
### Branches ### Branches
If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it just copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).) If you want Composer to check out a branch instead of a tag, you need to point it to the branch using the special `dev-*` prefix (or sometimes suffix; see below). If you're checking out a branch, it's assumed that you want to *work* on the branch and Composer actually clones the repo into the correct place in your `vendor` directory. For tags, it copies the right files without actually cloning the repo. (You can modify this behavior with --prefer-source and --prefer-dist, see [install options](../03-cli.md#install).)
In the above example, if you wanted to check out the `my-feature` branch, you would specify `dev-my-feature` as the version constraint in your `require` clause. This would result in Composer cloning the `my-library` repository into my `vendor` directory and checking out the `my-feature` branch. In the above example, if you wanted to check out the `my-feature` branch, you would specify `dev-my-feature` as the version constraint in your `require` clause. This would result in Composer cloning the `my-library` repository into my `vendor` directory and checking out the `my-feature` branch.
When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can just name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix. When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix.
### Minimum Stability ### Minimum Stability
@ -140,7 +140,7 @@ Example: `1.0.*`
The `~` operator is best explained by example: `~1.2` is equivalent to 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 `>=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 it is mostly useful for projects respecting [semantic
versioning](http://semver.org/). A common usage would be to mark the minimum versioning](https://semver.org/). A common usage would be to mark the minimum
minor version you depend on, like `~1.2` (which allows anything up to, but not 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 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 breaks until 2.0, that works well. Another way of looking at it is that using
@ -201,7 +201,7 @@ setting. All available stability flags are listed on the minimum-stability
section of the [schema page](../04-schema.md#minimum-stability). section of the [schema page](../04-schema.md#minimum-stability).
## Summary ## Summary
``` ```json
"require": { "require": {
"vendor/package": "1.3.2", // exactly 1.3.2 "vendor/package": "1.3.2", // exactly 1.3.2

View File

@ -23,7 +23,7 @@ WordPress theme:
Now when your theme is installed with Composer it will be placed into Now when your theme is installed with Composer it will be placed into
`wp-content/themes/themename/` folder. Check the `wp-content/themes/themename/` folder. Check the
[current supported types](https://github.com/composer/installers#current-supported-types) [current supported types](https://github.com/composer/installers#current-supported-package-types)
for your package. for your package.
As a **package consumer** you can set or override the install path for a package As a **package consumer** you can set or override the install path for a package

View File

@ -2,16 +2,16 @@
As noted on the download page, the installer script contains a As noted on the download page, the installer script contains a
signature which changes when the installer code changes and as such signature which changes when the installer code changes and as such
it should not be relied upon long term. it should not be relied upon in the long term.
An alternative is to use this script which only works with unix utils: An alternative is to use this script which only works with UNIX utilities:
```bash ```bash
#!/bin/sh #!/bin/sh
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig) EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then then
@ -29,13 +29,13 @@ exit $RESULT
The script will exit with 1 in case of failure, or 0 on success, and is quiet The script will exit with 1 in case of failure, or 0 on success, and is quiet
if no error occurs. if no error occurs.
Alternatively if you want to rely on an exact copy of the installer you can fetch Alternatively, if you want to rely on an exact copy of the installer, you can fetch
a specific version from github's history. The commit hash should be enough to a specific version from GitHub's history. The commit hash should be enough to
give it uniqueness and authenticity as long as you can trust the GitHub servers. give it uniqueness and authenticity as long as you can trust the GitHub servers.
For example: For example:
```bash ```bash
wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet wget https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer -O - -q | php -- --quiet
``` ```
You may replace the commit hash by whatever the last commit hash is on You may replace the commit hash by whatever the last commit hash is on

View File

@ -0,0 +1,4 @@
# Which version numbering system does Composer itself use?
Composer uses [Semantic Versioning (aka SemVer)
2.0.0](https://semver.org/spec/v2.0.0.html).

View File

@ -14,7 +14,7 @@ compatible with the new major version of your dependency.
For example instead of using `>=3.4` you should use `~3.4` which allows all For example instead of using `>=3.4` you should use `~3.4` which allows all
versions up to `3.999` but does not include `4.0` and above. The `^` operator versions up to `3.999` but does not include `4.0` and above. The `^` operator
works very well with libraries following [semantic versioning](http://semver.org). works very well with libraries following [semantic versioning](https://semver.org).
**Note:** As a package maintainer, you can make the life of your users easier **Note:** As a package maintainer, you can make the life of your users easier
by providing an [alias version](../articles/aliases.md) for your development by providing an [alias version](../articles/aliases.md) for your development

View File

@ -16,6 +16,6 @@ but it is not possible to determine if when you wrote that you were thinking
of a package in version 3.0.0 or not. Should it match because you asked for of a package in version 3.0.0 or not. Should it match because you asked for
`>=2` or should it not match because you asked for a `2.*`? `>=2` or should it not match because you asked for a `2.*`?
For this reason, Composer just throws an error and says that this is invalid. For this reason, Composer throws an error and says that this is invalid.
The easy way to fix it is to think about what you really mean, and use only The easy way to fix it is to think about what you really mean, and use only
one of those rules. one of those rules.

View File

@ -15,7 +15,7 @@ associated with inline VCS repositories.
There are three ways the dependency solver could work with custom repositories: There are three ways the dependency solver could work with custom repositories:
- Fetch the repositories of root package, get all the packages from the defined - Fetch the repositories of root package, get all the packages from the defined
repositories, resolve requirements. This is the current state and it works well repositories, then resolve requirements. This is the current state and it works well
except for the limitation of not loading repositories recursively. except for the limitation of not loading repositories recursively.
- Fetch the repositories of root package, while initializing packages from the - Fetch the repositories of root package, while initializing packages from the

View File

@ -20,4 +20,3 @@ All these repositories contain the following packages.
* `bar/baz` has a 1.0.0 version and 1.0.x-dev as well as dev-default branches. * `bar/baz` has a 1.0.0 version and 1.0.x-dev as well as dev-default branches.
Additionally, 1.1.x-dev is a branch alias for dev-default. Additionally, 1.1.x-dev is a branch alias for dev-default.

5
phpstan/autoload.php Normal file
View File

@ -0,0 +1,5 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../src/bootstrap.php';

42
phpstan/config.neon Normal file
View File

@ -0,0 +1,42 @@
parameters:
level: 0
excludes_analyse:
- 'tests/Composer/Test/Fixtures'
- 'tests/Composer/Test/Autoload/Fixtures'
- 'tests/Composer/Test/Plugin/Fixtures'
ignoreErrors:
# unused parameters
- '~^Constructor of class Composer\\Repository\\VcsRepository has an unused parameter \$dispatcher\.$~'
- '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$dispatcher\.$~'
- '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$disableTls\.$~'
- '~^Constructor of class Composer\\Util\\Http\\CurlDownloader has an unused parameter \$options\.$~'
- '~^Constructor of class Composer\\Repository\\PearRepository has an unused parameter \$config\.$~'
# unused uses
- '~^Anonymous function has an unused use \$io\.$~'
- '~^Anonymous function has an unused use \$cache\.$~'
- '~^Anonymous function has an unused use \$path\.$~'
- '~^Anonymous function has an unused use \$fileName\.$~'
# ion cube is not installed
- '~^Function ioncube_loader_\w+ not found\.$~'
# rar is not installed
- '~^Call to static method open\(\) on an unknown class RarArchive\.$~'
# imagick is not installed
- '~^Instantiated class Imagick not found\.$~'
# variables from global scope
- '~^Undefined variable: \$vendorDir$~'
- '~^Undefined variable: \$baseDir$~'
# variable defined in eval
- '~^Undefined variable: \$res$~'
# always checked whether the class exists
- '~^Instantiated class Symfony\\Component\\Console\\Terminal not found\.$~'
- '~^Class Symfony\\Component\\Console\\Input\\StreamableInputInterface not found\.$~'
- '~^Call to an undefined static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\).$~'
# parent call in test mocks
- '~^Composer\\Test\\Mock\\HttpDownloaderMock::__construct\(\) does not call parent constructor from Composer\\Util\\HttpDownloader\.$~'
- '~^Composer\\Test\\Mock\\InstallationManagerMock::__construct\(\) does not call parent constructor from Composer\\Installer\\InstallationManager\.$~'

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false" backupStaticAttributes="false"
colors="true" colors="true"
convertErrorsToExceptions="true" convertErrorsToExceptions="true"
@ -8,7 +10,6 @@
convertWarningsToExceptions="true" convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php" bootstrap="tests/bootstrap.php"
> >
<testsuites> <testsuites>

View File

@ -33,6 +33,10 @@
"description": "Homepage URL for the project.", "description": "Homepage URL for the project.",
"format": "uri" "format": "uri"
}, },
"readme": {
"type": "string",
"description": "Relative path to the readme document."
},
"version": { "version": {
"type": "string", "type": "string",
"description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes." "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes."
@ -267,6 +271,10 @@
"type": "string" "type": "string"
} }
}, },
"use-github-api": {
"type": "boolean",
"description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."
},
"archive-format": { "archive-format": {
"type": "string", "type": "string",
"description": "The default archiving format when not provided on cli, defaults to \"tar\"." "description": "The default archiving format when not provided on cli, defaults to \"tar\"."
@ -278,6 +286,10 @@
"htaccess-protect": { "htaccess-protect": {
"type": "boolean", "type": "boolean",
"description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories."
},
"sort-packages": {
"type": "boolean",
"description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
} }
} }
}, },
@ -364,8 +376,8 @@
"description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages."
}, },
"bin": { "bin": {
"type": ["array"], "type": ["string", "array"],
"description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
"items": { "items": {
"type": "string" "type": "string"
} }
@ -482,6 +494,11 @@
"description": "IRC channel for support, as irc://server/channel.", "description": "IRC channel for support, as irc://server/channel.",
"format": "uri" "format": "uri"
}, },
"chat": {
"type": "string",
"description": "URL to the support chat.",
"format": "uri"
},
"source": { "source": {
"type": "string", "type": "string",
"description": "URL to browse or download the sources.", "description": "URL to browse or download the sources.",
@ -670,15 +687,14 @@
{ "$ref": "#/definitions/inline-package" }, { "$ref": "#/definitions/inline-package" },
{ {
"type": "array", "type": "array",
"items": { "items": { "$ref": "#/definitions/inline-package" }
"type": { "$ref": "#/definitions/inline-package" }
}
} }
] ]
} }
} }
}, },
"inline-package": { "inline-package": {
"type": "object",
"required": ["name", "version"], "required": ["name", "version"],
"properties": { "properties": {
"name": { "name": {
@ -772,8 +788,8 @@
} }
}, },
"bin": { "bin": {
"type": ["array"], "type": ["string", "array"],
"description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).", "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
"items": { "items": {
"type": "string" "type": "string"
} }

View File

@ -21,6 +21,7 @@ use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Script\ScriptEvents; use Composer\Script\ScriptEvents;
use Composer\Util\PackageSorter;
/** /**
* @author Igor Wiedler <igor@wiedler.ch> * @author Igor Wiedler <igor@wiedler.ch>
@ -157,7 +158,7 @@ EOF;
// Collect information from all packages. // Collect information from all packages.
$packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages()); $packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages());
$autoloads = $this->parseAutoloads($packageMap, $mainPackage); $autoloads = $this->parseAutoloads($packageMap, $mainPackage, $this->devMode === false);
// Process the 'psr-0' base directories. // Process the 'psr-0' base directories.
foreach ($autoloads['psr-0'] as $namespace => $paths) { foreach ($autoloads['psr-0'] as $namespace => $paths) {
@ -312,6 +313,8 @@ EOF;
'optimize' => (bool) $scanPsr0Packages, 'optimize' => (bool) $scanPsr0Packages,
)); ));
} }
return count($classMap);
} }
private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array()) private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
@ -383,11 +386,15 @@ EOF;
* *
* @param array $packageMap array of array(package, installDir-relative-to-composer.json) * @param array $packageMap array of array(package, installDir-relative-to-composer.json)
* @param PackageInterface $mainPackage root package instance * @param PackageInterface $mainPackage root package instance
* @param bool $filterOutRequireDevPackages whether to filter out require-dev packages
* @return array array('psr-0' => array('Ns\\Foo' => array('installDir'))) * @return array array('psr-0' => array('Ns\\Foo' => array('installDir')))
*/ */
public function parseAutoloads(array $packageMap, PackageInterface $mainPackage) public function parseAutoloads(array $packageMap, PackageInterface $mainPackage, $filterOutRequireDevPackages = false)
{ {
$mainPackageMap = array_shift($packageMap); $mainPackageMap = array_shift($packageMap);
if ($filterOutRequireDevPackages) {
$packageMap = $this->filterPackageMap($packageMap, $mainPackage);
}
$sortedPackageMap = $this->sortPackageMap($packageMap); $sortedPackageMap = $this->sortPackageMap($packageMap);
$sortedPackageMap[] = $mainPackageMap; $sortedPackageMap[] = $mainPackageMap;
array_unshift($packageMap, $mainPackageMap); array_unshift($packageMap, $mainPackageMap);
@ -539,7 +546,7 @@ EOF;
} }
} }
if (preg_match('/\.phar.+$/', $path)) { if (strpos($path, '.phar') !== false) {
$baseDir = "'phar://' . " . $baseDir; $baseDir = "'phar://' . " . $baseDir;
} }
@ -763,10 +770,14 @@ HEADER;
$filesystem = new Filesystem(); $filesystem = new Filesystem();
$vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
$vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
$appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
$appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
$absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1);
$absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1);
$absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
$absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);
$initializer = ''; $initializer = '';
$prefix = "\0Composer\Autoload\ClassLoader\0"; $prefix = "\0Composer\Autoload\ClassLoader\0";
@ -789,9 +800,15 @@ HEADER;
// See https://bugs.php.net/68057 // See https://bugs.php.net/68057
$staticPhpVersion = 70000; $staticPhpVersion = 70000;
} }
$value = var_export($value, true); $value = strtr(
$value = str_replace($absoluteVendorPathCode, $vendorPathCode, $value); var_export($value, true),
$value = str_replace($absoluteAppBaseDirCode, $appBaseDirCode, $value); array(
$absoluteVendorPathCode => $vendorPathCode,
$absoluteVendorPharPathCode => $vendorPharPathCode,
$absoluteAppBaseDirCode => $appBaseDirCode,
$absoluteAppBaseDirPharCode => $appBaseDirPharCode,
)
);
$value = ltrim(preg_replace('/^ */m', ' $0$0', $value)); $value = ltrim(preg_replace('/^ */m', ' $0$0', $value));
$file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value);
@ -899,6 +916,52 @@ INITIALIZER;
return md5($package->getName() . ':' . $path); return md5($package->getName() . ':' . $path);
} }
/**
* Filters out dev-dependencies
*
* @param array $packageMap
* @param PackageInterface $mainPackage
* @return array
*/
protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage)
{
$packages = array();
$include = array();
foreach ($packageMap as $item) {
$package = $item[0];
$name = $package->getName();
$packages[$name] = $package;
}
$add = function (PackageInterface $package) use (&$add, $packages, &$include) {
foreach ($package->getRequires() as $link) {
$target = $link->getTarget();
if (!isset($include[$target])) {
$include[$target] = true;
if (isset($packages[$target])) {
$add($packages[$target]);
}
}
}
};
$add($mainPackage);
return array_filter(
$packageMap,
function ($item) use ($include) {
$package = $item[0];
foreach ($package->getNames() as $name) {
if (isset($include[$name])) {
return true;
}
}
return false;
}
);
}
/** /**
* Sorts packages by dependency weight * Sorts packages by dependency weight
* *
@ -911,80 +974,21 @@ INITIALIZER;
{ {
$packages = array(); $packages = array();
$paths = array(); $paths = array();
$usageList = array();
foreach ($packageMap as $item) { foreach ($packageMap as $item) {
list($package, $path) = $item; list($package, $path) = $item;
$name = $package->getName(); $name = $package->getName();
$packages[$name] = $package; $packages[$name] = $package;
$paths[$name] = $path; $paths[$name] = $path;
foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) {
$target = $link->getTarget();
$usageList[$target][] = $name;
}
} }
$computing = array(); $sortedPackages = PackageSorter::sortPackages($packages);
$computed = array();
$computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) {
// reusing computed importance
if (isset($computed[$name])) {
return $computed[$name];
}
// canceling circular dependency
if (isset($computing[$name])) {
return 0;
}
$computing[$name] = true;
$weight = 0;
if (isset($usageList[$name])) {
foreach ($usageList[$name] as $user) {
$weight -= 1 - $computeImportance($user);
}
}
unset($computing[$name]);
$computed[$name] = $weight;
return $weight;
};
$weightList = array();
foreach ($packages as $name => $package) {
$weight = $computeImportance($name);
$weightList[$name] = $weight;
}
$stable_sort = function (&$array) {
static $transform, $restore;
$i = 0;
if (!$transform) {
$transform = function (&$v, $k) use (&$i) {
$v = array($v, ++$i, $k, $v);
};
$restore = function (&$v, $k) {
$v = $v[3];
};
}
array_walk($array, $transform);
asort($array);
array_walk($array, $restore);
};
$stable_sort($weightList);
$sortedPackageMap = array(); $sortedPackageMap = array();
foreach (array_keys($weightList) as $name) { foreach ($sortedPackages as $package) {
$name = $package->getName();
$sortedPackageMap[] = array($packages[$name], $paths[$name]); $sortedPackageMap[] = array($packages[$name], $paths[$name]);
} }

View File

@ -279,7 +279,7 @@ class ClassLoader
*/ */
public function setApcuPrefix($apcuPrefix) public function setApcuPrefix($apcuPrefix)
{ {
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
} }
/** /**
@ -377,11 +377,11 @@ class ClassLoader
$subPath = $class; $subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) { while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos); $subPath = substr($subPath, 0, $lastPos);
$search = $subPath.'\\'; $search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) { if (isset($this->prefixDirsPsr4[$search])) {
$length = $this->prefixLengthsPsr4[$first][$search]; $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) { foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { if (file_exists($file = $dir . $pathEnd)) {
return $file; return $file;
} }
} }

View File

@ -94,6 +94,10 @@ class ClassMapGenerator
if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) { if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) {
continue; continue;
} }
// check non-realpath of file for directories symlink in project dir
if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
continue;
}
$classes = self::findClasses($filePath); $classes = self::findClasses($filePath);
@ -158,7 +162,7 @@ class ClassMapGenerator
} }
// strip heredocs/nowdocs // strip heredocs/nowdocs
$contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents);
// strip strings // strip strings
$contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
// strip leading non-php code if needed // strip leading non-php code if needed
@ -175,6 +179,10 @@ class ClassMapGenerator
if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) { if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
$contents = substr($contents, 0, $pos); $contents = substr($contents, 0, $pos);
} }
// strip comments if short open tags are in the file
if (preg_match('{(<\?)(?!(php|hh))}i', $contents)) {
$contents = preg_replace('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x', '', $contents);
}
preg_match_all('{ preg_match_all('{
(?: (?:

View File

@ -44,7 +44,7 @@ class Cache
$this->whitelist = $whitelist; $this->whitelist = $whitelist;
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
if (preg_match('{(^|[\\\\/])(\$null|NUL|/dev/null)([\\\\/]|$)}', $cacheDir)) { if (!self::isUsable($cacheDir)) {
$this->enabled = false; $this->enabled = false;
return; return;
@ -59,6 +59,11 @@ class Cache
} }
} }
public static function isUsable($path)
{
return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
}
public function isEnabled() public function isEnabled()
{ {
return $this->enabled; return $this->enabled;
@ -71,11 +76,13 @@ class Cache
public function read($file) public function read($file)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled) {
if ($this->enabled && file_exists($this->root . $file)) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); if (file_exists($this->root . $file)) {
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
return file_get_contents($this->root . $file); return file_get_contents($this->root . $file);
}
} }
return false; return false;
@ -142,19 +149,21 @@ class Cache
*/ */
public function copyTo($file, $target) public function copyTo($file, $target)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled) {
if ($this->enabled && file_exists($this->root . $file)) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
try { if (file_exists($this->root . $file)) {
touch($this->root . $file, filemtime($this->root . $file), time()); try {
} catch (\ErrorException $e) { touch($this->root . $file, filemtime($this->root . $file), time());
// fallback in case the above failed due to incorrect ownership } catch (\ErrorException $e) {
// see https://github.com/composer/composer/issues/4070 // fallback in case the above failed due to incorrect ownership
Silencer::call('touch', $this->root . $file); // see https://github.com/composer/composer/issues/4070
Silencer::call('touch', $this->root . $file);
}
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
return copy($this->root . $file, $target);
} }
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
return copy($this->root . $file, $target);
} }
return false; return false;
@ -167,9 +176,11 @@ class Cache
public function remove($file) public function remove($file)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled) {
if ($this->enabled && file_exists($this->root . $file)) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
return $this->filesystem->unlink($this->root . $file); if (file_exists($this->root . $file)) {
return $this->filesystem->unlink($this->root . $file);
}
} }
return false; return false;
@ -178,7 +189,8 @@ class Cache
public function clear() public function clear()
{ {
if ($this->enabled) { if ($this->enabled) {
return $this->filesystem->removeDirectory($this->root); $this->filesystem->emptyDirectory($this->root);
return true;
} }
return false; return false;
@ -216,9 +228,11 @@ class Cache
public function sha1($file) public function sha1($file)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled) {
if ($this->enabled && file_exists($this->root . $file)) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
return sha1_file($this->root . $file); if (file_exists($this->root . $file)) {
return sha1_file($this->root . $file);
}
} }
return false; return false;
@ -226,9 +240,11 @@ class Cache
public function sha256($file) public function sha256($file)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); if ($this->enabled) {
if ($this->enabled && file_exists($this->root . $file)) { $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
return hash_file('sha256', $this->root . $file); if (file_exists($this->root . $file)) {
return hash_file('sha256', $this->root . $file);
}
} }
return false; return false;

View File

@ -25,7 +25,8 @@ class AboutCommand extends BaseCommand
$this $this
->setName('about') ->setName('about')
->setDescription('Shows the short information about Composer.') ->setDescription('Shows the short information about Composer.')
->setHelp(<<<EOT ->setHelp(
<<<EOT
<info>php composer.phar about</info> <info>php composer.phar about</info>
EOT EOT
) )
@ -34,8 +35,9 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$this->getIO()->write(<<<EOT $this->getIO()->write(
<info>Composer - Package Management for PHP</info> <<<EOT
<info>Composer - Dependency Manager for PHP</info>
<comment>Composer is a dependency manager tracking local dependencies of your projects and libraries. <comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.
See https://getcomposer.org/ for more information.</comment> See https://getcomposer.org/ for more information.</comment>
EOT EOT

View File

@ -22,6 +22,7 @@ use Composer\Script\ScriptEvents;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -48,13 +49,15 @@ class ArchiveCommand extends BaseCommand
.' Note that the format will be appended.'), .' Note that the format will be appended.'),
new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'), new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>archive</info> command creates an archive of the specified format The <info>archive</info> command creates an archive of the specified format
containing the files and directories of the Composer project or the specified containing the files and directories of the Composer project or the specified
package in the specified version and writes it to the specified directory. package in the specified version and writes it to the specified directory.
<info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info> <info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>
Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT EOT
) )
; ;
@ -66,8 +69,9 @@ EOT
$composer = $this->getComposer(false); $composer = $this->getComposer(false);
if ($composer) { if ($composer) {
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $eventDispatcher = $composer->getEventDispatcher();
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);
$eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
} }
if (null === $input->getOption('format')) { if (null === $input->getOption('format')) {
@ -102,8 +106,9 @@ EOT
$archiveManager = $composer->getArchiveManager(); $archiveManager = $composer->getArchiveManager();
} else { } else {
$factory = new Factory; $factory = new Factory;
$downloadManager = $factory->createDownloadManager($io, $config); $httpDownloader = $factory->createHttpDownloader($io, $config);
$archiveManager = $factory->createArchiveManager($config, $downloadManager); $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader);
$archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader));
} }
if ($packageName) { if ($packageName) {

View File

@ -15,8 +15,11 @@ namespace Composer\Command;
use Composer\Composer; use Composer\Composer;
use Composer\Config; use Composer\Config;
use Composer\Console\Application; use Composer\Console\Application;
use Composer\Factory;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO; use Composer\IO\NullIO;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
@ -24,13 +27,15 @@ use Symfony\Component\Console\Command\Command;
/** /**
* Base class for Composer commands * Base class for Composer commands
* *
* @method Application getApplication()
*
* @author Ryan Weaver <ryan@knplabs.com> * @author Ryan Weaver <ryan@knplabs.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com> * @author Konstantin Kudryashov <ever.zet@gmail.com>
*/ */
abstract class BaseCommand extends Command abstract class BaseCommand extends Command
{ {
/** /**
* @var Composer * @var Composer|null
*/ */
private $composer; private $composer;
@ -43,7 +48,7 @@ abstract class BaseCommand extends Command
* @param bool $required * @param bool $required
* @param bool|null $disablePlugins * @param bool|null $disablePlugins
* @throws \RuntimeException * @throws \RuntimeException
* @return Composer * @return Composer|null
*/ */
public function getComposer($required = true, $disablePlugins = null) public function getComposer($required = true, $disablePlugins = null)
{ {
@ -123,6 +128,17 @@ abstract class BaseCommand extends Command
*/ */
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
{ {
// initialize a plugin-enabled Composer instance, either local or global
$disablePlugins = $input->hasParameterOption('--no-plugins');
$composer = $this->getComposer(false, $disablePlugins);
if (null === $composer) {
$composer = Factory::createGlobal($this->getIO(), $disablePlugins);
}
if ($composer) {
$preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName());
$composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent);
}
if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) {
$input->setOption('no-progress', true); $input->setOption('no-progress', true);
} }
@ -159,7 +175,7 @@ abstract class BaseCommand extends Command
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
$preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
$preferDist = $input->getOption('prefer-dist'); $preferDist = (bool) $input->getOption('prefer-dist');
} }
return array($preferSource, $preferDist); return array($preferSource, $preferDist);

View File

@ -12,7 +12,6 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\DependencyResolver\Pool;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository; use Composer\Repository\ArrayRepository;
@ -21,6 +20,7 @@ use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositoryFactory;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositorySet;
use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
@ -71,15 +71,15 @@ class BaseDependencyCommand extends BaseCommand
$commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
// Prepare repositories and set up a pool // Prepare repositories and set up a repo set
$platformOverrides = $composer->getConfig()->get('platform') ?: array(); $platformOverrides = $composer->getConfig()->get('platform') ?: array();
$repository = new CompositeRepository(array( $repository = new CompositeRepository(array(
new ArrayRepository(array($composer->getPackage())), new ArrayRepository(array($composer->getPackage())),
$composer->getRepositoryManager()->getLocalRepository(), $composer->getRepositoryManager()->getLocalRepository(),
new PlatformRepository(array(), $platformOverrides), new PlatformRepository(array(), $platformOverrides),
)); ));
$pool = new Pool(); $repositorySet = new RepositorySet();
$pool->addRepository($repository); $repositorySet->addRepository($repository);
// Parse package name and constraint // Parse package name and constraint
list($needle, $textConstraint) = array_pad( list($needle, $textConstraint) = array_pad(
@ -89,7 +89,7 @@ class BaseDependencyCommand extends BaseCommand
); );
// Find packages that are or provide the requested package first // Find packages that are or provide the requested package first
$packages = $pool->whatProvides($needle); $packages = $repositorySet->findPackages(strtolower($needle), null, false);
if (empty($packages)) { if (empty($packages)) {
throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
} }
@ -129,8 +129,11 @@ class BaseDependencyCommand extends BaseCommand
$results = $repository->getDependents($needles, $constraint, $inverted, $recursive); $results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
if (empty($results)) { if (empty($results)) {
$extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
$this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>', $this->getIO()->writeError(sprintf(
$needle, $extra)); '<info>There is no installed package depending on "%s"%s</info>',
$needle,
$extra
));
} elseif ($renderTree) { } elseif ($renderTree) {
$this->initStyles($output); $this->initStyles($output);
$root = $packages[0]; $root = $packages[0];
@ -180,8 +183,9 @@ class BaseDependencyCommand extends BaseCommand
// Render table // Render table
$renderer = new Table($output); $renderer = new Table($output);
$renderer->setStyle('compact'); $renderer->setStyle('compact');
$renderer->getStyle()->setVerticalBorderChar(''); $rendererStyle = $renderer->getStyle();
$renderer->getStyle()->setCellRowContentFormat('%s '); $rendererStyle->setVerticalBorderChar('');
$rendererStyle->setCellRowContentFormat('%s ');
$renderer->setRows($table)->render(); $renderer->setRows($table)->render();
} }

View File

@ -17,6 +17,7 @@ use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
@ -26,9 +27,15 @@ class CheckPlatformReqsCommand extends BaseCommand
{ {
$this->setName('check-platform-reqs') $this->setName('check-platform-reqs')
->setDescription('Check that platform requirements are satisfied.') ->setDescription('Check that platform requirements are satisfied.')
->setHelp(<<<EOT ->setDefinition(array(
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'),
))
->setHelp(
<<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages. Checks that your PHP and extensions versions match the platform requirements of the installed packages.
Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.
<info>php composer.phar check-platform-reqs</info> <info>php composer.phar check-platform-reqs</info>
EOT EOT
@ -39,22 +46,27 @@ EOT
{ {
$composer = $this->getComposer(); $composer = $this->getComposer();
$repos = $composer->getRepositoryManager()->getLocalRepository(); $requires = $composer->getPackage()->getRequires();
if ($input->getOption('no-dev')) {
$allPackages = array_merge(array($composer->getPackage()), $repos->getPackages()); $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
$requires = $composer->getPackage()->getDevRequires(); } else {
$dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
// fallback to lockfile if installed repo is empty
if (!$dependencies) {
$dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
}
$requires += $composer->getPackage()->getDevRequires();
}
foreach ($requires as $require => $link) { foreach ($requires as $require => $link) {
$requires[$require] = array($link); $requires[$require] = array($link);
} }
/** foreach ($dependencies as $package) {
* @var PackageInterface $package
*/
foreach ($allPackages as $package) {
foreach ($package->getRequires() as $require => $link) { foreach ($package->getRequires() as $require => $link) {
$requires[$require][] = $link; $requires[$require][] = $link;
} }
} }
ksort($requires); ksort($requires);
$platformRepo = new PlatformRepository(array(), array()); $platformRepo = new PlatformRepository(array(), array());
@ -73,7 +85,7 @@ EOT
$exitCode = 0; $exitCode = 0;
/** /**
* @var Link $require * @var Link[] $links
*/ */
foreach ($requires as $require => $links) { foreach ($requires as $require => $links) {
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) {
@ -142,8 +154,9 @@ EOT
// Render table // Render table
$renderer = new Table($output); $renderer = new Table($output);
$renderer->setStyle('compact'); $renderer->setStyle('compact');
$renderer->getStyle()->setVerticalBorderChar(''); $rendererStyle = $renderer->getStyle();
$renderer->getStyle()->setCellRowContentFormat('%s '); $rendererStyle->setVerticalBorderChar('');
$rendererStyle->setCellRowContentFormat('%s ');
$renderer->setRows($table)->render(); $renderer->setRows($table)->render();
} }
} }

View File

@ -28,9 +28,12 @@ class ClearCacheCommand extends BaseCommand
->setName('clear-cache') ->setName('clear-cache')
->setAliases(array('clearcache')) ->setAliases(array('clearcache'))
->setDescription('Clears composer\'s internal package cache.') ->setDescription('Clears composer\'s internal package cache.')
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>clear-cache</info> deletes all cached packages from composer's The <info>clear-cache</info> deletes all cached packages from composer's
cache directory. cache directory.
Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-
EOT EOT
) )
; ;

View File

@ -21,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Composer\Config; use Composer\Config;
use Composer\Config\JsonConfigSource; use Composer\Config\JsonConfigSource;
use Composer\Factory; use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
@ -75,7 +76,8 @@ class ConfigCommand extends BaseCommand
new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-key', null, 'Setting key'),
new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
This command allows you to edit composer config settings and repositories This command allows you to edit composer config settings and repositories
in either the local composer.json file or the global config.json file. in either the local composer.json file or the global config.json file.
@ -123,6 +125,8 @@ You can always pass more than one option. As an example, if you want to edit the
global config.json file. global config.json file.
<comment>%command.full_name% --editor --global</comment> <comment>%command.full_name% --editor --global</comment>
Read more at https://getcomposer.org/doc/03-cli.md#config
EOT EOT
) )
; ;
@ -224,7 +228,7 @@ EOT
} }
$settingKey = $input->getArgument('setting-key'); $settingKey = $input->getArgument('setting-key');
if (!$settingKey) { if (!$settingKey || !is_string($settingKey)) {
return 0; return 0;
} }
@ -283,7 +287,7 @@ EOT
$value = json_encode($value); $value = json_encode($value);
} }
$this->getIO()->write($value); $this->getIO()->write($value, true, IOInterface::QUIET);
return 0; return 0;
} }
@ -301,6 +305,7 @@ EOT
$uniqueConfigValues = array( $uniqueConfigValues = array(
'process-timeout' => array('is_numeric', 'intval'), 'process-timeout' => array('is_numeric', 'intval'),
'use-include-path' => array($booleanValidator, $booleanNormalizer), 'use-include-path' => array($booleanValidator, $booleanNormalizer),
'use-github-api' => array($booleanValidator, $booleanNormalizer),
'preferred-install' => array( 'preferred-install' => array(
function ($val) { function ($val) {
return in_array($val, array('auto', 'source', 'dist'), true); return in_array($val, array('auto', 'source', 'dist'), true);
@ -454,6 +459,10 @@ EOT
); );
if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) {
$this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
}
return $this->configSource->removeConfigSetting($settingKey); return $this->configSource->removeConfigSetting($settingKey);
} }
if (isset($uniqueConfigValues[$settingKey])) { if (isset($uniqueConfigValues[$settingKey])) {
@ -612,6 +621,15 @@ EOT
return; return;
} }
// handle script
if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) {
return $this->configSource->removeProperty($settingKey);
}
return $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
}
throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
} }
@ -629,7 +647,17 @@ EOT
)); ));
} }
return call_user_func(array($this->configSource, $method), $key, $normalizer($values[0])); $normalizedValue = $normalizer($values[0]);
if ($key === 'disable-tls') {
if (!$normalizedValue && $this->config->get('disable-tls')) {
$this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
} elseif ($normalizedValue && !$this->config->get('disable-tls')) {
$this->getIO()->writeError('<warning>You are now running Composer with SSL/TLS protection disabled.</warning>');
}
}
return call_user_func(array($this->configSource, $method), $key, $normalizedValue);
} }
protected function handleMultiValue($key, array $callbacks, array $values, $method) protected function handleMultiValue($key, array $callbacks, array $values, $method)
@ -685,9 +713,9 @@ EOT
} }
if (is_string($rawVal) && $rawVal != $value) { if (is_string($rawVal) && $rawVal != $value) {
$io->write('[<comment>' . $k . $key . '</comment>] <info>' . $rawVal . ' (' . $value . ')</info>'); $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $rawVal . ' (' . $value . ')</info>', true, IOInterface::QUIET);
} else { } else {
$io->write('[<comment>' . $k . $key . '</comment>] <info>' . $value . '</info>'); $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $value . '</info>', true, IOInterface::QUIET);
} }
} }
} }

View File

@ -20,7 +20,6 @@ use Composer\Installer\InstallationManager;
use Composer\Installer\SuggestedPackagesReporter; use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector; use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
@ -28,6 +27,7 @@ use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\InstalledFilesystemRepository; use Composer\Repository\InstalledFilesystemRepository;
use Composer\Repository\RepositorySet;
use Composer\Script\ScriptEvents; use Composer\Script\ScriptEvents;
use Composer\Util\Silencer; use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource; use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
/** /**
@ -75,11 +76,13 @@ class CreateProjectCommand extends BaseCommand
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'),
new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>create-project</info> command creates a new project from a given The <info>create-project</info> command creates a new project from a given
package into a new directory. If executed without params and in a directory package into a new directory. If executed without params and in a directory
with a composer.json file it installs the packages for the current project. with a composer.json file it installs the packages for the current project.
@ -102,6 +105,7 @@ controlled code by appending the <info>'--prefer-source'</info> flag.
To install a package from another repository than the default one you To install a package from another repository than the default one you
can pass the <info>'--repository=https://myrepository.org'</info> flag. can pass the <info>'--repository=https://myrepository.org'</info> flag.
Read more at https://getcomposer.org/doc/03-cli.md#create-project
EOT EOT
) )
; ;
@ -136,7 +140,6 @@ EOT
$input->getOption('repository') ?: $input->getOption('repository-url'), $input->getOption('repository') ?: $input->getOption('repository-url'),
$input->getOption('no-plugins'), $input->getOption('no-plugins'),
$input->getOption('no-scripts'), $input->getOption('no-scripts'),
$input->getOption('keep-vcs'),
$input->getOption('no-progress'), $input->getOption('no-progress'),
$input->getOption('no-install'), $input->getOption('no-install'),
$input->getOption('ignore-platform-reqs'), $input->getOption('ignore-platform-reqs'),
@ -144,7 +147,7 @@ EOT
); );
} }
public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true) public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true)
{ {
$oldCwd = getcwd(); $oldCwd = getcwd();
@ -154,13 +157,12 @@ EOT
$this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io);
if ($packageName !== null) { if ($packageName !== null) {
$installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $keepVcs, $noProgress, $ignorePlatformReqs, $secureHttp); $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $noProgress, $ignorePlatformReqs, $secureHttp);
} else { } else {
$installedFromVcs = false; $installedFromVcs = false;
} }
$composer = Factory::create($io, null, $disablePlugins); $composer = Factory::create($io, null, $disablePlugins);
$composer->getDownloadManager()->setOutputProgress(!$noProgress);
$fs = new Filesystem(); $fs = new Filesystem();
@ -182,7 +184,9 @@ EOT
->setRunScripts(!$noScripts) ->setRunScripts(!$noScripts)
->setIgnorePlatformRequirements($ignorePlatformReqs) ->setIgnorePlatformRequirements($ignorePlatformReqs)
->setSuggestedPackagesReporter($this->suggestedPackagesReporter) ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
->setOptimizeAutoloader($config->get('optimize-autoloader')); ->setOptimizeAutoloader($config->get('optimize-autoloader'))
->setClassMapAuthoritative($config->get('classmap-authoritative'))
->setApcuAutoloader($config->get('apcu-autoloader'));
if ($disablePlugins) { if ($disablePlugins) {
$installer->disablePlugins(); $installer->disablePlugins();
@ -195,9 +199,12 @@ EOT
} }
$hasVcs = $installedFromVcs; $hasVcs = $installedFromVcs;
if (!$keepVcs && $installedFromVcs if (
!$input->getOption('keep-vcs')
&& $installedFromVcs
&& ( && (
!$io->isInteractive() $input->getOption('remove-vcs')
|| !$io->isInteractive()
|| $io->askConfirmation('<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>Y,n</comment>]? ', true) || $io->askConfirmation('<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>Y,n</comment>]? ', true)
) )
) { ) {
@ -253,7 +260,7 @@ EOT
return 0; return 0;
} }
protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true) protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true)
{ {
if (!$secureHttp) { if (!$secureHttp) {
$config->merge(array('config' => array('secure-http' => false))); $config->merge(array('config' => array('secure-http' => false)));
@ -286,8 +293,8 @@ EOT
throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
} }
$pool = new Pool($stability); $repositorySet = new RepositorySet(array(), $stability);
$pool->addRepository($sourceRepo); $repositorySet->addRepository($sourceRepo);
$phpVersion = null; $phpVersion = null;
$prettyPhpVersion = null; $prettyPhpVersion = null;
@ -301,7 +308,7 @@ EOT
} }
// find the latest version if there are multiple // find the latest version if there are multiple
$versionSelector = new VersionSelector($pool); $versionSelector = new VersionSelector($repositorySet);
$package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability); $package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability);
if (!$package) { if (!$package) {
@ -319,13 +326,16 @@ EOT
} }
// handler Ctrl+C for unix-like systems // handler Ctrl+C for unix-like systems
if (function_exists('pcntl_signal')) { if (function_exists('pcntl_async_signals')) {
declare(ticks=100); @mkdir($directory, 0777, true);
pcntl_signal(SIGINT, function () use ($directory) { if ($realDir = realpath($directory)) {
$fs = new Filesystem(); pcntl_async_signals(true);
$fs->removeDirectory($directory); pcntl_signal(SIGINT, function () use ($realDir) {
exit(130); $fs = new Filesystem();
}); $fs->removeDirectory($realDir);
exit(130);
});
}
} }
$io->writeError('<info>Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')</info>'); $io->writeError('<info>Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')</info>');
@ -338,19 +348,17 @@ EOT
$package = $package->getAliasOf(); $package = $package->getAliasOf();
} }
if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) { $factory = new Factory();
$package->setSourceReference(substr($package->getPrettyVersion(), 4));
}
$dm = $this->createDownloadManager($io, $config); $httpDownloader = $factory->createHttpDownloader($io, $config);
$dm = $factory->createDownloadManager($io, $config, $httpDownloader);
$dm->setPreferSource($preferSource) $dm->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist);
->setOutputProgress(!$noProgress);
$projectInstaller = new ProjectInstaller($directory, $dm); $projectInstaller = new ProjectInstaller($directory, $dm);
$im = $this->createInstallationManager(); $im = $factory->createInstallationManager(new Loop($httpDownloader));
$im->addInstaller($projectInstaller); $im->addInstaller($projectInstaller);
$im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); $im->execute(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
$im->notifyInstalls($io); $im->notifyInstalls($io);
// collect suggestions // collect suggestions
@ -366,16 +374,4 @@ EOT
return $installedFromVcs; return $installedFromVcs;
} }
protected function createDownloadManager(IOInterface $io, Config $config)
{
$factory = new Factory();
return $factory->createDownloadManager($io, $config);
}
protected function createInstallationManager()
{
return new InstallationManager();
}
} }

View File

@ -31,11 +31,13 @@ class DependsCommand extends BaseDependencyCommand
->setName('depends') ->setName('depends')
->setAliases(array('why')) ->setAliases(array('why'))
->setDescription('Shows which packages cause the given package to be installed.') ->setDescription('Shows which packages cause the given package to be installed.')
->setHelp(<<<EOT ->setHelp(
<<<EOT
Displays detailed information about where a package is referenced. Displays detailed information about where a package is referenced.
<info>php composer.phar depends composer/composer</info> <info>php composer.phar depends composer/composer</info>
Read more at https://getcomposer.org/doc/03-cli.md#depends-why-
EOT EOT
) )
; ;

View File

@ -16,12 +16,13 @@ use Composer\Composer;
use Composer\Factory; use Composer\Factory;
use Composer\Config; use Composer\Config;
use Composer\Downloader\TransportException; use Composer\Downloader\TransportException;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator; use Composer\Util\ConfigValidator;
use Composer\Util\IniHelper; use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\HttpDownloader;
use Composer\Util\StreamContextFactory; use Composer\Util\StreamContextFactory;
use Composer\SelfUpdate\Keys; use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions; use Composer\SelfUpdate\Versions;
@ -34,8 +35,8 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class DiagnoseCommand extends BaseCommand class DiagnoseCommand extends BaseCommand
{ {
/** @var RemoteFileSystem */ /** @var HttpDownloader */
protected $rfs; protected $httpDownloader;
/** @var ProcessExecutor */ /** @var ProcessExecutor */
protected $process; protected $process;
@ -48,11 +49,13 @@ class DiagnoseCommand extends BaseCommand
$this $this
->setName('diagnose') ->setName('diagnose')
->setDescription('Diagnoses the system to identify common errors.') ->setDescription('Diagnoses the system to identify common errors.')
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>diagnose</info> command checks common errors to help debugging problems. The <info>diagnose</info> command checks common errors to help debugging problems.
The process exit code will be 1 in case of warnings and 2 for errors. The process exit code will be 1 in case of warnings and 2 for errors.
Read more at https://getcomposer.org/doc/03-cli.md#diagnose
EOT EOT
) )
; ;
@ -81,9 +84,9 @@ EOT
} }
$config->merge(array('config' => array('secure-http' => false))); $config->merge(array('config' => array('secure-http' => false)));
$config->prohibitUrlByConfig('http://packagist.org', new NullIO); $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);
$this->rfs = Factory::createRemoteFilesystem($io, $config); $this->httpDownloader = Factory::createHttpDownloader($io, $config);
$this->process = new ProcessExecutor($io); $this->process = new ProcessExecutor($io);
$io->write('Checking platform settings: ', false); $io->write('Checking platform settings: ', false);
@ -117,8 +120,9 @@ EOT
$io->write('Checking github.com rate limit: ', false); $io->write('Checking github.com rate limit: ', false);
try { try {
$rate = $this->getGithubRateLimit('github.com'); $rate = $this->getGithubRateLimit('github.com');
$this->outputResult(true); if (!is_array($rate)) {
if (10 > $rate['remaining']) { $this->outputResult($rate);
} elseif (10 > $rate['remaining']) {
$io->write('<warning>WARNING</warning>'); $io->write('<warning>WARNING</warning>');
$io->write(sprintf( $io->write(sprintf(
'<comment>Github has a rate limit on their API. ' '<comment>Github has a rate limit on their API. '
@ -129,6 +133,8 @@ EOT
$rate['remaining'], $rate['remaining'],
$rate['limit'] $rate['limit']
)); ));
} else {
$this->outputResult(true);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
if ($e instanceof TransportException && $e->getCode() === 401) { if ($e instanceof TransportException && $e->getCode() === 401) {
@ -150,9 +156,17 @@ EOT
$this->outputResult($this->checkVersion($config)); $this->outputResult($this->checkVersion($config));
} }
$io->write(sprintf('Composer version: <comment>%s</comment>', Composer::VERSION)); $io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));
$io->write(sprintf('PHP version: <comment>%s</comment>', PHP_VERSION)); $platformOverrides = $config->get('platform') ?: array();
$platformRepo = new PlatformRepository(array(), $platformOverrides);
$phpPkg = $platformRepo->findPackage('php', '*');
$phpVersion = $phpPkg->getPrettyVersion();
if (false !== strpos($phpPkg->getDescription(), 'overridden')) {
$phpVersion .= ' - ' . $phpPkg->getDescription();
}
$io->write(sprintf('PHP version: <comment>%s</comment>', $phpVersion));
if (defined('PHP_BINARY')) { if (defined('PHP_BINARY')) {
$io->write(sprintf('PHP binary path: <comment>%s</comment>', PHP_BINARY)); $io->write(sprintf('PHP binary path: <comment>%s</comment>', PHP_BINARY));
@ -197,6 +211,11 @@ EOT
private function checkHttp($proto, Config $config) private function checkHttp($proto, Config $config)
{ {
$result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
$disableTls = false; $disableTls = false;
$result = array(); $result = array();
if ($proto === 'https' && $config->get('disable-tls') === true) { if ($proto === 'https' && $config->get('disable-tls') === true) {
@ -208,7 +227,7 @@ EOT
} }
try { try {
$this->rfs->getContents('packagist.org', $proto . '://packagist.org/packages.json', false); $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
} catch (TransportException $e) { } catch (TransportException $e) {
if (false !== strpos($e->getMessage(), 'cafile')) { if (false !== strpos($e->getMessage(), 'cafile')) {
$result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>'; $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
@ -228,13 +247,18 @@ EOT
private function checkHttpProxy() private function checkHttpProxy()
{ {
$result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
$protocol = extension_loaded('openssl') ? 'https' : 'http'; $protocol = extension_loaded('openssl') ? 'https' : 'http';
try { try {
$json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://packagist.org/packages.json', false), true); $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson();
$hash = reset($json['provider-includes']); $hash = reset($json['provider-includes']);
$hash = $hash['sha256']; $hash = $hash['sha256'];
$path = str_replace('%hash%', $hash, key($json['provider-includes'])); $path = str_replace('%hash%', $hash, key($json['provider-includes']));
$provider = $this->rfs->getContents('packagist.org', $protocol . '://packagist.org/'.$path, false); $provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/'.$path)->getBody();
if (hash('sha256', $provider) !== $hash) { if (hash('sha256', $provider) !== $hash) {
return 'It seems that your proxy is modifying http traffic on the fly'; return 'It seems that your proxy is modifying http traffic on the fly';
@ -255,12 +279,17 @@ EOT
*/ */
private function checkHttpProxyFullUriRequestParam() private function checkHttpProxyFullUriRequestParam()
{ {
$url = 'http://packagist.org/packages.json'; $result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
$url = 'http://repo.packagist.org/packages.json';
try { try {
$this->rfs->getContents('packagist.org', $url, false); $this->httpDownloader->get($url);
} catch (TransportException $e) { } catch (TransportException $e) {
try { try {
$this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false))); $this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
} catch (TransportException $e) { } catch (TransportException $e) {
return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')'; return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
} }
@ -280,16 +309,21 @@ EOT
*/ */
private function checkHttpsProxyFullUriRequestParam() private function checkHttpsProxyFullUriRequestParam()
{ {
$result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
if (!extension_loaded('openssl')) { if (!extension_loaded('openssl')) {
return 'You need the openssl extension installed for this check'; return 'You need the openssl extension installed for this check';
} }
$url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0'; $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
try { try {
$this->rfs->getContents('github.com', $url, false); $this->httpDownloader->get($url);
} catch (TransportException $e) { } catch (TransportException $e) {
try { try {
$this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false))); $this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
} catch (TransportException $e) { } catch (TransportException $e) {
return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')'; return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
} }
@ -302,11 +336,16 @@ EOT
private function checkGithubOauth($domain, $token) private function checkGithubOauth($domain, $token)
{ {
$result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
$this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
try { try {
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';
return $this->rfs->getContents($domain, $url, false, array( return $this->httpDownloader->get($url, array(
'retry-auth-failure' => false, 'retry-auth-failure' => false,
)) ? true : 'Unexpected error'; )) ? true : 'Unexpected error';
} catch (\Exception $e) { } catch (\Exception $e) {
@ -322,17 +361,21 @@ EOT
* @param string $domain * @param string $domain
* @param string $token * @param string $token
* @throws TransportException * @throws TransportException
* @return array * @return array|string
*/ */
private function getGithubRateLimit($domain, $token = null) private function getGithubRateLimit($domain, $token = null)
{ {
$result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
if ($token) { if ($token) {
$this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
} }
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
$json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false)); $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->decodeJson();
$data = json_decode($json, true);
return $data['resources']['core']; return $data['resources']['core'];
} }
@ -380,7 +423,12 @@ EOT
private function checkVersion($config) private function checkVersion($config)
{ {
$versionsUtil = new Versions($config, $this->rfs); $result = $this->checkConnectivity();
if ($result !== true) {
return $result;
}
$versionsUtil = new Versions($config, $this->httpDownloader);
$latest = $versionsUtil->getLatest(); $latest = $versionsUtil->getLatest();
if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
@ -403,6 +451,7 @@ EOT
} }
$hadError = false; $hadError = false;
$hadWarning = false;
if ($result instanceof \Exception) { if ($result instanceof \Exception) {
$result = '<error>['.get_class($result).'] '.$result->getMessage().'</error>'; $result = '<error>['.get_class($result).'] '.$result->getMessage().'</error>';
} }
@ -417,16 +466,18 @@ EOT
foreach ($result as $message) { foreach ($result as $message) {
if (false !== strpos($message, '<error>')) { if (false !== strpos($message, '<error>')) {
$hadError = true; $hadError = true;
} elseif (false !== strpos($message, '<warning>')) {
$hadWarning = true;
} }
} }
} }
if ($hadError) { if ($hadError) {
$io->write('<error>FAIL</error>'); $io->write('<error>FAIL</error>');
$this->exitCode = 2; $this->exitCode = max($this->exitCode, 2);
} else { } elseif ($hadWarning) {
$io->write('<warning>WARNING</warning>'); $io->write('<warning>WARNING</warning>');
$this->exitCode = 1; $this->exitCode = max($this->exitCode, 1);
} }
if ($result) { if ($result) {
@ -471,7 +522,7 @@ EOT
$errors['iconv_mbstring'] = true; $errors['iconv_mbstring'] = true;
} }
if (!ini_get('allow_url_fopen')) { if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
$errors['allow_url_fopen'] = true; $errors['allow_url_fopen'] = true;
} }
@ -495,7 +546,7 @@ EOT
$warnings['openssl_version'] = true; $warnings['openssl_version'] = true;
} }
if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
$warnings['apc_cli'] = true; $warnings['apc_cli'] = true;
} }
@ -518,7 +569,7 @@ EOT
} }
} }
if (ini_get('xdebug.profiler_enabled')) { if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) {
$warnings['xdebug_profile'] = true; $warnings['xdebug_profile'] = true;
} elseif (extension_loaded('xdebug')) { } elseif (extension_loaded('xdebug')) {
$warnings['xdebug_loaded'] = true; $warnings['xdebug_loaded'] = true;
@ -552,20 +603,6 @@ EOT
$text .= "Install either of them or recompile php without --disable-iconv"; $text .= "Install either of them or recompile php without --disable-iconv";
break; break;
case 'unicode':
$text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
$text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
$text .= " detect_unicode = Off";
$displayIniMessage = true;
break;
case 'suhosin':
$text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
$text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
$text .= " suhosin.executor.include.whitelist = phar ".$current;
$displayIniMessage = true;
break;
case 'php': case 'php':
$text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher."; $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
break; break;
@ -658,4 +695,20 @@ EOT
return !$warnings && !$errors ? true : $output; return !$warnings && !$errors ? true : $output;
} }
/**
* Check if allow_url_fopen is ON
*
* @return true|string
*/
private function checkConnectivity()
{
if (!ini_get('allow_url_fopen')) {
$result = '<info>Skipped because allow_url_fopen is missing.</info>';
return $result;
}
return true;
}
} }

View File

@ -36,8 +36,11 @@ class DumpAutoloadCommand extends BaseCommand
new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
<info>php composer.phar dump-autoload</info> <info>php composer.phar dump-autoload</info>
Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
EOT EOT
) )
; ;
@ -60,11 +63,11 @@ EOT
$apcu = $input->getOption('apcu') || $config->get('apcu-autoloader'); $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');
if ($authoritative) { if ($authoritative) {
$this->getIO()->writeError('<info>Generating optimized autoload files (authoritative)</info>'); $this->getIO()->writeError('<info>Generating optimized autoload files (authoritative)</info>', false);
} elseif ($optimize) { } elseif ($optimize) {
$this->getIO()->writeError('<info>Generating optimized autoload files</info>'); $this->getIO()->writeError('<info>Generating optimized autoload files</info>', false);
} else { } else {
$this->getIO()->writeError('<info>Generating autoload files</info>'); $this->getIO()->writeError('<info>Generating autoload files</info>', false);
} }
$generator = $composer->getAutoloadGenerator(); $generator = $composer->getAutoloadGenerator();
@ -72,6 +75,14 @@ EOT
$generator->setClassMapAuthoritative($authoritative); $generator->setClassMapAuthoritative($authoritative);
$generator->setApcu($apcu); $generator->setApcu($apcu);
$generator->setRunScripts(!$input->getOption('no-scripts')); $generator->setRunScripts(!$input->getOption('no-scripts'));
$generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
if ($authoritative) {
$this->getIO()->overwriteError('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
} elseif ($optimize) {
$this->getIO()->overwriteError('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
} else {
$this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
}
} }
} }

View File

@ -36,6 +36,13 @@ class ExecCommand extends BaseCommand
'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments' 'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
), ),
)) ))
->setHelp(
<<<EOT
Executes a vendored binary/script.
Read more at https://getcomposer.org/doc/03-cli.md#exec
EOT
)
; ;
} }
@ -53,7 +60,8 @@ class ExecCommand extends BaseCommand
throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)");
} }
$this->getIO()->write(<<<EOT $this->getIO()->write(
<<<EOT
<comment>Available binaries:</comment> <comment>Available binaries:</comment>
EOT EOT
); );
@ -66,7 +74,8 @@ EOT
$previousBin = $bin; $previousBin = $bin;
$bin = basename($bin); $bin = basename($bin);
$this->getIO()->write(<<<EOT $this->getIO()->write(
<<<EOT
<info>- $bin</info> <info>- $bin</info>
EOT EOT
); );

View File

@ -13,6 +13,7 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\Factory; use Composer\Factory;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Input\StringInput;
@ -32,7 +33,8 @@ class GlobalCommand extends BaseCommand
new InputArgument('command-name', InputArgument::REQUIRED, ''), new InputArgument('command-name', InputArgument::REQUIRED, ''),
new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
Use this command as a wrapper to run other Composer commands Use this command as a wrapper to run other Composer commands
within the global context of COMPOSER_HOME. within the global context of COMPOSER_HOME.
@ -48,6 +50,7 @@ XDG_CONFIG_HOME or default to /home/<user>/.config/composer
Note: This path may vary depending on customizations to bin-dir in Note: This path may vary depending on customizations to bin-dir in
composer.json or the environmental variable COMPOSER_BIN_DIR. composer.json or the environmental variable COMPOSER_BIN_DIR.
Read more at https://getcomposer.org/doc/03-cli.md#global
EOT EOT
) )
; ;
@ -74,8 +77,22 @@ EOT
// change to global dir // change to global dir
$config = Factory::createConfig(); $config = Factory::createConfig();
chdir($config->get('home')); $home = $config->get('home');
$this->getIO()->writeError('<info>Changed current directory to '.$config->get('home').'</info>');
if (!is_dir($home)) {
$fs = new Filesystem();
$fs->ensureDirectoryExists($home);
if (!is_dir($home)) {
throw new \RuntimeException('Could not create home directory');
}
}
try {
chdir($home);
} catch (\Exception $e) {
throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e);
}
$this->getIO()->writeError('<info>Changed current directory to '.$home.'</info>');
// create new input without "global" command prefix // create new input without "global" command prefix
$input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); $input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));

View File

@ -42,12 +42,15 @@ class HomeCommand extends BaseCommand
new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The home command opens or shows a package's repository URL or The home command opens or shows a package's repository URL or
homepage in your default browser. homepage in your default browser.
To open the homepage by default, use -H or --homepage. To open the homepage by default, use -H or --homepage.
To show instead of open the repository or homepage URL, use -s or --show. To show instead of open the repository or homepage URL, use -s or --show.
Read more at https://getcomposer.org/doc/03-cli.md#browse-home
EOT EOT
); );
} }

View File

@ -12,7 +12,6 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\DependencyResolver\Pool;
use Composer\Factory; use Composer\Factory;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
@ -21,7 +20,9 @@ use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -40,8 +41,8 @@ class InitCommand extends BaseCommand
/** @var array */ /** @var array */
private $gitConfig; private $gitConfig;
/** @var Pool[] */ /** @var RepositorySet[] */
private $pools; private $repositorySets;
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -64,12 +65,14 @@ class InitCommand extends BaseCommand
new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'),
new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>init</info> command creates a basic composer.json file The <info>init</info> command creates a basic composer.json file
in the current directory. in the current directory.
<info>php composer.phar init</info> <info>php composer.phar init</info>
Read more at https://getcomposer.org/doc/03-cli.md#init
EOT EOT
) )
; ;
@ -144,6 +147,11 @@ EOT
} }
} }
} }
$question = 'Would you like to install dependencies now [<comment>yes</comment>]? ';
if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
$this->installDependencies($output);
}
} }
/** /**
@ -160,13 +168,25 @@ EOT
if ($repositories) { if ($repositories) {
$config = Factory::createConfig($io); $config = Factory::createConfig($io);
$repos = array(new PlatformRepository); $repos = array(new PlatformRepository);
$createDefaultPackagistRepo = true;
foreach ($repositories as $repo) { foreach ($repositories as $repo) {
$repos[] = RepositoryFactory::fromString($io, $config, $repo); $repoConfig = RepositoryFactory::configFromString($io, $config, $repo);
if (
(isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false))
|| (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
) {
$createDefaultPackagistRepo = false;
continue;
}
$repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig);
}
if ($createDefaultPackagistRepo) {
$repos[] = RepositoryFactory::createRepo($io, $config, array(
'type' => 'composer',
'url' => 'https://repo.packagist.org',
));
} }
$repos[] = RepositoryFactory::createRepo($io, $config, array(
'type' => 'composer',
'url' => 'https://packagist.org',
));
$this->repos = new CompositeRepository($repos); $this->repos = new CompositeRepository($repos);
unset($repos, $config, $repositories); unset($repos, $config, $repositories);
@ -203,11 +223,11 @@ EOT
$name = get_current_user() . '/' . $name; $name = get_current_user() . '/' . $name;
} else { } else {
// package names must be in the format foo/bar // package names must be in the format foo/bar
$name = $name . '/' . $name; $name .= '/' . $name;
} }
$name = strtolower($name); $name = strtolower($name);
} else { } else {
if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $name)) { if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $name)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The package name '.$name.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' 'The package name '.$name.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
); );
@ -221,7 +241,7 @@ EOT
return $name; return $name;
} }
if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $value)) { if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $value)) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
); );
@ -279,7 +299,7 @@ EOT
$minimumStability = $input->getOption('stability') ?: null; $minimumStability = $input->getOption('stability') ?: null;
$minimumStability = $io->askAndValidate( $minimumStability = $io->askAndValidate(
'Minimum Stability [<comment>'.$minimumStability.'</comment>]: ', 'Minimum Stability [<comment>'.$minimumStability.'</comment>]: ',
function ($value) use ($self, $minimumStability) { function ($value) use ($minimumStability) {
if (null === $value) { if (null === $value) {
return $minimumStability; return $minimumStability;
} }
@ -319,11 +339,16 @@ EOT
$io->writeError(array('', 'Define your dependencies.', '')); $io->writeError(array('', 'Define your dependencies.', ''));
// prepare to resolve dependencies
$repos = $this->getRepos();
$preferredStability = $minimumStability ?: 'stable';
$phpVersion = $repos->findPackage('php', '*')->getPrettyVersion();
$question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? '; $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
$require = $input->getOption('require'); $require = $input->getOption('require');
$requirements = array(); $requirements = array();
if ($require || $io->askConfirmation($question, true)) { if ($require || $io->askConfirmation($question, true)) {
$requirements = $this->determineRequirements($input, $output, $require); $requirements = $this->determineRequirements($input, $output, $require, $phpVersion, $preferredStability);
} }
$input->setOption('require', $requirements); $input->setOption('require', $requirements);
@ -331,7 +356,7 @@ EOT
$requireDev = $input->getOption('require-dev'); $requireDev = $input->getOption('require-dev');
$devRequirements = array(); $devRequirements = array();
if ($requireDev || $io->askConfirmation($question, true)) { if ($requireDev || $io->askConfirmation($question, true)) {
$devRequirements = $this->determineRequirements($input, $output, $requireDev); $devRequirements = $this->determineRequirements($input, $output, $requireDev, $phpVersion, $preferredStability);
} }
$input->setOption('require-dev', $devRequirements); $input->setOption('require-dev', $devRequirements);
} }
@ -375,7 +400,7 @@ EOT
return $this->repos; return $this->repos;
} }
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable') protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true)
{ {
if ($requires) { if ($requires) {
$requires = $this->normalizeRequirements($requires); $requires = $this->normalizeRequirements($requires);
@ -385,9 +410,12 @@ EOT
foreach ($requires as $requirement) { foreach ($requires as $requirement) {
if (!isset($requirement['version'])) { if (!isset($requirement['version'])) {
// determine the best version automatically // determine the best version automatically
$version = $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion, $preferredStability); list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability);
$requirement['version'] = $version; $requirement['version'] = $version;
// replace package name from packagist.org
$requirement['name'] = $name;
$io->writeError(sprintf( $io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>', 'Using version <info>%s</info> for <info>%s</info>',
$requirement['version'], $requirement['version'],
@ -395,7 +423,10 @@ EOT
)); ));
} else { } else {
// check that the specified version/constraint exists before we proceed // check that the specified version/constraint exists before we proceed
$this->findBestVersionForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $requirement['version'], 'dev'); list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev');
// replace package name from packagist.org
$requirement['name'] = $name;
} }
$result[] = $requirement['name'] . ' ' . $requirement['version']; $result[] = $requirement['name'] . ' ' . $requirement['version'];
@ -493,7 +524,7 @@ EOT
); );
if (false === $constraint) { if (false === $constraint) {
$constraint = $this->findBestVersionForPackage($input, $package, $phpVersion, $preferredStability); list($name, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $phpVersion, $preferredStability);
$io->writeError(sprintf( $io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>', 'Using version <info>%s</info> for <info>%s</info>',
@ -539,7 +570,12 @@ EOT
$finder = new ExecutableFinder(); $finder = new ExecutableFinder();
$gitBin = $finder->find('git'); $gitBin = $finder->find('git');
$cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); // TODO in v3 always call with an array
if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
$cmd = new Process(array($gitBin, 'config', '-l'));
} else {
$cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin)));
}
$cmd->run(); $cmd->run();
if ($cmd->isSuccessful()) { if ($cmd->isSuccessful()) {
@ -625,16 +661,16 @@ EOT
return false !== filter_var($email, FILTER_VALIDATE_EMAIL); return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
} }
private function getPool(InputInterface $input, $minimumStability = null) private function getRepositorySet(InputInterface $input, $minimumStability = null)
{ {
$key = $minimumStability ?: 'default'; $key = $minimumStability ?: 'default';
if (!isset($this->pools[$key])) { if (!isset($this->repositorySets[$key])) {
$this->pools[$key] = $pool = new Pool($minimumStability ?: $this->getMinimumStability($input)); $this->repositorySets[$key] = $repositorySet = new RepositorySet(array(), $minimumStability ?: $this->getMinimumStability($input));
$pool->addRepository($this->getRepos()); $repositorySet->addRepository($this->getRepos());
} }
return $this->pools[$key]; return $this->repositorySets[$key];
} }
private function getMinimumStability(InputInterface $input) private function getMinimumStability(InputInterface $input)
@ -660,40 +696,71 @@ EOT
* *
* @param InputInterface $input * @param InputInterface $input
* @param string $name * @param string $name
* @param string $phpVersion * @param string|null $phpVersion
* @param string $preferredStability * @param string $preferredStability
* @param string|null $requiredVersion
* @param string $minimumStability * @param string $minimumStability
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @return string * @return array name version
*/ */
private function findBestVersionForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null)
{ {
// find the latest version allowed in this pool // find the latest version allowed in this repo set
$versionSelector = new VersionSelector($this->getPool($input, $minimumStability)); $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
$ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');
// ignore phpVersion if platform requirements are ignored
if ($ignorePlatformReqs) {
$phpVersion = null;
}
$package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability); $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);
if (!$package) { if (!$package) {
// platform packages can not be found in the pool in versions other than the local platform's has
// so if platform reqs are ignored we just take the user's word for it
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
return array($name, $requiredVersion ?: '*');
}
// Check whether the PHP version was the problem // Check whether the PHP version was the problem
if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) { if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
throw new \InvalidArgumentException(sprintf( throw new \InvalidArgumentException(sprintf(
'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)', $name, $requiredVersion, $phpVersion 'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)',
$name,
$requiredVersion,
$phpVersion
)); ));
} }
// Check whether the required version was the problem // Check whether the required version was the problem
if ($requiredVersion && $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability)) { if ($requiredVersion && $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability)) {
throw new \InvalidArgumentException(sprintf( throw new \InvalidArgumentException(sprintf(
'Could not find package %s in a version matching %s', $name, $requiredVersion 'Could not find package %s in a version matching %s',
$name,
$requiredVersion
)); ));
} }
// Check whether the PHP version was the problem // Check whether the PHP version was the problem
if ($phpVersion && $versionSelector->findBestCandidate($name)) { if ($phpVersion && $versionSelector->findBestCandidate($name)) {
throw new \InvalidArgumentException(sprintf( throw new \InvalidArgumentException(sprintf(
'Could not find package %s in any version matching your PHP version (%s)', $name, $phpVersion 'Could not find package %s in any version matching your PHP version (%s)',
$name,
$phpVersion
)); ));
} }
// Check for similar names/typos
$similar = $this->findSimilar($name); $similar = $this->findSimilar($name);
if ($similar) { if ($similar) {
// Check whether the minimum stability was the problem but the package exists
if ($requiredVersion === null && in_array($name, $similar, true)) {
throw new \InvalidArgumentException(sprintf(
'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
$name,
$this->getMinimumStability($input)
));
}
throw new \InvalidArgumentException(sprintf( throw new \InvalidArgumentException(sprintf(
"Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s",
$name, $name,
@ -708,7 +775,10 @@ EOT
)); ));
} }
return $versionSelector->findRecommendedRequireVersion($package); return array(
$package->getPrettyName(),
$versionSelector->findRecommendedRequireVersion($package),
);
} }
private function findSimilar($package) private function findSimilar($package)
@ -728,4 +798,23 @@ EOT
return array_keys(array_slice($similarPackages, 0, 5)); return array_keys(array_slice($similarPackages, 0, 5));
} }
private function installDependencies($output)
{
try {
$installCommand = $this->getApplication()->find('install');
$installCommand->run(new ArrayInput(array()), $output);
} catch (\Exception $e) {
$this->getIO()->writeError('Could not install dependencies. Run `composer install` to see more information.');
}
}
private function hasDependencies($options)
{
$requires = (array) $options['require'];
$devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : array();
return !empty($requires) || !empty($devRequires);
}
} }

View File

@ -32,6 +32,7 @@ class InstallCommand extends BaseCommand
{ {
$this $this
->setName('install') ->setName('install')
->setAliases(array('i'))
->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.') ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.')
->setDefinition(array( ->setDefinition(array(
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
@ -51,7 +52,8 @@ class InstallCommand extends BaseCommand
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), 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.'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>install</info> command reads the composer.lock file from The <info>install</info> command reads the composer.lock file from
the current directory, processes it, and downloads and installs all the the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not libraries and dependencies outlined in that file. If the file does not
@ -59,6 +61,7 @@ exist it will look for composer.json and do the same.
<info>php composer.phar install</info> <info>php composer.phar install</info>
Read more at https://getcomposer.org/doc/03-cli.md#install-i
EOT EOT
) )
; ;
@ -83,7 +86,6 @@ EOT
} }
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

View File

@ -36,10 +36,12 @@ class LicensesCommand extends BaseCommand
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The license command displays detailed information about the licenses of The license command displays detailed information about the licenses of
the installed dependencies. the installed dependencies.
Read more at https://getcomposer.org/doc/03-cli.md#licenses
EOT EOT
) )
; ;
@ -74,8 +76,9 @@ EOT
$table = new Table($output); $table = new Table($output);
$table->setStyle('compact'); $table->setStyle('compact');
$table->getStyle()->setVerticalBorderChar(''); $tableStyle = $table->getStyle();
$table->getStyle()->setCellRowContentFormat('%s '); $tableStyle->setVerticalBorderChar('');
$tableStyle->setCellRowContentFormat('%s ');
$table->setHeaders(array('Name', 'Version', 'License')); $table->setHeaders(array('Name', 'Version', 'License'));
foreach ($packages as $package) { foreach ($packages as $package) {
$table->addRow(array( $table->addRow(array(

View File

@ -36,8 +36,10 @@ class OutdatedCommand extends ShowCommand
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The outdated command is just a proxy for `composer show -l` The outdated command is just a proxy for `composer show -l`
The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such: The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such:
@ -48,7 +50,7 @@ The color coding (or signage if you have ANSI colors disabled) for dependency ve
may involve work. may involve work.
- <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it. - <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.
Read more at https://getcomposer.org/doc/03-cli.md#outdated
EOT EOT
) )
; ;
@ -76,6 +78,7 @@ EOT
$args['--minor-only'] = true; $args['--minor-only'] = true;
} }
$args['--format'] = $input->getOption('format'); $args['--format'] = $input->getOption('format');
$args['--ignore'] = $input->getOption('ignore');
$input = new ArrayInput($args); $input = new ArrayInput($args);

View File

@ -31,11 +31,13 @@ class ProhibitsCommand extends BaseDependencyCommand
->setName('prohibits') ->setName('prohibits')
->setAliases(array('why-not')) ->setAliases(array('why-not'))
->setDescription('Shows which packages prevent the given package from being installed.') ->setDescription('Shows which packages prevent the given package from being installed.')
->setHelp(<<<EOT ->setHelp(
<<<EOT
Displays detailed information about why a package cannot be installed. Displays detailed information about why a package cannot be installed.
<info>php composer.phar prohibits composer/composer</info> <info>php composer.phar prohibits composer/composer</info>
Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not-
EOT EOT
) )
; ;

View File

@ -22,6 +22,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\BasePackage;
/** /**
* @author Pierre du Plessis <pdples@gmail.com> * @author Pierre du Plessis <pdples@gmail.com>
@ -48,12 +49,14 @@ class RemoveCommand extends BaseCommand
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>remove</info> command removes a package from the current The <info>remove</info> command removes a package from the current
list of installed packages list of installed packages
<info>php composer.phar remove</info> <info>php composer.phar remove</info>
Read more at https://getcomposer.org/doc/03-cli.md#remove
EOT EOT
) )
; ;
@ -93,12 +96,25 @@ EOT
if (isset($composer[$type][$package])) { if (isset($composer[$type][$package])) {
$json->removeLink($type, $composer[$type][$package]); $json->removeLink($type, $composer[$type][$package]);
} elseif (isset($composer[$altType][$package])) { } elseif (isset($composer[$altType][$package])) {
$io->writeError('<warning>'.$composer[$altType][$package].' could not be found in '.$type.' but it is present in '.$altType.'</warning>'); $io->writeError('<warning>' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
if ($io->isInteractive()) { if ($io->isInteractive()) {
if ($io->askConfirmation('Do you want to remove it from '.$altType.' [<comment>yes</comment>]? ', true)) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
$json->removeLink($altType, $composer[$altType][$package]); $json->removeLink($altType, $composer[$altType][$package]);
} }
} }
} elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
foreach ($matches as $matchedPackage) {
$json->removeLink($type, $matchedPackage);
}
} elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) {
foreach ($matches as $matchedPackage) {
$io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
if ($io->isInteractive()) {
if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
$json->removeLink($altType, $matchedPackage);
}
}
}
} else { } else {
$io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>'); $io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
} }
@ -111,7 +127,6 @@ EOT
// Update packages // Update packages
$this->resetComposer(); $this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

View File

@ -25,6 +25,8 @@ use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Util\Silencer;
/** /**
* @author Jérémy Romey <jeremy@free-agent.fr> * @author Jérémy Romey <jeremy@free-agent.fr>
@ -32,6 +34,11 @@ use Composer\Repository\PlatformRepository;
*/ */
class RequireCommand extends InitCommand class RequireCommand extends InitCommand
{ {
private $newlyCreated;
private $json;
private $file;
private $composerBackup;
protected function configure() protected function configure()
{ {
$this $this
@ -57,7 +64,8 @@ class RequireCommand extends InitCommand
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The require command adds required packages to your composer.json and installs them. The require command adds required packages to your composer.json and installs them.
If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of
@ -67,6 +75,7 @@ If you do not specify a version constraint, composer will choose a suitable one
If you do not want to install the new dependencies immediately you can call it with --no-update If you do not want to install the new dependencies immediately you can call it with --no-update
Read more at https://getcomposer.org/doc/03-cli.md#require
EOT EOT
) )
; ;
@ -74,32 +83,42 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$file = Factory::getComposerFile(); if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
pcntl_signal(SIGINT, array($this, 'revertComposerFile'));
pcntl_signal(SIGTERM, array($this, 'revertComposerFile'));
pcntl_signal(SIGHUP, array($this, 'revertComposerFile'));
}
$this->file = Factory::getComposerFile();
$io = $this->getIO(); $io = $this->getIO();
$newlyCreated = !file_exists($file); $this->newlyCreated = !file_exists($this->file);
if ($newlyCreated && !file_put_contents($file, "{\n}\n")) { if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) {
$io->writeError('<error>'.$file.' could not be created.</error>'); $io->writeError('<error>'.$this->file.' could not be created.</error>');
return 1; return 1;
} }
if (!is_readable($file)) { if (!is_readable($this->file)) {
$io->writeError('<error>'.$file.' is not readable.</error>'); $io->writeError('<error>'.$this->file.' is not readable.</error>');
return 1;
}
if (!is_writable($file)) {
$io->writeError('<error>'.$file.' is not writable.</error>');
return 1; return 1;
} }
if (filesize($file) === 0) { if (filesize($this->file) === 0) {
file_put_contents($file, "{\n}\n"); file_put_contents($this->file, "{\n}\n");
} }
$json = new JsonFile($file); $this->json = new JsonFile($this->file);
$composerBackup = file_get_contents($json->getPath()); $this->composerBackup = file_get_contents($this->json->getPath());
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
$io->writeError('<error>'.$this->file.' is not writable.</error>');
return 1;
}
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$repos = $composer->getRepositoryManager()->getRepositories(); $repos = $composer->getRepositoryManager()->getRepositories();
@ -118,7 +137,7 @@ EOT
} }
$phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion(); $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability); $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'));
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
@ -126,36 +145,51 @@ EOT
// validate requirements format // validate requirements format
$versionParser = new VersionParser(); $versionParser = new VersionParser();
foreach ($requirements as $constraint) { foreach ($requirements as $package => $constraint) {
if (strtolower($package) === $composer->getPackage()->getName()) {
$io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));
return 1;
}
$versionParser->parseConstraints($constraint); $versionParser->parseConstraints($constraint);
} }
$sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');
if (!$this->updateFileCleanly($json, $requirements, $requireKey, $removeKey, $sortPackages)) { if (!$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) {
$composerDefinition = $json->read(); $composerDefinition = $this->json->read();
foreach ($requirements as $package => $version) { foreach ($requirements as $package => $version) {
$composerDefinition[$requireKey][$package] = $version; $composerDefinition[$requireKey][$package] = $version;
unset($composerDefinition[$removeKey][$package]); unset($composerDefinition[$removeKey][$package]);
} }
$json->write($composerDefinition); $this->json->write($composerDefinition);
} }
$io->writeError('<info>'.$file.' has been '.($newlyCreated ? 'created' : 'updated').'</info>'); $io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').'</info>');
if ($input->getOption('no-update')) { if ($input->getOption('no-update')) {
return 0; return 0;
} }
try {
return $this->doUpdate($input, $output, $io, $requirements);
} catch (\Exception $e) {
$this->revertComposerFile(false);
throw $e;
}
}
private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements)
{
// Update packages
$this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
$updateDevMode = !$input->getOption('update-no-dev'); $updateDevMode = !$input->getOption('update-no-dev');
$optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
$authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
$apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
// Update packages
$this->resetComposer();
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
@ -182,13 +216,7 @@ EOT
$status = $install->run(); $status = $install->run();
if ($status !== 0) { if ($status !== 0) {
if ($newlyCreated) { $this->revertComposerFile(false);
$io->writeError("\n".'<error>Installation failed, deleting '.$file.'.</error>');
unlink($json->getPath());
} else {
$io->writeError("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
file_put_contents($json->getPath(), $composerBackup);
}
} }
return $status; return $status;
@ -218,4 +246,21 @@ EOT
{ {
return; return;
} }
public function revertComposerFile($hardExit = true)
{
$io = $this->getIO();
if ($this->newlyCreated) {
$io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
unlink($this->json->getPath());
} else {
$io->writeError("\n".'<error>Installation failed, reverting '.$this->file.' to its original content.</error>');
file_put_contents($this->json->getPath(), $this->composerBackup);
}
if ($hardExit) {
exit(1);
}
}
} }

View File

@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
/** /**
* @author Fabien Potencier <fabien.potencier@gmail.com> * @author Fabien Potencier <fabien.potencier@gmail.com>
@ -47,6 +48,7 @@ class RunScriptCommand extends BaseCommand
{ {
$this $this
->setName('run-script') ->setName('run-script')
->setAliases(array('run'))
->setDescription('Runs the scripts defined in composer.json.') ->setDescription('Runs the scripts defined in composer.json.')
->setDefinition(array( ->setDefinition(array(
new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'),
@ -56,10 +58,13 @@ class RunScriptCommand extends BaseCommand
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>run-script</info> command runs scripts defined in composer.json: The <info>run-script</info> command runs scripts defined in composer.json:
<info>php composer.phar run-script post-update-cmd</info> <info>php composer.phar run-script post-update-cmd</info>
Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT EOT
) )
; ;
@ -68,7 +73,7 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
if ($input->getOption('list')) { if ($input->getOption('list')) {
return $this->listScripts(); return $this->listScripts($output);
} elseif (!$input->getArgument('script')) { } elseif (!$input->getArgument('script')) {
throw new \RuntimeException('Missing required argument "script"'); throw new \RuntimeException('Missing required argument "script"');
} }
@ -101,7 +106,7 @@ EOT
return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args);
} }
protected function listScripts() protected function listScripts(OutputInterface $output)
{ {
$scripts = $this->getComposer()->getPackage()->getScripts(); $scripts = $this->getComposer()->getPackage()->getScripts();
@ -111,10 +116,27 @@ EOT
$io = $this->getIO(); $io = $this->getIO();
$io->writeError('<info>scripts:</info>'); $io->writeError('<info>scripts:</info>');
$table = array();
foreach ($scripts as $name => $script) { foreach ($scripts as $name => $script) {
$io->write(' ' . $name); $description = '';
try {
$cmd = $this->getApplication()->find($name);
if ($cmd instanceof ScriptAliasCommand) {
$description = $cmd->getDescription();
}
} catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) {
// ignore scripts that have no command associated, like native Composer script listeners
}
$table[] = array(' '.$name, $description);
} }
$renderer = new Table($output);
$renderer->setStyle('compact');
$rendererStyle = $renderer->getStyle();
$rendererStyle->setVerticalBorderChar('');
$rendererStyle->setCellRowContentFormat('%s ');
$renderer->setRows($table)->render();
return 0; return 0;
} }
} }

View File

@ -43,10 +43,13 @@ class ScriptAliasCommand extends BaseCommand
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>run-script</info> command runs scripts defined in composer.json: The <info>run-script</info> command runs scripts defined in composer.json:
<info>php composer.phar run-script post-update-cmd</info> <info>php composer.phar run-script post-update-cmd</info>
Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT EOT
) )
; ;

View File

@ -44,10 +44,12 @@ class SearchCommand extends BaseCommand
new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'),
new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The search command searches for packages by its name The search command searches for packages by its name
<info>php composer.phar search symfony composer</info> <info>php composer.phar search symfony composer</info>
Read more at https://getcomposer.org/doc/03-cli.md#search
EOT EOT
) )
; ;
@ -59,7 +61,7 @@ EOT
$platformRepo = new PlatformRepository; $platformRepo = new PlatformRepository;
$io = $this->getIO(); $io = $this->getIO();
if (!($composer = $this->getComposer(false))) { if (!($composer = $this->getComposer(false))) {
$composer = Factory::create($this->getIO(), array()); $composer = Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins'));
} }
$localRepo = $composer->getRepositoryManager()->getLocalRepository(); $localRepo = $composer->getRepositoryManager()->getLocalRepository();
$installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo));

View File

@ -53,12 +53,14 @@ class SelfUpdateCommand extends BaseCommand
new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'),
new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>self-update</info> command checks getcomposer.org for newer The <info>self-update</info> command checks getcomposer.org for newer
versions of composer and if found, installs the latest. versions of composer and if found, installs the latest.
<info>php composer.phar self-update</info> <info>php composer.phar self-update</info>
Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate-
EOT EOT
) )
; ;
@ -75,9 +77,9 @@ EOT
} }
$io = $this->getIO(); $io = $this->getIO();
$remoteFilesystem = Factory::createRemoteFilesystem($io, $config); $httpDownloader = Factory::createHttpDownloader($io, $config);
$versionsUtil = new Versions($config, $remoteFilesystem); $versionsUtil = new Versions($config, $httpDownloader);
// switch channel if requested // switch channel if requested
foreach (array('stable', 'preview', 'snapshot') as $channel) { foreach (array('stable', 'preview', 'snapshot') as $channel) {
@ -107,6 +109,15 @@ EOT
throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
} }
// check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable
if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
$composeUser = posix_getpwuid(posix_geteuid());
$homeOwner = posix_getpwuid(fileowner($home));
if (isset($composeUser['name']) && isset($homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) {
$io->writeError('<warning>You are running composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
}
}
if ($input->getOption('rollback')) { if ($input->getOption('rollback')) {
return $this->rollback($output, $rollbackDir, $localFilename); return $this->rollback($output, $rollbackDir, $localFilename);
} }
@ -145,9 +156,9 @@ EOT
$io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $versionsUtil->getChannel())); $io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $versionsUtil->getChannel()));
$remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
$signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false); $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody();
$io->writeError(' ', false); $io->writeError(' ', false);
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress')); $httpDownloader->copy($remoteFilename, $tempFilename);
$io->writeError(''); $io->writeError('');
if (!file_exists($tempFilename) || !$signature) { if (!file_exists($tempFilename) || !$signature) {
@ -167,7 +178,9 @@ EOT
$sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub');
if (!file_exists($sigFile)) { if (!file_exists($sigFile)) {
file_put_contents($home.'/keys.dev.pub', <<<DEVPUBKEY file_put_contents(
$home.'/keys.dev.pub',
<<<DEVPUBKEY
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
@ -183,8 +196,11 @@ r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ== wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
-----END PUBLIC KEY----- -----END PUBLIC KEY-----
DEVPUBKEY DEVPUBKEY
); );
file_put_contents($home.'/keys.tags.pub', <<<TAGSPUBKEY
file_put_contents(
$home.'/keys.tags.pub',
<<<TAGSPUBKEY
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2 MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
@ -200,12 +216,12 @@ TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
RGv89BPD+2DLnJysngsvVaUCAwEAAQ== RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
-----END PUBLIC KEY----- -----END PUBLIC KEY-----
TAGSPUBKEY TAGSPUBKEY
); );
} }
$pubkeyid = openssl_pkey_get_public($sigFile); $pubkeyid = openssl_pkey_get_public($sigFile);
$algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384'; $algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
if (!in_array('SHA384', openssl_get_md_methods())) { if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) {
throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity'); throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
} }
$signature = json_decode($signature, true); $signature = json_decode($signature, true);

View File

@ -14,7 +14,6 @@ namespace Composer\Command;
use Composer\Composer; use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface; use Composer\Package\CompletePackageInterface;
@ -29,6 +28,7 @@ use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Semver; use Composer\Semver\Semver;
use Composer\Spdx\SpdxLicenses; use Composer\Spdx\SpdxLicenses;
@ -52,8 +52,8 @@ class ShowCommand extends BaseCommand
protected $versionParser; protected $versionParser;
protected $colors; protected $colors;
/** @var Pool */ /** @var RepositorySet */
private $pool; private $repositorySet;
protected function configure() protected function configure()
{ {
@ -74,15 +74,18 @@ class ShowCommand extends BaseCommand
new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'),
new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The show command displays detailed information about a package, or The show command displays detailed information about a package, or
lists all packages available. lists all packages available.
Read more at https://getcomposer.org/doc/03-cli.md#show
EOT EOT
) )
; ;
@ -104,6 +107,8 @@ EOT
if ($input->getOption('outdated')) { if ($input->getOption('outdated')) {
$input->setOption('latest', true); $input->setOption('latest', true);
} elseif ($input->getOption('ignore')) {
$io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>');
} }
if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) {
@ -118,6 +123,12 @@ EOT
return 1; return 1;
} }
if ($input->getOption('tree') && $input->getOption('latest')) {
$io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)');
return 1;
}
$format = $input->getOption('format'); $format = $input->getOption('format');
if (!in_array($format, array('text', 'json'))) { if (!in_array($format, array('text', 'json'))) {
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
@ -178,9 +189,6 @@ EOT
// show single package or single version // show single package or single version
if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) { if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) {
if ('json' === $format) {
$io->writeError('Format "json" is only supported for package listings, falling back to format "text"');
}
if (empty($package)) { if (empty($package)) {
list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
@ -200,7 +208,13 @@ EOT
$exitCode = 0; $exitCode = 0;
if ($input->getOption('tree')) { if ($input->getOption('tree')) {
$this->displayPackageTree($package, $installedRepo, $repos); $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos);
if ('json' === $format) {
$io->write(JsonFile::encode(array('installed' => array($arrayTree))));
} else {
$this->displayPackageTree(array($arrayTree));
}
} else { } else {
$latestPackage = null; $latestPackage = null;
if ($input->getOption('latest')) { if ($input->getOption('latest')) {
@ -209,6 +223,12 @@ EOT
if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) { if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) {
$exitCode = 1; $exitCode = 1;
} }
if ($input->getOption('path')) {
$io->write($package->getName(), false);
$io->write(' ' . strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"));
return $exitCode;
}
$this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null);
$this->printLinks($package, 'requires'); $this->printLinks($package, 'requires');
$this->printLinks($package, 'devRequires', 'requires (dev)'); $this->printLinks($package, 'devRequires', 'requires (dev)');
@ -228,18 +248,22 @@ EOT
// show tree view if requested // show tree view if requested
if ($input->getOption('tree')) { if ($input->getOption('tree')) {
if ('json' === $format) {
$io->writeError('Format "json" is only supported for package listings, falling back to format "text"');
}
$rootRequires = $this->getRootRequires(); $rootRequires = $this->getRootRequires();
$packages = $installedRepo->getPackages(); $packages = $installedRepo->getPackages();
usort($packages, 'strcmp'); usort($packages, 'strcmp');
$arrayTree = array();
foreach ($packages as $package) { foreach ($packages as $package) {
if (in_array($package->getName(), $rootRequires, true)) { if (in_array($package->getName(), $rootRequires, true)) {
$this->displayPackageTree($package, $installedRepo, $repos); $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos);
} }
} }
if ('json' === $format) {
$io->write(JsonFile::encode(array('installed' => $arrayTree)));
} else {
$this->displayPackageTree($arrayTree);
}
return 0; return 0;
} }
@ -294,8 +318,8 @@ EOT
} else { } else {
$type = 'available'; $type = 'available';
} }
if ($repo instanceof ComposerRepository && $repo->hasProviders()) { if ($repo instanceof ComposerRepository) {
foreach ($repo->getProviderNames() as $name) { foreach ($repo->getPackageNames() as $name) {
if (!$packageFilter || preg_match($packageFilter, $name)) { if (!$packageFilter || preg_match($packageFilter, $name)) {
$packages[$type][$name] = $name; $packages[$type][$name] = $name;
} }
@ -319,6 +343,7 @@ EOT
$showAllTypes = $input->getOption('all'); $showAllTypes = $input->getOption('all');
$showLatest = $input->getOption('latest'); $showLatest = $input->getOption('latest');
$showMinorOnly = $input->getOption('minor-only'); $showMinorOnly = $input->getOption('minor-only');
$ignoredPackages = array_map('strtolower', $input->getOption('ignore'));
$indent = $showAllTypes ? ' ' : ''; $indent = $showAllTypes ? ' ' : '';
$latestPackages = array(); $latestPackages = array();
$exitCode = 0; $exitCode = 0;
@ -329,23 +354,17 @@ EOT
ksort($packages[$type]); ksort($packages[$type]);
$nameLength = $versionLength = $latestLength = 0; $nameLength = $versionLength = $latestLength = 0;
foreach ($packages[$type] as $package) {
if (is_object($package)) {
$nameLength = max($nameLength, strlen($package->getPrettyName()));
if ($showVersion) {
$versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
if ($showLatest) {
$latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly);
if ($latestPackage === false) {
continue;
}
$latestPackages[$package->getPrettyName()] = $latestPackage; if ($showLatest && $showVersion) {
$latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion())); foreach ($packages[$type] as $package) {
if (is_object($package)) {
$latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly);
if ($latestPackage === false) {
continue;
} }
$latestPackages[$package->getPrettyName()] = $latestPackage;
} }
} else {
$nameLength = max($nameLength, strlen($package));
} }
} }
@ -357,11 +376,6 @@ EOT
$hasOutdatedPackages = false; $hasOutdatedPackages = false;
$viewData[$type] = array(); $viewData[$type] = array();
$viewMetaData[$type] = array(
'nameLength' => $nameLength,
'versionLength' => $versionLength,
'latestLength' => $latestLength,
);
foreach ($packages[$type] as $package) { foreach ($packages[$type] as $package) {
$packageViewData = array(); $packageViewData = array();
if (is_object($package)) { if (is_object($package)) {
@ -369,19 +383,26 @@ EOT
if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
$latestPackage = $latestPackages[$package->getPrettyName()]; $latestPackage = $latestPackages[$package->getPrettyName()];
} }
if ($input->getOption('outdated') && $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) {
// Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
$packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned();
$packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true);
if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
continue; continue;
} elseif ($input->getOption('outdated') || $input->getOption('strict')) { } elseif ($input->getOption('outdated') || $input->getOption('strict')) {
$hasOutdatedPackages = true; $hasOutdatedPackages = true;
} }
$packageViewData['name'] = $package->getPrettyName(); $packageViewData['name'] = $package->getPrettyName();
$nameLength = max($nameLength, strlen($package->getPrettyName()));
if ($writeVersion) { if ($writeVersion) {
$packageViewData['version'] = $package->getFullPrettyVersion(); $packageViewData['version'] = $package->getFullPrettyVersion();
$versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
} }
if ($writeLatest && $latestPackage) { if ($writeLatest && $latestPackage) {
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
$latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion()));
} }
if ($writeDescription) { if ($writeDescription) {
$packageViewData['description'] = $package->getDescription(); $packageViewData['description'] = $package->getDescription();
@ -391,7 +412,7 @@ EOT
} }
if ($latestPackage && $latestPackage->isAbandoned()) { if ($latestPackage && $latestPackage->isAbandoned()) {
$replacement = (is_string($latestPackage->getReplacementPackage())) $replacement = is_string($latestPackage->getReplacementPackage())
? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
: 'No replacement was suggested'; : 'No replacement was suggested';
$packageWarning = sprintf( $packageWarning = sprintf(
@ -403,9 +424,15 @@ EOT
} }
} else { } else {
$packageViewData['name'] = $package; $packageViewData['name'] = $package;
$nameLength = max($nameLength, strlen($package));
} }
$viewData[$type][] = $packageViewData; $viewData[$type][] = $packageViewData;
} }
$viewMetaData[$type] = array(
'nameLength' => $nameLength,
'versionLength' => $versionLength,
'latestLength' => $latestLength,
);
if ($input->getOption('strict') && $hasOutdatedPackages) { if ($input->getOption('strict') && $hasOutdatedPackages) {
$exitCode = 1; $exitCode = 1;
break; break;
@ -467,7 +494,7 @@ EOT
} }
$io->write(''); $io->write('');
if (isset($package['warning'])) { if (isset($package['warning'])) {
$io->write('<warning>' . $package['warning'] . '</warning>'); $io->writeError('<warning>' . $package['warning'] . '</warning>');
} }
} }
@ -511,19 +538,13 @@ EOT
$constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;
$policy = new DefaultPolicy(); $policy = new DefaultPolicy();
$pool = new Pool('dev'); $repositorySet = new RepositorySet(array(), 'dev');
$pool->addRepository($repos); $repositorySet->addRepository($repos);
$matchedPackage = null; $matchedPackage = null;
$versions = array(); $versions = array();
$matches = $pool->whatProvides($name, $constraint); $matches = $repositorySet->findPackages($name, $constraint);
foreach ($matches as $index => $package) { foreach ($matches as $index => $package) {
// skip providers/replacers
if ($package->getName() !== $name) {
unset($matches[$index]);
continue;
}
// select an exact match if it is in the installed repo and no specific version was required // select an exact match if it is in the installed repo and no specific version was required
if (null === $version && $installedRepo->hasPackage($package)) { if (null === $version && $installedRepo->hasPackage($package)) {
$matchedPackage = $package; $matchedPackage = $package;
@ -533,6 +554,8 @@ EOT
$matches[$index] = $package->getId(); $matches[$index] = $package->getId();
} }
$pool = $repositorySet->createPoolForPackage($package->getName());
// select preferred package according to policy rules // select preferred package according to policy rules
if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) { if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, array(), $matches)) {
$matchedPackage = $pool->literalToPackage($preferred[0]); $matchedPackage = $pool->literalToPackage($preferred[0]);
@ -565,6 +588,9 @@ EOT
$this->printLicenses($package); $this->printLicenses($package);
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); $io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
if ($installedRepo->hasPackage($package)) {
$io->write('<info>path</info> : ' . sprintf('%s', realpath($this->getComposer()->getInstallationManager()->getInstallPath($package))));
}
$io->write('<info>names</info> : ' . implode(', ', $package->getNames())); $io->write('<info>names</info> : ' . implode(', ', $package->getNames()));
if ($latestPackage->isAbandoned()) { if ($latestPackage->isAbandoned()) {
@ -708,40 +734,142 @@ EOT
/** /**
* Display the tree * Display the tree
* *
* @param PackageInterface|string $package * @param array $arrayTree
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $distantRepos
*/ */
protected function displayPackageTree(PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos) protected function displayPackageTree(array $arrayTree)
{ {
$io = $this->getIO(); $io = $this->getIO();
$io->write(sprintf('<info>%s</info>', $package->getPrettyName()), false); foreach ($arrayTree as $package) {
$io->write(' ' . $package->getPrettyVersion(), false); $io->write(sprintf('<info>%s</info>', $package['name']), false);
$io->write(' ' . strtok($package->getDescription(), "\r\n")); $io->write(' ' . $package['version'], false);
$io->write(' ' . strtok($package['description'], "\r\n"));
if (is_object($package)) { if (isset($package['requires'])) {
$requires = $package->getRequires(); $requires = $package['requires'];
ksort($requires); $treeBar = '├';
$treeBar = '├'; $j = 0;
$j = 0; $total = count($requires);
foreach ($requires as $require) {
$requireName = $require['name'];
$j++;
if ($j === $total) {
$treeBar = '└';
}
$level = 1;
$color = $this->colors[$level];
$info = sprintf(
'%s──<%s>%s</%s> %s',
$treeBar,
$color,
$requireName,
$color,
$require['version']
);
$this->writeTreeLine($info);
$treeBar = str_replace('└', ' ', $treeBar);
$packagesInTree = array($package['name'], $requireName);
$this->displayTree($require, $packagesInTree, $treeBar, $level + 1);
}
}
}
}
/**
* Generate the package tree
*
* @param PackageInterface $package
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $distantRepos
* @return array
*/
protected function generatePackageTree(
PackageInterface $package,
RepositoryInterface $installedRepo,
RepositoryInterface $distantRepos
) {
$requires = $package->getRequires();
ksort($requires);
$children = array();
foreach ($requires as $requireName => $require) {
$packagesInTree = array($package->getName(), $requireName);
$treeChildDesc = array(
'name' => $requireName,
'version' => $require->getPrettyConstraint(),
);
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree);
if ($deepChildren) {
$treeChildDesc['requires'] = $deepChildren;
}
$children[] = $treeChildDesc;
}
$tree = array(
'name' => $package->getPrettyName(),
'version' => $package->getPrettyVersion(),
'description' => $package->getDescription(),
);
if ($children) {
$tree['requires'] = $children;
}
return $tree;
}
/**
* Display a package tree
*
* @param array|string $package
* @param array $packagesInTree
* @param string $previousTreeBar
* @param int $level
*/
protected function displayTree(
$package,
array $packagesInTree,
$previousTreeBar = '├',
$level = 1
) {
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
if (is_array($package) && isset($package['requires'])) {
$requires = $package['requires'];
$treeBar = $previousTreeBar . ' ├';
$i = 0;
$total = count($requires); $total = count($requires);
foreach ($requires as $requireName => $require) { foreach ($requires as $require) {
$j++; $currentTree = $packagesInTree;
if ($j == 0) { $i++;
$this->writeTreeLine($treeBar); if ($i === $total) {
$treeBar = $previousTreeBar . ' └';
} }
if ($j == $total) { $colorIdent = $level % count($this->colors);
$treeBar = '└'; $color = $this->colors[$colorIdent];
}
$level = 1; $circularWarn = in_array(
$color = $this->colors[$level]; $require['name'],
$info = sprintf('%s──<%s>%s</%s> %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint()); $currentTree,
true
) ? '(circular dependency aborted here)' : '';
$info = rtrim(sprintf(
'%s──<%s>%s</%s> %s %s',
$treeBar,
$color,
$require['name'],
$color,
$require['version'],
$circularWarn
));
$this->writeTreeLine($info); $this->writeTreeLine($info);
$treeBar = str_replace('└', ' ', $treeBar); $treeBar = str_replace('└', ' ', $treeBar);
$packagesInTree = array($package->getName(), $requireName);
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1); $currentTree[] = $require['name'];
$this->displayTree($require, $currentTree, $treeBar, $level + 1);
} }
} }
} }
@ -749,44 +877,51 @@ EOT
/** /**
* Display a package tree * Display a package tree
* *
* @param string $name * @param string $name
* @param PackageInterface|string $package * @param PackageInterface|string $package
* @param RepositoryInterface $installedRepo * @param RepositoryInterface $installedRepo
* @param RepositoryInterface $distantRepos * @param RepositoryInterface $distantRepos
* @param array $packagesInTree * @param array $packagesInTree
* @param string $previousTreeBar * @return array
* @param int $level
*/ */
protected function displayTree($name, $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos, array $packagesInTree, $previousTreeBar = '├', $level = 1) protected function addTree(
{ $name,
$previousTreeBar = str_replace('├', '│', $previousTreeBar); $package,
list($package, $versions) = $this->getPackage($installedRepo, $distantRepos, $name, $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()); RepositoryInterface $installedRepo,
RepositoryInterface $distantRepos,
array $packagesInTree
) {
$children = array();
list($package, $versions) = $this->getPackage(
$installedRepo,
$distantRepos,
$name,
$package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()
);
if (is_object($package)) { if (is_object($package)) {
$requires = $package->getRequires(); $requires = $package->getRequires();
ksort($requires); ksort($requires);
$treeBar = $previousTreeBar . ' ├';
$i = 0;
$total = count($requires);
foreach ($requires as $requireName => $require) { foreach ($requires as $requireName => $require) {
$currentTree = $packagesInTree; $currentTree = $packagesInTree;
$i++;
if ($i == $total) {
$treeBar = $previousTreeBar . ' └';
}
$colorIdent = $level % count($this->colors);
$color = $this->colors[$colorIdent];
$circularWarn = in_array($requireName, $currentTree) ? '(circular dependency aborted here)' : ''; $treeChildDesc = array(
$info = rtrim(sprintf('%s──<%s>%s</%s> %s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint(), $circularWarn)); 'name' => $requireName,
$this->writeTreeLine($info); 'version' => $require->getPrettyConstraint(),
);
$treeBar = str_replace('└', ' ', $treeBar); if (!in_array($requireName, $currentTree, true)) {
if (!in_array($requireName, $currentTree)) {
$currentTree[] = $requireName; $currentTree[] = $requireName;
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $currentTree, $treeBar, $level + 1); $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree);
if ($deepChildren) {
$treeChildDesc['requires'] = $deepChildren;
}
} }
$children[] = $treeChildDesc;
} }
} }
return $children;
} }
private function updateStatusToVersionStyle($updateStatus) private function updateStatusToVersionStyle($updateStatus)
@ -834,13 +969,13 @@ EOT
* @param string $phpVersion * @param string $phpVersion
* @param bool $minorOnly * @param bool $minorOnly
* *
* @return PackageInterface|null * @return PackageInterface|false
*/ */
private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false) private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false)
{ {
// find the latest version allowed in this pool // find the latest version allowed in this repo set
$name = $package->getName(); $name = $package->getName();
$versionSelector = new VersionSelector($this->getPool($composer)); $versionSelector = new VersionSelector($this->getRepositorySet($composer));
$stability = $composer->getPackage()->getMinimumStability(); $stability = $composer->getPackage()->getMinimumStability();
$flags = $composer->getPackage()->getStabilityFlags(); $flags = $composer->getPackage()->getStabilityFlags();
if (isset($flags[$name])) { if (isset($flags[$name])) {
@ -864,13 +999,13 @@ EOT
return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability); return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability);
} }
private function getPool(Composer $composer) private function getRepositorySet(Composer $composer)
{ {
if (!$this->pool) { if (!$this->repositorySet) {
$this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->repositorySet = new RepositorySet(array(), $composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
$this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
} }
return $this->pool; return $this->repositorySet;
} }
} }

View File

@ -36,23 +36,33 @@ class StatusCommand extends BaseCommand
const EXIT_CODE_UNPUSHED_CHANGES = 2; const EXIT_CODE_UNPUSHED_CHANGES = 2;
const EXIT_CODE_VERSION_CHANGES = 4; const EXIT_CODE_VERSION_CHANGES = 4;
/**
* @throws \Symfony\Component\Console\Exception\InvalidArgumentException
*/
protected function configure() protected function configure()
{ {
$this $this
->setName('status') ->setName('status')
->setDescription('Shows a list of locally modified packages.') ->setDescription('Shows a list of locally modified packages, for packages installed from source.')
->setDefinition(array( ->setDefinition(array(
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The status command displays a list of dependencies that have The status command displays a list of dependencies that have
been modified locally. been modified locally.
Read more at https://getcomposer.org/doc/03-cli.md#status
EOT EOT
) )
; ;
} }
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
// init repos // init repos
@ -80,7 +90,7 @@ EOT
// list packages // list packages
foreach ($installedRepo->getCanonicalPackages() as $package) { foreach ($installedRepo->getCanonicalPackages() as $package) {
$downloader = $dm->getDownloaderForInstalledPackage($package); $downloader = $dm->getDownloaderForPackage($package);
$targetDir = $im->getInstallPath($package); $targetDir = $im->getInstallPath($package);
if ($downloader instanceof ChangeReportInterface) { if ($downloader instanceof ChangeReportInterface) {

View File

@ -31,17 +31,22 @@ class SuggestsCommand extends BaseCommand
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), 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.'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>%command.name%</info> command shows a sorted list of suggested packages. The <info>%command.name%</info> command shows a sorted list of suggested packages.
Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists. Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.
Read more at https://getcomposer.org/doc/03-cli.md#suggests
EOT EOT
) )
; ;
} }
/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$lock = $this->getComposer()->getLocker()->getLockData(); $lock = $this->getComposer()->getLocker()->getLockData();
@ -116,7 +121,7 @@ EOT
$io->write(sprintf('<info>%s</info>', $suggestion)); $io->write(sprintf('<info>%s</info>', $suggestion));
} }
return; return null;
} }
// Grouped by package // Grouped by package

View File

@ -34,7 +34,7 @@ class UpdateCommand extends BaseCommand
{ {
$this $this
->setName('update') ->setName('update')
->setAliases(array('upgrade')) ->setAliases(array('u', 'upgrade'))
->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.') ->setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.')
->setDefinition(array( ->setDefinition(array(
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'),
@ -61,7 +61,8 @@ class UpdateCommand extends BaseCommand
new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The <info>update</info> command reads the composer.json file from the The <info>update</info> command reads the composer.json file from the
current directory, processes it, and updates, removes or installs all the current directory, processes it, and updates, removes or installs all the
dependencies. dependencies.
@ -80,6 +81,7 @@ from a specific vendor:
To select packages names interactively with auto-completion use <info>-i</info>. To select packages names interactively with auto-completion use <info>-i</info>.
Read more at https://getcomposer.org/doc/03-cli.md#update-u
EOT EOT
) )
; ;
@ -119,8 +121,6 @@ EOT
} }
} }
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

View File

@ -39,14 +39,15 @@ class ValidateCommand extends BaseCommand
->setName('validate') ->setName('validate')
->setDescription('Validates a composer.json and composer.lock.') ->setDescription('Validates a composer.json and composer.lock.')
->setDefinition(array( ->setDefinition(array(
new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'),
new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'),
new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'),
new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), 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 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'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'),
)) ))
->setHelp(<<<EOT ->setHelp(
<<<EOT
The validate command validates a given composer.json and composer.lock The validate command validates a given composer.json and composer.lock
Exit codes in case of errors are: Exit codes in case of errors are:
@ -54,6 +55,7 @@ Exit codes in case of errors are:
2 validation error(s) 2 validation error(s)
3 file unreadable or missing 3 file unreadable or missing
Read more at https://getcomposer.org/doc/03-cli.md#validate
EOT EOT
); );
} }
@ -88,15 +90,16 @@ EOT
list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);
$lockErrors = array(); $lockErrors = array();
$composer = Factory::create($io, $file); $composer = Factory::create($io, $file, $input->hasParameterOption('--no-plugins'));
$locker = $composer->getLocker(); $locker = $composer->getLocker();
if ($locker->isLocked() && !$locker->isFresh()) { if ($locker->isLocked() && !$locker->isFresh()) {
$lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update`.'; $lockErrors[] = 'The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update`.';
} }
$this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true, $isStrict);
$exitCode = $errors || ($publishErrors && $checkPublish) || ($lockErrors && $checkLock) ? 2 : ($isStrict && $warnings ? 1 : 0); // $errors include publish and lock errors when exists
$exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0);
if ($input->getOption('with-dependencies')) { if ($input->getOption('with-dependencies')) {
$localRepo = $composer->getRepositoryManager()->getLocalRepository(); $localRepo = $composer->getRepositoryManager()->getLocalRepository();
@ -107,7 +110,7 @@ EOT
list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll); list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);
$this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors);
$depCode = $errors || ($publishErrors && $checkPublish) ? 2 : ($isStrict && $warnings ? 1 : 0); $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0);
$exitCode = max($depCode, $exitCode); $exitCode = max($depCode, $exitCode);
} }
} }
@ -120,7 +123,7 @@ EOT
return $exitCode; return $exitCode;
} }
private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false) private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false, $isStrict = false)
{ {
if (!$errors && !$publishErrors && !$warnings) { if (!$errors && !$publishErrors && !$warnings) {
$io->write('<info>' . $name . ' is valid</info>'); $io->write('<info>' . $name . ' is valid</info>');
@ -140,16 +143,18 @@ EOT
} }
// If checking publish errors, display them as errors, otherwise just show them as warnings // If checking publish errors, display them as errors, otherwise just show them as warnings
// Skip when it is a strict check and we don't want to check publish errors
if ($checkPublish) { if ($checkPublish) {
$errors = array_merge($errors, $publishErrors); $errors = array_merge($errors, $publishErrors);
} else { } elseif (!$isStrict) {
$warnings = array_merge($warnings, $publishErrors); $warnings = array_merge($warnings, $publishErrors);
} }
// If checking lock errors, display them as errors, otherwise just show them as warnings // If checking lock errors, display them as errors, otherwise just show them as warnings
// Skip when it is a strict check and we don't want to check lock errors
if ($checkLock) { if ($checkLock) {
$errors = array_merge($errors, $lockErrors); $errors = array_merge($errors, $lockErrors);
} else { } elseif (!$isStrict) {
$warnings = array_merge($warnings, $lockErrors); $warnings = array_merge($warnings, $lockErrors);
} }

View File

@ -105,7 +105,7 @@ class Compiler
foreach ($finder as $file) { foreach ($finder as $file) {
$this->addFile($phar, $file, false); $this->addFile($phar, $file, false);
} }
$this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/seld/cli-prompt/res/hiddeninput.exe'), false); $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe'), false);
$finder = new Finder(); $finder = new Finder();
$finder->files() $finder->files()
@ -117,12 +117,13 @@ class Compiler
->exclude('docs') ->exclude('docs')
->in(__DIR__.'/../../vendor/symfony/') ->in(__DIR__.'/../../vendor/symfony/')
->in(__DIR__.'/../../vendor/seld/jsonlint/') ->in(__DIR__.'/../../vendor/seld/jsonlint/')
->in(__DIR__.'/../../vendor/seld/cli-prompt/')
->in(__DIR__.'/../../vendor/justinrainbow/json-schema/') ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/')
->in(__DIR__.'/../../vendor/composer/spdx-licenses/') ->in(__DIR__.'/../../vendor/composer/spdx-licenses/')
->in(__DIR__.'/../../vendor/composer/semver/') ->in(__DIR__.'/../../vendor/composer/semver/')
->in(__DIR__.'/../../vendor/composer/ca-bundle/') ->in(__DIR__.'/../../vendor/composer/ca-bundle/')
->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
->in(__DIR__.'/../../vendor/psr/') ->in(__DIR__.'/../../vendor/psr/')
->in(__DIR__.'/../../vendor/react/')
->sort($finderSort) ->sort($finderSort)
; ;
@ -193,6 +194,7 @@ class Compiler
$content = str_replace('@package_version@', $this->version, $content); $content = str_replace('@package_version@', $this->version, $content);
$content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content); $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
$content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content); $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content);
$content = preg_replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
} }
$phar->addFromString($path, $content); $phar->addFromString($path, $content);
@ -255,7 +257,7 @@ class Compiler
*/ */
// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && ini_get('apc.enable_cli') && ini_get('apc.cache_by_default')) { if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
if (version_compare(phpversion('apc'), '3.0.12', '>=')) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
ini_set('apc.cache_by_default', 0); ini_set('apc.cache_by_default', 0);
} else { } else {

View File

@ -29,9 +29,46 @@ use Composer\Package\Archiver\ArchiveManager;
*/ */
class Composer class Composer
{ {
/*
* Examples of the following constants in the various configurations they can be in
*
* releases (phar):
* const VERSION = '1.8.2';
* const BRANCH_ALIAS_VERSION = '';
* const RELEASE_DATE = '2019-01-29 15:00:53';
* const SOURCE_VERSION = '';
*
* snapshot builds (phar):
* const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7';
* const BRANCH_ALIAS_VERSION = '1.9-dev';
* const RELEASE_DATE = '2019-02-20 07:43:56';
* const SOURCE_VERSION = '';
*
* source (git clone):
* const VERSION = '@package_version@';
* const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
* const RELEASE_DATE = '@release_date@';
* const SOURCE_VERSION = '1.8-dev+source';
*/
const VERSION = '@package_version@'; const VERSION = '@package_version@';
const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@'; const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
const RELEASE_DATE = '@release_date@'; const RELEASE_DATE = '@release_date@';
const SOURCE_VERSION = '2.0-dev+source';
public static function getVersion()
{
// no replacement done, this must be a source checkout
if (self::VERSION === '@package_version'.'@') {
return self::SOURCE_VERSION;
}
// we have a branch alias and version is a commit id, this must be a snapshot build
if (self::BRANCH_ALIAS_VERSION !== '' && preg_match('{^[a-f0-9]{40}$}', self::VERSION)) {
return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
}
return self::VERSION;
}
/** /**
* @var Package\RootPackageInterface * @var Package\RootPackageInterface

View File

@ -16,6 +16,7 @@ use Composer\Config\ConfigSourceInterface;
use Composer\Downloader\TransportException; use Composer\Downloader\TransportException;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
@ -61,6 +62,7 @@ class Config
'archive-format' => 'tar', 'archive-format' => 'tar',
'archive-dir' => '.', 'archive-dir' => '.',
'htaccess-protect' => true, 'htaccess-protect' => true,
'use-github-api' => true,
// valid keys without defaults (auth config stuff): // valid keys without defaults (auth config stuff):
// bitbucket-oauth // bitbucket-oauth
// github-oauth // github-oauth
@ -72,7 +74,7 @@ class Config
public static $defaultRepositories = array( public static $defaultRepositories = array(
'packagist.org' => array( 'packagist.org' => array(
'type' => 'composer', 'type' => 'composer',
'url' => 'https?://packagist.org', 'url' => 'https?://repo.packagist.org',
'allow_ssl_downgrade' => true, 'allow_ssl_downgrade' => true,
), ),
); );
@ -216,7 +218,6 @@ class Config
case 'cache-vcs-dir': case 'cache-vcs-dir':
case 'cafile': case 'cafile':
case 'capath': case 'capath':
case 'htaccess-protect':
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
@ -230,6 +231,13 @@ class Config
return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
case 'htaccess-protect':
$value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT');
if (false === $value) {
$value = $this->config[$key];
}
return $value !== 'false' && (bool) $value;
case 'cache-ttl': case 'cache-ttl':
return (int) $this->config[$key]; return (int) $this->config[$key];
@ -245,9 +253,11 @@ class Config
case 'g': case 'g':
$size *= 1024; $size *= 1024;
// intentional fallthrough // intentional fallthrough
// no break
case 'm': case 'm':
$size *= 1024; $size *= 1024;
// intentional fallthrough // intentional fallthrough
// no break
case 'k': case 'k':
$size *= 1024; $size *= 1024;
break; break;
@ -315,10 +325,10 @@ class Config
case 'disable-tls': case 'disable-tls':
return $this->config[$key] !== 'false' && (bool) $this->config[$key]; return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'secure-http': case 'secure-http':
return $this->config[$key] !== 'false' && (bool) $this->config[$key]; return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'use-github-api':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
default: default:
if (!isset($this->config[$key])) { if (!isset($this->config[$key])) {
return null; return null;
@ -450,4 +460,20 @@ class Config
} }
} }
} }
/**
* Used by long-running custom scripts in composer.json
*
* "scripts": {
* "watch": [
* "Composer\\Config::disableProcessTimeout",
* "vendor/bin/long-running-script --watch"
* ]
* }
*/
public static function disableProcessTimeout()
{
// Override global timeout set earlier by environment or config
ProcessExecutor::setTimeout(0);
}
} }

View File

@ -39,7 +39,7 @@ interface ConfigSourceInterface
* Add a config setting * Add a config setting
* *
* @param string $name Name * @param string $name Name
* @param string $value Value * @param string|array $value Value
*/ */
public function addConfigSetting($name, $value); public function addConfigSetting($name, $value);

View File

@ -135,10 +135,10 @@ class JsonConfigSource implements ConfigSourceInterface
public function addProperty($name, $value) public function addProperty($name, $value)
{ {
$this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) { $this->manipulateJson('addProperty', $name, $value, function (&$config, $key, $val) {
if (substr($key, 0, 6) === 'extra.') { if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
$bits = explode('.', $key); $bits = explode('.', $key);
$last = array_pop($bits); $last = array_pop($bits);
$arr = &$config['extra']; $arr = &$config[reset($bits)];
foreach ($bits as $bit) { foreach ($bits as $bit) {
if (!isset($arr[$bit])) { if (!isset($arr[$bit])) {
$arr[$bit] = array(); $arr[$bit] = array();
@ -159,10 +159,10 @@ class JsonConfigSource implements ConfigSourceInterface
{ {
$authConfig = $this->authConfig; $authConfig = $this->authConfig;
$this->manipulateJson('removeProperty', $name, function (&$config, $key) { $this->manipulateJson('removeProperty', $name, function (&$config, $key) {
if (substr($key, 0, 6) === 'extra.') { if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
$bits = explode('.', $key); $bits = explode('.', $key);
$last = array_pop($bits); $last = array_pop($bits);
$arr = &$config['extra']; $arr = &$config[reset($bits)];
foreach ($bits as $bit) { foreach ($bits as $bit) {
if (!isset($arr[$bit])) { if (!isset($arr[$bit])) {
return; return;
@ -193,6 +193,10 @@ class JsonConfigSource implements ConfigSourceInterface
{ {
$this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) { $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) {
unset($config[$type][$name]); unset($config[$type][$name]);
if (0 === count($config[$type])) {
unset($config[$type]);
}
}); });
} }
@ -255,7 +259,7 @@ class JsonConfigSource implements ConfigSourceInterface
* *
* @param array $array * @param array $array
* @param mixed $value * @param mixed $value
* @return array * @return int
*/ */
private function arrayUnshiftRef(&$array, &$value) private function arrayUnshiftRef(&$array, &$value)
{ {

View File

@ -12,9 +12,13 @@
namespace Composer\Console; namespace Composer\Console;
use Composer\IO\NullIO;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\Silencer; use Composer\Util\Silencer;
use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -85,7 +89,9 @@ class Application extends BaseApplication
}); });
} }
parent::__construct('Composer', Composer::VERSION); $this->io = new NullIO();
parent::__construct('Composer', Composer::getVersion());
} }
/** /**
@ -107,9 +113,16 @@ class Application extends BaseApplication
{ {
$this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
$io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
new QuestionHelper(),
)));
ErrorHandler::register($io); ErrorHandler::register($io);
if ($input->hasParameterOption('--no-cache')) {
$io->writeError('Disabling cache usage', true, IOInterface::DEBUG);
putenv('COMPOSER_CACHE_DIR='.(Platform::isWindows() ? 'nul' : '/dev/null'));
}
// switch working dir // switch working dir
if ($newWorkDir = $this->getNewWorkingDir($input)) { if ($newWorkDir = $this->getNewWorkingDir($input)) {
$oldWorkingDir = getcwd(); $oldWorkingDir = getcwd();
@ -122,6 +135,9 @@ class Application extends BaseApplication
if ($name = $this->getCommandName($input)) { if ($name = $this->getCommandName($input)) {
try { try {
$commandName = $this->find($name)->getName(); $commandName = $this->find($name)->getName();
} catch (CommandNotFoundException $e) {
// we'll check command validity again later after plugins are loaded
$commandName = false;
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
} }
} }
@ -174,7 +190,7 @@ class Application extends BaseApplication
if (!$isProxyCommand) { if (!$isProxyCommand) {
$io->writeError(sprintf( $io->writeError(sprintf(
'Running %s (%s) with %s on %s', 'Running %s (%s) with %s on %s',
Composer::VERSION, Composer::getVersion(),
Composer::RELEASE_DATE, Composer::RELEASE_DATE,
defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
@ -200,6 +216,12 @@ class Application extends BaseApplication
if (function_exists('posix_getuid') && posix_getuid() === 0) { if (function_exists('posix_getuid') && posix_getuid() === 0) {
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
$io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>'); $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');
if ($io->isInteractive()) {
if (!$io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ', true)) {
return 1;
}
}
} }
if ($uid = (int) getenv('SUDO_UID')) { if ($uid = (int) getenv('SUDO_UID')) {
// Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on
@ -255,14 +277,14 @@ class Application extends BaseApplication
} }
if (isset($startTime)) { if (isset($startTime)) {
$io->writeError('<info>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('<info>Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s');
} }
restore_error_handler(); restore_error_handler();
return $result; return $result;
} catch (ScriptExecutionException $e) { } catch (ScriptExecutionException $e) {
return $e->getCode(); return (int) $e->getCode();
} catch (\Exception $e) { } catch (\Exception $e) {
$this->hintCommonErrors($e); $this->hintCommonErrors($e);
restore_error_handler(); restore_error_handler();
@ -357,6 +379,9 @@ class Application extends BaseApplication
public function resetComposer() public function resetComposer()
{ {
$this->composer = null; $this->composer = null;
if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) {
$this->getIO()->resetAuthentications();
}
} }
/** /**
@ -418,7 +443,7 @@ class Application extends BaseApplication
*/ */
public function getLongVersion() public function getLongVersion()
{ {
if (Composer::BRANCH_ALIAS_VERSION) { if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
return sprintf( return sprintf(
'<info>%s</info> version <comment>%s (%s)</comment> %s', '<info>%s</info> version <comment>%s (%s)</comment> %s',
$this->getName(), $this->getName(),
@ -440,6 +465,7 @@ class Application extends BaseApplication
$definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information'));
$definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'));
$definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.'));
$definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache'));
return $definition; return $definition;
} }

View File

@ -196,4 +196,16 @@ class Decisions implements \Iterator, \Countable
$this->decisionMap[$packageId] = -$level; $this->decisionMap[$packageId] = -$level;
} }
} }
public function __toString()
{
$decisionMap = $this->decisionMap;
ksort($decisionMap);
$str = '[';
foreach ($decisionMap as $packageId => $level) {
$str .= $packageId.':'.$level.',';
}
$str .= ']';
return $str;
}
} }

View File

@ -57,11 +57,6 @@ class DefaultPolicy implements PolicyInterface
return $packages; return $packages;
} }
public function getPriority(Pool $pool, PackageInterface $package)
{
return $pool->getPriority($package->getRepository());
}
public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null) public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
{ {
$packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals); $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);
@ -168,7 +163,7 @@ class DefaultPolicy implements PolicyInterface
return 1; return 1;
} }
return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1; return ($pool->getPriority($a->id) > $pool->getPriority($b->id)) ? -1 : 1;
} }
/** /**
@ -236,10 +231,10 @@ class DefaultPolicy implements PolicyInterface
} }
if (null === $priority) { if (null === $priority) {
$priority = $this->getPriority($pool, $package); $priority = $pool->getPriority($package->id);
} }
if ($this->getPriority($pool, $package) != $priority) { if ($pool->getPriority($package->id) != $priority) {
break; break;
} }

View File

@ -23,10 +23,10 @@ class GenericRule extends Rule
protected $literals; protected $literals;
/** /**
* @param array $literals * @param array $literals
* @param int $reason A RULE_* constant describing the reason for generating this rule * @param int|null $reason A RULE_* constant describing the reason for generating this rule
* @param Link|PackageInterface $reasonData * @param Link|PackageInterface|int|null $reasonData
* @param array $job The job this rule was created from * @param array $job The job this rule was created from
*/ */
public function __construct(array $literals, $reason, $reasonData, $job = null) public function __construct(array $literals, $reason, $reasonData, $job = null)
{ {
@ -75,7 +75,7 @@ class GenericRule extends Rule
*/ */
public function __toString() public function __toString()
{ {
$result = ($this->isDisabled()) ? 'disabled(' : '('; $result = $this->isDisabled() ? 'disabled(' : '(';
foreach ($this->literals as $i => $literal) { foreach ($this->literals as $i => $literal) {
if ($i != 0) { if ($i != 0) {

View File

@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\EmptyConstraint; use Composer\Semver\Constraint\EmptyConstraint;
@ -26,7 +27,7 @@ use Composer\Repository\PlatformRepository;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
/** /**
* A package pool contains repositories that provide packages. * A package pool contains all packages for dependency resolution
* *
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
@ -40,113 +41,42 @@ class Pool implements \Countable
const MATCH_REPLACE = 3; const MATCH_REPLACE = 3;
const MATCH_FILTERED = 4; const MATCH_FILTERED = 4;
protected $repositories = array();
protected $providerRepos = array(); protected $providerRepos = array();
protected $packages = array(); protected $packages = array();
protected $packageByName = array(); protected $packageByName = array();
protected $packageByExactName = array(); protected $packageByExactName = array();
protected $acceptableStabilities; protected $priorities = array();
protected $stabilityFlags;
protected $versionParser; protected $versionParser;
protected $providerCache = array(); protected $providerCache = array();
protected $filterRequires; protected $filterRequires;
protected $whitelist = null;
protected $id = 1;
public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) public function __construct(array $filterRequires = array())
{ {
$this->versionParser = new VersionParser;
$this->acceptableStabilities = array();
foreach (BasePackage::$stabilities as $stability => $value) {
if ($value <= BasePackage::$stabilities[$minimumStability]) {
$this->acceptableStabilities[$stability] = $value;
}
}
$this->stabilityFlags = $stabilityFlags;
$this->filterRequires = $filterRequires; $this->filterRequires = $filterRequires;
foreach ($filterRequires as $name => $constraint) { $this->versionParser = new VersionParser;
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) { }
unset($this->filterRequires[$name]);
public function setPackages(array $packages, array $priorities = array())
{
$id = 1;
foreach ($packages as $i => $package) {
$this->packages[] = $package;
$this->priorities[] = isset($priorities[$i]) ? $priorities[$i] : 0;
$package->id = $id++;
$names = $package->getNames();
$this->packageByExactName[$package->getName()][$package->id] = $package;
foreach ($names as $provided) {
$this->packageByName[$provided][] = $package;
} }
} }
} }
public function setWhitelist($whitelist) public function getPriority($id)
{ {
$this->whitelist = $whitelist; return $this->priorities[$id - 1];
$this->providerCache = array();
}
/**
* Adds a repository and its packages to this package pool
*
* @param RepositoryInterface $repo A package repository
* @param array $rootAliases
*/
public function addRepository(RepositoryInterface $repo, $rootAliases = array())
{
if ($repo instanceof CompositeRepository) {
$repos = $repo->getRepositories();
} else {
$repos = array($repo);
}
foreach ($repos as $repo) {
$this->repositories[] = $repo;
$exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
$this->providerRepos[] = $repo;
$repo->setRootAliases($rootAliases);
$repo->resetPackageIds();
} else {
foreach ($repo->getPackages() as $package) {
$names = $package->getNames();
$stability = $package->getStability();
if ($exempt || $this->isPackageAcceptable($names, $stability)) {
$package->setId($this->id++);
$this->packages[] = $package;
$this->packageByExactName[$package->getName()][$package->id] = $package;
foreach ($names as $provided) {
$this->packageByName[$provided][] = $package;
}
// handle root package aliases
$name = $package->getName();
if (isset($rootAliases[$name][$package->getVersion()])) {
$alias = $rootAliases[$name][$package->getVersion()];
if ($package instanceof AliasPackage) {
$package = $package->getAliasOf();
}
$aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
$aliasPackage->setRootPackageAlias(true);
$aliasPackage->setId($this->id++);
$package->getRepository()->addPackage($aliasPackage);
$this->packages[] = $aliasPackage;
$this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
foreach ($aliasPackage->getNames() as $name) {
$this->packageByName[$name][] = $aliasPackage;
}
}
}
}
}
}
}
public function getPriority(RepositoryInterface $repo)
{
$priority = array_search($repo, $this->repositories, true);
if (false === $priority) {
throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
}
return -$priority;
} }
/** /**
@ -200,45 +130,18 @@ class Pool implements \Countable
{ {
$candidates = array(); $candidates = array();
foreach ($this->providerRepos as $repo) {
foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) {
$candidates[] = $candidate;
if ($candidate->id < 1) {
$candidate->setId($this->id++);
$this->packages[$this->id - 2] = $candidate;
}
}
}
if ($mustMatchName) { if ($mustMatchName) {
$candidates = array_filter($candidates, function ($candidate) use ($name) {
return $candidate->getName() == $name;
});
if (isset($this->packageByExactName[$name])) { if (isset($this->packageByExactName[$name])) {
$candidates = array_merge($candidates, $this->packageByExactName[$name]); $candidates = $this->packageByExactName[$name];
} }
} elseif (isset($this->packageByName[$name])) { } elseif (isset($this->packageByName[$name])) {
$candidates = array_merge($candidates, $this->packageByName[$name]); $candidates = $this->packageByName[$name];
} }
$matches = $provideMatches = array(); $matches = $provideMatches = array();
$nameMatch = false; $nameMatch = false;
foreach ($candidates as $candidate) { foreach ($candidates as $candidate) {
$aliasOfCandidate = null;
// alias packages are not white listed, make sure that the package
// being aliased is white listed
if ($candidate instanceof AliasPackage) {
$aliasOfCandidate = $candidate->getAliasOf();
}
if ($this->whitelist !== null && !$bypassFilters && (
(!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
)) {
continue;
}
switch ($this->match($candidate, $name, $constraint, $bypassFilters)) { switch ($this->match($candidate, $name, $constraint, $bypassFilters)) {
case self::MATCH_NONE: case self::MATCH_NONE:
break; break;
@ -296,33 +199,16 @@ class Pool implements \Countable
return $prefix.' '.$package->getPrettyString(); return $prefix.' '.$package->getPrettyString();
} }
public function isPackageAcceptable($name, $stability)
{
foreach ((array) $name as $n) {
// allow if package matches the global stability requirement and has no exception
if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
return true;
}
// allow if package matches the package-specific stability flag
if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
return true;
}
}
return false;
}
/** /**
* Checks if the package matches the given constraint directly or through * Checks if the package matches the given constraint directly or through
* provided or replaced packages * provided or replaced packages
* *
* @param array|PackageInterface $candidate * @param PackageInterface $candidate
* @param string $name Name of the package to be matched * @param string $name Name of the package to be matched
* @param ConstraintInterface $constraint The constraint to verify * @param ConstraintInterface $constraint The constraint to verify
* @return int One of the MATCH* constants of this class or 0 if there is no match * @return int One of the MATCH* constants of this class or 0 if there is no match
*/ */
private function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters)
{ {
$candidateName = $candidate->getName(); $candidateName = $candidate->getName();
$candidateVersion = $candidate->getVersion(); $candidateVersion = $candidate->getVersion();

View File

@ -0,0 +1,194 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\DependencyResolver;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Repository\AsyncRepositoryInterface;
use Composer\Repository\ComposerRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\LockArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MultiConstraint;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
class PoolBuilder
{
private $isPackageAcceptableCallable;
private $filterRequires;
private $rootAliases;
private $aliasMap = array();
private $nameConstraints = array();
private $loadedNames = array();
private $packages = array();
private $priorities = array();
public function __construct($isPackageAcceptableCallable, array $filterRequires = array())
{
$this->isPackageAcceptableCallable = $isPackageAcceptableCallable;
$this->filterRequires = $filterRequires;
}
public function buildPool(array $repositories, array $rootAliases, Request $request)
{
$pool = new Pool($this->filterRequires);
$this->rootAliases = $rootAliases;
// TODO do we really want the request here? kind of want a root requirements thingy instead
$loadNames = array();
foreach ($request->getJobs() as $job) {
switch ($job['cmd']) {
case 'install':
$loadNames[$job['packageName']] = $job['constraint'];
$this->nameConstraints[$job['packageName']] = $job['constraint'] ? new MultiConstraint(array($job['constraint']), false) : null;
break;
}
}
while (!empty($loadNames)) {
$loadIds = array();
foreach ($repositories as $key => $repository) {
if ($repository instanceof AsyncRepositoryInterface) {
$loadIds[$key] = $repository->requestPackages($loadNames);
}
}
foreach ($loadNames as $name => $void) {
$this->loadedNames[$name] = true;
}
$newLoadNames = array();
foreach ($repositories as $key => $repository) {
if ($repository instanceof PlatformRepository || $repository instanceof InstalledRepositoryInterface) {
continue;
}
if ($repository instanceof AsyncRepositoryInterface) {
// TODO ispackageacceptablecallable in here?
$packages = $repository->returnPackages($loadIds[$key]);
} else {
// TODO should we really pass the callable into here?
$packages = $repository->loadPackages($loadNames, $this->isPackageAcceptableCallable);
}
foreach ($packages as $package) {
if (call_user_func($this->isPackageAcceptableCallable, $package->getNames(), $package->getStability())) {
$newLoadNames += $this->loadPackage($package, $key);
}
}
}
$loadNames = $newLoadNames;
}
foreach ($this->packages as $i => $package) {
// we check all alias related packages at once, so no need ot check individual aliases
// isset also checks non-null value
if (!$package instanceof AliasPackage && isset($this->nameConstraints[$package->getName()])) {
$constraint = $this->nameConstraints[$package->getName()];
$aliasedPackages = array($i => $package);
if (isset($this->aliasMap[spl_object_hash($package)])) {
$aliasedPackages += $this->aliasMap[spl_object_hash($package)];
}
$found = false;
foreach ($aliasedPackages as $packageOrAlias) {
if ($constraint->matches(new Constraint('==', $packageOrAlias->getVersion()))) {
$found = true;
}
}
if (!$found) {
foreach ($aliasedPackages as $index => $packageOrAlias) {
unset($this->packages[$index]);
unset($this->priorities[$index]);
}
}
}
}
foreach ($repositories as $key => $repository) {
if ($repository instanceof PlatformRepository ||
$repository instanceof InstalledRepositoryInterface) {
foreach ($repository->getPackages() as $package) {
$this->loadPackage($package, $key);
}
}
}
$pool->setPackages($this->packages, $this->priorities);
unset($this->aliasMap);
unset($this->loadedNames);
unset($this->nameConstraints);
return $pool;
}
private function loadPackage(PackageInterface $package, $repoIndex)
{
$index = count($this->packages);
$this->packages[] = $package;
$this->priorities[] = -$repoIndex;
if ($package instanceof AliasPackage) {
$this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package;
}
// handle root package aliases
$name = $package->getName();
if (isset($this->rootAliases[$name][$package->getVersion()])) {
$alias = $this->rootAliases[$name][$package->getVersion()];
if ($package instanceof AliasPackage) {
$basePackage = $package->getAliasOf();
} else {
$basePackage = $package;
}
$aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']);
$aliasPackage->setRootPackageAlias(true);
$package->getRepository()->addPackage($aliasPackage); // TODO do we need this?
$this->packages[] = $aliasPackage;
$this->priorities[] = -$repoIndex;
$this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$index+1] = $aliasPackage;
}
$loadNames = array();
foreach ($package->getRequires() as $link) {
$require = $link->getTarget();
if (!isset($this->loadedNames[$require])) {
$loadNames[$require] = null;
}
if ($linkConstraint = $link->getConstraint()) {
if (!array_key_exists($require, $this->nameConstraints)) {
$this->nameConstraints[$require] = new MultiConstraint(array($linkConstraint), false);
} elseif ($this->nameConstraints[$require]) {
// TODO addConstraint function?
$this->nameConstraints[$require] = new MultiConstraint(array_merge(array($linkConstraint), $this->nameConstraints[$require]->getConstraints()), false);
}
} else {
$this->nameConstraints[$require] = null;
}
}
return $loadNames;
}
}

View File

@ -12,6 +12,8 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Package\CompletePackageInterface;
/** /**
* Represents a problem detected while solving dependencies * Represents a problem detected while solving dependencies
* *
@ -69,7 +71,7 @@ class Problem
* @param array $installedMap A map of all installed packages * @param array $installedMap A map of all installed packages
* @return string * @return string
*/ */
public function getPrettyString(array $installedMap = array()) public function getPrettyString(array $installedMap = array(), array $learnedPool = array())
{ {
$reasons = call_user_func_array('array_merge', array_reverse($this->reasons)); $reasons = call_user_func_array('array_merge', array_reverse($this->reasons));
@ -77,11 +79,13 @@ class Problem
reset($reasons); reset($reasons);
$reason = current($reasons); $reason = current($reasons);
$rule = $reason['rule'];
$job = $reason['job']; $job = $reason['job'];
if (isset($job['constraint'])) { $packageName = $job['packageName'];
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); $constraint = $job['constraint'];
if (isset($constraint)) {
$packages = $this->pool->whatProvides($packageName, $constraint);
} else { } else {
$packages = array(); $packages = array();
} }
@ -89,17 +93,26 @@ class Problem
if ($job && $job['cmd'] === 'install' && empty($packages)) { if ($job && $job['cmd'] === 'install' && empty($packages)) {
// handle php/hhvm // handle php/hhvm
if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
$available = $this->pool->whatProvides($job['packageName']); $version = phpversion();
$version = count($available) ? $available[0]->getPrettyVersion() : phpversion(); $available = $this->pool->whatProvides($packageName);
$msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; if (count($available)) {
$firstAvailable = reset($available);
$version = $firstAvailable->getPrettyVersion();
$extra = $firstAvailable->getExtra();
if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
$version .= '; ' . $firstAvailable->getDescription();
}
}
if (defined('HHVM_VERSION')) { $msg = "\n - This package requires ".$packageName.$this->constraintToText($constraint).' but ';
if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
return $msg . 'your HHVM version does not satisfy that requirement.'; return $msg . 'your HHVM version does not satisfy that requirement.';
} }
if ($job['packageName'] === 'hhvm') { if ($packageName === 'hhvm') {
return $msg . 'you are running this with PHP and not HHVM.'; return $msg . 'you are running this with PHP and not HHVM.';
} }
@ -107,39 +120,43 @@ class Problem
} }
// handle php extensions // handle php extensions
if (0 === stripos($job['packageName'], 'ext-')) { if (0 === stripos($packageName, 'ext-')) {
$ext = substr($job['packageName'], 4); if (false !== strpos($packageName, ' ')) {
return "\n - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
}
$ext = substr($packageName, 4);
$error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; return "\n - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
} }
// handle linked libs // handle linked libs
if (0 === stripos($job['packageName'], 'lib-')) { if (0 === stripos($packageName, 'lib-')) {
if (strtolower($job['packageName']) === 'lib-icu') { if (strtolower($packageName) === 'lib-icu') {
$error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.'; $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';
return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error; return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
} }
return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
} }
if (!preg_match('{^[A-Za-z0-9_./-]+$}', $job['packageName'])) { if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
$illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $job['packageName']); $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
return "\n - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; return "\n - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
} }
if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) { if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) {
return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
} }
if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) { if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
} }
return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; return "\n - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
} }
} }
@ -153,7 +170,7 @@ class Problem
$messages[] = $this->jobToText($job); $messages[] = $this->jobToText($job);
} elseif ($rule) { } elseif ($rule) {
if ($rule instanceof Rule) { if ($rule instanceof Rule) {
$messages[] = $rule->getPrettyString($this->pool, $installedMap); $messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool);
} }
} }
} }
@ -165,7 +182,7 @@ class Problem
* Store a reason descriptor but ignore duplicates * Store a reason descriptor but ignore duplicates
* *
* @param string $id A canonical identifier for the reason * @param string $id A canonical identifier for the reason
* @param string $reason The reason descriptor * @param string|array $reason The reason descriptor
*/ */
protected function addReason($id, $reason) protected function addReason($id, $reason)
{ {
@ -188,27 +205,29 @@ class Problem
*/ */
protected function jobToText($job) protected function jobToText($job)
{ {
$packageName = $job['packageName'];
$constraint = $job['constraint'];
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); $packages = $this->pool->whatProvides($packageName, $constraint);
if (!$packages) { if (!$packages) {
return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']); return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
} }
return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.'; return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.';
case 'update': case 'update':
return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.'; return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
case 'remove': case 'remove':
return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).''; return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
} }
if (isset($job['constraint'])) { if (isset($constraint)) {
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); $packages = $this->pool->whatProvides($packageName, $constraint);
} else { } else {
$packages = array(); $packages = array();
} }
return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])'; return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])';
} }
protected function getPackageList($packages) protected function getPackageList($packages)
@ -233,6 +252,6 @@ class Problem
*/ */
protected function constraintToText($constraint) protected function constraintToText($constraint)
{ {
return ($constraint) ? ' '.$constraint->getPrettyString() : ''; return $constraint ? ' '.$constraint->getPrettyString() : '';
} }
} }

View File

@ -41,6 +41,7 @@ abstract class Rule
const BITFIELD_DISABLED = 16; const BITFIELD_DISABLED = 16;
protected $bitfield; protected $bitfield;
protected $job;
protected $reasonData; protected $reasonData;
/** /**
@ -67,7 +68,7 @@ abstract class Rule
public function getJob() public function getJob()
{ {
return isset($this->job) ? $this->job : null; return $this->job;
} }
abstract public function equals(Rule $rule); abstract public function equals(Rule $rule);
@ -110,7 +111,7 @@ abstract class Rule
public function enable() public function enable()
{ {
$this->bitfield = $this->bitfield & ~(255 << self::BITFIELD_DISABLED); $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
} }
public function isDisabled() public function isDisabled()
@ -125,7 +126,7 @@ abstract class Rule
abstract public function isAssertion(); abstract public function isAssertion();
public function getPrettyString(Pool $pool, array $installedMap = array()) public function getPrettyString(Pool $pool, array $installedMap = array(), array $learnedPool = array())
{ {
$literals = $this->getLiterals(); $literals = $this->getLiterals();
@ -174,13 +175,18 @@ abstract class Rule
return $text . ' -> your HHVM version does not satisfy that requirement.'; return $text . ' -> your HHVM version does not satisfy that requirement.';
} }
if ($targetName === 'hhvm') {
return $text . ' -> you are running this with PHP and not HHVM.';
}
$packages = $pool->whatProvides($targetName); $packages = $pool->whatProvides($targetName);
$package = count($packages) ? current($packages) : phpversion(); $package = count($packages) ? current($packages) : phpversion();
if ($targetName === 'hhvm') {
if ($package instanceof CompletePackage) {
return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
} else {
return $text . ' -> you are running this with PHP and not HHVM.';
}
}
if (!($package instanceof CompletePackage)) { if (!($package instanceof CompletePackage)) {
return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.'; return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
} }
@ -229,7 +235,18 @@ abstract class Rule
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
return $ruleText; return $ruleText;
case self::RULE_LEARNED: case self::RULE_LEARNED:
return 'Conclusion: '.$ruleText; // TODO not sure this is a good idea, most of these rules should be listed in the problem anyway
$learnedString = '(learned rule, ';
if (isset($learnedPool[$this->reasonData])) {
foreach ($learnedPool[$this->reasonData] as $learnedRule) {
$learnedString .= $learnedRule->getPrettyString($pool, $installedMap, $learnedPool);
}
} else {
$learnedString .= 'reasoning unavailable';
}
$learnedString .= ')';
return 'Conclusion: '.$ruleText.' '.$learnedString;
case self::RULE_PACKAGE_ALIAS: case self::RULE_PACKAGE_ALIAS:
return $ruleText; return $ruleText;
default: default:

View File

@ -50,9 +50,7 @@ class Rule2Literals extends Rule
public function getHash() public function getHash()
{ {
$data = unpack('ihash', md5($this->literal1.','.$this->literal2, true)); return $this->literal1.','.$this->literal2;
return $data['hash'];
} }
/** /**
@ -65,6 +63,19 @@ class Rule2Literals extends Rule
*/ */
public function equals(Rule $rule) public function equals(Rule $rule)
{ {
// specialized fast-case
if ($rule instanceof self) {
if ($this->literal1 !== $rule->literal1) {
return false;
}
if ($this->literal2 !== $rule->literal2) {
return false;
}
return true;
}
$literals = $rule->getLiterals(); $literals = $rule->getLiterals();
if (2 != count($literals)) { if (2 != count($literals)) {
return false; return false;
@ -93,7 +104,7 @@ class Rule2Literals extends Rule
*/ */
public function __toString() public function __toString()
{ {
$result = ($this->isDisabled()) ? 'disabled(' : '('; $result = $this->isDisabled() ? 'disabled(' : '(';
$result .= $this->literal1 . '|' . $this->literal2 . ')'; $result .= $this->literal1 . '|' . $this->literal2 . ')';

View File

@ -26,8 +26,10 @@ class RuleSetGenerator
protected $rules; protected $rules;
protected $jobs; protected $jobs;
protected $installedMap; protected $installedMap;
protected $whitelistedMap;
protected $addedMap; protected $addedMap;
protected $conflictAddedMap;
protected $addedPackages;
protected $addedPackagesByNames;
public function __construct(PolicyInterface $policy, Pool $pool) public function __construct(PolicyInterface $policy, Pool $pool)
{ {
@ -47,7 +49,7 @@ class RuleSetGenerator
* reason for generating this rule * reason for generating this rule
* @param mixed $reasonData Any data, e.g. the requirement name, * @param mixed $reasonData Any data, e.g. the requirement name,
* that goes with the reason * that goes with the reason
* @return Rule The generated rule or null if tautological * @return Rule|null The generated rule or null if tautological
*/ */
protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
{ {
@ -114,7 +116,7 @@ class RuleSetGenerator
* reason for generating this rule * reason for generating this rule
* @param mixed $reasonData Any data, e.g. the package name, that * @param mixed $reasonData Any data, e.g. the package name, that
* goes with the reason * goes with the reason
* @return Rule The generated rule * @return Rule|null The generated rule
*/ */
protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
{ {
@ -144,47 +146,13 @@ class RuleSetGenerator
$this->rules->add($newRule, $type); $this->rules->add($newRule, $type);
} }
protected function whitelistFromPackage(PackageInterface $package)
{
$workQueue = new \SplQueue;
$workQueue->enqueue($package);
while (!$workQueue->isEmpty()) {
$package = $workQueue->dequeue();
if (isset($this->whitelistedMap[$package->id])) {
continue;
}
$this->whitelistedMap[$package->id] = true;
foreach ($package->getRequires() as $link) {
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);
foreach ($possibleRequires as $require) {
$workQueue->enqueue($require);
}
}
$obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
$workQueue->enqueue($provider);
}
}
}
}
protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs) protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs)
{ {
$workQueue = new \SplQueue; $workQueue = new \SplQueue;
$workQueue->enqueue($package); $workQueue->enqueue($package);
while (!$workQueue->isEmpty()) { while (!$workQueue->isEmpty()) {
/** @var PackageInterface $package */
$package = $workQueue->dequeue(); $package = $workQueue->dequeue();
if (isset($this->addedMap[$package->id])) { if (isset($this->addedMap[$package->id])) {
continue; continue;
@ -192,6 +160,11 @@ class RuleSetGenerator
$this->addedMap[$package->id] = true; $this->addedMap[$package->id] = true;
$this->addedPackages[] = $package;
foreach ($package->getNames() as $name) {
$this->addedPackagesByNames[$name][] = $package;
}
foreach ($package->getRequires() as $link) { foreach ($package->getRequires() as $link) {
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
continue; continue;
@ -199,40 +172,15 @@ class RuleSetGenerator
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));
foreach ($possibleRequires as $require) { foreach ($possibleRequires as $require) {
$workQueue->enqueue($require); $workQueue->enqueue($require);
} }
} }
foreach ($package->getConflicts() as $link) { $packageName = $package->getName();
$possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $obsoleteProviders = $this->pool->whatProvides($packageName, null);
foreach ($possibleConflicts as $conflict) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
}
}
// check obsoletes and implicit obsoletes of a package
$isInstalled = (isset($this->installedMap[$package->id]));
foreach ($package->getReplaces() as $link) {
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($obsoleteProviders as $provider) {
if ($provider === $package) {
continue;
}
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
}
}
}
$obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
foreach ($obsoleteProviders as $provider) { foreach ($obsoleteProviders as $provider) {
if ($provider === $package) { if ($provider === $package) {
@ -240,10 +188,57 @@ class RuleSetGenerator
} }
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) { if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) { } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES; $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRule2Literals($package, $provider, $reason, $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
}
}
}
}
protected function addConflictRules($ignorePlatformReqs = false)
{
/** @var PackageInterface $package */
foreach ($this->addedPackages as $package) {
foreach ($package->getConflicts() as $link) {
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
continue;
}
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
continue;
}
/** @var PackageInterface $possibleConflict */
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
$conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
}
}
}
// check obsoletes and implicit obsoletes of a package
$isInstalled = isset($this->installedMap[$package->id]);
foreach ($package->getReplaces() as $link) {
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
continue;
}
/** @var PackageInterface $possibleConflict */
foreach ($this->addedPackagesByNames[$link->getTarget()] as $provider) {
if ($provider === $package) {
continue;
}
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
}
} }
} }
} }
@ -263,20 +258,6 @@ class RuleSetGenerator
return $impossible; return $impossible;
} }
protected function whitelistFromJobs()
{
foreach ($this->jobs as $job) {
switch ($job['cmd']) {
case 'install':
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
foreach ($packages as $package) {
$this->whitelistFromPackage($package);
}
break;
}
}
}
protected function addRulesForJobs($ignorePlatformReqs) protected function addRulesForJobs($ignorePlatformReqs)
{ {
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
@ -317,21 +298,21 @@ class RuleSetGenerator
$this->rules = new RuleSet; $this->rules = new RuleSet;
$this->installedMap = $installedMap; $this->installedMap = $installedMap;
$this->whitelistedMap = array();
foreach ($this->installedMap as $package) {
$this->whitelistFromPackage($package);
}
$this->whitelistFromJobs();
$this->pool->setWhitelist($this->whitelistedMap);
$this->addedMap = array(); $this->addedMap = array();
$this->conflictAddedMap = array();
$this->addedPackages = array();
$this->addedPackagesByNames = array();
foreach ($this->installedMap as $package) { foreach ($this->installedMap as $package) {
$this->addRulesForPackage($package, $ignorePlatformReqs); $this->addRulesForPackage($package, $ignorePlatformReqs);
} }
$this->addRulesForJobs($ignorePlatformReqs); $this->addRulesForJobs($ignorePlatformReqs);
$this->addConflictRules($ignorePlatformReqs);
// Remove references to packages
$this->addedPackages = $this->addedPackagesByNames = null;
return $this->rules; return $this->rules;
} }
} }

View File

@ -127,9 +127,9 @@ class RuleWatchGraph
* *
* The rule node's watched literals are updated accordingly. * The rule node's watched literals are updated accordingly.
* *
* @param $fromLiteral mixed A literal the node used to watch * @param int $fromLiteral A literal the node used to watch
* @param $toLiteral mixed A literal the node should watch now * @param int $toLiteral A literal the node should watch now
* @param $node mixed The rule node to be moved * @param RuleWatchNode $node The rule node to be moved
*/ */
protected function moveWatch($fromLiteral, $toLiteral, $node) protected function moveWatch($fromLiteral, $toLiteral, $node)
{ {

View File

@ -37,8 +37,9 @@ class RuleWatchNode
$literals = $rule->getLiterals(); $literals = $rule->getLiterals();
$this->watch1 = count($literals) > 0 ? $literals[0] : 0; $literalCount = count($literals);
$this->watch2 = count($literals) > 1 ? $literals[1] : 0; $this->watch1 = $literalCount > 0 ? $literals[0] : 0;
$this->watch2 = $literalCount > 1 ? $literals[1] : 0;
} }
/** /**

View File

@ -13,8 +13,10 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositorySet;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -27,10 +29,10 @@ class Solver
/** @var PolicyInterface */ /** @var PolicyInterface */
protected $policy; protected $policy;
/** @var Pool */ /** @var Pool */
protected $pool; protected $pool = null;
/** @var RepositoryInterface */ /** @var RepositoryInterface */
protected $installed; protected $installed;
/** @var Ruleset */ /** @var RuleSet */
protected $rules; protected $rules;
/** @var RuleSetGenerator */ /** @var RuleSetGenerator */
protected $ruleSetGenerator; protected $ruleSetGenerator;
@ -43,7 +45,7 @@ class Solver
protected $watchGraph; protected $watchGraph;
/** @var Decisions */ /** @var Decisions */
protected $decisions; protected $decisions;
/** @var int[] */ /** @var PackageInterface[] */
protected $installedMap; protected $installedMap;
/** @var int */ /** @var int */
@ -57,6 +59,9 @@ class Solver
/** @var array */ /** @var array */
protected $learnedWhy = array(); protected $learnedWhy = array();
/** @var bool */
public $testFlagLearnedPositiveLiteral = false;
/** @var IOInterface */ /** @var IOInterface */
protected $io; protected $io;
@ -72,7 +77,6 @@ class Solver
$this->policy = $policy; $this->policy = $policy;
$this->pool = $pool; $this->pool = $pool;
$this->installed = $installed; $this->installed = $installed;
$this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
} }
/** /**
@ -83,6 +87,11 @@ class Solver
return count($this->rules); return count($this->rules);
} }
public function getPool()
{
return $this->pool;
}
// aka solver_makeruledecisions // aka solver_makeruledecisions
private function makeAssertionRuleDecisions() private function makeAssertionRuleDecisions()
@ -100,7 +109,7 @@ class Solver
$literals = $rule->getLiterals(); $literals = $rule->getLiterals();
$literal = $literals[0]; $literal = $literals[0];
if (!$this->decisions->decided(abs($literal))) { if (!$this->decisions->decided($literal)) {
$this->decisions->decide($literal, 1, $rule); $this->decisions->decide($literal, 1, $rule);
continue; continue;
} }
@ -211,6 +220,9 @@ class Solver
$this->jobs = $request->getJobs(); $this->jobs = $request->getJobs();
$this->setupInstalledMap(); $this->setupInstalledMap();
$this->io->writeError('Generating rules', true, IOInterface::DEBUG);
$this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
$this->checkForRootRequireProblems($ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs);
$this->decisions = new Decisions($this->pool); $this->decisions = new Decisions($this->pool);
@ -226,6 +238,7 @@ class Solver
$this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
$before = microtime(true); $before = microtime(true);
$this->runSat(true); $this->runSat(true);
$this->io->writeError('', true, IOInterface::DEBUG);
$this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
// decide to remove everything that's installed and undecided // decide to remove everything that's installed and undecided
@ -236,7 +249,7 @@ class Solver
} }
if ($this->problems) { if ($this->problems) {
throw new SolverProblemsException($this->problems, $this->installedMap); throw new SolverProblemsException($this->problems, $this->installedMap, $this->learnedPool);
} }
$transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions); $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);
@ -469,7 +482,10 @@ class Solver
unset($seen[abs($literal)]); unset($seen[abs($literal)]);
if ($num && 0 === --$num) { if ($num && 0 === --$num) {
$learnedLiterals[0] = -abs($literal); if ($literal < 0) {
$this->testFlagLearnedPositiveLiteral = true;
}
$learnedLiterals[0] = -$literal;
if (!$l1num) { if (!$l1num) {
break 2; break 2;
@ -509,9 +525,8 @@ class Solver
*/ */
private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule) private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule)
{ {
$why = spl_object_hash($conflictRule);
if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) { if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
$why = spl_object_hash($conflictRule);
$learnedWhy = $this->learnedWhy[$why]; $learnedWhy = $this->learnedWhy[$why];
$problemRules = $this->learnedPool[$learnedWhy]; $problemRules = $this->learnedPool[$learnedWhy];
@ -677,7 +692,7 @@ class Solver
/** /**
* @todo this makes $disableRules always false; determine the rationale and possibly remove dead code? * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
*/ */
$disableRules = array(); $disableRules = false;
$level = 1; $level = 1;
$systemLevel = $level + 1; $systemLevel = $level + 1;
@ -759,10 +774,19 @@ class Solver
} }
$rulesCount = count($this->rules); $rulesCount = count($this->rules);
$pass = 1;
$this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
if ($i == $rulesCount) { if ($i == $rulesCount) {
if (1 === $pass) {
$this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
} else {
$this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
}
$i = 0; $i = 0;
$pass++;
} }
$rule = $this->rules->ruleById[$i]; $rule = $this->rules->ruleById[$i];
@ -782,14 +806,14 @@ class Solver
// //
foreach ($literals as $literal) { foreach ($literals as $literal) {
if ($literal <= 0) { if ($literal <= 0) {
if (!$this->decisions->decidedInstall(abs($literal))) { if (!$this->decisions->decidedInstall($literal)) {
continue 2; // next rule continue 2; // next rule
} }
} else { } else {
if ($this->decisions->decidedInstall(abs($literal))) { if ($this->decisions->decidedInstall($literal)) {
continue 2; // next rule continue 2; // next rule
} }
if ($this->decisions->undecided(abs($literal))) { if ($this->decisions->undecided($literal)) {
$decisionQueue[] = $literal; $decisionQueue[] = $literal;
} }
} }

View File

@ -21,6 +21,7 @@ class SolverBugException extends \RuntimeException
{ {
parent::__construct( parent::__construct(
$message."\nThis exception was most likely caused by a bug in Composer.\n". $message."\nThis exception was most likely caused by a bug in Composer.\n".
"Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"); "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"
);
} }
} }

View File

@ -21,11 +21,13 @@ class SolverProblemsException extends \RuntimeException
{ {
protected $problems; protected $problems;
protected $installedMap; protected $installedMap;
protected $learnedPool;
public function __construct(array $problems, array $installedMap) public function __construct(array $problems, array $installedMap, array $learnedPool)
{ {
$this->problems = $problems; $this->problems = $problems;
$this->installedMap = $installedMap; $this->installedMap = $installedMap;
$this->learnedPool = $learnedPool;
parent::__construct($this->createMessage(), 2); parent::__construct($this->createMessage(), 2);
} }
@ -35,7 +37,7 @@ class SolverProblemsException extends \RuntimeException
$text = "\n"; $text = "\n";
$hasExtensionProblems = false; $hasExtensionProblems = false;
foreach ($this->problems as $i => $problem) { foreach ($this->problems as $i => $problem) {
$text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n"; $text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap, $this->learnedPool)."\n";
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) { if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
$hasExtensionProblems = true; $hasExtensionProblems = true;

View File

@ -49,7 +49,7 @@ class Transaction
$package = $this->pool->literalToPackage($literal); $package = $this->pool->literalToPackage($literal);
// wanted & installed || !wanted & !installed // wanted & installed || !wanted & !installed
if (($literal > 0) == (isset($this->installedMap[$package->id]))) { if (($literal > 0) == isset($this->installedMap[$package->id])) {
continue; continue;
} }

View File

@ -27,32 +27,53 @@ abstract class ArchiveDownloader extends FileDownloader
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/ */
public function download(PackageInterface $package, $path, $output = true) public function install(PackageInterface $package, $path, $output = true)
{ {
if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): Extracting archive");
} else {
$this->io->writeError('Extracting archive', false);
}
$this->filesystem->emptyDirectory($path);
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
$retries = 3; $fileName = $this->getFileName($package, $path);
while ($retries--) {
$fileName = parent::download($package, $path, $output);
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
try {
$this->filesystem->ensureDirectoryExists($temporaryDir);
try { try {
$this->filesystem->ensureDirectoryExists($temporaryDir); $this->extract($package, $fileName, $temporaryDir);
try { } catch (\Exception $e) {
$this->extract($fileName, $temporaryDir); // remove cache if the file was corrupted
} catch (\Exception $e) { parent::clearLastCacheWrite($package);
// remove cache if the file was corrupted throw $e;
parent::clearLastCacheWrite($package); }
throw $e;
$this->filesystem->unlink($fileName);
$renameAsOne = false;
if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
$renameAsOne = true;
}
$contentDir = $this->getFolderContent($temporaryDir);
$singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir));
if ($renameAsOne) {
// if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
if ($singleDirAtTopLevel) {
$extractedDir = (string) reset($contentDir);
} else {
$extractedDir = $temporaryDir;
} }
$this->filesystem->rename($extractedDir, $path);
$this->filesystem->unlink($fileName); } else {
$contentDir = $this->getFolderContent($temporaryDir);
// only one dir in the archive, extract its contents out of it // only one dir in the archive, extract its contents out of it
if (1 === count($contentDir) && is_dir(reset($contentDir))) { if ($singleDirAtTopLevel) {
$contentDir = $this->getFolderContent((string) reset($contentDir)); $contentDir = $this->getFolderContent((string) reset($contentDir));
} }
@ -61,35 +82,24 @@ abstract class ArchiveDownloader extends FileDownloader
$file = (string) $file; $file = (string) $file;
$this->filesystem->rename($file, $path . '/' . basename($file)); $this->filesystem->rename($file, $path . '/' . basename($file));
} }
$this->filesystem->removeDirectory($temporaryDir);
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
}
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
}
} catch (\Exception $e) {
// clean up
$this->filesystem->removeDirectory($path);
$this->filesystem->removeDirectory($temporaryDir);
// retry downloading if we have an invalid zip file
if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
$this->io->writeError('');
if ($this->io->isDebug()) {
$this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...');
} else {
$this->io->writeError(' Invalid zip file, retrying...');
}
usleep(500000);
continue;
}
throw $e;
} }
break; $this->filesystem->removeDirectory($temporaryDir);
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
}
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
}
} catch (\Exception $e) {
// clean up
$this->filesystem->removeDirectory($path);
$this->filesystem->removeDirectory($temporaryDir);
if (file_exists($fileName)) {
$this->filesystem->unlink($fileName);
}
throw $e;
} }
} }
@ -98,33 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader
*/ */
protected function getFileName(PackageInterface $package, $path) protected function getFileName(PackageInterface $package, $path)
{ {
return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); return rtrim($path.'_'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
}
/**
* {@inheritdoc}
*/
protected function processUrl(PackageInterface $package, $url)
{
if ($package->getDistReference() && strpos($url, 'github.com')) {
if (preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) {
// update legacy github archives to API calls with the proper reference
$url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference();
} elseif ($package->getDistReference() && preg_match('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) {
// update current github web archives to API calls with the proper reference
$url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference();
} elseif ($package->getDistReference() && preg_match('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) {
// update api archives to the proper reference
$url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $package->getDistReference();
}
} elseif ($package->getDistReference() && strpos($url, 'bitbucket.org')) {
if (preg_match('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) {
// update Bitbucket archives to the proper reference
$url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $package->getDistReference() . '.' . $match[4];
}
}
return parent::processUrl($package, $url);
} }
/** /**
@ -135,7 +119,7 @@ abstract class ArchiveDownloader extends FileDownloader
* *
* @throws \UnexpectedValueException If can not extract downloaded file to path * @throws \UnexpectedValueException If can not extract downloaded file to path
*/ */
abstract protected function extract($file, $path); abstract protected function extract(PackageInterface $package, $file, $path);
/** /**
* Returns the folder content, excluding dotfiles * Returns the folder content, excluding dotfiles

View File

@ -15,6 +15,7 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
/** /**
* Downloaders manager. * Downloaders manager.
@ -24,6 +25,7 @@ use Composer\Util\Filesystem;
class DownloadManager class DownloadManager
{ {
private $io; private $io;
private $httpDownloader;
private $preferDist = false; private $preferDist = false;
private $preferSource = false; private $preferSource = false;
private $packagePreferences = array(); private $packagePreferences = array();
@ -33,9 +35,9 @@ class DownloadManager
/** /**
* Initializes download manager. * Initializes download manager.
* *
* @param IOInterface $io The Input Output Interface * @param IOInterface $io The Input Output Interface
* @param bool $preferSource prefer downloading from source * @param bool $preferSource prefer downloading from source
* @param Filesystem|null $filesystem custom Filesystem object * @param Filesystem|null $filesystem custom Filesystem object
*/ */
public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null) public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
{ {
@ -83,22 +85,6 @@ class DownloadManager
return $this; return $this;
} }
/**
* Sets whether to output download progress information for all registered
* downloaders
*
* @param bool $outputProgress
* @return DownloadManager
*/
public function setOutputProgress($outputProgress)
{
foreach ($this->downloaders as $downloader) {
$downloader->setOutputProgress($outputProgress);
}
return $this;
}
/** /**
* Sets installer downloader for a specific installation type. * Sets installer downloader for a specific installation type.
* *
@ -140,7 +126,7 @@ class DownloadManager
* wrong type * wrong type
* @return DownloaderInterface|null * @return DownloaderInterface|null
*/ */
public function getDownloaderForInstalledPackage(PackageInterface $package) public function getDownloaderForPackage(PackageInterface $package)
{ {
$installationSource = $package->getInstallationSource(); $installationSource = $package->getInstallationSource();
@ -154,77 +140,112 @@ class DownloadManager
$downloader = $this->getDownloader($package->getSourceType()); $downloader = $this->getDownloader($package->getSourceType());
} else { } else {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Package '.$package.' seems not been installed properly' 'Package '.$package.' does not have an installation source set'
); );
} }
if ($installationSource !== $downloader->getInstallationSource()) { if ($installationSource !== $downloader->getInstallationSource()) {
throw new \LogicException(sprintf( throw new \LogicException(sprintf(
'Downloader "%s" is a %s type downloader and can not be used to download %s', 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s',
get_class($downloader), $downloader->getInstallationSource(), $installationSource get_class($downloader),
$downloader->getInstallationSource(),
$installationSource,
$package
)); ));
} }
return $downloader; return $downloader;
} }
public function getDownloaderType(DownloaderInterface $downloader)
{
return array_search($downloader, $this->downloaders);
}
/** /**
* Downloads package into target dir. * Downloads package into target dir.
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @param string $targetDir target dir * @param string $targetDir target dir
* @param bool $preferSource prefer installation from source * @param PackageInterface $prevPackage previous package instance in case of updates
*
* @return PromiseInterface
* @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException
*/
public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
{
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
$sources = $this->getAvailableSources($package, $prevPackage);
$io = $this->io;
$self = $this;
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) {
$source = array_shift($sources);
if ($retry) {
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
}
$package->setInstallationSource($source);
$downloader = $self->getDownloaderForPackage($package);
if (!$downloader) {
return \React\Promise\resolve();
}
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
if ($e instanceof \RuntimeException) {
if (!$sources) {
throw $e;
}
$io->writeError(
' <warning>Failed to download '.
$package->getPrettyName().
' from ' . $source . ': '.
$e->getMessage().'</warning>'
);
return $download(true);
}
throw $e;
};
try {
$result = $downloader->download($package, $targetDir);
} catch (\Exception $e) {
return $handleError($e);
}
if (!$result instanceof PromiseInterface) {
return \React\Promise\resolve($result);
}
$res = $result->then(function ($res) {
return $res;
}, $handleError);
return $res;
};
return $download();
}
/**
* Installs package into target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* *
* @throws \InvalidArgumentException if package have no urls to download from * @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException * @throws \RuntimeException
*/ */
public function download(PackageInterface $package, $targetDir, $preferSource = null) public function install(PackageInterface $package, $targetDir)
{ {
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource; $downloader = $this->getDownloaderForPackage($package);
$sourceType = $package->getSourceType(); if ($downloader) {
$distType = $package->getDistType(); $downloader->install($package, $targetDir);
$sources = array();
if ($sourceType) {
$sources[] = 'source';
}
if ($distType) {
$sources[] = 'dist';
}
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
$sources = array_reverse($sources);
}
$this->filesystem->ensureDirectoryExists($targetDir);
foreach ($sources as $i => $source) {
if (isset($e)) {
$this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
}
$package->setInstallationSource($source);
try {
$downloader = $this->getDownloaderForInstalledPackage($package);
if ($downloader) {
$downloader->download($package, $targetDir);
}
break;
} catch (\RuntimeException $e) {
if ($i === count($sources) - 1) {
throw $e;
}
$this->io->writeError(
' <warning>Failed to download '.
$package->getPrettyName().
' from ' . $source . ': '.
$e->getMessage().'</warning>'
);
}
} }
} }
@ -239,31 +260,23 @@ class DownloadManager
*/ */
public function update(PackageInterface $initial, PackageInterface $target, $targetDir) public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($initial); $downloader = $this->getDownloaderForPackage($target);
$initialDownloader = $this->getDownloaderForPackage($initial);
// no downloaders present means update from metapackage to metapackage, nothing to do
if (!$initialDownloader && !$downloader) {
return;
}
// if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
if (!$downloader) { if (!$downloader) {
$initialDownloader->remove($initial, $targetDir);
return; return;
} }
$installationSource = $initial->getInstallationSource(); $initialType = $this->getDownloaderType($initialDownloader);
$targetType = $this->getDownloaderType($downloader);
if ('dist' === $installationSource) {
$initialType = $initial->getDistType();
$targetType = $target->getDistType();
} else {
$initialType = $initial->getSourceType();
$targetType = $target->getSourceType();
}
// upgrading from a dist stable package to a dev package, force source reinstall
if ($target->isDev() && 'dist' === $installationSource) {
$downloader->remove($initial, $targetDir);
$this->download($target, $targetDir);
return;
}
if ($initialType === $targetType) { if ($initialType === $targetType) {
$target->setInstallationSource($installationSource);
try { try {
$downloader->update($initial, $target, $targetDir); $downloader->update($initial, $target, $targetDir);
@ -279,8 +292,10 @@ class DownloadManager
} }
} }
$downloader->remove($initial, $targetDir); // if downloader type changed, or update failed and user asks for reinstall,
$this->download($target, $targetDir, 'source' === $installationSource); // we wipe the dir and do a new install instead of updating it
$initialDownloader->remove($initial, $targetDir);
$this->install($target, $targetDir);
} }
/** /**
@ -291,7 +306,7 @@ class DownloadManager
*/ */
public function remove(PackageInterface $package, $targetDir) public function remove(PackageInterface $package, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($package); $downloader = $this->getDownloaderForPackage($package);
if ($downloader) { if ($downloader) {
$downloader->remove($package, $targetDir); $downloader->remove($package, $targetDir);
} }
@ -319,4 +334,48 @@ class DownloadManager
return $package->isDev() ? 'source' : 'dist'; return $package->isDev() ? 'source' : 'dist';
} }
/**
* @return string[]
*/
private function getAvailableSources(PackageInterface $package, PackageInterface $prevPackage = null)
{
$sourceType = $package->getSourceType();
$distType = $package->getDistType();
// add source before dist by default
$sources = array();
if ($sourceType) {
$sources[] = 'source';
}
if ($distType) {
$sources[] = 'dist';
}
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
if (
$prevPackage
// if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
&& in_array($prevPackage->getInstallationSource(), $sources, true)
// unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
&& !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
) {
$prevSource = $prevPackage->getInstallationSource();
usort($sources, function ($a, $b) use ($prevSource) {
return $a === $prevSource ? -1 : 1;
});
return $sources;
}
// reverse sources in case dist is the preferred source for this package
if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
$sources = array_reverse($sources);
}
return $sources;
}
} }

View File

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;
/** /**
* Downloader interface. * Downloader interface.
@ -29,13 +30,20 @@ interface DownloaderInterface
*/ */
public function getInstallationSource(); public function getInstallationSource();
/**
* This should do any network-related tasks to prepare for install/update
*
* @return PromiseInterface|null
*/
public function download(PackageInterface $package, $path);
/** /**
* Downloads specific package into specific folder. * Downloads specific package into specific folder.
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @param string $path download path * @param string $path download path
*/ */
public function download(PackageInterface $package, $path); public function install(PackageInterface $package, $path);
/** /**
* Updates specific package in specific folder from initial to target version. * Updates specific package in specific folder from initial to target version.
@ -53,12 +61,4 @@ interface DownloaderInterface
* @param string $path download path * @param string $path download path
*/ */
public function remove(PackageInterface $package, $path); public function remove(PackageInterface $package, $path);
/**
* Sets whether to output download progress information or not
*
* @param bool $outputProgress
* @return DownloaderInterface
*/
public function setOutputProgress($outputProgress);
} }

View File

@ -16,12 +16,17 @@ use Composer\Config;
use Composer\Cache; use Composer\Cache;
use Composer\Factory; use Composer\Factory;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\Comparer\Comparer;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent; use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem; use Composer\Util\HttpDownloader;
use Composer\Util\Url as UrlUtil;
use Composer\Downloader\TransportException;
/** /**
* Base downloader for files * Base downloader for files
@ -31,15 +36,17 @@ use Composer\Util\RemoteFilesystem;
* @author François Pluchino <francois.pluchino@opendisplay.com> * @author François Pluchino <francois.pluchino@opendisplay.com>
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
*/ */
class FileDownloader implements DownloaderInterface class FileDownloader implements DownloaderInterface, ChangeReportInterface
{ {
protected $io; protected $io;
protected $config; protected $config;
protected $rfs; protected $httpDownloader;
protected $filesystem; protected $filesystem;
protected $cache; protected $cache;
protected $outputProgress = true; /**
private $lastCacheWrites = array(); * @private this is only public for php 5.3 support in closures
*/
public $lastCacheWrites = array();
private $eventDispatcher; private $eventDispatcher;
/** /**
@ -47,17 +54,17 @@ class FileDownloader implements DownloaderInterface
* *
* @param IOInterface $io The IO instance * @param IOInterface $io The IO instance
* @param Config $config The config * @param Config $config The config
* @param HttpDownloader $httpDownloader The remote filesystem
* @param EventDispatcher $eventDispatcher The event dispatcher * @param EventDispatcher $eventDispatcher The event dispatcher
* @param Cache $cache Optional cache instance * @param Cache $cache Cache instance
* @param RemoteFilesystem $rfs The remote filesystem
* @param Filesystem $filesystem The filesystem * @param Filesystem $filesystem The filesystem
*/ */
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null) public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $filesystem = null)
{ {
$this->io = $io; $this->io = $io;
$this->config = $config; $this->config = $config;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config); $this->httpDownloader = $httpDownloader;
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
$this->cache = $cache; $this->cache = $cache;
@ -83,120 +90,157 @@ class FileDownloader implements DownloaderInterface
throw new \InvalidArgumentException('The given package is missing url information'); throw new \InvalidArgumentException('The given package is missing url information');
} }
if ($output) { $retries = 3;
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
}
$urls = $package->getDistUrls(); $urls = $package->getDistUrls();
while ($url = array_shift($urls)) { foreach ($urls as $index => $url) {
try { $processedUrl = $this->processUrl($package, $url);
$fileName = $this->doDownload($package, $path, $url); $urls[$index] = array(
break; 'base' => $url,
} catch (\Exception $e) { 'processed' => $processedUrl,
if ($this->io->isDebug()) { 'cacheKey' => $this->getCacheKey($package, $processedUrl)
$this->io->writeError(''); );
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
} elseif (count($urls)) {
$this->io->writeError('');
$this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
}
if (!count($urls)) {
throw $e;
}
}
} }
if ($output) { $this->filesystem->ensureDirectoryExists($path);
$this->io->writeError('');
}
return $fileName;
}
protected function doDownload(PackageInterface $package, $path, $url)
{
$this->filesystem->emptyDirectory($path);
$fileName = $this->getFileName($package, $path); $fileName = $this->getFileName($package, $path);
$processedUrl = $this->processUrl($package, $url); $io = $this->io;
$hostname = parse_url($processedUrl, PHP_URL_HOST); $cache = $this->cache;
$httpDownloader = $this->httpDownloader;
$eventDispatcher = $this->eventDispatcher;
$filesystem = $this->filesystem;
$self = $this;
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); $accept = null;
if ($this->eventDispatcher) { $reject = null;
$this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) {
} $url = reset($urls);
$rfs = $preFileDownloadEvent->getRemoteFilesystem();
if ($eventDispatcher) {
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']);
$eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
}
try {
$checksum = $package->getDistSha1Checksum(); $checksum = $package->getDistSha1Checksum();
$cacheKey = $this->getCacheKey($package, $processedUrl); $cacheKey = $url['cacheKey'];
// download if we don't have it in cache or the cache is invalidated // use from cache if it is present and has a valid checksum or we have no checksum to check against
if (!$this->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) { if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) {
if (!$this->outputProgress) { if ($output) {
$this->io->writeError('Downloading', false); $io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache");
}
// try to download 3 times then fail hard
$retries = 3;
while ($retries--) {
try {
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
break;
} catch (TransportException $e) {
// if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
throw $e;
}
$this->io->writeError('');
$this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE);
usleep(500000);
}
}
if (!$this->outputProgress) {
$this->io->writeError(' (<comment>100%</comment>)', false);
}
if ($this->cache) {
$this->lastCacheWrites[$package->getName()] = $cacheKey;
$this->cache->copyFrom($cacheKey, $fileName);
} }
$result = \React\Promise\resolve($fileName);
} else { } else {
$this->io->writeError('Loading from cache', false); if ($output) {
$io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
}
$result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
->then($accept, $reject);
} }
if (!file_exists($fileName)) { return $result->then(function ($result) use ($fileName, $checksum, $url) {
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' // in case of retry, the first call's Promise chain finally calls this twice at the end,
.' directory is writable and you have internet connectivity'); // once with $result being the returned $fileName from $accept, and then once for every
// failed request with a null result, which can be skipped.
if (null === $result) {
return $fileName;
}
if (!file_exists($fileName)) {
throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
.' directory is writable and you have internet connectivity');
}
if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
}
return $fileName;
});
};
$accept = function ($response) use ($io, $cache, $package, $fileName, $path, $self, &$urls) {
$url = reset($urls);
$cacheKey = $url['cacheKey'];
if ($cache) {
$self->lastCacheWrites[$package->getName()] = $cacheKey;
$cache->copyFrom($cacheKey, $fileName);
} }
if ($checksum && hash_file('sha1', $fileName) !== $checksum) { $response->collect();
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
} return $fileName;
} catch (\Exception $e) { };
$reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
// clean up // clean up
$this->filesystem->removeDirectory($path); if (file_exists($fileName)) {
$this->clearLastCacheWrite($package); $filesystem->unlink($fileName);
throw $e; }
} $self->clearLastCacheWrite($package);
return $fileName; if ($e instanceof TransportException) {
// if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
$retries = 0;
}
}
// special error code returned when network is being artificially disabled
if ($e instanceof TransportException && $e->getStatusCode() === 499) {
$retries = 0;
$urls = array();
}
if ($retries) {
usleep(500000);
$retries--;
return $download();
}
array_shift($urls);
if ($urls) {
if ($io->isDebug()) {
$io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
$io->writeError(' Trying the next URL for '.$package->getName());
} elseif (count($urls)) {
$io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
}
$retries = 3;
usleep(100000);
return $download();
}
throw $e;
};
return $download();
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function setOutputProgress($outputProgress) public function install(PackageInterface $package, $path, $output = true)
{ {
$this->outputProgress = $outputProgress; if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
}
return $this; $this->filesystem->emptyDirectory($path);
$this->filesystem->ensureDirectoryExists($path);
$this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME));
} }
protected function clearLastCacheWrite(PackageInterface $package) /**
* TODO mark private in v3
* @protected This is public due to PHP 5.3
*/
public function clearLastCacheWrite(PackageInterface $package)
{ {
if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
$this->cache->remove($this->lastCacheWrites[$package->getName()]); $this->cache->remove($this->lastCacheWrites[$package->getName()]);
@ -210,13 +254,14 @@ class FileDownloader implements DownloaderInterface
public function update(PackageInterface $initial, PackageInterface $target, $path) public function update(PackageInterface $initial, PackageInterface $target, $path)
{ {
$name = $target->getName(); $name = $target->getName();
$from = $initial->getPrettyVersion(); $from = $initial->getFullPrettyVersion();
$to = $target->getPrettyVersion(); $to = $target->getFullPrettyVersion();
$this->io->writeError(" - Updating <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false); $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
$this->remove($initial, $path, false); $this->remove($initial, $path, false);
$this->download($target, $path, false); $this->install($target, $path, false);
$this->io->writeError(''); $this->io->writeError('');
} }
@ -243,7 +288,7 @@ class FileDownloader implements DownloaderInterface
*/ */
protected function getFileName(PackageInterface $package, $path) protected function getFileName(PackageInterface $package, $path)
{ {
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); return $path.'_'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
} }
/** /**
@ -260,6 +305,10 @@ class FileDownloader implements DownloaderInterface
throw new \RuntimeException('You must enable the openssl extension to download files via https'); throw new \RuntimeException('You must enable the openssl extension to download files via https');
} }
if ($package->getDistReference()) {
$url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference());
}
return $url; return $url;
} }
@ -273,4 +322,39 @@ class FileDownloader implements DownloaderInterface
return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); return $package->getName().'/'.$cacheKey.'.'.$package->getDistType();
} }
/**
* {@inheritDoc}
* @throws \RuntimeException
*/
public function getLocalChanges(PackageInterface $package, $targetDir)
{
$prevIO = $this->io;
$this->io = new NullIO;
$this->io->loadConfiguration($this->config);
$e = null;
try {
$res = $this->download($package, $targetDir.'_compare', false);
$this->httpDownloader->wait();
$res = $this->install($package, $targetDir.'_compare', false);
$comparer = new Comparer();
$comparer->setSource($targetDir.'_compare');
$comparer->setUpdate($targetDir);
$comparer->doCompare();
$output = $comparer->getChanged(true, true);
$this->filesystem->removeDirectory($targetDir.'_compare');
} catch (\Exception $e) {
}
$this->io = $prevIO;
if ($e) {
throw $e;
}
return trim($output);
}
} }

View File

@ -23,7 +23,7 @@ class FossilDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
// Ensure we are allowed to use this URL by config // Ensure we are allowed to use this URL by config
$this->config->prohibitUrlByConfig($url, $this->io); $this->config->prohibitUrlByConfig($url, $this->io);
@ -36,7 +36,7 @@ class FossilDownloader extends VcsDownloader
if (0 !== $this->process->execute($command, $ignoredOutput)) { if (0 !== $this->process->execute($command, $ignoredOutput)) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
} }
$command = sprintf('fossil open %s', ProcessExecutor::escape($repoFile)); $command = sprintf('fossil open %s --nested', ProcessExecutor::escape($repoFile));
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
} }
@ -87,7 +87,7 @@ class FossilDownloader extends VcsDownloader
*/ */
protected function getCommitLogs($fromReference, $toReference, $path) protected function getCommitLogs($fromReference, $toReference, $path)
{ {
$command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', $toReference); $command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference));
if (0 !== $this->process->execute($command, $output, realpath($path))) { if (0 !== $this->process->execute($command, $output, realpath($path))) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());

View File

@ -19,6 +19,7 @@ use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil; use Composer\Util\Git as GitUtil;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Cache;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
@ -38,7 +39,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
GitUtil::cleanEnv(); GitUtil::cleanEnv();
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
@ -51,7 +52,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$msg = "Cloning ".$this->getShortHash($ref); $msg = "Cloning ".$this->getShortHash($ref);
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer'; $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer';
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=')) { if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
$this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError('', true, IOInterface::DEBUG);
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
try { try {
@ -361,7 +362,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
) { ) {
$command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference)); $command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
if (0 === $this->process->execute($command, $output, $path)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return null;
} }
} }
@ -379,14 +380,14 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
) { ) {
$command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
if (0 === $this->process->execute($command, $output, $path)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return null;
} }
} }
} }
$command = sprintf($template, ProcessExecutor::escape($gitRef)); $command = sprintf($template, ProcessExecutor::escape($gitRef));
if (0 === $this->process->execute($command, $output, $path)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return null;
} }
// reference was not found (prints "fatal: reference is not a tree: $ref") // reference was not found (prints "fatal: reference is not a tree: $ref")
@ -423,7 +424,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
protected function getCommitLogs($fromReference, $toReference, $path) protected function getCommitLogs($fromReference, $toReference, $path)
{ {
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
$command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', $fromReference, $toReference); $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));
if (0 !== $this->process->execute($command, $output, $path)) { if (0 !== $this->process->execute($command, $output, $path)) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
@ -433,7 +434,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
} }
/** /**
* @param $path * @param string $path
* @throws \RuntimeException * @throws \RuntimeException
*/ */
protected function discardChanges($path) protected function discardChanges($path)
@ -447,7 +448,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
} }
/** /**
* @param $path * @param string $path
* @throws \RuntimeException * @throws \RuntimeException
*/ */
protected function stashChanges($path) protected function stashChanges($path)
@ -461,7 +462,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
} }
/** /**
* @param $path * @param string $path
* @throws \RuntimeException * @throws \RuntimeException
*/ */
protected function viewDiff($path) protected function viewDiff($path)

View File

@ -18,7 +18,7 @@ use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\HttpDownloader;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
/** /**
@ -28,17 +28,19 @@ use Composer\IO\IOInterface;
*/ */
class GzipDownloader extends ArchiveDownloader class GzipDownloader extends ArchiveDownloader
{ {
/** @var ProcessExecutor */
protected $process; protected $process;
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null) public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
{ {
$this->process = $process ?: new ProcessExecutor($io); $this->process = $process ?: new ProcessExecutor($io);
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs); parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
} }
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); $filename = pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME);
$targetFilepath = $path . DIRECTORY_SEPARATOR . $filename;
// Try to use gunzip on *nix // Try to use gunzip on *nix
if (!Platform::isWindows()) { if (!Platform::isWindows()) {
@ -63,14 +65,6 @@ class GzipDownloader extends ArchiveDownloader
$this->extractUsingExt($file, $targetFilepath); $this->extractUsingExt($file, $targetFilepath);
} }
/**
* {@inheritdoc}
*/
protected function getFileName(PackageInterface $package, $path)
{
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
}
private function extractUsingExt($file, $targetFilepath) private function extractUsingExt($file, $targetFilepath)
{ {
$archiveFile = gzopen($file, 'rb'); $archiveFile = gzopen($file, 'rb');

View File

@ -14,6 +14,7 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\Hg as HgUtils;
/** /**
* @author Per Bernhardt <plb@webfactory.de> * @author Per Bernhardt <plb@webfactory.de>
@ -23,18 +24,17 @@ class HgDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
// Ensure we are allowed to use this URL by config $hgUtils = new HgUtils($this->io, $this->config, $this->process);
$this->config->prohibitUrlByConfig($url, $this->io);
$cloneCommand = function ($url) use ($path) {
return sprintf('hg clone %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path));
};
$hgUtils->runCommand($cloneCommand, $url, $path);
$url = ProcessExecutor::escape($url);
$ref = ProcessExecutor::escape($package->getSourceReference()); $ref = ProcessExecutor::escape($package->getSourceReference());
$this->io->writeError("Cloning ".$package->getSourceReference());
$command = sprintf('hg clone %s %s', $url, ProcessExecutor::escape($path));
if (0 !== $this->process->execute($command, $ignoredOutput)) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
}
$command = sprintf('hg up %s', $ref); $command = sprintf('hg up %s', $ref);
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
@ -46,21 +46,20 @@ class HgDownloader extends VcsDownloader
*/ */
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{ {
// Ensure we are allowed to use this URL by config $hgUtils = new HgUtils($this->io, $this->config, $this->process);
$this->config->prohibitUrlByConfig($url, $this->io);
$url = ProcessExecutor::escape($url); $ref = $target->getSourceReference();
$ref = ProcessExecutor::escape($target->getSourceReference());
$this->io->writeError(" Updating to ".$target->getSourceReference()); $this->io->writeError(" Updating to ".$target->getSourceReference());
if (!$this->hasMetadataRepository($path)) { if (!$this->hasMetadataRepository($path)) {
throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
} }
$command = sprintf('hg pull %s && hg up %s', $url, $ref); $command = function ($url) use ($ref) {
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { return sprintf('hg pull %s && hg up %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref));
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); };
}
$hgUtils->runCommand($command, $url, $path);
} }
/** /**
@ -82,7 +81,7 @@ class HgDownloader extends VcsDownloader
*/ */
protected function getCommitLogs($fromReference, $toReference, $path) protected function getCommitLogs($fromReference, $toReference, $path)
{ {
$command = sprintf('hg log -r %s:%s --style compact', $fromReference, $toReference); $command = sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));
if (0 !== $this->process->execute($command, $output, realpath($path))) { if (0 !== $this->process->execute($command, $output, realpath($path))) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());

Some files were not shown because too many files have changed in this diff Show More