Merge branch '2.0'
commit
87757de6bc
|
@ -15,3 +15,4 @@
|
||||||
.travis.yml export-ignore
|
.travis.yml export-ignore
|
||||||
appveyor.yml export-ignore
|
appveyor.yml export-ignore
|
||||||
phpunit.xml.dist export-ignore
|
phpunit.xml.dist export-ignore
|
||||||
|
/phpstan/ export-ignore
|
||||||
|
|
43
.travis.yml
43
.travis.yml
|
@ -1,6 +1,6 @@
|
||||||
language: php
|
language: php
|
||||||
|
|
||||||
dist: trusty
|
dist: bionic
|
||||||
|
|
||||||
git:
|
git:
|
||||||
depth: 5
|
depth: 5
|
||||||
|
@ -9,27 +9,40 @@ cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.composer/cache
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- parallel
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- php: 5.3
|
- php: 5.3
|
||||||
dist: precise
|
dist: precise
|
||||||
- php: 5.4
|
- php: 5.4
|
||||||
|
dist: trusty
|
||||||
- php: 5.5
|
- php: 5.5
|
||||||
|
dist: trusty
|
||||||
- php: 5.6
|
- php: 5.6
|
||||||
|
dist: xenial
|
||||||
- php: 7.0
|
- php: 7.0
|
||||||
|
dist: xenial
|
||||||
- php: 7.1
|
- php: 7.1
|
||||||
|
dist: xenial
|
||||||
- php: 7.2
|
- php: 7.2
|
||||||
|
dist: xenial
|
||||||
- php: 7.3
|
- php: 7.3
|
||||||
- php: nightly
|
dist: xenial
|
||||||
|
# Regular 7.4 build with locked deps
|
||||||
|
- php: 7.4
|
||||||
|
env:
|
||||||
|
- SYMFONY_PHPUNIT_VERSION=7.5
|
||||||
|
# High deps check
|
||||||
- php: 7.4
|
- php: 7.4
|
||||||
env:
|
env:
|
||||||
- deps=high
|
- deps=high
|
||||||
- SYMFONY_PHPUNIT_VERSION=7.5
|
- SYMFONY_PHPUNIT_VERSION=7.5
|
||||||
|
# PHPStan checks
|
||||||
|
- php: 7.4
|
||||||
|
env:
|
||||||
|
- deps=high
|
||||||
|
- PHPSTAN=1
|
||||||
|
- SYMFONY_PHPUNIT_VERSION=7.5
|
||||||
|
- php: nightly
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- php: nightly
|
- php: nightly
|
||||||
|
@ -44,9 +57,9 @@ before_install:
|
||||||
|
|
||||||
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-progress"
|
||||||
# update deps to latest in case of high deps build
|
# update deps to latest in case of high deps build
|
||||||
- if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi
|
- if [ "$deps" == "high" ]; then composer config platform.php 7.4.0; composer update $flags; fi
|
||||||
# install dependencies using system provided composer binary
|
# install dependencies using system provided composer binary
|
||||||
- composer install $flags
|
- composer install $flags
|
||||||
# install dependencies using composer from source
|
# install dependencies using composer from source
|
||||||
|
@ -58,9 +71,13 @@ before_script:
|
||||||
- git config --global user.email travis@example.com
|
- git config --global user.email travis@example.com
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./vendor/bin/simple-phpunit
|
- if [[ $PHPSTAN == "1" ]]; then
|
||||||
# run test suite directories in parallel using GNU parallel
|
bin/composer require --dev phpstan/phpstan:^0.12 phpunit/phpunit:^7.5 --no-update &&
|
||||||
# - 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);'
|
bin/composer update phpstan/* phpunit/* sebastian/* --with-dependencies &&
|
||||||
|
vendor/bin/phpstan analyse --configuration=phpstan/config.neon;
|
||||||
|
else
|
||||||
|
vendor/bin/simple-phpunit;
|
||||||
|
fi
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- php -d phar.readonly=0 bin/compile
|
- php -d phar.readonly=0 bin/compile
|
||||||
|
@ -73,4 +90,4 @@ deploy:
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: composer/composer
|
repo: composer/composer
|
||||||
php: '7.2'
|
php: '7.3'
|
||||||
|
|
11
appveyor.yml
11
appveyor.yml
|
@ -3,7 +3,7 @@ clone_depth: 5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
# This sets the PHP version (from Chocolatey)
|
# This sets the PHP version (from Chocolatey)
|
||||||
PHPCI_CHOCO_VERSION: 7.3.1
|
PHPCI_CHOCO_VERSION: 7.3.14
|
||||||
PHPCI_CACHE: C:\tools\phpci
|
PHPCI_CACHE: C:\tools\phpci
|
||||||
PHPCI_PHP: C:\tools\phpci\php
|
PHPCI_PHP: C:\tools\phpci\php
|
||||||
PHPCI_COMPOSER: C:\tools\phpci\composer
|
PHPCI_COMPOSER: C:\tools\phpci\composer
|
||||||
|
@ -25,6 +25,15 @@ install:
|
||||||
- IF %PHP%==0 cinst composer -i -y --ia "/DEV=%PHPCI_COMPOSER%"
|
- 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)
|
||||||
|
- IF %PHP%==0 cd %PHPCI_PHP%
|
||||||
|
- IF %PHP%==0 copy php.ini-production php.ini /Y
|
||||||
|
- IF %PHP%==0 echo date.timezone="UTC" >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension_dir=ext >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension=php_openssl.dll >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension=php_mbstring.dll >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension=php_fileinfo.dll >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension=php_intl.dll >> php.ini
|
||||||
|
- IF %PHP%==0 echo extension=php_curl.dll >> php.ini
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
- cd %APPVEYOR_BUILD_FOLDER%
|
||||||
- composer install --prefer-dist --no-progress
|
- composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.3.2 || ^7.0",
|
"php": "^5.3.2 || ^7.0",
|
||||||
"composer/ca-bundle": "^1.0",
|
"composer/ca-bundle": "^1.0",
|
||||||
"composer/semver": "^1.0",
|
"composer/semver": "^2.0@dev",
|
||||||
"composer/spdx-licenses": "^1.2",
|
"composer/spdx-licenses": "^1.2",
|
||||||
"composer/xdebug-handler": "^1.1",
|
"composer/xdebug-handler": "^1.1",
|
||||||
"justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
|
"justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
|
||||||
|
@ -34,7 +34,8 @@
|
||||||
"symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
"symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
||||||
"symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
"symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
||||||
"symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
"symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
||||||
"symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0"
|
"symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0",
|
||||||
|
"react/promise": "^1.2 || ^2.7"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/console": "2.8.38"
|
"symfony/console": "2.8.38"
|
||||||
|
@ -55,7 +56,7 @@
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.10-dev"
|
"dev-master": "2.0-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -65,8 +66,13 @@
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Composer\\Test\\": "tests/Composer/Test"
|
"Composer\\Test\\": "tests/Composer/Test",
|
||||||
}
|
"Composer\\PHPStanRules\\": "phpstan/Rules/src",
|
||||||
|
"Composer\\PHPStanRulesTests\\": "phpstan/Rules/tests"
|
||||||
|
},
|
||||||
|
"classmap": [
|
||||||
|
"phpstan/Rules/tests/data"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
"bin/composer"
|
"bin/composer"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"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": "cc6f9640996dfad00a5b03a8be01a571",
|
"content-hash": "a0a9399315ac0b612d4296b8df745112",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "composer/ca-bundle",
|
"name": "composer/ca-bundle",
|
||||||
|
@ -60,20 +60,25 @@
|
||||||
"ssl",
|
"ssl",
|
||||||
"tls"
|
"tls"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
|
"issues": "https://github.com/composer/ca-bundle/issues",
|
||||||
|
"source": "https://github.com/composer/ca-bundle/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-01-13T10:02:55+00:00"
|
"time": "2020-01-13T10:02:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/semver",
|
"name": "composer/semver",
|
||||||
"version": "1.5.1",
|
"version": "2.0.x-dev",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/semver.git",
|
"url": "https://github.com/composer/semver.git",
|
||||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
|
"reference": "4df5ff3249f01018504939d66040d8d2b783d820"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
"url": "https://api.github.com/repos/composer/semver/zipball/4df5ff3249f01018504939d66040d8d2b783d820",
|
||||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
"reference": "4df5ff3249f01018504939d66040d8d2b783d820",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -85,7 +90,7 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.x-dev"
|
"dev-master": "2.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -121,7 +126,22 @@
|
||||||
"validation",
|
"validation",
|
||||||
"versioning"
|
"versioning"
|
||||||
],
|
],
|
||||||
"time": "2020-01-13T12:06:48+00:00"
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
|
"issues": "https://github.com/composer/semver/issues",
|
||||||
|
"source": "https://github.com/composer/semver/tree/2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-03-11T13:41:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/spdx-licenses",
|
"name": "composer/spdx-licenses",
|
||||||
|
@ -181,6 +201,11 @@
|
||||||
"spdx",
|
"spdx",
|
||||||
"validator"
|
"validator"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
|
"issues": "https://github.com/composer/spdx-licenses/issues",
|
||||||
|
"source": "https://github.com/composer/spdx-licenses/tree/1.5.3"
|
||||||
|
},
|
||||||
"time": "2020-02-14T07:44:31+00:00"
|
"time": "2020-02-14T07:44:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -225,6 +250,11 @@
|
||||||
"Xdebug",
|
"Xdebug",
|
||||||
"performance"
|
"performance"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
|
"issues": "https://github.com/composer/xdebug-handler/issues",
|
||||||
|
"source": "https://github.com/composer/xdebug-handler/tree/master"
|
||||||
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"url": "https://packagist.com",
|
"url": "https://packagist.com",
|
||||||
|
@ -353,6 +383,44 @@
|
||||||
},
|
},
|
||||||
"time": "2019-11-01T11:05:21+00:00"
|
"time": "2019-11-01T11:05:21+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "react/promise",
|
||||||
|
"version": "v1.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reactphp/promise.git",
|
||||||
|
"reference": "eefff597e67ff66b719f8171480add3c91474a1e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/reactphp/promise/zipball/eefff597e67ff66b719f8171480add3c91474a1e",
|
||||||
|
"reference": "eefff597e67ff66b719f8171480add3c91474a1e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"React\\Promise": "src/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/React/Promise/functions_include.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||||
|
"time": "2016-03-07T13:46:50+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "seld/jsonlint",
|
"name": "seld/jsonlint",
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
|
@ -448,6 +516,10 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"phar"
|
"phar"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Seldaek/phar-utils/issues",
|
||||||
|
"source": "https://github.com/Seldaek/phar-utils/tree/1.1.0"
|
||||||
|
},
|
||||||
"time": "2020-02-14T15:25:33+00:00"
|
"time": "2020-02-14T15:25:33+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -735,6 +807,9 @@
|
||||||
"polyfill",
|
"polyfill",
|
||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-01-13T11:15:53+00:00"
|
"time": "2020-01-13T11:15:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -794,6 +869,9 @@
|
||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/master"
|
||||||
|
},
|
||||||
"time": "2020-01-13T11:15:53+00:00"
|
"time": "2020-01-13T11:15:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -949,6 +1027,16 @@
|
||||||
"license": [
|
"license": [
|
||||||
"MIT"
|
"MIT"
|
||||||
],
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mike van Riel",
|
||||||
|
"email": "mike.vanriel@naenius.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
|
||||||
|
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/2.x"
|
||||||
|
},
|
||||||
"time": "2016-01-25T08:17:30+00:00"
|
"time": "2016-01-25T08:17:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1012,6 +1100,10 @@
|
||||||
"spy",
|
"spy",
|
||||||
"stub"
|
"stub"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpspec/prophecy/issues",
|
||||||
|
"source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
|
||||||
|
},
|
||||||
"time": "2020-03-05T15:02:03+00:00"
|
"time": "2020-03-05T15:02:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1076,6 +1168,10 @@
|
||||||
"compare",
|
"compare",
|
||||||
"equality"
|
"equality"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sebastianbergmann/comparator/issues",
|
||||||
|
"source": "https://github.com/sebastianbergmann/comparator/tree/1.2"
|
||||||
|
},
|
||||||
"time": "2017-01-29T09:50:25+00:00"
|
"time": "2017-01-29T09:50:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1128,6 +1224,10 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"diff"
|
"diff"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||||
|
"source": "https://github.com/sebastianbergmann/diff/tree/1.4"
|
||||||
|
},
|
||||||
"time": "2017-05-22T07:24:03+00:00"
|
"time": "2017-05-22T07:24:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1195,6 +1295,10 @@
|
||||||
"export",
|
"export",
|
||||||
"exporter"
|
"exporter"
|
||||||
],
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sebastianbergmann/exporter/issues",
|
||||||
|
"source": "https://github.com/sebastianbergmann/exporter/tree/master"
|
||||||
|
},
|
||||||
"time": "2016-11-19T08:54:04+00:00"
|
"time": "2016-11-19T08:54:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1248,6 +1352,10 @@
|
||||||
],
|
],
|
||||||
"description": "Provides functionality to recursively process PHP variables",
|
"description": "Provides functionality to recursively process PHP variables",
|
||||||
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
|
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
|
||||||
|
"source": "https://github.com/sebastianbergmann/recursion-context/tree/master"
|
||||||
|
},
|
||||||
"time": "2016-11-19T07:33:16+00:00"
|
"time": "2016-11-19T07:33:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1313,6 +1421,9 @@
|
||||||
],
|
],
|
||||||
"description": "Symfony PHPUnit Bridge",
|
"description": "Symfony PHPUnit Bridge",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/phpunit-bridge/tree/v3.4.38"
|
||||||
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"url": "https://symfony.com/sponsor",
|
"url": "https://symfony.com/sponsor",
|
||||||
|
@ -1332,7 +1443,9 @@
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": {
|
||||||
|
"composer/semver": 20
|
||||||
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
|
@ -1342,5 +1455,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "5.3.9"
|
"php": "5.3.9"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "1.1.0"
|
"plugin-api-version": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,7 @@ php composer.phar update
|
||||||
> if the `composer.lock` has not been updated since changes were made to the
|
> if the `composer.lock` has not been updated since changes were made to the
|
||||||
> `composer.json` that might affect dependency resolution.
|
> `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, upgrade or remove one dependency, you can explicitly list it as an argument:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
php composer.phar update monolog/monolog [...]
|
php composer.phar update monolog/monolog [...]
|
||||||
|
|
|
@ -106,7 +106,6 @@ resolution.
|
||||||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||||
* **--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-suggest:** Skips suggested packages in the output.
|
|
||||||
* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
|
* **--optimize-autoloader (-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.
|
||||||
|
@ -156,9 +155,8 @@ php composer.phar update "vendor/*"
|
||||||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||||
* **--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-suggest:** Skips suggested packages in the output.
|
* **--with-dependencies:** Update also dependencies of packages in the argument list, except those which are root requirements.
|
||||||
* **--with-dependencies:** Add also dependencies of whitelisted packages to the whitelist, except those that are root requirements.
|
* **--with-all-dependencies:** Update also dependencies of packages in the argument list, including those which are root requirements.
|
||||||
* **--with-all-dependencies:** Add also all dependencies of whitelisted packages to the whitelist, including those that are root requirements.
|
|
||||||
* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
|
* **--optimize-autoloader (-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.
|
||||||
|
@ -198,11 +196,11 @@ If you do not specify a package, composer will prompt you to search for a packag
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
* **--dev:** Add packages to `require-dev`.
|
* **--dev:** Add packages to `require-dev`.
|
||||||
|
* **--dry-run:** Simulate the command without actually doing anything.
|
||||||
* **--prefer-source:** Install packages from `source` when available.
|
* **--prefer-source:** Install packages from `source` when available.
|
||||||
* **--prefer-dist:** Install packages from `dist` when available.
|
* **--prefer-dist:** Install packages from `dist` when available.
|
||||||
* **--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-suggest:** Skips suggested packages in the output.
|
|
||||||
* **--no-update:** Disables the automatic update of the dependencies.
|
* **--no-update:** Disables the automatic update of the dependencies.
|
||||||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||||
* **--update-no-dev:** Run the dependency update with the `--no-dev` option.
|
* **--update-no-dev:** Run the dependency update with the `--no-dev` option.
|
||||||
|
@ -236,6 +234,7 @@ uninstalled.
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
* **--dev:** Remove packages from `require-dev`.
|
* **--dev:** Remove packages from `require-dev`.
|
||||||
|
* **--dry-run:** Simulate the command without actually doing anything.
|
||||||
* **--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-update:** Disables the automatic update of the dependencies.
|
* **--no-update:** Disables the automatic update of the dependencies.
|
||||||
|
@ -408,16 +407,18 @@ Lists all packages suggested by currently installed set of packages. You can
|
||||||
optionally pass one or multiple package names in the format of `vendor/package`
|
optionally pass one or multiple package names in the format of `vendor/package`
|
||||||
to limit output to suggestions made by those packages only.
|
to limit output to suggestions made by those packages only.
|
||||||
|
|
||||||
Use the `--by-package` or `--by-suggestion` flags to group the output by
|
Use the `--by-package` (default) or `--by-suggestion` flags to group the output by
|
||||||
the package offering the suggestions or the suggested packages respectively.
|
the package offering the suggestions or the suggested packages respectively.
|
||||||
|
|
||||||
Use the `--verbose (-v)` flag to display the suggesting package and the suggestion reason.
|
If you only want a list of suggested package names, use `--list`.
|
||||||
This implies `--by-package --by-suggestion`, showing both lists.
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
* **--by-package:** Groups output by suggesting package.
|
* **--by-package:** Groups output by suggesting package (default).
|
||||||
* **--by-suggestion:** Groups output by suggested package.
|
* **--by-suggestion:** Groups output by suggested package.
|
||||||
|
* **--all:** Show suggestions from all dependencies, including transitive ones (by
|
||||||
|
default only direct dependencies' suggestions are shown).
|
||||||
|
* **--list:** Show only list of suggested package names.
|
||||||
* **--no-dev:** Excludes suggestions from `require-dev` packages.
|
* **--no-dev:** Excludes suggestions from `require-dev` packages.
|
||||||
|
|
||||||
## fund
|
## fund
|
||||||
|
@ -952,4 +953,9 @@ 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
|
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.
|
can also set it to `*` to ignore the proxy for all HTTP requests.
|
||||||
|
|
||||||
|
### COMPOSER_DISABLE_NETWORK
|
||||||
|
|
||||||
|
If set to `1`, disables network access (best effort). This can be used for debugging or
|
||||||
|
to run Composer on a plane or a starship with poor connectivity.
|
||||||
|
|
||||||
← [Libraries](02-libraries.md) | [Schema](04-schema.md) →
|
← [Libraries](02-libraries.md) | [Schema](04-schema.md) →
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,8 @@ Composer fires the following named events during its execution process:
|
||||||
|
|
||||||
### Installer Events
|
### Installer Events
|
||||||
|
|
||||||
- **pre-dependencies-solving**: occurs before the dependencies are resolved.
|
- **pre-operations-exec**: occurs before the install/upgrade/.. operations
|
||||||
- **post-dependencies-solving**: occurs after the dependencies have been resolved.
|
are executed when installing a lock file.
|
||||||
|
|
||||||
### Package Events
|
### Package Events
|
||||||
|
|
||||||
|
@ -61,11 +61,13 @@ 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
|
- **pre-command-run**: occurs before a command is executed and allows you to
|
||||||
manipulate the `InputInterface` object's options and arguments to tweak
|
manipulate the `InputInterface` object's options and arguments to tweak
|
||||||
a command's behavior.
|
a command's behavior.
|
||||||
|
- **pre-pool-create**: occurs before the Pool of packages is created, and lets
|
||||||
|
you filter the list of packages which is going to enter the Solver.
|
||||||
|
|
||||||
> **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
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace Composer\PHPStanRules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-implements Rule<\PhpParser\Node\Expr\Variable>
|
||||||
|
*/
|
||||||
|
final class AnonymousFunctionWithThisRule implements Rule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return \PhpParser\Node\Expr\Variable::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (!\is_string($node->name) || $node->name !== 'this') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scope->isInClosureBind()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope->isInClass()) {
|
||||||
|
// reported in other standard rule on level 0
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scope->isInAnonymousFunction()) {
|
||||||
|
return ['Using $this inside anonymous function is prohibited because of PHP 5.3 support.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace Composer\PHPStanRulesTests;
|
||||||
|
|
||||||
|
use Composer\PHPStanRules\AnonymousFunctionWithThisRule;
|
||||||
|
use PHPStan\Testing\RuleTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-extends RuleTestCase<AnonymousFunctionWithThisRule>
|
||||||
|
*/
|
||||||
|
final class AnonymousFunctionWithThisRuleTest extends RuleTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
protected function getRule(): \PHPStan\Rules\Rule
|
||||||
|
{
|
||||||
|
return new AnonymousFunctionWithThisRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithThis(): void
|
||||||
|
{
|
||||||
|
$this->analyse([__DIR__ . '/data/method-with-this.php'], [
|
||||||
|
['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 13],
|
||||||
|
['Using $this inside anonymous function is prohibited because of PHP 5.3 support.', 17],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FirstClass
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $firstProp = 9;
|
||||||
|
|
||||||
|
public function funMethod()
|
||||||
|
{
|
||||||
|
function() {
|
||||||
|
$this->firstProp;
|
||||||
|
};
|
||||||
|
|
||||||
|
call_user_func(function() {
|
||||||
|
$this->funMethod();
|
||||||
|
}, $this);
|
||||||
|
|
||||||
|
$bind = 'bind';
|
||||||
|
function() use($bind) {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function global_ok() {
|
||||||
|
$_SERVER['REMOTE_ADDR'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function global_this() {
|
||||||
|
// not checked by our rule, it is checked with standard phpstan rule on level 0
|
||||||
|
$this['REMOTE_ADDR'];
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/bootstrap.php';
|
|
@ -0,0 +1,39 @@
|
||||||
|
parameters:
|
||||||
|
autoload_files:
|
||||||
|
- autoload.php
|
||||||
|
level: 0
|
||||||
|
excludes_analyse:
|
||||||
|
- '../tests/Composer/Test/Fixtures/*'
|
||||||
|
- '../tests/Composer/Test/Autoload/Fixtures/*'
|
||||||
|
- '../tests/Composer/Test/Plugin/Fixtures/*'
|
||||||
|
ignoreErrors:
|
||||||
|
# ion cube is not installed
|
||||||
|
- '~^Function ioncube_loader_\w+ not found\.$~'
|
||||||
|
|
||||||
|
# variables from global scope
|
||||||
|
- '~^Undefined variable: \$vendorDir$~'
|
||||||
|
- '~^Undefined variable: \$baseDir$~'
|
||||||
|
|
||||||
|
# variable defined in eval
|
||||||
|
- '~^Undefined variable: \$res$~'
|
||||||
|
|
||||||
|
# erroneous detection of missing const, see https://github.com/phpstan/phpstan/issues/2960
|
||||||
|
- '~^Access to undefined constant ZipArchive::LIBZIP_VERSION.$~'
|
||||||
|
|
||||||
|
# we don't have different constructors for parent/child
|
||||||
|
- '~^Unsafe usage of new static\(\)\.$~'
|
||||||
|
|
||||||
|
# BC with older PHPUnit
|
||||||
|
- '~^Call to an undefined static method PHPUnit\\Framework\\TestCase::setExpectedException\(\)\.$~'
|
||||||
|
|
||||||
|
# hhvm should have support for $this in closures
|
||||||
|
-
|
||||||
|
count: 1
|
||||||
|
message: '~^Using \$this inside anonymous function is prohibited because of PHP 5\.3 support\.$~'
|
||||||
|
path: '../tests/Composer/Test/Repository/PlatformRepositoryTest.php'
|
||||||
|
paths:
|
||||||
|
- ../src
|
||||||
|
- ../tests
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- Composer\PHPStanRules\AnonymousFunctionWithThisRule
|
|
@ -113,10 +113,10 @@ class ClassMapGenerator
|
||||||
|
|
||||||
$classes = self::findClasses($filePath);
|
$classes = self::findClasses($filePath);
|
||||||
if (null !== $autoloadType) {
|
if (null !== $autoloadType) {
|
||||||
list($classes, $validClasses) = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
|
$classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
|
||||||
|
|
||||||
// if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
|
// if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
|
||||||
if ($validClasses) {
|
if ($classes) {
|
||||||
$scannedFiles[$realPath] = true;
|
$scannedFiles[$realPath] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,8 +126,7 @@ class ClassMapGenerator
|
||||||
|
|
||||||
foreach ($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
// skip classes not within the given namespace prefix
|
// skip classes not within the given namespace prefix
|
||||||
// TODO enable in Composer v1.11 or 2.0 whichever comes first
|
if (null === $autoloadType && null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
|
||||||
if (/* null === $autoloadType && */ null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,19 +195,15 @@ class ClassMapGenerator
|
||||||
// warn only if no valid classes, else silently skip invalid
|
// warn only if no valid classes, else silently skip invalid
|
||||||
if (empty($validClasses)) {
|
if (empty($validClasses)) {
|
||||||
foreach ($rejectedClasses as $class) {
|
foreach ($rejectedClasses as $class) {
|
||||||
trigger_error(
|
if ($io) {
|
||||||
"Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v2.0.",
|
$io->writeError("<warning>Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. Skipping.</warning>");
|
||||||
E_USER_DEPRECATED
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO enable in Composer 2.0
|
return array();
|
||||||
//return array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO enable in Composer 2.0 & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
|
return $validClasses;
|
||||||
//return $validClasses;
|
|
||||||
return array($classes, $validClasses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,20 +28,20 @@ class Cache
|
||||||
private $io;
|
private $io;
|
||||||
private $root;
|
private $root;
|
||||||
private $enabled = true;
|
private $enabled = true;
|
||||||
private $whitelist;
|
private $allowlist;
|
||||||
private $filesystem;
|
private $filesystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
* @param string $cacheDir location of the cache
|
* @param string $cacheDir location of the cache
|
||||||
* @param string $whitelist List of characters that are allowed in path names (used in a regex character class)
|
* @param string $allowlist List of characters that are allowed in path names (used in a regex character class)
|
||||||
* @param Filesystem $filesystem optional filesystem instance
|
* @param Filesystem $filesystem optional filesystem instance
|
||||||
*/
|
*/
|
||||||
public function __construct(IOInterface $io, $cacheDir, $whitelist = 'a-z0-9.', Filesystem $filesystem = null)
|
public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null)
|
||||||
{
|
{
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
$this->root = rtrim($cacheDir, '/\\') . '/';
|
$this->root = rtrim($cacheDir, '/\\') . '/';
|
||||||
$this->whitelist = $whitelist;
|
$this->allowlist = $allowlist;
|
||||||
$this->filesystem = $filesystem ?: new Filesystem();
|
$this->filesystem = $filesystem ?: new Filesystem();
|
||||||
|
|
||||||
if (!self::isUsable($cacheDir)) {
|
if (!self::isUsable($cacheDir)) {
|
||||||
|
@ -77,7 +77,7 @@ class Cache
|
||||||
public function read($file)
|
public function read($file)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
if (file_exists($this->root . $file)) {
|
if (file_exists($this->root . $file)) {
|
||||||
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
|
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class Cache
|
||||||
public function write($file, $contents)
|
public function write($file, $contents)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
|
|
||||||
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
|
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class Cache
|
||||||
public function copyFrom($file, $source)
|
public function copyFrom($file, $source)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file));
|
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file));
|
||||||
|
|
||||||
if (!file_exists($source)) {
|
if (!file_exists($source)) {
|
||||||
|
@ -150,7 +150,7 @@ class Cache
|
||||||
public function copyTo($file, $target)
|
public function copyTo($file, $target)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
if (file_exists($this->root . $file)) {
|
if (file_exists($this->root . $file)) {
|
||||||
try {
|
try {
|
||||||
touch($this->root . $file, filemtime($this->root . $file), time());
|
touch($this->root . $file, filemtime($this->root . $file), time());
|
||||||
|
@ -177,7 +177,7 @@ class Cache
|
||||||
public function remove($file)
|
public function remove($file)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
if (file_exists($this->root . $file)) {
|
if (file_exists($this->root . $file)) {
|
||||||
return $this->filesystem->unlink($this->root . $file);
|
return $this->filesystem->unlink($this->root . $file);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ class Cache
|
||||||
public function sha1($file)
|
public function sha1($file)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
if (file_exists($this->root . $file)) {
|
if (file_exists($this->root . $file)) {
|
||||||
return sha1_file($this->root . $file);
|
return sha1_file($this->root . $file);
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ class Cache
|
||||||
public function sha256($file)
|
public function sha256($file)
|
||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||||
if (file_exists($this->root . $file)) {
|
if (file_exists($this->root . $file)) {
|
||||||
return hash_file('sha256', $this->root . $file);
|
return hash_file('sha256', $this->root . $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;
|
||||||
|
@ -111,8 +112,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) {
|
||||||
|
|
|
@ -27,6 +27,8 @@ 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>
|
||||||
*/
|
*/
|
||||||
|
@ -46,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)
|
||||||
{
|
{
|
||||||
|
@ -173,7 +175,7 @@ abstract class BaseCommand extends Command
|
||||||
|
|
||||||
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
|
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);
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
|
|
||||||
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\InstalledArrayRepository;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\CompositeRepository;
|
||||||
|
use Composer\Repository\RootPackageRepository;
|
||||||
|
use Composer\Repository\InstalledRepository;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
use Composer\Repository\RepositoryFactory;
|
use Composer\Repository\RepositoryFactory;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
|
@ -71,15 +72,12 @@ 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
|
|
||||||
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
||||||
$repository = new CompositeRepository(array(
|
$installedRepo = new InstalledRepository(array(
|
||||||
new ArrayRepository(array($composer->getPackage())),
|
new RootPackageRepository($composer->getPackage()),
|
||||||
$composer->getRepositoryManager()->getLocalRepository(),
|
$composer->getRepositoryManager()->getLocalRepository(),
|
||||||
new PlatformRepository(array(), $platformOverrides),
|
new PlatformRepository(array(), $platformOverrides),
|
||||||
));
|
));
|
||||||
$pool = new Pool();
|
|
||||||
$pool->addRepository($repository);
|
|
||||||
|
|
||||||
// Parse package name and constraint
|
// Parse package name and constraint
|
||||||
list($needle, $textConstraint) = array_pad(
|
list($needle, $textConstraint) = array_pad(
|
||||||
|
@ -89,17 +87,17 @@ 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(strtolower($needle));
|
$packages = $installedRepo->findPackagesWithReplacersAndProviders($needle);
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the version we ask for is not installed then we need to locate it in remote repos and add it.
|
// If the version we ask for is not installed then we need to locate it in remote repos and add it.
|
||||||
// This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
|
// This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
|
||||||
if (!$repository->findPackage($needle, $textConstraint)) {
|
if (!$installedRepo->findPackage($needle, $textConstraint)) {
|
||||||
$defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
|
$defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
|
||||||
if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
|
if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
|
||||||
$repository->addRepository(new ArrayRepository(array(clone $match)));
|
$installedRepo->addRepository(new InstalledArrayRepository(array(clone $match)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +124,7 @@ class BaseDependencyCommand extends BaseCommand
|
||||||
$recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
|
$recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
|
||||||
|
|
||||||
// Resolve dependencies
|
// Resolve dependencies
|
||||||
$results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
|
$results = $installedRepo->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(
|
$this->getIO()->writeError(sprintf(
|
||||||
|
|
|
@ -20,6 +20,7 @@ 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;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Repository\InstalledRepository;
|
||||||
|
|
||||||
class CheckPlatformReqsCommand extends BaseCommand
|
class CheckPlatformReqsCommand extends BaseCommand
|
||||||
{
|
{
|
||||||
|
@ -48,12 +49,13 @@ EOT
|
||||||
|
|
||||||
$requires = $composer->getPackage()->getRequires();
|
$requires = $composer->getPackage()->getRequires();
|
||||||
if ($input->getOption('no-dev')) {
|
if ($input->getOption('no-dev')) {
|
||||||
$dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
|
$installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'));
|
||||||
|
$dependencies = $installedRepo->getPackages();
|
||||||
} else {
|
} else {
|
||||||
$dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
|
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
// fallback to lockfile if installed repo is empty
|
// fallback to lockfile if installed repo is empty
|
||||||
if (!$dependencies) {
|
if (!$installedRepo->getPackages()) {
|
||||||
$dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
|
$installedRepo = $composer->getLocker()->getLockedRepository(true);
|
||||||
}
|
}
|
||||||
$requires += $composer->getPackage()->getDevRequires();
|
$requires += $composer->getPackage()->getDevRequires();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +63,8 @@ EOT
|
||||||
$requires[$require] = array($link);
|
$requires[$require] = array($link);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($dependencies as $package) {
|
$installedRepo = new InstalledRepository(array($installedRepo));
|
||||||
|
foreach ($installedRepo->getPackages() as $package) {
|
||||||
foreach ($package->getRequires() as $require => $link) {
|
foreach ($package->getRequires() as $require => $link) {
|
||||||
$requires[$require][] = $link;
|
$requires[$require][] = $link;
|
||||||
}
|
}
|
||||||
|
@ -69,19 +72,9 @@ EOT
|
||||||
|
|
||||||
ksort($requires);
|
ksort($requires);
|
||||||
|
|
||||||
$platformRepo = new PlatformRepository(array(), array());
|
$installedRepo->addRepository(new PlatformRepository(array(), array()));
|
||||||
$currentPlatformPackages = $platformRepo->getPackages();
|
|
||||||
$currentPlatformPackageMap = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PackageInterface $currentPlatformPackage
|
|
||||||
*/
|
|
||||||
foreach ($currentPlatformPackages as $currentPlatformPackage) {
|
|
||||||
$currentPlatformPackageMap[$currentPlatformPackage->getName()] = $currentPlatformPackage;
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
$exitCode = 0;
|
$exitCode = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,42 +82,62 @@ EOT
|
||||||
*/
|
*/
|
||||||
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)) {
|
||||||
if (isset($currentPlatformPackageMap[$require])) {
|
$candidates = $installedRepo->findPackagesWithReplacersAndProviders($require);
|
||||||
$pass = true;
|
if ($candidates) {
|
||||||
$version = $currentPlatformPackageMap[$require]->getVersion();
|
$reqResults = array();
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
foreach ($links as $link) {
|
if ($candidate->getName() === $require) {
|
||||||
if (!$link->getConstraint()->matches(new Constraint('=', $version))) {
|
$candidateConstraint = new Constraint('=', $candidate->getVersion());
|
||||||
$results[] = array(
|
$candidateConstraint->setPrettyString($candidate->getPrettyVersion());
|
||||||
$currentPlatformPackageMap[$require]->getPrettyName(),
|
} else {
|
||||||
$currentPlatformPackageMap[$require]->getPrettyVersion(),
|
foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
|
||||||
$link,
|
if ($link->getTarget() === $require) {
|
||||||
'<error>failed</error>',
|
$candidateConstraint = $link->getConstraint();
|
||||||
);
|
break;
|
||||||
$pass = false;
|
}
|
||||||
|
}
|
||||||
$exitCode = max($exitCode, 1);
|
}
|
||||||
|
|
||||||
|
foreach ($links as $link) {
|
||||||
|
if (!$link->getConstraint()->matches($candidateConstraint)) {
|
||||||
|
$reqResults[] = array(
|
||||||
|
$candidate->getName() === $require ? $candidate->getPrettyName() : $require,
|
||||||
|
$candidateConstraint->getPrettyString(),
|
||||||
|
$link,
|
||||||
|
'<error>failed</error>'.($candidate->getName() === $require ? '' : ' <comment>provided by '.$candidate->getPrettyName().'</comment>'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// skip to next candidate
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($pass) {
|
|
||||||
$results[] = array(
|
$results[] = array(
|
||||||
$currentPlatformPackageMap[$require]->getPrettyName(),
|
$candidate->getName() === $require ? $candidate->getPrettyName() : $require,
|
||||||
$currentPlatformPackageMap[$require]->getPrettyVersion(),
|
$candidateConstraint->getPrettyString(),
|
||||||
null,
|
null,
|
||||||
'<info>success</info>',
|
'<info>success</info>'.($candidate->getName() === $require ? '' : ' <comment>provided by '.$candidate->getPrettyName().'</comment>'),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$results[] = array(
|
|
||||||
$require,
|
|
||||||
'n/a',
|
|
||||||
$links[0],
|
|
||||||
'<error>missing</error>',
|
|
||||||
);
|
|
||||||
|
|
||||||
$exitCode = max($exitCode, 2);
|
// candidate matched, skip to next requirement
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the first error from every failed candidate
|
||||||
|
$results = array_merge($results, $reqResults);
|
||||||
|
$exitCode = max($exitCode, 1);
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$results[] = array(
|
||||||
|
$require,
|
||||||
|
'n/a',
|
||||||
|
$links[0],
|
||||||
|
'<error>missing</error>',
|
||||||
|
);
|
||||||
|
|
||||||
|
$exitCode = max($exitCode, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -236,7 +236,7 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
$settingKey = $input->getArgument('setting-key');
|
$settingKey = $input->getArgument('setting-key');
|
||||||
if (!$settingKey) {
|
if (!$settingKey || !is_string($settingKey)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,8 +183,6 @@ EOT
|
||||||
$composer = Factory::create($io, null, $disablePlugins);
|
$composer = Factory::create($io, null, $disablePlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
$composer->getDownloadManager()->setOutputProgress(!$noProgress);
|
|
||||||
|
|
||||||
$fs = new Filesystem();
|
$fs = new Filesystem();
|
||||||
|
|
||||||
if ($noScripts === false) {
|
if ($noScripts === false) {
|
||||||
|
@ -334,8 +333,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($stability);
|
||||||
$pool->addRepository($sourceRepo);
|
$repositorySet->addRepository($sourceRepo);
|
||||||
|
|
||||||
$phpVersion = null;
|
$phpVersion = null;
|
||||||
$prettyPhpVersion = null;
|
$prettyPhpVersion = null;
|
||||||
|
@ -349,7 +348,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) {
|
||||||
|
@ -384,15 +383,17 @@ EOT
|
||||||
$package = $package->getAliasOf();
|
$package = $package->getAliasOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
$dm = $this->createDownloadManager($io, $config);
|
$factory = new Factory();
|
||||||
|
|
||||||
|
$httpDownloader = $factory->createHttpDownloader($io, $config);
|
||||||
|
$dm = $factory->createDownloadManager($io, $config, $httpDownloader);
|
||||||
$dm->setPreferSource($preferSource)
|
$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), $io);
|
||||||
$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')), array(new InstallOperation($package)));
|
||||||
$im->notifyInstalls($io);
|
$im->notifyInstalls($io);
|
||||||
|
|
||||||
// collect suggestions
|
// collect suggestions
|
||||||
|
@ -408,16 +409,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ 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;
|
||||||
|
@ -35,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;
|
||||||
|
@ -86,7 +86,7 @@ EOT
|
||||||
$config->merge(array('config' => array('secure-http' => false)));
|
$config->merge(array('config' => array('secure-http' => false)));
|
||||||
$config->prohibitUrlByConfig('http://repo.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);
|
||||||
|
@ -156,7 +156,7 @@ 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()));
|
||||||
|
|
||||||
$platformOverrides = $config->get('platform') ?: array();
|
$platformOverrides = $config->get('platform') ?: array();
|
||||||
$platformRepo = new PlatformRepository(array(), $platformOverrides);
|
$platformRepo = new PlatformRepository(array(), $platformOverrides);
|
||||||
|
@ -229,7 +229,7 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false);
|
$this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
|
||||||
} catch (TransportException $e) {
|
} 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>';
|
||||||
|
@ -256,11 +256,11 @@ EOT
|
||||||
|
|
||||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||||
try {
|
try {
|
||||||
$json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true);
|
$json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->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 . '://repo.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';
|
||||||
|
@ -288,10 +288,10 @@ EOT
|
||||||
|
|
||||||
$url = 'http://repo.packagist.org/packages.json';
|
$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().')';
|
||||||
}
|
}
|
||||||
|
@ -322,10 +322,10 @@ EOT
|
||||||
|
|
||||||
$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().')';
|
||||||
}
|
}
|
||||||
|
@ -347,7 +347,7 @@ EOT
|
||||||
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) {
|
||||||
|
@ -377,8 +377,7 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
$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'];
|
||||||
}
|
}
|
||||||
|
@ -431,7 +430,7 @@ EOT
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$versionsUtil = new Versions($config, $this->rfs);
|
$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@') {
|
||||||
|
@ -613,20 +612,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;
|
||||||
|
@ -729,7 +714,7 @@ EOT
|
||||||
/**
|
/**
|
||||||
* Check if allow_url_fopen is ON
|
* Check if allow_url_fopen is ON
|
||||||
*
|
*
|
||||||
* @return bool|string
|
* @return true|string
|
||||||
*/
|
*/
|
||||||
private function checkConnectivity()
|
private function checkConnectivity()
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\Package\CompletePackageInterface;
|
use Composer\Package\CompletePackageInterface;
|
||||||
use Composer\Repository\RepositoryInterface;
|
use Composer\Repository\RepositoryInterface;
|
||||||
use Composer\Repository\ArrayRepository;
|
use Composer\Repository\RootPackageRepository;
|
||||||
use Composer\Repository\RepositoryFactory;
|
use Composer\Repository\RepositoryFactory;
|
||||||
use Composer\Util\Platform;
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
@ -157,7 +157,7 @@ EOT
|
||||||
|
|
||||||
if ($composer) {
|
if ($composer) {
|
||||||
return array_merge(
|
return array_merge(
|
||||||
array(new ArrayRepository(array($composer->getPackage()))), // root package
|
array(new RootPackageRepository($composer->getPackage())), // root package
|
||||||
array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
|
array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
|
||||||
$composer->getRepositoryManager()->getRepositories() // remotes
|
$composer->getRepositoryManager()->getRepositories() // remotes
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -22,6 +21,7 @@ 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\ArrayInput;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
@ -42,8 +42,8 @@ class InitCommand extends BaseCommand
|
||||||
/** @var array */
|
/** @var array */
|
||||||
private $gitConfig;
|
private $gitConfig;
|
||||||
|
|
||||||
/** @var Pool[] */
|
/** @var RepositorySet[] */
|
||||||
private $pools;
|
private $repositorySets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@ -86,8 +86,8 @@ EOT
|
||||||
{
|
{
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
||||||
$whitelist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
|
$allowlist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
|
||||||
$options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist)));
|
$options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)));
|
||||||
|
|
||||||
if (isset($options['author'])) {
|
if (isset($options['author'])) {
|
||||||
$options['authors'] = $this->formatAuthors($options['author']);
|
$options['authors'] = $this->formatAuthors($options['author']);
|
||||||
|
@ -688,16 +688,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($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)
|
||||||
|
@ -733,8 +733,8 @@ EOT
|
||||||
*/
|
*/
|
||||||
private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
|
private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = 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');
|
$ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');
|
||||||
|
|
||||||
// ignore phpVersion if platform requirements are ignored
|
// ignore phpVersion if platform requirements are ignored
|
||||||
|
|
|
@ -44,7 +44,6 @@ class InstallCommand extends BaseCommand
|
||||||
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
|
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
|
||||||
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||||
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
|
|
||||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
|
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
|
||||||
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
||||||
|
@ -86,7 +85,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);
|
||||||
|
@ -108,7 +106,6 @@ EOT
|
||||||
->setDevMode(!$input->getOption('no-dev'))
|
->setDevMode(!$input->getOption('no-dev'))
|
||||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||||
->setRunScripts(!$input->getOption('no-scripts'))
|
->setRunScripts(!$input->getOption('no-scripts'))
|
||||||
->setSkipSuggest($input->getOption('no-suggest'))
|
|
||||||
->setOptimizeAutoloader($optimize)
|
->setOptimizeAutoloader($optimize)
|
||||||
->setClassMapAuthoritative($authoritative)
|
->setClassMapAuthoritative($authoritative)
|
||||||
->setApcuAutoloader($apcu)
|
->setApcuAutoloader($apcu)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\Config\JsonConfigSource;
|
use Composer\Config\JsonConfigSource;
|
||||||
|
use Composer\DependencyResolver\Request;
|
||||||
use Composer\Installer;
|
use Composer\Installer;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
|
@ -38,6 +39,7 @@ class RemoveCommand extends BaseCommand
|
||||||
->setDefinition(array(
|
->setDefinition(array(
|
||||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'),
|
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'),
|
||||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
|
new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
|
||||||
|
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
|
||||||
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-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
||||||
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||||
|
@ -92,26 +94,44 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dryRun = $input->getOption('dry-run');
|
||||||
|
$toRemove = array();
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $package) {
|
||||||
if (isset($composer[$type][$package])) {
|
if (isset($composer[$type][$package])) {
|
||||||
$json->removeLink($type, $composer[$type][$package]);
|
if ($dryRun) {
|
||||||
|
$toRemove[$type][] = $composer[$type][$package];
|
||||||
|
} else {
|
||||||
|
$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]);
|
if ($dryRun) {
|
||||||
|
$toRemove[$altType][] = $composer[$altType][$package];
|
||||||
|
} else {
|
||||||
|
$json->removeLink($altType, $composer[$altType][$package]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
|
} elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
|
||||||
foreach ($matches as $matchedPackage) {
|
foreach ($matches as $matchedPackage) {
|
||||||
$json->removeLink($type, $matchedPackage);
|
if ($dryRun) {
|
||||||
|
$toRemove[$type][] = $matchedPackage;
|
||||||
|
} else {
|
||||||
|
$json->removeLink($type, $matchedPackage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) {
|
} elseif (isset($composer[$altType]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) {
|
||||||
foreach ($matches as $matchedPackage) {
|
foreach ($matches as $matchedPackage) {
|
||||||
$io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
|
$io->writeError('<warning>' . $matchedPackage . ' 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, $matchedPackage);
|
if ($dryRun) {
|
||||||
|
$toRemove[$altType][] = $matchedPackage;
|
||||||
|
} else {
|
||||||
|
$json->removeLink($altType, $matchedPackage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +147,21 @@ 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'));
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$rootPackage = $composer->getPackage();
|
||||||
|
$links = array(
|
||||||
|
'require' => $rootPackage->getRequires(),
|
||||||
|
'require-dev' => $rootPackage->getDevRequires(),
|
||||||
|
);
|
||||||
|
foreach ($toRemove as $type => $packages) {
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
unset($links[$type][$package]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rootPackage->setRequires($links['require']);
|
||||||
|
$rootPackage->setDevRequires($links['require-dev']);
|
||||||
|
}
|
||||||
|
|
||||||
$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);
|
||||||
|
@ -146,10 +180,11 @@ EOT
|
||||||
->setClassMapAuthoritative($authoritative)
|
->setClassMapAuthoritative($authoritative)
|
||||||
->setApcuAutoloader($apcu)
|
->setApcuAutoloader($apcu)
|
||||||
->setUpdate(true)
|
->setUpdate(true)
|
||||||
->setUpdateWhitelist($packages)
|
->setUpdateAllowList($packages)
|
||||||
->setWhitelistTransitiveDependencies(!$input->getOption('no-update-with-dependencies'))
|
->setUpdateAllowTransitiveDependencies($input->getOption('no-update-with-dependencies') ? Request::UPDATE_ONLY_LISTED : Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE)
|
||||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||||
->setRunScripts(!$input->getOption('no-scripts'))
|
->setRunScripts(!$input->getOption('no-scripts'))
|
||||||
|
->setDryRun($dryRun)
|
||||||
;
|
;
|
||||||
|
|
||||||
$status = $install->run();
|
$status = $install->run();
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\DependencyResolver\Request;
|
||||||
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\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -21,6 +22,8 @@ use Composer\Installer;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\Json\JsonManipulator;
|
use Composer\Json\JsonManipulator;
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
|
use Composer\Package\Loader\ArrayLoader;
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\CompositeRepository;
|
||||||
|
@ -35,9 +38,14 @@ use Composer\Util\Silencer;
|
||||||
class RequireCommand extends InitCommand
|
class RequireCommand extends InitCommand
|
||||||
{
|
{
|
||||||
private $newlyCreated;
|
private $newlyCreated;
|
||||||
|
private $firstRequire;
|
||||||
private $json;
|
private $json;
|
||||||
private $file;
|
private $file;
|
||||||
private $composerBackup;
|
private $composerBackup;
|
||||||
|
/** @var string file name */
|
||||||
|
private $lock;
|
||||||
|
/** @var ?string contents before modification if the lock file exists */
|
||||||
|
private $lockBackup;
|
||||||
|
|
||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
|
@ -47,16 +55,19 @@ class RequireCommand extends InitCommand
|
||||||
->setDefinition(array(
|
->setDefinition(array(
|
||||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
||||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
|
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
|
||||||
|
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
|
||||||
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.'),
|
||||||
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
|
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
|
||||||
|
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
|
||||||
new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
|
new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
|
||||||
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-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
|
|
||||||
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
||||||
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||||
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
||||||
new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
|
new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
|
||||||
new InputOption('update-with-all-dependencies', null, InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
|
new InputOption('update-with-all-dependencies', null, InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
|
||||||
|
new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'),
|
||||||
|
new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-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).'),
|
||||||
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
|
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
|
||||||
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
|
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
|
||||||
|
@ -113,7 +124,9 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->json = new JsonFile($this->file);
|
$this->json = new JsonFile($this->file);
|
||||||
|
$this->lock = Factory::getLockFile($this->file);
|
||||||
$this->composerBackup = file_get_contents($this->json->getPath());
|
$this->composerBackup = file_get_contents($this->json->getPath());
|
||||||
|
$this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null;
|
||||||
|
|
||||||
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
|
// 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
|
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
|
||||||
|
@ -186,7 +199,15 @@ EOT
|
||||||
|
|
||||||
$sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');
|
$sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');
|
||||||
|
|
||||||
if (!$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) {
|
$this->firstRequire = $this->newlyCreated;
|
||||||
|
if (!$this->firstRequire) {
|
||||||
|
$composerDefinition = $this->json->read();
|
||||||
|
if (empty($composerDefinition['require']) && empty($composerDefinition['require-dev'])) {
|
||||||
|
$this->firstRequire = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$input->getOption('dry-run') && !$this->updateFileCleanly($this->json, $requirements, $requireKey, $removeKey, $sortPackages)) {
|
||||||
$composerDefinition = $this->json->read();
|
$composerDefinition = $this->json->read();
|
||||||
foreach ($requirements as $package => $version) {
|
foreach ($requirements as $package => $version) {
|
||||||
$composerDefinition[$requireKey][$package] = $version;
|
$composerDefinition[$requireKey][$package] = $version;
|
||||||
|
@ -202,51 +223,78 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $this->doUpdate($input, $output, $io, $requirements);
|
return $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->revertComposerFile(false);
|
$this->revertComposerFile(false);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements)
|
private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, $requireKey, $removeKey)
|
||||||
{
|
{
|
||||||
// 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'));
|
|
||||||
|
if ($input->getOption('dry-run')) {
|
||||||
|
$rootPackage = $composer->getPackage();
|
||||||
|
$links = array(
|
||||||
|
'require' => $rootPackage->getRequires(),
|
||||||
|
'require-dev' => $rootPackage->getDevRequires(),
|
||||||
|
);
|
||||||
|
$loader = new ArrayLoader();
|
||||||
|
$newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['description'], $requirements);
|
||||||
|
$links[$requireKey] = array_merge($links[$requireKey], $newLinks);
|
||||||
|
foreach ($requirements as $package => $constraint) {
|
||||||
|
unset($links[$removeKey][$package]);
|
||||||
|
}
|
||||||
|
$rootPackage->setRequires($links['require']);
|
||||||
|
$rootPackage->setDevRequires($links['require-dev']);
|
||||||
|
}
|
||||||
|
|
||||||
$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');
|
||||||
|
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
|
||||||
|
if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
|
||||||
|
} elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) {
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
|
||||||
|
}
|
||||||
|
|
||||||
$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);
|
||||||
|
|
||||||
$install = Installer::create($io, $composer);
|
$install = Installer::create($io, $composer);
|
||||||
|
|
||||||
$install
|
$install
|
||||||
|
->setDryRun($input->getOption('dry-run'))
|
||||||
->setVerbose($input->getOption('verbose'))
|
->setVerbose($input->getOption('verbose'))
|
||||||
->setPreferSource($input->getOption('prefer-source'))
|
->setPreferSource($input->getOption('prefer-source'))
|
||||||
->setPreferDist($input->getOption('prefer-dist'))
|
->setPreferDist($input->getOption('prefer-dist'))
|
||||||
->setDevMode($updateDevMode)
|
->setDevMode($updateDevMode)
|
||||||
->setRunScripts(!$input->getOption('no-scripts'))
|
->setRunScripts(!$input->getOption('no-scripts'))
|
||||||
->setSkipSuggest($input->getOption('no-suggest'))
|
|
||||||
->setOptimizeAutoloader($optimize)
|
->setOptimizeAutoloader($optimize)
|
||||||
->setClassMapAuthoritative($authoritative)
|
->setClassMapAuthoritative($authoritative)
|
||||||
->setApcuAutoloader($apcu)
|
->setApcuAutoloader($apcu)
|
||||||
->setUpdate(true)
|
->setUpdate(true)
|
||||||
->setUpdateWhitelist(array_keys($requirements))
|
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
|
||||||
->setWhitelistTransitiveDependencies($input->getOption('update-with-dependencies'))
|
|
||||||
->setWhitelistAllDependencies($input->getOption('update-with-all-dependencies'))
|
|
||||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||||
->setPreferStable($input->getOption('prefer-stable'))
|
->setPreferStable($input->getOption('prefer-stable'))
|
||||||
->setPreferLowest($input->getOption('prefer-lowest'))
|
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||||
|
->setDryRun($input->getOption('dry-run'))
|
||||||
;
|
;
|
||||||
|
|
||||||
|
// if no lock is present, or the file is brand new, we do not do a
|
||||||
|
// partial update as this is not supported by the Installer
|
||||||
|
if (!$this->firstRequire && $composer->getConfig()->get('lock')) {
|
||||||
|
$install->setUpdateAllowList(array_keys($requirements));
|
||||||
|
}
|
||||||
|
|
||||||
$status = $install->run();
|
$status = $install->run();
|
||||||
if ($status !== 0) {
|
if ($status !== 0 || $input->getOption('dry-run')) {
|
||||||
$this->revertComposerFile(false);
|
$this->revertComposerFile(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,9 +333,19 @@ EOT
|
||||||
if ($this->newlyCreated) {
|
if ($this->newlyCreated) {
|
||||||
$io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
|
$io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
|
||||||
unlink($this->json->getPath());
|
unlink($this->json->getPath());
|
||||||
|
if (file_exists($this->lock)) {
|
||||||
|
unlink($this->lock);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$io->writeError("\n".'<error>Installation failed, reverting '.$this->file.' to its original content.</error>');
|
$msg = ' to its ';
|
||||||
|
if ($this->lockBackup) {
|
||||||
|
$msg = ' and '.$this->lock.' to their ';
|
||||||
|
}
|
||||||
|
$io->writeError("\n".'<error>Installation failed, reverting '.$this->file.$msg.'original content.</error>');
|
||||||
file_put_contents($this->json->getPath(), $this->composerBackup);
|
file_put_contents($this->json->getPath(), $this->composerBackup);
|
||||||
|
if ($this->lockBackup) {
|
||||||
|
file_put_contents($this->lock, $this->lockBackup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hardExit) {
|
if ($hardExit) {
|
||||||
|
|
|
@ -77,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) {
|
||||||
|
@ -154,11 +154,11 @@ EOT
|
||||||
|
|
||||||
$updatingToTag = !preg_match('{^[0-9a-f]{40}$}', $updateVersion);
|
$updatingToTag = !preg_match('{^[0-9a-f]{40}$}', $updateVersion);
|
||||||
|
|
||||||
$io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $versionsUtil->getChannel()));
|
$io->write(sprintf("Upgrading 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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -23,12 +22,14 @@ use Composer\Package\Version\VersionParser;
|
||||||
use Composer\Package\Version\VersionSelector;
|
use Composer\Package\Version\VersionSelector;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
use Composer\Repository\ArrayRepository;
|
|
||||||
use Composer\Repository\ComposerRepository;
|
use Composer\Repository\ComposerRepository;
|
||||||
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\InstalledRepository;
|
||||||
use Composer\Repository\RepositoryInterface;
|
use Composer\Repository\RepositoryInterface;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\Repository\RootPackageRepository;
|
||||||
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 +53,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()
|
||||||
{
|
{
|
||||||
|
@ -152,13 +153,14 @@ EOT
|
||||||
|
|
||||||
if ($input->getOption('self')) {
|
if ($input->getOption('self')) {
|
||||||
$package = $this->getComposer()->getPackage();
|
$package = $this->getComposer()->getPackage();
|
||||||
$repos = $installedRepo = new ArrayRepository(array($package));
|
$repos = $installedRepo = new InstalledRepository(array(new RootPackageRepository($package)));
|
||||||
} elseif ($input->getOption('platform')) {
|
} elseif ($input->getOption('platform')) {
|
||||||
$repos = $installedRepo = $platformRepo;
|
$repos = $installedRepo = new InstalledRepository(array($platformRepo));
|
||||||
} elseif ($input->getOption('available')) {
|
} elseif ($input->getOption('available')) {
|
||||||
$installedRepo = $platformRepo;
|
$installedRepo = new InstalledRepository(array($platformRepo));
|
||||||
if ($composer) {
|
if ($composer) {
|
||||||
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
|
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
|
||||||
|
$installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
|
||||||
} else {
|
} else {
|
||||||
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
||||||
$repos = new CompositeRepository($defaultRepos);
|
$repos = new CompositeRepository($defaultRepos);
|
||||||
|
@ -166,15 +168,15 @@ EOT
|
||||||
}
|
}
|
||||||
} elseif ($input->getOption('all') && $composer) {
|
} elseif ($input->getOption('all') && $composer) {
|
||||||
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
|
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
$installedRepo = new CompositeRepository(array($localRepo, $platformRepo));
|
$installedRepo = new InstalledRepository(array($localRepo, $platformRepo));
|
||||||
$repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories()));
|
$repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories()));
|
||||||
} elseif ($input->getOption('all')) {
|
} elseif ($input->getOption('all')) {
|
||||||
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
||||||
$io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
|
$io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
|
||||||
$installedRepo = $platformRepo;
|
$installedRepo = new InstalledRepository(array($platformRepo));
|
||||||
$repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
|
$repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
|
||||||
} else {
|
} else {
|
||||||
$repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
$repos = $installedRepo = new InstalledRepository(array($this->getComposer()->getRepositoryManager()->getLocalRepository()));
|
||||||
$rootPkg = $this->getComposer()->getPackage();
|
$rootPkg = $this->getComposer()->getPackage();
|
||||||
if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
|
if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
|
||||||
$io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
|
$io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
|
||||||
|
@ -313,16 +315,13 @@ EOT
|
||||||
foreach ($repos as $repo) {
|
foreach ($repos as $repo) {
|
||||||
if ($repo === $platformRepo) {
|
if ($repo === $platformRepo) {
|
||||||
$type = 'platform';
|
$type = 'platform';
|
||||||
} elseif (
|
} elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) {
|
||||||
$repo === $installedRepo
|
|
||||||
|| ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
|
|
||||||
) {
|
|
||||||
$type = 'installed';
|
$type = 'installed';
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
@ -528,32 +527,27 @@ EOT
|
||||||
/**
|
/**
|
||||||
* finds a package by name and version if provided
|
* finds a package by name and version if provided
|
||||||
*
|
*
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param RepositoryInterface $repos
|
* @param RepositoryInterface $repos
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param ConstraintInterface|string $version
|
* @param ConstraintInterface|string $version
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
* @return array array(CompletePackageInterface, array of versions)
|
* @return array array(CompletePackageInterface, array of versions)
|
||||||
*/
|
*/
|
||||||
protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null)
|
protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, $name, $version = null)
|
||||||
{
|
{
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
$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('dev');
|
||||||
$pool->addRepository($repos);
|
$repositorySet->allowInstalledRepositories();
|
||||||
|
$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;
|
||||||
|
@ -563,8 +557,10 @@ EOT
|
||||||
$matches[$index] = $package->getId();
|
$matches[$index] = $package->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$pool = $repositorySet->createPoolForPackage($name);
|
||||||
|
|
||||||
// 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, $matches)) {
|
||||||
$matchedPackage = $pool->literalToPackage($preferred[0]);
|
$matchedPackage = $pool->literalToPackage($preferred[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,10 +572,10 @@ EOT
|
||||||
*
|
*
|
||||||
* @param CompletePackageInterface $package
|
* @param CompletePackageInterface $package
|
||||||
* @param array $versions
|
* @param array $versions
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param PackageInterface|null $latestPackage
|
* @param PackageInterface|null $latestPackage
|
||||||
*/
|
*/
|
||||||
protected function printPackageInfo(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
|
protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, PackageInterface $latestPackage = null)
|
||||||
{
|
{
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
@ -604,10 +600,10 @@ EOT
|
||||||
*
|
*
|
||||||
* @param CompletePackageInterface $package
|
* @param CompletePackageInterface $package
|
||||||
* @param array $versions
|
* @param array $versions
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param PackageInterface|null $latestPackage
|
* @param PackageInterface|null $latestPackage
|
||||||
*/
|
*/
|
||||||
protected function printMeta(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
|
protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, PackageInterface $latestPackage = null)
|
||||||
{
|
{
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
$io->write('<info>name</info> : ' . $package->getPrettyName());
|
$io->write('<info>name</info> : ' . $package->getPrettyName());
|
||||||
|
@ -676,19 +672,21 @@ EOT
|
||||||
*
|
*
|
||||||
* @param CompletePackageInterface $package
|
* @param CompletePackageInterface $package
|
||||||
* @param array $versions
|
* @param array $versions
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
*/
|
*/
|
||||||
protected function printVersions(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo)
|
protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo)
|
||||||
{
|
{
|
||||||
uasort($versions, 'version_compare');
|
$versions = array_keys($versions);
|
||||||
$versions = array_keys(array_reverse($versions));
|
$versions = Semver::rsort($versions);
|
||||||
|
|
||||||
// highlight installed version
|
// highlight installed version
|
||||||
if ($installedRepo->hasPackage($package)) {
|
if ($installedPackages = $installedRepo->findPackages($package->getName())) {
|
||||||
$installedVersion = $package->getPrettyVersion();
|
foreach ($installedPackages as $installedPackage) {
|
||||||
$key = array_search($installedVersion, $versions);
|
$installedVersion = $installedPackage->getPrettyVersion();
|
||||||
if (false !== $key) {
|
$key = array_search($installedVersion, $versions);
|
||||||
$versions[$key] = '<info>* ' . $installedVersion . '</info>';
|
if (false !== $key) {
|
||||||
|
$versions[$key] = '<info>* ' . $installedVersion . '</info>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,10 +750,10 @@ EOT
|
||||||
*
|
*
|
||||||
* @param CompletePackageInterface $package
|
* @param CompletePackageInterface $package
|
||||||
* @param array $versions
|
* @param array $versions
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param PackageInterface|null $latestPackage
|
* @param PackageInterface|null $latestPackage
|
||||||
*/
|
*/
|
||||||
protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $latestPackage = null)
|
protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, PackageInterface $latestPackage = null)
|
||||||
{
|
{
|
||||||
$json = array(
|
$json = array(
|
||||||
'name' => $package->getPrettyName(),
|
'name' => $package->getPrettyName(),
|
||||||
|
@ -975,15 +973,15 @@ EOT
|
||||||
/**
|
/**
|
||||||
* Generate the package tree
|
* Generate the package tree
|
||||||
*
|
*
|
||||||
* @param PackageInterface $package
|
* @param PackageInterface $package
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param RepositoryInterface $distantRepos
|
* @param RepositoryInterface $remoteRepos
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function generatePackageTree(
|
protected function generatePackageTree(
|
||||||
PackageInterface $package,
|
PackageInterface $package,
|
||||||
RepositoryInterface $installedRepo,
|
InstalledRepository $installedRepo,
|
||||||
RepositoryInterface $distantRepos
|
RepositoryInterface $remoteRepos
|
||||||
) {
|
) {
|
||||||
$requires = $package->getRequires();
|
$requires = $package->getRequires();
|
||||||
ksort($requires);
|
ksort($requires);
|
||||||
|
@ -996,7 +994,7 @@ EOT
|
||||||
'version' => $require->getPrettyConstraint(),
|
'version' => $require->getPrettyConstraint(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree);
|
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree);
|
||||||
|
|
||||||
if ($deepChildren) {
|
if ($deepChildren) {
|
||||||
$treeChildDesc['requires'] = $deepChildren;
|
$treeChildDesc['requires'] = $deepChildren;
|
||||||
|
@ -1020,10 +1018,10 @@ EOT
|
||||||
/**
|
/**
|
||||||
* Display a package tree
|
* Display a package tree
|
||||||
*
|
*
|
||||||
* @param PackageInterface|string $package
|
* @param array|string $package
|
||||||
* @param array $packagesInTree
|
* @param array $packagesInTree
|
||||||
* @param string $previousTreeBar
|
* @param string $previousTreeBar
|
||||||
* @param int $level
|
* @param int $level
|
||||||
*/
|
*/
|
||||||
protected function displayTree(
|
protected function displayTree(
|
||||||
$package,
|
$package,
|
||||||
|
@ -1032,7 +1030,7 @@ EOT
|
||||||
$level = 1
|
$level = 1
|
||||||
) {
|
) {
|
||||||
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
|
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
|
||||||
if (isset($package['requires'])) {
|
if (is_array($package) && isset($package['requires'])) {
|
||||||
$requires = $package['requires'];
|
$requires = $package['requires'];
|
||||||
$treeBar = $previousTreeBar . ' ├';
|
$treeBar = $previousTreeBar . ' ├';
|
||||||
$i = 0;
|
$i = 0;
|
||||||
|
@ -1075,22 +1073,22 @@ EOT
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param PackageInterface|string $package
|
* @param PackageInterface|string $package
|
||||||
* @param RepositoryInterface $installedRepo
|
* @param InstalledRepository $installedRepo
|
||||||
* @param RepositoryInterface $distantRepos
|
* @param RepositoryInterface $remoteRepos
|
||||||
* @param array $packagesInTree
|
* @param array $packagesInTree
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function addTree(
|
protected function addTree(
|
||||||
$name,
|
$name,
|
||||||
$package,
|
$package,
|
||||||
RepositoryInterface $installedRepo,
|
InstalledRepository $installedRepo,
|
||||||
RepositoryInterface $distantRepos,
|
RepositoryInterface $remoteRepos,
|
||||||
array $packagesInTree
|
array $packagesInTree
|
||||||
) {
|
) {
|
||||||
$children = array();
|
$children = array();
|
||||||
list($package, $versions) = $this->getPackage(
|
list($package, $versions) = $this->getPackage(
|
||||||
$installedRepo,
|
$installedRepo,
|
||||||
$distantRepos,
|
$remoteRepos,
|
||||||
$name,
|
$name,
|
||||||
$package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()
|
$package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()
|
||||||
);
|
);
|
||||||
|
@ -1107,7 +1105,7 @@ EOT
|
||||||
|
|
||||||
if (!in_array($requireName, $currentTree, true)) {
|
if (!in_array($requireName, $currentTree, true)) {
|
||||||
$currentTree[] = $requireName;
|
$currentTree[] = $requireName;
|
||||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree);
|
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree);
|
||||||
if ($deepChildren) {
|
if ($deepChildren) {
|
||||||
$treeChildDesc['requires'] = $deepChildren;
|
$treeChildDesc['requires'] = $deepChildren;
|
||||||
}
|
}
|
||||||
|
@ -1165,13 +1163,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])) {
|
||||||
|
@ -1195,13 +1193,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($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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,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) {
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Repository\RootPackageRepository;
|
||||||
|
use Composer\Repository\CompositeRepository;
|
||||||
|
use Composer\Installer\SuggestedPackagesReporter;
|
||||||
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;
|
||||||
|
@ -26,8 +29,10 @@ class SuggestsCommand extends BaseCommand
|
||||||
->setName('suggests')
|
->setName('suggests')
|
||||||
->setDescription('Shows package suggestions.')
|
->setDescription('Shows package suggestions.')
|
||||||
->setDefinition(array(
|
->setDefinition(array(
|
||||||
new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package'),
|
new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'),
|
||||||
new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
|
new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
|
||||||
|
new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'),
|
||||||
|
new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'),
|
||||||
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.'),
|
||||||
))
|
))
|
||||||
|
@ -36,118 +41,66 @@ class SuggestsCommand extends BaseCommand
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Read more at https://getcomposer.org/doc/03-cli.md#suggests
|
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();
|
$composer = $this->getComposer();
|
||||||
|
|
||||||
if (empty($lock)) {
|
$installedRepos = array(
|
||||||
throw new \RuntimeException('Lockfile seems to be empty?');
|
new RootPackageRepository(clone $composer->getPackage()),
|
||||||
|
);
|
||||||
|
|
||||||
|
$locker = $composer->getLocker();
|
||||||
|
if ($locker->isLocked()) {
|
||||||
|
$installedRepos[] = new PlatformRepository(array(), $locker->getPlatformOverrides());
|
||||||
|
$installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev'));
|
||||||
|
} else {
|
||||||
|
$installedRepos[] = new PlatformRepository(array(), $composer->getConfig()->get('platform') ?: array());
|
||||||
|
$installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
$packages = $lock['packages'];
|
$installedRepo = new CompositeRepository($installedRepos);
|
||||||
|
$reporter = new SuggestedPackagesReporter($this->getIO());
|
||||||
if (!$input->getOption('no-dev')) {
|
|
||||||
$packages += $lock['packages-dev'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$filter = $input->getArgument('packages');
|
$filter = $input->getArgument('packages');
|
||||||
|
if (empty($filter) && !$input->getOption('all')) {
|
||||||
// First assemble lookup list of packages that are installed, replaced or provided
|
$filter = array_map(function ($link) {
|
||||||
$installed = array();
|
return $link->getTarget();
|
||||||
foreach ($packages as $package) {
|
}, array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()));
|
||||||
$installed[] = $package['name'];
|
|
||||||
|
|
||||||
if (!empty($package['provide'])) {
|
|
||||||
$installed = array_merge($installed, array_keys($package['provide']));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($package['replace'])) {
|
|
||||||
$installed = array_merge($installed, array_keys($package['replace']));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
foreach ($installedRepo->getPackages() as $package) {
|
||||||
// Undub and sort the install list into a sorted lookup array
|
if (!empty($filter) && !in_array($package->getName(), $filter)) {
|
||||||
$installed = array_flip($installed);
|
|
||||||
ksort($installed);
|
|
||||||
|
|
||||||
// Init platform repo
|
|
||||||
$platform = new PlatformRepository(array(), $this->getComposer()->getConfig()->get('platform') ?: array());
|
|
||||||
|
|
||||||
// Next gather all suggestions that are not in that list
|
|
||||||
$suggesters = array();
|
|
||||||
$suggested = array();
|
|
||||||
foreach ($packages as $package) {
|
|
||||||
$packageName = $package['name'];
|
|
||||||
if ((!empty($filter) && !in_array($packageName, $filter)) || empty($package['suggest'])) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($package['suggest'] as $suggestion => $reason) {
|
|
||||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!isset($installed[$suggestion])) {
|
|
||||||
$suggesters[$packageName][$suggestion] = $reason;
|
|
||||||
$suggested[$suggestion][$packageName] = $reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ksort($suggesters);
|
|
||||||
ksort($suggested);
|
|
||||||
|
|
||||||
// Determine output mode
|
$reporter->addSuggestionsFromPackage($package);
|
||||||
$mode = 0;
|
}
|
||||||
|
|
||||||
|
// Determine output mode, default is by-package
|
||||||
|
$mode = SuggestedPackagesReporter::MODE_BY_PACKAGE;
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
if ($input->getOption('by-package') || $io->isVerbose()) {
|
// if by-suggestion is given we override the default
|
||||||
$mode |= 1;
|
|
||||||
}
|
|
||||||
if ($input->getOption('by-suggestion')) {
|
if ($input->getOption('by-suggestion')) {
|
||||||
$mode |= 2;
|
$mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION;
|
||||||
|
}
|
||||||
|
// unless by-package is also present then we enable both
|
||||||
|
if ($input->getOption('by-package')) {
|
||||||
|
$mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE;
|
||||||
|
}
|
||||||
|
// list is exclusive and overrides everything else
|
||||||
|
if ($input->getOption('list')) {
|
||||||
|
$mode = SuggestedPackagesReporter::MODE_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple mode
|
$reporter->output($mode, $installedRepo);
|
||||||
if ($mode === 0) {
|
|
||||||
foreach (array_keys($suggested) as $suggestion) {
|
|
||||||
$io->write(sprintf('<info>%s</info>', $suggestion));
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grouped by package
|
|
||||||
if ($mode & 1) {
|
|
||||||
foreach ($suggesters as $suggester => $suggestions) {
|
|
||||||
$io->write(sprintf('<comment>%s</comment> suggests:', $suggester));
|
|
||||||
|
|
||||||
foreach ($suggestions as $suggestion => $reason) {
|
|
||||||
$io->write(sprintf(' - <info>%s</info>: %s', $suggestion, $reason ?: '*'));
|
|
||||||
}
|
|
||||||
$io->write('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grouped by suggestion
|
|
||||||
if ($mode & 2) {
|
|
||||||
// Improve readability in full mode
|
|
||||||
if ($mode & 1) {
|
|
||||||
$io->write(str_repeat('-', 78));
|
|
||||||
}
|
|
||||||
foreach ($suggested as $suggestion => $suggesters) {
|
|
||||||
$io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));
|
|
||||||
|
|
||||||
foreach ($suggesters as $suggester => $reason) {
|
|
||||||
$io->write(sprintf(' - <info>%s</info>: %s', $suggester, $reason ?: '*'));
|
|
||||||
}
|
|
||||||
$io->write('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\Composer;
|
use Composer\Composer;
|
||||||
|
use Composer\DependencyResolver\Request;
|
||||||
use Composer\Installer;
|
use Composer\Installer;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
|
@ -48,9 +49,8 @@ class UpdateCommand extends BaseCommand
|
||||||
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
|
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
|
||||||
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||||
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
|
new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements.'),
|
||||||
new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also dependencies of whitelisted packages to the whitelist, except those defined in root package.'),
|
new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements.'),
|
||||||
new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist, including those defined in root package.'),
|
|
||||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
||||||
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
||||||
|
@ -121,7 +121,18 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
// the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead
|
||||||
|
// they are further mutually exclusive with listing actual package names
|
||||||
|
$filteredPackages = array_filter($packages, function ($package) {
|
||||||
|
return !in_array($package, array('lock', 'nothing', 'mirrors'), true);
|
||||||
|
});
|
||||||
|
$updateMirrors = $input->getOption('lock') || count($filteredPackages) != count($packages);
|
||||||
|
$packages = $filteredPackages;
|
||||||
|
|
||||||
|
if ($updateMirrors && !empty($packages)) {
|
||||||
|
$io->writeError('<error>You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.</error>');
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
$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);
|
||||||
|
@ -135,6 +146,13 @@ EOT
|
||||||
$authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
|
$authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
|
||||||
$apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');
|
$apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');
|
||||||
|
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
|
||||||
|
if ($input->getOption('with-all-dependencies')) {
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
|
||||||
|
} elseif ($input->getOption('with-dependencies')) {
|
||||||
|
$updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
|
||||||
|
}
|
||||||
|
|
||||||
$install
|
$install
|
||||||
->setDryRun($input->getOption('dry-run'))
|
->setDryRun($input->getOption('dry-run'))
|
||||||
->setVerbose($input->getOption('verbose'))
|
->setVerbose($input->getOption('verbose'))
|
||||||
|
@ -143,14 +161,13 @@ EOT
|
||||||
->setDevMode(!$input->getOption('no-dev'))
|
->setDevMode(!$input->getOption('no-dev'))
|
||||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||||
->setRunScripts(!$input->getOption('no-scripts'))
|
->setRunScripts(!$input->getOption('no-scripts'))
|
||||||
->setSkipSuggest($input->getOption('no-suggest'))
|
|
||||||
->setOptimizeAutoloader($optimize)
|
->setOptimizeAutoloader($optimize)
|
||||||
->setClassMapAuthoritative($authoritative)
|
->setClassMapAuthoritative($authoritative)
|
||||||
->setApcuAutoloader($apcu)
|
->setApcuAutoloader($apcu)
|
||||||
->setUpdate(true)
|
->setUpdate(true)
|
||||||
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages)
|
->setUpdateMirrors($updateMirrors)
|
||||||
->setWhitelistTransitiveDependencies($input->getOption('with-dependencies'))
|
->setUpdateAllowList($packages)
|
||||||
->setWhitelistAllDependencies($input->getOption('with-all-dependencies'))
|
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
|
||||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||||
->setPreferStable($input->getOption('prefer-stable'))
|
->setPreferStable($input->getOption('prefer-stable'))
|
||||||
->setPreferLowest($input->getOption('prefer-lowest'))
|
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||||
|
|
|
@ -124,6 +124,7 @@ class Compiler
|
||||||
->in(__DIR__.'/../../vendor/composer/ca-bundle/')
|
->in(__DIR__.'/../../vendor/composer/ca-bundle/')
|
||||||
->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
|
->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
|
||||||
->in(__DIR__.'/../../vendor/psr/')
|
->in(__DIR__.'/../../vendor/psr/')
|
||||||
|
->in(__DIR__.'/../../vendor/react/')
|
||||||
->sort($finderSort)
|
->sort($finderSort)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Composer
|
||||||
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 = '1.10-dev+source';
|
const SOURCE_VERSION = '2.0-dev+source';
|
||||||
|
|
||||||
public static function getVersion()
|
public static function getVersion()
|
||||||
{
|
{
|
||||||
|
|
|
@ -265,7 +265,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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -230,6 +230,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
|
||||||
|
@ -292,7 +298,7 @@ class Application extends BaseApplication
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -44,54 +44,33 @@ class DefaultPolicy implements PolicyInterface
|
||||||
return $constraint->matchSpecific($version, true);
|
return $constraint->matchSpecific($version, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
|
public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null)
|
||||||
{
|
{
|
||||||
$packages = array();
|
$packages = $this->groupLiteralsByName($pool, $literals);
|
||||||
|
|
||||||
foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
|
foreach ($packages as &$nameLiterals) {
|
||||||
if ($candidate !== $package) {
|
|
||||||
$packages[] = $candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPriority(Pool $pool, PackageInterface $package)
|
|
||||||
{
|
|
||||||
return $pool->getPriority($package->getRepository());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
|
|
||||||
{
|
|
||||||
$packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);
|
|
||||||
|
|
||||||
foreach ($packages as &$literals) {
|
|
||||||
$policy = $this;
|
$policy = $this;
|
||||||
usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
|
usort($nameLiterals, function ($a, $b) use ($policy, $pool, $requiredPackage) {
|
||||||
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
|
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($packages as &$literals) {
|
foreach ($packages as &$sortedLiterals) {
|
||||||
$literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
|
$sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals);
|
||||||
|
$sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
|
||||||
$literals = $this->pruneToBestVersion($pool, $literals);
|
|
||||||
|
|
||||||
$literals = $this->pruneRemoteAliases($pool, $literals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$selected = call_user_func_array('array_merge', $packages);
|
$selected = call_user_func_array('array_merge', $packages);
|
||||||
|
|
||||||
// now sort the result across all packages to respect replaces across packages
|
// now sort the result across all packages to respect replaces across packages
|
||||||
usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
|
usort($selected, function ($a, $b) use ($policy, $pool, $requiredPackage) {
|
||||||
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
|
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $selected;
|
return $selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
|
protected function groupLiteralsByName(Pool $pool, $literals)
|
||||||
{
|
{
|
||||||
$packages = array();
|
$packages = array();
|
||||||
foreach ($literals as $literal) {
|
foreach ($literals as $literal) {
|
||||||
|
@ -100,12 +79,7 @@ class DefaultPolicy implements PolicyInterface
|
||||||
if (!isset($packages[$packageName])) {
|
if (!isset($packages[$packageName])) {
|
||||||
$packages[$packageName] = array();
|
$packages[$packageName] = array();
|
||||||
}
|
}
|
||||||
|
$packages[$packageName][] = $literal;
|
||||||
if (isset($installedMap[abs($literal)])) {
|
|
||||||
array_unshift($packages[$packageName], $literal);
|
|
||||||
} else {
|
|
||||||
$packages[$packageName][] = $literal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $packages;
|
return $packages;
|
||||||
|
@ -114,61 +88,49 @@ class DefaultPolicy implements PolicyInterface
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
|
public function compareByPriority(Pool $pool, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
|
||||||
{
|
{
|
||||||
if ($a->getRepository() === $b->getRepository()) {
|
// prefer aliases to the original package
|
||||||
// prefer aliases to the original package
|
if ($a->getName() === $b->getName()) {
|
||||||
if ($a->getName() === $b->getName()) {
|
$aAliased = $a instanceof AliasPackage;
|
||||||
$aAliased = $a instanceof AliasPackage;
|
$bAliased = $b instanceof AliasPackage;
|
||||||
$bAliased = $b instanceof AliasPackage;
|
if ($aAliased && !$bAliased) {
|
||||||
if ($aAliased && !$bAliased) {
|
return -1; // use a
|
||||||
return -1; // use a
|
|
||||||
}
|
|
||||||
if (!$aAliased && $bAliased) {
|
|
||||||
return 1; // use b
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!$aAliased && $bAliased) {
|
||||||
if (!$ignoreReplace) {
|
return 1; // use b
|
||||||
// return original, not replaced
|
|
||||||
if ($this->replaces($a, $b)) {
|
|
||||||
return 1; // use b
|
|
||||||
}
|
|
||||||
if ($this->replaces($b, $a)) {
|
|
||||||
return -1; // use a
|
|
||||||
}
|
|
||||||
|
|
||||||
// for replacers not replacing each other, put a higher prio on replacing
|
|
||||||
// packages with the same vendor as the required package
|
|
||||||
if ($requiredPackage && false !== ($pos = strpos($requiredPackage, '/'))) {
|
|
||||||
$requiredVendor = substr($requiredPackage, 0, $pos);
|
|
||||||
|
|
||||||
$aIsSameVendor = substr($a->getName(), 0, $pos) === $requiredVendor;
|
|
||||||
$bIsSameVendor = substr($b->getName(), 0, $pos) === $requiredVendor;
|
|
||||||
|
|
||||||
if ($bIsSameVendor !== $aIsSameVendor) {
|
|
||||||
return $aIsSameVendor ? -1 : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// priority equal, sort by package id to make reproducible
|
|
||||||
if ($a->id === $b->id) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($a->id < $b->id) ? -1 : 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($installedMap[$a->id])) {
|
if (!$ignoreReplace) {
|
||||||
return -1;
|
// return original, not replaced
|
||||||
|
if ($this->replaces($a, $b)) {
|
||||||
|
return 1; // use b
|
||||||
|
}
|
||||||
|
if ($this->replaces($b, $a)) {
|
||||||
|
return -1; // use a
|
||||||
|
}
|
||||||
|
|
||||||
|
// for replacers not replacing each other, put a higher prio on replacing
|
||||||
|
// packages with the same vendor as the required package
|
||||||
|
if ($requiredPackage && false !== ($pos = strpos($requiredPackage, '/'))) {
|
||||||
|
$requiredVendor = substr($requiredPackage, 0, $pos);
|
||||||
|
|
||||||
|
$aIsSameVendor = substr($a->getName(), 0, $pos) === $requiredVendor;
|
||||||
|
$bIsSameVendor = substr($b->getName(), 0, $pos) === $requiredVendor;
|
||||||
|
|
||||||
|
if ($bIsSameVendor !== $aIsSameVendor) {
|
||||||
|
return $aIsSameVendor ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($installedMap[$b->id])) {
|
// priority equal, sort by package id to make reproducible
|
||||||
return 1;
|
if ($a->id === $b->id) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1;
|
return ($a->id < $b->id) ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,37 +180,6 @@ class DefaultPolicy implements PolicyInterface
|
||||||
return $bestLiterals;
|
return $bestLiterals;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Assumes that installed packages come first and then all highest priority packages
|
|
||||||
*/
|
|
||||||
protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
|
|
||||||
{
|
|
||||||
$selected = array();
|
|
||||||
|
|
||||||
$priority = null;
|
|
||||||
|
|
||||||
foreach ($literals as $literal) {
|
|
||||||
$package = $pool->literalToPackage($literal);
|
|
||||||
|
|
||||||
if (isset($installedMap[$package->id])) {
|
|
||||||
$selected[] = $literal;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $priority) {
|
|
||||||
$priority = $this->getPriority($pool, $package);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getPriority($pool, $package) != $priority) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$selected[] = $literal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
|
* Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
|
||||||
*
|
*
|
||||||
|
|
|
@ -23,14 +23,13 @@ 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
|
|
||||||
*/
|
*/
|
||||||
public function __construct(array $literals, $reason, $reasonData, $job = null)
|
public function __construct(array $literals, $reason, $reasonData)
|
||||||
{
|
{
|
||||||
parent::__construct($reason, $reasonData, $job);
|
parent::__construct($reason, $reasonData);
|
||||||
|
|
||||||
// sort all packages ascending by id
|
// sort all packages ascending by id
|
||||||
sort($literals);
|
sort($literals);
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?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\DependencyResolver\Operation\MarkAliasUninstalledOperation;
|
||||||
|
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\Link;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Repository\RepositoryInterface;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class LocalRepoTransaction extends Transaction
|
||||||
|
{
|
||||||
|
public function __construct(RepositoryInterface $lockedRepository, $localRepository)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
$localRepository->getPackages(),
|
||||||
|
$lockedRepository->getPackages()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
<?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\DependencyResolver\Operation\OperationInterface;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\RootAliasPackage;
|
||||||
|
use Composer\Package\RootPackageInterface;
|
||||||
|
use Composer\Repository\ArrayRepository;
|
||||||
|
use Composer\Repository\RepositoryInterface;
|
||||||
|
use Composer\Test\Repository\ArrayRepositoryTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class LockTransaction extends Transaction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* packages in current lock file, platform repo or otherwise present
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $presentMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packages which cannot be mapped, platform repo, root package, other fixed repos
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $unlockableMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $resultPackages;
|
||||||
|
|
||||||
|
public function __construct(Pool $pool, $presentMap, $unlockableMap, $decisions)
|
||||||
|
{
|
||||||
|
$this->presentMap = $presentMap;
|
||||||
|
$this->unlockableMap = $unlockableMap;
|
||||||
|
|
||||||
|
$this->setResultPackages($pool, $decisions);
|
||||||
|
parent::__construct($this->presentMap, $this->resultPackages['all']);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this a bit prettier instead of the two text indexes?
|
||||||
|
public function setResultPackages(Pool $pool, Decisions $decisions)
|
||||||
|
{
|
||||||
|
$this->resultPackages = array('all' => array(), 'non-dev' => array(), 'dev' => array());
|
||||||
|
foreach ($decisions as $i => $decision) {
|
||||||
|
$literal = $decision[Decisions::DECISION_LITERAL];
|
||||||
|
|
||||||
|
if ($literal > 0) {
|
||||||
|
$package = $pool->literalToPackage($literal);
|
||||||
|
$this->resultPackages['all'][] = $package;
|
||||||
|
if (!isset($this->unlockableMap[$package->id])) {
|
||||||
|
$this->resultPackages['non-dev'][] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNonDevPackages(LockTransaction $extractionResult)
|
||||||
|
{
|
||||||
|
$packages = $extractionResult->getNewLockPackages(false);
|
||||||
|
|
||||||
|
$this->resultPackages['dev'] = $this->resultPackages['non-dev'];
|
||||||
|
$this->resultPackages['non-dev'] = array();
|
||||||
|
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
foreach ($this->resultPackages['dev'] as $i => $resultPackage) {
|
||||||
|
// TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible?
|
||||||
|
if ($package->getName() == $resultPackage->getName()) {
|
||||||
|
$this->resultPackages['non-dev'][] = $resultPackage;
|
||||||
|
unset($this->resultPackages['dev'][$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO additionalFixedRepository needs to be looked at here as well?
|
||||||
|
public function getNewLockPackages($devMode, $updateMirrors = false)
|
||||||
|
{
|
||||||
|
$packages = array();
|
||||||
|
foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) {
|
||||||
|
if (!($package instanceof AliasPackage) && !($package instanceof RootAliasPackage)) {
|
||||||
|
// if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is
|
||||||
|
// we do not reset references if the currently present package didn't have any, or if the type of VCS has changed
|
||||||
|
if ($updateMirrors && !isset($this->presentMap[spl_object_hash($package)])) {
|
||||||
|
foreach ($this->presentMap as $presentPackage) {
|
||||||
|
if ($package->getName() == $presentPackage->getName() &&
|
||||||
|
$package->getVersion() == $presentPackage->getVersion() &&
|
||||||
|
$presentPackage->getSourceReference() &&
|
||||||
|
$presentPackage->getSourceType() === $package->getSourceType()
|
||||||
|
) {
|
||||||
|
$package->setSourceDistReferences($presentPackage->getSourceReference());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$packages[] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks which of the given aliases from composer.json are actually in use for the lock file
|
||||||
|
*/
|
||||||
|
public function getAliases($aliases)
|
||||||
|
{
|
||||||
|
$usedAliases = array();
|
||||||
|
|
||||||
|
foreach ($this->resultPackages['all'] as $package) {
|
||||||
|
if ($package instanceof AliasPackage) {
|
||||||
|
if (isset($aliases[$package->getName()])) {
|
||||||
|
$usedAliases[$package->getName()] = $aliases[$package->getName()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $usedAliases;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?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\PackageInterface;
|
||||||
|
use Composer\Package\Link;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*
|
||||||
|
* MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
|
||||||
|
*/
|
||||||
|
class MultiConflictRule extends Rule
|
||||||
|
{
|
||||||
|
protected $literals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $literals
|
||||||
|
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||||
|
* @param Link|PackageInterface $reasonData
|
||||||
|
*/
|
||||||
|
public function __construct(array $literals, $reason, $reasonData)
|
||||||
|
{
|
||||||
|
parent::__construct($reason, $reasonData);
|
||||||
|
|
||||||
|
if (count($literals) < 3) {
|
||||||
|
throw new \RuntimeException("multi conflict rule requires at least 3 literals");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort all packages ascending by id
|
||||||
|
sort($literals);
|
||||||
|
|
||||||
|
$this->literals = $literals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLiterals()
|
||||||
|
{
|
||||||
|
return $this->literals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash()
|
||||||
|
{
|
||||||
|
$data = unpack('ihash', md5('c:'.implode(',', $this->literals), true));
|
||||||
|
|
||||||
|
return $data['hash'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this rule is equal to another one
|
||||||
|
*
|
||||||
|
* Ignores whether either of the rules is disabled.
|
||||||
|
*
|
||||||
|
* @param Rule $rule The rule to check against
|
||||||
|
* @return bool Whether the rules are equal
|
||||||
|
*/
|
||||||
|
public function equals(Rule $rule)
|
||||||
|
{
|
||||||
|
if ($rule instanceof MultiConflictRule) {
|
||||||
|
return $this->literals === $rule->getLiterals();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAssertion()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a rule as a string of the format (Literal1|Literal2|...)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
// TODO multi conflict?
|
||||||
|
$result = $this->isDisabled() ? 'disabled(multi(' : '(multi(';
|
||||||
|
|
||||||
|
foreach ($this->literals as $i => $literal) {
|
||||||
|
if ($i != 0) {
|
||||||
|
$result .= '|';
|
||||||
|
}
|
||||||
|
$result .= $literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result .= '))';
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,20 +47,28 @@ class InstallOperation extends SolverOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType()
|
public function getOperationType()
|
||||||
{
|
{
|
||||||
return 'install';
|
return 'install';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function show($lock)
|
||||||
|
{
|
||||||
|
return ($lock ? 'Locking ' : 'Installing ').$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
|
return $this->show(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,20 +48,28 @@ class MarkAliasInstalledOperation extends SolverOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType()
|
public function getOperationType()
|
||||||
{
|
{
|
||||||
return 'markAliasInstalled';
|
return 'markAliasInstalled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function show($lock)
|
||||||
|
{
|
||||||
|
return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
|
return $this->show(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,20 +48,28 @@ class MarkAliasUninstalledOperation extends SolverOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType()
|
public function getOperationType()
|
||||||
{
|
{
|
||||||
return 'markAliasUninstalled';
|
return 'markAliasUninstalled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function show($lock)
|
||||||
|
{
|
||||||
|
return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
|
return $this->show(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ namespace Composer\DependencyResolver\Operation;
|
||||||
interface OperationInterface
|
interface OperationInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType();
|
public function getOperationType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns operation reason.
|
* Returns operation reason.
|
||||||
|
@ -33,6 +33,14 @@ interface OperationInterface
|
||||||
*/
|
*/
|
||||||
public function getReason();
|
public function getReason();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the operation in a human readable format
|
||||||
|
*
|
||||||
|
* @param $lock bool Whether this is an operation on the lock file
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function show($lock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the operation in a human readable format
|
* Serializes the operation in a human readable format
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,8 +43,9 @@ abstract class SolverOperation implements OperationInterface
|
||||||
return $this->reason;
|
return $this->reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function formatVersion(PackageInterface $package)
|
/**
|
||||||
{
|
* @param $lock bool Whether this is an operation on the lock file
|
||||||
return $package->getFullPrettyVersion();
|
* @return string
|
||||||
}
|
*/
|
||||||
|
abstract public function show($lock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,20 +47,28 @@ class UninstallOperation extends SolverOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType()
|
public function getOperationType()
|
||||||
{
|
{
|
||||||
return 'uninstall';
|
return 'uninstall';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function show($lock)
|
||||||
|
{
|
||||||
|
return 'Removing '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
|
return $this->show(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,23 +61,41 @@ class UpdateOperation extends SolverOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns job type.
|
* Returns operation type.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getJobType()
|
public function getOperationType()
|
||||||
{
|
{
|
||||||
return 'update';
|
return 'update';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function show($lock)
|
||||||
|
{
|
||||||
|
$fromVersion = $this->initialPackage->getFullPrettyVersion();
|
||||||
|
$toVersion = $this->targetPackage->getFullPrettyVersion();
|
||||||
|
|
||||||
|
if ($fromVersion === $toVersion && $this->initialPackage->getSourceReference() !== $this->targetPackage->getSourceReference()) {
|
||||||
|
$fromVersion = $this->initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
|
||||||
|
$toVersion = $this->targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
|
||||||
|
} elseif ($fromVersion === $toVersion && $this->initialPackage->getDistReference() !== $this->targetPackage->getDistReference()) {
|
||||||
|
$fromVersion = $this->initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
|
||||||
|
$toVersion = $this->targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
|
||||||
|
}
|
||||||
|
|
||||||
|
$actionName = VersionParser::isUpgrade($this->initialPackage->getVersion(), $this->targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading';
|
||||||
|
|
||||||
|
return $actionName.' '.$this->initialPackage->getPrettyName().' ('.$fromVersion.' => '.$toVersion.')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
$actionName = VersionParser::isUpgrade($this->initialPackage->getVersion(), $this->targetPackage->getVersion()) ? 'Updating' : 'Downgrading';
|
return $this->show(false);
|
||||||
|
|
||||||
return $actionName.' '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '.
|
|
||||||
$this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,5 @@ use Composer\Package\PackageInterface;
|
||||||
interface PolicyInterface
|
interface PolicyInterface
|
||||||
{
|
{
|
||||||
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
|
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
|
||||||
|
public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null);
|
||||||
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);
|
|
||||||
|
|
||||||
public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,143 +12,56 @@
|
||||||
|
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
use Composer\Package\BasePackage;
|
|
||||||
use Composer\Package\AliasPackage;
|
use Composer\Package\AliasPackage;
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
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;
|
||||||
use Composer\Repository\RepositoryInterface;
|
|
||||||
use Composer\Repository\CompositeRepository;
|
|
||||||
use Composer\Repository\ComposerRepository;
|
|
||||||
use Composer\Repository\InstalledRepositoryInterface;
|
|
||||||
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>
|
||||||
*/
|
*/
|
||||||
class Pool implements \Countable
|
class Pool implements \Countable
|
||||||
{
|
{
|
||||||
const MATCH_NAME = -1;
|
|
||||||
const MATCH_NONE = 0;
|
const MATCH_NONE = 0;
|
||||||
const MATCH = 1;
|
const MATCH = 1;
|
||||||
const MATCH_PROVIDE = 2;
|
const MATCH_PROVIDE = 2;
|
||||||
const MATCH_REPLACE = 3;
|
const MATCH_REPLACE = 3;
|
||||||
const MATCH_FILTERED = 4;
|
|
||||||
|
|
||||||
protected $repositories = 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 $stabilityFlags;
|
|
||||||
protected $versionParser;
|
protected $versionParser;
|
||||||
protected $providerCache = array();
|
protected $providerCache = array();
|
||||||
protected $filterRequires;
|
protected $unacceptableFixedPackages;
|
||||||
protected $whitelist = null;
|
|
||||||
protected $id = 1;
|
|
||||||
|
|
||||||
public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
|
public function __construct(array $packages = array(), array $unacceptableFixedPackages = array())
|
||||||
{
|
{
|
||||||
$this->versionParser = new VersionParser;
|
$this->versionParser = new VersionParser;
|
||||||
$this->acceptableStabilities = array();
|
$this->setPackages($packages);
|
||||||
foreach (BasePackage::$stabilities as $stability => $value) {
|
$this->unacceptableFixedPackages = $unacceptableFixedPackages;
|
||||||
if ($value <= BasePackage::$stabilities[$minimumStability]) {
|
|
||||||
$this->acceptableStabilities[$stability] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->stabilityFlags = $stabilityFlags;
|
|
||||||
$this->filterRequires = $filterRequires;
|
|
||||||
foreach ($filterRequires as $name => $constraint) {
|
|
||||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
|
|
||||||
unset($this->filterRequires[$name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setWhitelist($whitelist)
|
private function setPackages(array $packages)
|
||||||
{
|
{
|
||||||
$this->whitelist = $whitelist;
|
$id = 1;
|
||||||
$this->providerCache = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
foreach ($packages as $package) {
|
||||||
* Adds a repository and its packages to this package pool
|
$this->packages[] = $package;
|
||||||
*
|
|
||||||
* @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) {
|
$package->id = $id++;
|
||||||
$this->repositories[] = $repo;
|
$this->packageByExactName[$package->getName()][$package->id] = $package;
|
||||||
|
|
||||||
$exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
|
foreach ($package->getNames() as $provided) {
|
||||||
|
$this->packageByName[$provided][] = $package;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the package object for a given package id.
|
* Retrieves the package object for a given package id.
|
||||||
*
|
*
|
||||||
|
@ -176,104 +89,52 @@ class Pool implements \Countable
|
||||||
* packages must match or null to return all
|
* packages must match or null to return all
|
||||||
* @param bool $mustMatchName Whether the name of returned packages
|
* @param bool $mustMatchName Whether the name of returned packages
|
||||||
* must match the given name
|
* must match the given name
|
||||||
* @param bool $bypassFilters If enabled, filterRequires and stability matching is ignored
|
|
||||||
* @return PackageInterface[] A set of packages
|
* @return PackageInterface[] A set of packages
|
||||||
*/
|
*/
|
||||||
public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false, $bypassFilters = false)
|
public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false)
|
||||||
{
|
{
|
||||||
if ($bypassFilters) {
|
|
||||||
return $this->computeWhatProvides($name, $constraint, $mustMatchName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = ((int) $mustMatchName).$constraint;
|
$key = ((int) $mustMatchName).$constraint;
|
||||||
if (isset($this->providerCache[$name][$key])) {
|
if (isset($this->providerCache[$name][$key])) {
|
||||||
return $this->providerCache[$name][$key];
|
return $this->providerCache[$name][$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName, $bypassFilters);
|
return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see whatProvides
|
* @see whatProvides
|
||||||
*/
|
*/
|
||||||
private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false)
|
private function computeWhatProvides($name, $constraint, $mustMatchName = false)
|
||||||
{
|
{
|
||||||
$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 = array();
|
||||||
$nameMatch = false;
|
|
||||||
|
|
||||||
foreach ($candidates as $candidate) {
|
foreach ($candidates as $candidate) {
|
||||||
$aliasOfCandidate = null;
|
switch ($this->match($candidate, $name, $constraint)) {
|
||||||
|
|
||||||
// alias packages are not white listed, make sure that the package
|
|
||||||
// being aliased is white listed
|
|
||||||
if ($candidate instanceof AliasPackage) {
|
|
||||||
$aliasOfCandidate = $candidate->getAliasOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->whitelist !== null && !$bypassFilters && (
|
|
||||||
(!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
|
|
||||||
($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
|
|
||||||
)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
switch ($this->match($candidate, $name, $constraint, $bypassFilters)) {
|
|
||||||
case self::MATCH_NONE:
|
case self::MATCH_NONE:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case self::MATCH_NAME:
|
|
||||||
$nameMatch = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case self::MATCH:
|
case self::MATCH:
|
||||||
$nameMatch = true;
|
|
||||||
$matches[] = $candidate;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case self::MATCH_PROVIDE:
|
case self::MATCH_PROVIDE:
|
||||||
$provideMatches[] = $candidate;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case self::MATCH_REPLACE:
|
case self::MATCH_REPLACE:
|
||||||
$matches[] = $candidate;
|
$matches[] = $candidate;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case self::MATCH_FILTERED:
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \UnexpectedValueException('Unexpected match type');
|
throw new \UnexpectedValueException('Unexpected match type');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a package with the required name exists, we ignore providers
|
return $matches;
|
||||||
if ($nameMatch) {
|
|
||||||
return $matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_merge($matches, $provideMatches);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function literalToPackage($literal)
|
public function literalToPackage($literal)
|
||||||
|
@ -296,23 +157,6 @@ 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
|
||||||
|
@ -322,27 +166,19 @@ class Pool implements \Countable
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters)
|
public function match($candidate, $name, ConstraintInterface $constraint = null)
|
||||||
{
|
{
|
||||||
$candidateName = $candidate->getName();
|
$candidateName = $candidate->getName();
|
||||||
$candidateVersion = $candidate->getVersion();
|
$candidateVersion = $candidate->getVersion();
|
||||||
$isDev = $candidate->getStability() === 'dev';
|
|
||||||
$isAlias = $candidate instanceof AliasPackage;
|
|
||||||
|
|
||||||
if (!$bypassFilters && !$isDev && !$isAlias && isset($this->filterRequires[$name])) {
|
|
||||||
$requireFilter = $this->filterRequires[$name];
|
|
||||||
} else {
|
|
||||||
$requireFilter = new EmptyConstraint;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($candidateName === $name) {
|
if ($candidateName === $name) {
|
||||||
$pkgConstraint = new Constraint('==', $candidateVersion);
|
$pkgConstraint = new Constraint('==', $candidateVersion);
|
||||||
|
|
||||||
if ($constraint === null || $constraint->matches($pkgConstraint)) {
|
if ($constraint === null || $constraint->matches($pkgConstraint)) {
|
||||||
return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED;
|
return self::MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::MATCH_NAME;
|
return self::MATCH_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$provides = $candidate->getProvides();
|
$provides = $candidate->getProvides();
|
||||||
|
@ -352,13 +188,13 @@ class Pool implements \Countable
|
||||||
if (isset($replaces[0]) || isset($provides[0])) {
|
if (isset($replaces[0]) || isset($provides[0])) {
|
||||||
foreach ($provides as $link) {
|
foreach ($provides as $link) {
|
||||||
if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
|
if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
|
||||||
return $requireFilter->matches($link->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
|
return self::MATCH_PROVIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($replaces as $link) {
|
foreach ($replaces as $link) {
|
||||||
if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
|
if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
|
||||||
return $requireFilter->matches($link->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
|
return self::MATCH_REPLACE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,13 +202,18 @@ class Pool implements \Countable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
|
if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
|
||||||
return $requireFilter->matches($provides[$name]->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
|
return self::MATCH_PROVIDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
|
if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
|
||||||
return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
|
return self::MATCH_REPLACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::MATCH_NONE;
|
return self::MATCH_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isUnacceptableFixedPackage(PackageInterface $package)
|
||||||
|
{
|
||||||
|
return in_array($package, $this->unacceptableFixedPackages, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,376 @@
|
||||||
|
<?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\IO\IOInterface;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\BasePackage;
|
||||||
|
use Composer\Package\Package;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Package\Version\StabilityFilter;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Repository\RootPackageRepository;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
use Composer\Semver\Constraint\EmptyConstraint;
|
||||||
|
use Composer\Semver\Constraint\MultiConstraint;
|
||||||
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
|
use Composer\Plugin\PrePoolCreateEvent;
|
||||||
|
use Composer\Plugin\PluginEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
*/
|
||||||
|
class PoolBuilder
|
||||||
|
{
|
||||||
|
private $acceptableStabilities;
|
||||||
|
private $stabilityFlags;
|
||||||
|
private $rootAliases;
|
||||||
|
private $rootReferences;
|
||||||
|
private $eventDispatcher;
|
||||||
|
private $io;
|
||||||
|
|
||||||
|
private $aliasMap = array();
|
||||||
|
private $nameConstraints = array();
|
||||||
|
private $loadedNames = array();
|
||||||
|
private $packages = array();
|
||||||
|
private $unacceptableFixedPackages = array();
|
||||||
|
private $updateAllowList = array();
|
||||||
|
private $skippedLoad = array();
|
||||||
|
private $updateAllowWarned = array();
|
||||||
|
|
||||||
|
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, EventDispatcher $eventDispatcher = null)
|
||||||
|
{
|
||||||
|
$this->acceptableStabilities = $acceptableStabilities;
|
||||||
|
$this->stabilityFlags = $stabilityFlags;
|
||||||
|
$this->rootAliases = $rootAliases;
|
||||||
|
$this->rootReferences = $rootReferences;
|
||||||
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
|
$this->io = $io;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildPool(array $repositories, Request $request)
|
||||||
|
{
|
||||||
|
if ($request->getUpdateAllowList()) {
|
||||||
|
$this->updateAllowList = $request->getUpdateAllowList();
|
||||||
|
$this->warnAboutNonMatchingUpdateAllowList($request);
|
||||||
|
|
||||||
|
foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
|
||||||
|
if (!$this->isUpdateAllowed($lockedPackage)) {
|
||||||
|
$request->fixPackage($lockedPackage);
|
||||||
|
$lockedName = $lockedPackage->getName();
|
||||||
|
// remember which packages we skipped loading remote content for in this partial update
|
||||||
|
$this->skippedLoad[$lockedPackage->getName()] = $lockedName;
|
||||||
|
foreach ($lockedPackage->getReplaces() as $link) {
|
||||||
|
$this->skippedLoad[$link->getTarget()] = $lockedName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loadNames = array();
|
||||||
|
foreach ($request->getFixedPackages() as $package) {
|
||||||
|
$this->nameConstraints[$package->getName()] = null;
|
||||||
|
$this->loadedNames[$package->getName()] = true;
|
||||||
|
|
||||||
|
// replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways
|
||||||
|
foreach ($package->getReplaces() as $link) {
|
||||||
|
$this->nameConstraints[$package->getName()] = null;
|
||||||
|
$this->loadedNames[$link->getTarget()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to
|
||||||
|
// specific versions while replace is a conflict with all versions of the name
|
||||||
|
|
||||||
|
if (
|
||||||
|
$package->getRepository() instanceof RootPackageRepository
|
||||||
|
|| $package->getRepository() instanceof PlatformRepository
|
||||||
|
|| StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability())
|
||||||
|
) {
|
||||||
|
$loadNames += $this->loadPackage($request, $package, false);
|
||||||
|
} else {
|
||||||
|
$this->unacceptableFixedPackages[] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($request->getRequires() as $packageName => $constraint) {
|
||||||
|
// fixed packages have already been added, so if a root require needs one of them, no need to do anything
|
||||||
|
if (isset($this->loadedNames[$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$loadNames[$packageName] = $constraint;
|
||||||
|
$this->nameConstraints[$packageName] = $constraint ? new MultiConstraint(array($constraint), false) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up loadNames for anything we manually marked loaded above
|
||||||
|
foreach ($loadNames as $name => $void) {
|
||||||
|
if (isset($this->loadedNames[$name])) {
|
||||||
|
unset($loadNames[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!empty($loadNames)) {
|
||||||
|
foreach ($loadNames as $name => $void) {
|
||||||
|
$this->loadedNames[$name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newLoadNames = array();
|
||||||
|
foreach ($repositories as $repository) {
|
||||||
|
// these repos have their packages fixed if they need to be loaded so we
|
||||||
|
// never need to load anything else from them
|
||||||
|
if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result = $repository->loadPackages($loadNames, $this->acceptableStabilities, $this->stabilityFlags);
|
||||||
|
|
||||||
|
foreach ($result['namesFound'] as $name) {
|
||||||
|
// avoid loading the same package again from other repositories once it has been found
|
||||||
|
unset($loadNames[$name]);
|
||||||
|
}
|
||||||
|
foreach ($result['packages'] as $package) {
|
||||||
|
$newLoadNames += $this->loadPackage($request, $package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loadNames = $newLoadNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter packages according to all the require statements collected for each package
|
||||||
|
foreach ($this->packages as $i => $package) {
|
||||||
|
// we check all alias related packages at once, so no need to 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->eventDispatcher) {
|
||||||
|
$prePoolCreateEvent = new PrePoolCreateEvent(
|
||||||
|
PluginEvents::PRE_POOL_CREATE,
|
||||||
|
$repositories,
|
||||||
|
$request,
|
||||||
|
$this->acceptableStabilities,
|
||||||
|
$this->stabilityFlags,
|
||||||
|
$this->rootAliases,
|
||||||
|
$this->rootReferences,
|
||||||
|
$this->packages,
|
||||||
|
$this->unacceptableFixedPackages
|
||||||
|
);
|
||||||
|
$this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent);
|
||||||
|
$this->packages = $prePoolCreateEvent->getPackages();
|
||||||
|
$this->unacceptableFixedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pool = new Pool($this->packages, $this->unacceptableFixedPackages);
|
||||||
|
|
||||||
|
$this->aliasMap = array();
|
||||||
|
$this->nameConstraints = array();
|
||||||
|
$this->loadedNames = array();
|
||||||
|
$this->packages = array();
|
||||||
|
$this->unacceptableFixedPackages = array();
|
||||||
|
|
||||||
|
return $pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadPackage(Request $request, PackageInterface $package, $propagateUpdate = true)
|
||||||
|
{
|
||||||
|
end($this->packages);
|
||||||
|
$index = key($this->packages) + 1;
|
||||||
|
$this->packages[] = $package;
|
||||||
|
|
||||||
|
if ($package instanceof AliasPackage) {
|
||||||
|
$this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $package->getName();
|
||||||
|
|
||||||
|
// we're simply setting the root references on all versions for a name here and rely on the solver to pick the
|
||||||
|
// right version. It'd be more work to figure out which versions and which aliases of those versions this may
|
||||||
|
// apply to
|
||||||
|
if (isset($this->rootReferences[$name])) {
|
||||||
|
// do not modify the references on already locked packages
|
||||||
|
if (!$request->isFixedPackage($package)) {
|
||||||
|
$package->setSourceDistReferences($this->rootReferences[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if propogateUpdate is false we are loading a fixed package, root aliases do not apply as they are manually
|
||||||
|
// loaded as separate packages in this case
|
||||||
|
if ($propagateUpdate && 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);
|
||||||
|
|
||||||
|
$this->packages[] = $aliasPackage;
|
||||||
|
$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 this is a partial update with transitive dependencies we need to unfix the package we now know is a
|
||||||
|
// dependency of another package which we are trying to update, and then attempt to load it again
|
||||||
|
} elseif ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies() && isset($this->skippedLoad[$require])) {
|
||||||
|
if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$require])) {
|
||||||
|
$this->unfixPackage($request, $require);
|
||||||
|
$loadNames[$require] = null;
|
||||||
|
} elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $require) && !isset($this->updateAllowWarned[$require])) {
|
||||||
|
$this->updateAllowWarned[$require] = true;
|
||||||
|
$this->io->writeError('<warning>Dependency "'.$require.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.</warning>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$linkConstraint = $link->getConstraint();
|
||||||
|
if ($linkConstraint && !($linkConstraint instanceof EmptyConstraint)) {
|
||||||
|
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 it is null and should stay null
|
||||||
|
} else {
|
||||||
|
$this->nameConstraints[$require] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're doing a partial update with deps we also need to unfix packages which are being replaced in case they
|
||||||
|
// are currently locked and thus prevent this updateable package from being installable/updateable
|
||||||
|
if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
|
||||||
|
foreach ($package->getReplaces() as $link) {
|
||||||
|
$replace = $link->getTarget();
|
||||||
|
if (isset($this->loadedNames[$replace]) && isset($this->skippedLoad[$replace])) {
|
||||||
|
if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$replace])) {
|
||||||
|
$this->unfixPackage($request, $replace);
|
||||||
|
$loadNames[$replace] = null;
|
||||||
|
// TODO should we try to merge constraints here?
|
||||||
|
$this->nameConstraints[$replace] = null;
|
||||||
|
} elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $replace) && !isset($this->updateAllowWarned[$require])) {
|
||||||
|
$this->updateAllowWarned[$replace] = true;
|
||||||
|
$this->io->writeError('<warning>Dependency "'.$require.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.</warning>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $loadNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a particular name is required directly in the request
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isRootRequire(Request $request, $name)
|
||||||
|
{
|
||||||
|
$rootRequires = $request->getRequires();
|
||||||
|
return isset($rootRequires[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the update allow list allows this package in the lock file to be updated
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isUpdateAllowed(PackageInterface $package)
|
||||||
|
{
|
||||||
|
foreach ($this->updateAllowList as $pattern => $void) {
|
||||||
|
$patternRegexp = BasePackage::packageNameToRegexp($pattern);
|
||||||
|
if (preg_match($patternRegexp, $package->getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function warnAboutNonMatchingUpdateAllowList(Request $request)
|
||||||
|
{
|
||||||
|
foreach ($this->updateAllowList as $pattern => $void) {
|
||||||
|
$patternRegexp = BasePackage::packageNameToRegexp($pattern);
|
||||||
|
// update pattern matches a locked package? => all good
|
||||||
|
foreach ($request->getLockedRepository()->getPackages() as $package) {
|
||||||
|
if (preg_match($patternRegexp, $package->getName())) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update pattern matches a root require? => all good, probably a new package
|
||||||
|
foreach ($request->getRequires() as $packageName => $constraint) {
|
||||||
|
if (preg_match($patternRegexp, $packageName)) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (strpos($pattern, '*') !== false) {
|
||||||
|
$this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update does not match any locked packages.</warning>');
|
||||||
|
} else {
|
||||||
|
$this->io->writeError('<warning>Package "' . $pattern . '" listed for update is not locked.</warning>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts the decision to use a fixed package from lock file if a partial update with transitive dependencies
|
||||||
|
* found that this package actually needs to be updated
|
||||||
|
*/
|
||||||
|
private function unfixPackage(Request $request, $name)
|
||||||
|
{
|
||||||
|
// remove locked package by this name which was already initialized
|
||||||
|
foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
|
||||||
|
if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) {
|
||||||
|
if (false !== $index = array_search($lockedPackage, $this->packages, true)) {
|
||||||
|
$request->unfixPackage($lockedPackage);
|
||||||
|
unset($this->packages[$index]);
|
||||||
|
if (isset($this->aliasMap[spl_object_hash($lockedPackage)])) {
|
||||||
|
foreach ($this->aliasMap[spl_object_hash($lockedPackage)] as $aliasIndex => $aliasPackage) {
|
||||||
|
$request->unfixPackage($aliasPackage);
|
||||||
|
unset($this->packages[$aliasIndex]);
|
||||||
|
}
|
||||||
|
unset($this->aliasMap[spl_object_hash($lockedPackage)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we unfixed a replaced package name, we also need to unfix the replacer itself
|
||||||
|
if ($this->skippedLoad[$name] !== $name) {
|
||||||
|
$this->unfixPackage($request, $this->skippedLoad[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->skippedLoad[$name]);
|
||||||
|
unset($this->loadedNames[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
use Composer\Package\CompletePackageInterface;
|
use Composer\Package\CompletePackageInterface;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a problem detected while solving dependencies
|
* Represents a problem detected while solving dependencies
|
||||||
|
@ -28,20 +30,13 @@ class Problem
|
||||||
protected $reasonSeen;
|
protected $reasonSeen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of reasons for the problem, each is a rule or a job and a rule
|
* A set of reasons for the problem, each is a rule or a root require and a rule
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $reasons = array();
|
protected $reasons = array();
|
||||||
|
|
||||||
protected $section = 0;
|
protected $section = 0;
|
||||||
|
|
||||||
protected $pool;
|
|
||||||
|
|
||||||
public function __construct(Pool $pool)
|
|
||||||
{
|
|
||||||
$this->pool = $pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a rule as a reason
|
* Add a rule as a reason
|
||||||
*
|
*
|
||||||
|
@ -49,10 +44,7 @@ class Problem
|
||||||
*/
|
*/
|
||||||
public function addRule(Rule $rule)
|
public function addRule(Rule $rule)
|
||||||
{
|
{
|
||||||
$this->addReason(spl_object_hash($rule), array(
|
$this->addReason(spl_object_hash($rule), $rule);
|
||||||
'rule' => $rule,
|
|
||||||
'job' => $rule->getJob(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,124 +60,67 @@ class Problem
|
||||||
/**
|
/**
|
||||||
* A human readable textual representation of the problem's reasons
|
* A human readable textual representation of the problem's reasons
|
||||||
*
|
*
|
||||||
* @param array $installedMap A map of all installed packages
|
* @param array $installedMap A map of all present packages
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getPrettyString(array $installedMap = array())
|
public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
|
||||||
{
|
{
|
||||||
|
// TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections?
|
||||||
$reasons = call_user_func_array('array_merge', array_reverse($this->reasons));
|
$reasons = call_user_func_array('array_merge', array_reverse($this->reasons));
|
||||||
|
|
||||||
if (count($reasons) === 1) {
|
if (count($reasons) === 1) {
|
||||||
reset($reasons);
|
reset($reasons);
|
||||||
$reason = current($reasons);
|
$rule = current($reasons);
|
||||||
|
|
||||||
$job = $reason['job'];
|
if (!in_array($rule->getReason(), array(Rule::RULE_ROOT_REQUIRE, Rule::RULE_FIXED), true)) {
|
||||||
|
throw new \LogicException("Single reason problems must contain a request rule.");
|
||||||
|
}
|
||||||
|
|
||||||
$packageName = $job['packageName'];
|
$reasonData = $rule->getReasonData();
|
||||||
$constraint = $job['constraint'];
|
$packageName = $reasonData['packageName'];
|
||||||
|
$constraint = $reasonData['constraint'];
|
||||||
|
|
||||||
if (isset($constraint)) {
|
if (isset($constraint)) {
|
||||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
$packages = $pool->whatProvides($packageName, $constraint);
|
||||||
} else {
|
} else {
|
||||||
$packages = array();
|
$packages = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($job && $job['cmd'] === 'install' && empty($packages)) {
|
if (empty($packages)) {
|
||||||
|
return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $packageName, $constraint));
|
||||||
// handle php/hhvm
|
|
||||||
if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
|
|
||||||
$version = phpversion();
|
|
||||||
$available = $this->pool->whatProvides($packageName);
|
|
||||||
|
|
||||||
if (count($available)) {
|
|
||||||
$firstAvailable = reset($available);
|
|
||||||
$version = $firstAvailable->getPrettyVersion();
|
|
||||||
$extra = $firstAvailable->getExtra();
|
|
||||||
if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
|
|
||||||
$version .= '; ' . $firstAvailable->getDescription();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$msg = "\n - This package requires ".$packageName.$this->constraintToText($constraint).' but ';
|
|
||||||
|
|
||||||
if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
|
|
||||||
return $msg . 'your HHVM version does not satisfy that requirement.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($packageName === 'hhvm') {
|
|
||||||
return $msg . 'you are running this with PHP and not HHVM.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle php extensions
|
|
||||||
if (0 === stripos($packageName, 'ext-')) {
|
|
||||||
if (false !== strpos($packageName, ' ')) {
|
|
||||||
return "\n - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$ext = substr($packageName, 4);
|
|
||||||
$error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
|
|
||||||
|
|
||||||
return "\n - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle linked libs
|
|
||||||
if (0 === stripos($packageName, 'lib-')) {
|
|
||||||
if (strtolower($packageName) === 'lib-icu') {
|
|
||||||
$error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';
|
|
||||||
|
|
||||||
return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_./-]+$}', $packageName)) {
|
|
||||||
$illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
|
|
||||||
|
|
||||||
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($packageName, $constraint, true, true)) {
|
|
||||||
return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
|
|
||||||
return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return "\n - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages = array();
|
$messages = array();
|
||||||
|
foreach ($reasons as $rule) {
|
||||||
foreach ($reasons as $reason) {
|
$messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
|
||||||
$rule = $reason['rule'];
|
|
||||||
$job = $reason['job'];
|
|
||||||
|
|
||||||
if ($job) {
|
|
||||||
$messages[] = $this->jobToText($job);
|
|
||||||
} elseif ($rule) {
|
|
||||||
if ($rule instanceof Rule) {
|
|
||||||
$messages[] = $rule->getPrettyString($this->pool, $installedMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "\n - ".implode("\n - ", $messages);
|
return "\n - ".implode("\n - ", $messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCausedByLock()
|
||||||
|
{
|
||||||
|
foreach ($this->reasons as $sectionRules) {
|
||||||
|
foreach ($sectionRules as $rule) {
|
||||||
|
if ($rule->isCausedByLock()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 Rule $reason The reason descriptor
|
||||||
*/
|
*/
|
||||||
protected function addReason($id, $reason)
|
protected function addReason($id, Rule $reason)
|
||||||
{
|
{
|
||||||
|
// TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message
|
||||||
|
// that is important to understand the issue?
|
||||||
|
|
||||||
if (!isset($this->reasonSeen[$id])) {
|
if (!isset($this->reasonSeen[$id])) {
|
||||||
$this->reasonSeen[$id] = true;
|
$this->reasonSeen[$id] = true;
|
||||||
$this->reasons[$this->section][] = $reason;
|
$this->reasons[$this->section][] = $reason;
|
||||||
|
@ -198,39 +133,150 @@ class Problem
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a job into a human readable description
|
* @internal
|
||||||
*
|
|
||||||
* @param array $job
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
protected function jobToText($job)
|
public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, $packageName, $constraint = null)
|
||||||
{
|
{
|
||||||
$packageName = $job['packageName'];
|
// handle php/hhvm
|
||||||
$constraint = $job['constraint'];
|
if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
|
||||||
switch ($job['cmd']) {
|
$version = phpversion();
|
||||||
case 'install':
|
$available = $pool->whatProvides($packageName);
|
||||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
|
||||||
if (!$packages) {
|
if (count($available)) {
|
||||||
return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
|
$firstAvailable = reset($available);
|
||||||
|
$version = $firstAvailable->getPrettyVersion();
|
||||||
|
$extra = $firstAvailable->getExtra();
|
||||||
|
if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
|
||||||
|
$version .= '; ' . str_replace('Package ', '', $firstAvailable->getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';
|
||||||
|
|
||||||
|
if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
|
||||||
|
return array($msg, 'your HHVM version does not satisfy that requirement.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($packageName === 'hhvm') {
|
||||||
|
return array($msg, 'you are running this with PHP and not HHVM.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle php extensions
|
||||||
|
if (0 === stripos($packageName, 'ext-')) {
|
||||||
|
if (false !== strpos($packageName, ' ')) {
|
||||||
|
return array('- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ext = substr($packageName, 4);
|
||||||
|
$error = extension_loaded($ext) ? 'it has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'it is missing from your system';
|
||||||
|
|
||||||
|
return array("- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ', $error.'. Install or enable PHP\'s '.$ext.' extension.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle linked libs
|
||||||
|
if (0 === stripos($packageName, 'lib-')) {
|
||||||
|
if (strtolower($packageName) === 'lib-icu') {
|
||||||
|
$error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';
|
||||||
|
|
||||||
|
return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fixedPackage = null;
|
||||||
|
foreach ($request->getFixedPackages() as $package) {
|
||||||
|
if ($package->getName() === $packageName) {
|
||||||
|
$fixedPackage = $package;
|
||||||
|
if ($pool->isUnacceptableFixedPackage($package)) {
|
||||||
|
return array("- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check if the actual requested package is found in normal conditions
|
||||||
|
// if so it must mean it is rejected by another constraint than the one given here
|
||||||
|
if ($packages = $repositorySet->findPackages($packageName, $constraint)) {
|
||||||
|
$rootReqs = $repositorySet->getRootRequires();
|
||||||
|
if (isset($rootReqs[$packageName])) {
|
||||||
|
$filtered = array_filter($packages, function ($p) use ($rootReqs, $packageName) {
|
||||||
|
return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion()));
|
||||||
|
});
|
||||||
|
if (0 === count($filtered)) {
|
||||||
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fixedPackage) {
|
||||||
|
$fixedConstraint = new Constraint('==', $fixedPackage->getVersion());
|
||||||
|
$filtered = array_filter($packages, function ($p) use ($fixedConstraint) {
|
||||||
|
return $fixedConstraint->matches(new Constraint('==', $p->getVersion()));
|
||||||
|
});
|
||||||
|
if (0 === count($filtered)) {
|
||||||
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but the package is fixed to '.$fixedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with another require.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the package is found when bypassing stability checks
|
||||||
|
if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
|
||||||
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the package is found when bypassing the constraint check
|
||||||
|
if ($packages = $repositorySet->findPackages($packageName, null)) {
|
||||||
|
// we must first verify if a valid package would be found in a lower priority repository
|
||||||
|
if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
|
||||||
|
$higherRepoPackages = $repositorySet->findPackages($packageName, null);
|
||||||
|
$nextRepoPackages = array();
|
||||||
|
$nextRepo = null;
|
||||||
|
|
||||||
|
foreach ($allReposPackages as $package) {
|
||||||
|
if ($nextRepo === null || $nextRepo === $package->getRepository()) {
|
||||||
|
$nextRepoPackages[] = $package;
|
||||||
|
$nextRepo = $package->getRepository();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.';
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable.');
|
||||||
case 'update':
|
}
|
||||||
return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
|
|
||||||
case 'remove':
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your constraint.');
|
||||||
return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($constraint)) {
|
if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
|
||||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
$illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
|
||||||
} else {
|
|
||||||
$packages = array();
|
return array("- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])';
|
if ($providers = $repositorySet->getProviders($packageName)) {
|
||||||
|
$maxProviders = 20;
|
||||||
|
$providersStr = implode(array_map(function ($p) {
|
||||||
|
$description = $p['description'] ? ' '.substr($p['description'], 0, 100) : '';
|
||||||
|
return " - ${p['name']}".$description."\n";
|
||||||
|
}, count($providers) > $maxProviders+1 ? array_slice($providers, 0, $maxProviders) : $providers));
|
||||||
|
if (count($providers) > $maxProviders+1) {
|
||||||
|
$providersStr .= ' ... and '.(count($providers)-$maxProviders).' more.'."\n";
|
||||||
|
}
|
||||||
|
return array("- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return array("- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPackageList($packages)
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function getPackageList(array $packages)
|
||||||
{
|
{
|
||||||
$prepared = array();
|
$prepared = array();
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $package) {
|
||||||
|
@ -238,19 +284,37 @@ class Problem
|
||||||
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
|
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
|
||||||
}
|
}
|
||||||
foreach ($prepared as $name => $package) {
|
foreach ($prepared as $name => $package) {
|
||||||
|
// remove the implicit dev-master alias to avoid cruft in the display
|
||||||
|
if (isset($package['versions']['9999999-dev']) && isset($package['versions']['dev-master'])) {
|
||||||
|
unset($package['versions']['9999999-dev']);
|
||||||
|
}
|
||||||
$prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
|
$prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $prepared);
|
return implode(', ', $prepared);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function hasMultipleNames(array $packages)
|
||||||
|
{
|
||||||
|
$name = null;
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
if ($name === null || $name === $package->getName()) {
|
||||||
|
$name = $package->getName();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a constraint into text usable in a sentence describing a job
|
* Turns a constraint into text usable in a sentence describing a request
|
||||||
*
|
*
|
||||||
* @param \Composer\Semver\Constraint\ConstraintInterface $constraint
|
* @param \Composer\Semver\Constraint\ConstraintInterface $constraint
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function constraintToText($constraint)
|
protected static function constraintToText($constraint)
|
||||||
{
|
{
|
||||||
return $constraint ? ' '.$constraint->getPrettyString() : '';
|
return $constraint ? ' '.$constraint->getPrettyString() : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
|
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
|
use Composer\Package\Package;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Package\RootAliasPackage;
|
||||||
|
use Composer\Repository\LockArrayRepository;
|
||||||
use Composer\Semver\Constraint\ConstraintInterface;
|
use Composer\Semver\Constraint\ConstraintInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,60 +23,129 @@ use Composer\Semver\Constraint\ConstraintInterface;
|
||||||
*/
|
*/
|
||||||
class Request
|
class Request
|
||||||
{
|
{
|
||||||
protected $jobs;
|
/**
|
||||||
|
* Identifies a partial update for listed packages only, all dependencies will remain at locked versions
|
||||||
|
*/
|
||||||
|
const UPDATE_ONLY_LISTED = 0;
|
||||||
|
|
||||||
public function __construct()
|
/**
|
||||||
|
* Identifies a partial update for listed packages and recursively all their dependencies, however dependencies
|
||||||
|
* also directly required by the root composer.json and their dependencies will remain at the locked version.
|
||||||
|
*/
|
||||||
|
const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies a partial update for listed packages and recursively all their dependencies, even dependencies
|
||||||
|
* also directly required by the root composer.json will be updated.
|
||||||
|
*/
|
||||||
|
const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2;
|
||||||
|
|
||||||
|
protected $lockedRepository;
|
||||||
|
protected $requires = array();
|
||||||
|
protected $fixedPackages = array();
|
||||||
|
protected $unlockables = array();
|
||||||
|
protected $updateAllowList = array();
|
||||||
|
protected $updateAllowTransitiveDependencies = false;
|
||||||
|
|
||||||
|
public function __construct(LockArrayRepository $lockedRepository = null)
|
||||||
{
|
{
|
||||||
$this->jobs = array();
|
$this->lockedRepository = $lockedRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function install($packageName, ConstraintInterface $constraint = null)
|
public function requireName($packageName, ConstraintInterface $constraint = null)
|
||||||
{
|
{
|
||||||
$this->addJob($packageName, 'install', $constraint);
|
$packageName = strtolower($packageName);
|
||||||
}
|
$this->requires[$packageName] = $constraint;
|
||||||
|
|
||||||
public function update($packageName, ConstraintInterface $constraint = null)
|
|
||||||
{
|
|
||||||
$this->addJob($packageName, 'update', $constraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove($packageName, ConstraintInterface $constraint = null)
|
|
||||||
{
|
|
||||||
$this->addJob($packageName, 'remove', $constraint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark an existing package as being installed and having to remain installed
|
* Mark an existing package as being installed and having to remain installed
|
||||||
*
|
*
|
||||||
* These jobs will not be tempered with by the solver
|
* @param bool $lockable if set to false, the package will not be written to the lock file
|
||||||
*
|
|
||||||
* @param string $packageName
|
|
||||||
* @param ConstraintInterface|null $constraint
|
|
||||||
*/
|
*/
|
||||||
public function fix($packageName, ConstraintInterface $constraint = null)
|
public function fixPackage(PackageInterface $package, $lockable = true)
|
||||||
{
|
{
|
||||||
$this->addJob($packageName, 'install', $constraint, true);
|
$this->fixedPackages[spl_object_hash($package)] = $package;
|
||||||
|
|
||||||
|
if (!$lockable) {
|
||||||
|
$this->unlockables[spl_object_hash($package)] = $package;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false)
|
public function unfixPackage(PackageInterface $package)
|
||||||
{
|
{
|
||||||
$packageName = strtolower($packageName);
|
unset($this->fixedPackages[spl_object_hash($package)]);
|
||||||
|
unset($this->unlockables[spl_object_hash($package)]);
|
||||||
$this->jobs[] = array(
|
|
||||||
'cmd' => $cmd,
|
|
||||||
'packageName' => $packageName,
|
|
||||||
'constraint' => $constraint,
|
|
||||||
'fixed' => $fixed,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateAll()
|
public function setUpdateAllowList($updateAllowList, $updateAllowTransitiveDependencies)
|
||||||
{
|
{
|
||||||
$this->jobs[] = array('cmd' => 'update-all');
|
$this->updateAllowList = $updateAllowList;
|
||||||
|
$this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJobs()
|
public function getUpdateAllowList()
|
||||||
{
|
{
|
||||||
return $this->jobs;
|
return $this->updateAllowList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdateAllowTransitiveDependencies()
|
||||||
|
{
|
||||||
|
return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdateAllowTransitiveRootDependencies()
|
||||||
|
{
|
||||||
|
return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequires()
|
||||||
|
{
|
||||||
|
return $this->requires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFixedPackages()
|
||||||
|
{
|
||||||
|
return $this->fixedPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFixedPackage(PackageInterface $package)
|
||||||
|
{
|
||||||
|
return isset($this->fixedPackages[spl_object_hash($package)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO look into removing the packageIds option, the only place true is used is for the installed map in the solver problems
|
||||||
|
// some locked packages may not be in the pool so they have a package->id of -1
|
||||||
|
public function getPresentMap($packageIds = false)
|
||||||
|
{
|
||||||
|
$presentMap = array();
|
||||||
|
|
||||||
|
if ($this->lockedRepository) {
|
||||||
|
foreach ($this->lockedRepository->getPackages() as $package) {
|
||||||
|
$presentMap[$packageIds ? $package->id : spl_object_hash($package)] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->fixedPackages as $package) {
|
||||||
|
$presentMap[$packageIds ? $package->id : spl_object_hash($package)] = $package;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $presentMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUnlockableMap()
|
||||||
|
{
|
||||||
|
$unlockableMap = array();
|
||||||
|
|
||||||
|
foreach ($this->unlockables as $package) {
|
||||||
|
$unlockableMap[$package->id] = $package;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $unlockableMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLockedRepository()
|
||||||
|
{
|
||||||
|
return $this->lockedRepository;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
|
||||||
use Composer\Package\CompletePackage;
|
use Composer\Package\CompletePackage;
|
||||||
use Composer\Package\Link;
|
use Composer\Package\Link;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
@ -24,8 +25,8 @@ abstract class Rule
|
||||||
{
|
{
|
||||||
// reason constants
|
// reason constants
|
||||||
const RULE_INTERNAL_ALLOW_UPDATE = 1;
|
const RULE_INTERNAL_ALLOW_UPDATE = 1;
|
||||||
const RULE_JOB_INSTALL = 2;
|
const RULE_ROOT_REQUIRE = 2;
|
||||||
const RULE_JOB_REMOVE = 3;
|
const RULE_FIXED = 3;
|
||||||
const RULE_PACKAGE_CONFLICT = 6;
|
const RULE_PACKAGE_CONFLICT = 6;
|
||||||
const RULE_PACKAGE_REQUIRES = 7;
|
const RULE_PACKAGE_REQUIRES = 7;
|
||||||
const RULE_PACKAGE_OBSOLETES = 8;
|
const RULE_PACKAGE_OBSOLETES = 8;
|
||||||
|
@ -41,22 +42,17 @@ abstract class Rule
|
||||||
const BITFIELD_DISABLED = 16;
|
const BITFIELD_DISABLED = 16;
|
||||||
|
|
||||||
protected $bitfield;
|
protected $bitfield;
|
||||||
protected $job;
|
protected $request;
|
||||||
protected $reasonData;
|
protected $reasonData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||||
* @param Link|PackageInterface $reasonData
|
* @param Link|PackageInterface $reasonData
|
||||||
* @param array $job The job this rule was created from
|
|
||||||
*/
|
*/
|
||||||
public function __construct($reason, $reasonData, $job = null)
|
public function __construct($reason, $reasonData)
|
||||||
{
|
{
|
||||||
$this->reasonData = $reasonData;
|
$this->reasonData = $reasonData;
|
||||||
|
|
||||||
if ($job) {
|
|
||||||
$this->job = $job;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->bitfield = (0 << self::BITFIELD_DISABLED) |
|
$this->bitfield = (0 << self::BITFIELD_DISABLED) |
|
||||||
($reason << self::BITFIELD_REASON) |
|
($reason << self::BITFIELD_REASON) |
|
||||||
(255 << self::BITFIELD_TYPE);
|
(255 << self::BITFIELD_TYPE);
|
||||||
|
@ -66,11 +62,6 @@ abstract class Rule
|
||||||
|
|
||||||
abstract public function getHash();
|
abstract public function getHash();
|
||||||
|
|
||||||
public function getJob()
|
|
||||||
{
|
|
||||||
return $this->job;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function equals(Rule $rule);
|
abstract public function equals(Rule $rule);
|
||||||
|
|
||||||
public function getReason()
|
public function getReason()
|
||||||
|
@ -85,11 +76,17 @@ abstract class Rule
|
||||||
|
|
||||||
public function getRequiredPackage()
|
public function getRequiredPackage()
|
||||||
{
|
{
|
||||||
if ($this->getReason() === self::RULE_JOB_INSTALL) {
|
$reason = $this->getReason();
|
||||||
return $this->reasonData;
|
|
||||||
|
if ($reason === self::RULE_ROOT_REQUIRE) {
|
||||||
|
return $this->reasonData['packageName'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) {
|
if ($reason === self::RULE_FIXED) {
|
||||||
|
return $this->reasonData['package']->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reason === self::RULE_PACKAGE_REQUIRES) {
|
||||||
return $this->reasonData->getTarget();
|
return $this->reasonData->getTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +123,12 @@ abstract class Rule
|
||||||
|
|
||||||
abstract public function isAssertion();
|
abstract public function isAssertion();
|
||||||
|
|
||||||
public function getPrettyString(Pool $pool, array $installedMap = array())
|
public function isCausedByLock()
|
||||||
|
{
|
||||||
|
return $this->getReason() === self::RULE_FIXED && $this->reasonData['lockable'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
|
||||||
{
|
{
|
||||||
$literals = $this->getLiterals();
|
$literals = $this->getLiterals();
|
||||||
|
|
||||||
|
@ -142,17 +144,30 @@ abstract class Rule
|
||||||
case self::RULE_INTERNAL_ALLOW_UPDATE:
|
case self::RULE_INTERNAL_ALLOW_UPDATE:
|
||||||
return $ruleText;
|
return $ruleText;
|
||||||
|
|
||||||
case self::RULE_JOB_INSTALL:
|
case self::RULE_ROOT_REQUIRE:
|
||||||
return "Install command rule ($ruleText)";
|
$packageName = $this->reasonData['packageName'];
|
||||||
|
$constraint = $this->reasonData['constraint'];
|
||||||
|
|
||||||
case self::RULE_JOB_REMOVE:
|
$packages = $pool->whatProvides($packageName, $constraint);
|
||||||
return "Remove command rule ($ruleText)";
|
if (!$packages) {
|
||||||
|
return 'No package found to satisfy root composer.json require '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages).'.';
|
||||||
|
|
||||||
|
case self::RULE_FIXED:
|
||||||
|
$package = $this->reasonData['package'];
|
||||||
|
if ($this->reasonData['lockable']) {
|
||||||
|
return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer';
|
||||||
|
|
||||||
case self::RULE_PACKAGE_CONFLICT:
|
case self::RULE_PACKAGE_CONFLICT:
|
||||||
$package1 = $pool->literalToPackage($literals[0]);
|
$package1 = $pool->literalToPackage($literals[0]);
|
||||||
$package2 = $pool->literalToPackage($literals[1]);
|
$package2 = $pool->literalToPackage($literals[1]);
|
||||||
|
|
||||||
return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.';
|
return $package2->getPrettyString().' conflicts with '.$package1->getPrettyString().'.';
|
||||||
|
|
||||||
case self::RULE_PACKAGE_REQUIRES:
|
case self::RULE_PACKAGE_REQUIRES:
|
||||||
$sourceLiteral = array_shift($literals);
|
$sourceLiteral = array_shift($literals);
|
||||||
|
@ -169,73 +184,103 @@ abstract class Rule
|
||||||
} else {
|
} else {
|
||||||
$targetName = $this->reasonData->getTarget();
|
$targetName = $this->reasonData->getTarget();
|
||||||
|
|
||||||
if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') {
|
$reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $targetName, $this->reasonData->getConstraint());
|
||||||
// handle php/hhvm
|
|
||||||
if (defined('HHVM_VERSION')) {
|
|
||||||
return $text . ' -> your HHVM version does not satisfy that requirement.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$packages = $pool->whatProvides($targetName);
|
return $text . ' -> ' . $reason[1];
|
||||||
$package = count($packages) ? current($packages) : phpversion();
|
|
||||||
|
|
||||||
if ($targetName === 'hhvm') {
|
|
||||||
if ($package instanceof CompletePackage) {
|
|
||||||
return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
|
|
||||||
} else {
|
|
||||||
return $text . ' -> you are running this with PHP and not HHVM.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!($package instanceof CompletePackage)) {
|
|
||||||
return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$extra = $package->getExtra();
|
|
||||||
|
|
||||||
if (!empty($extra['config.platform'])) {
|
|
||||||
$text .= ' -> your PHP version ('.phpversion().') overridden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
|
|
||||||
} else {
|
|
||||||
$text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 === strpos($targetName, 'ext-')) {
|
|
||||||
// handle php extensions
|
|
||||||
$ext = substr($targetName, 4);
|
|
||||||
$error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
|
|
||||||
|
|
||||||
return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 === strpos($targetName, 'lib-')) {
|
|
||||||
// handle linked libs
|
|
||||||
$lib = substr($targetName, 4);
|
|
||||||
|
|
||||||
return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($providers = $pool->whatProvides($targetName, $this->reasonData->getConstraint(), true, true)) {
|
|
||||||
return $text . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $providers) .' but these conflict with your requirements or minimum-stability.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $text . ' -> no matching package found.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $text;
|
return $text;
|
||||||
|
|
||||||
case self::RULE_PACKAGE_OBSOLETES:
|
case self::RULE_PACKAGE_OBSOLETES:
|
||||||
|
if (count($literals) === 2 && $literals[0] < 0 && $literals[1] < 0) {
|
||||||
|
$package1 = $pool->literalToPackage($literals[0]);
|
||||||
|
$package2 = $pool->literalToPackage($literals[1]);
|
||||||
|
|
||||||
|
$replaces1 = $this->getReplacedNames($package1);
|
||||||
|
$replaces2 = $this->getReplacedNames($package2);
|
||||||
|
|
||||||
|
$reason = null;
|
||||||
|
if ($conflictingNames = array_values(array_intersect($replaces1, $replaces2))) {
|
||||||
|
$reason = 'They both replace '.(count($conflictingNames) > 1 ? '['.implode(', ', $conflictingNames).']' : $conflictingNames[0]).' and thus cannot coexist.';
|
||||||
|
} elseif (in_array($package1->getName(), $replaces2, true)) {
|
||||||
|
$reason = $package2->getName().' replaces '.$package1->getName().' and thus cannot coexist with it.';
|
||||||
|
} elseif (in_array($package2->getName(), $replaces1, true)) {
|
||||||
|
$reason = $package1->getName().' replaces '.$package2->getName().' and thus cannot coexist with it.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reason) {
|
||||||
|
if (isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
|
||||||
|
// swap vars so the if below passes
|
||||||
|
$tmp = $package2;
|
||||||
|
$package2 = $package1;
|
||||||
|
$package1 = $tmp;
|
||||||
|
}
|
||||||
|
if (!isset($installedMap[$package1->id]) && isset($installedMap[$package2->id])) {
|
||||||
|
return $package1->getPrettyString().' cannot be installed as that would require removing '.$package2->getPrettyString().'. '.$reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
|
||||||
|
return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'. '.$reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'.';
|
||||||
|
}
|
||||||
|
|
||||||
return $ruleText;
|
return $ruleText;
|
||||||
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
|
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
|
||||||
return $ruleText;
|
return $ruleText;
|
||||||
case self::RULE_PACKAGE_SAME_NAME:
|
case self::RULE_PACKAGE_SAME_NAME:
|
||||||
return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
|
$replacedNames = null;
|
||||||
|
$packageNames = array();
|
||||||
|
foreach ($literals as $literal) {
|
||||||
|
$package = $pool->literalToPackage($literal);
|
||||||
|
$pkgReplaces = $this->getReplacedNames($package);
|
||||||
|
if ($pkgReplaces) {
|
||||||
|
if ($replacedNames === null) {
|
||||||
|
$replacedNames = $this->getReplacedNames($package);
|
||||||
|
} else {
|
||||||
|
$replacedNames = array_intersect($replacedNames, $this->getReplacedNames($package));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$packageNames[$package->getName()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($replacedNames) {
|
||||||
|
$replacedNames = array_values(array_intersect(array_keys($packageNames), $replacedNames));
|
||||||
|
}
|
||||||
|
if ($replacedNames && count($packageNames) > 1) {
|
||||||
|
$replacer = null;
|
||||||
|
foreach ($literals as $literal) {
|
||||||
|
$package = $pool->literalToPackage($literal);
|
||||||
|
if (array_intersect($replacedNames, $this->getReplacedNames($package))) {
|
||||||
|
$replacer = $package;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$replacedNames = count($replacedNames) > 1 ? '['.implode(', ', $replacedNames).']' : $replacedNames[0];
|
||||||
|
|
||||||
|
if ($replacer) {
|
||||||
|
return 'Only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '. '.$replacer->getName().' replaces '.$replacedNames.' and thus cannot coexist with it.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '.';
|
||||||
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;
|
if (isset($learnedPool[$this->reasonData])) {
|
||||||
|
$learnedString = ', learned rules:'."\n - ";
|
||||||
|
$reasons = array();
|
||||||
|
foreach ($learnedPool[$this->reasonData] as $learnedRule) {
|
||||||
|
$reasons[] = $learnedRule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
|
||||||
|
}
|
||||||
|
$learnedString .= implode("\n - ", array_unique($reasons));
|
||||||
|
} else {
|
||||||
|
$learnedString = ' (reasoning unavailable)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Conclusion: '.$ruleText.$learnedString;
|
||||||
case self::RULE_PACKAGE_ALIAS:
|
case self::RULE_PACKAGE_ALIAS:
|
||||||
return $ruleText;
|
return $ruleText;
|
||||||
default:
|
default:
|
||||||
|
@ -252,17 +297,22 @@ abstract class Rule
|
||||||
protected function formatPackagesUnique($pool, array $packages)
|
protected function formatPackagesUnique($pool, array $packages)
|
||||||
{
|
{
|
||||||
$prepared = array();
|
$prepared = array();
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $index => $package) {
|
||||||
if (!is_object($package)) {
|
if (!is_object($package)) {
|
||||||
$package = $pool->literalToPackage($package);
|
$packages[$index] = $pool->literalToPackage($package);
|
||||||
}
|
}
|
||||||
$prepared[$package->getName()]['name'] = $package->getPrettyName();
|
|
||||||
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
|
|
||||||
}
|
|
||||||
foreach ($prepared as $name => $package) {
|
|
||||||
$prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(', ', $prepared);
|
return Problem::getPackageList($packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getReplacedNames(PackageInterface $package)
|
||||||
|
{
|
||||||
|
$names = array();
|
||||||
|
foreach ($package->getReplaces() as $link) {
|
||||||
|
$names[] = $link->getTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $names;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,10 @@ class Rule2Literals extends Rule
|
||||||
* @param int $literal2
|
* @param int $literal2
|
||||||
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||||
* @param Link|PackageInterface $reasonData
|
* @param Link|PackageInterface $reasonData
|
||||||
* @param array $job The job this rule was created from
|
|
||||||
*/
|
*/
|
||||||
public function __construct($literal1, $literal2, $reason, $reasonData, $job = null)
|
public function __construct($literal1, $literal2, $reason, $reasonData)
|
||||||
{
|
{
|
||||||
parent::__construct($reason, $reasonData, $job);
|
parent::__construct($reason, $reasonData);
|
||||||
|
|
||||||
if ($literal1 < $literal2) {
|
if ($literal1 < $literal2) {
|
||||||
$this->literal1 = $literal1;
|
$this->literal1 = $literal1;
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
*/
|
*/
|
||||||
|
@ -19,7 +21,7 @@ class RuleSet implements \IteratorAggregate, \Countable
|
||||||
{
|
{
|
||||||
// highest priority => lowest number
|
// highest priority => lowest number
|
||||||
const TYPE_PACKAGE = 0;
|
const TYPE_PACKAGE = 0;
|
||||||
const TYPE_JOB = 1;
|
const TYPE_REQUEST = 1;
|
||||||
const TYPE_LEARNED = 4;
|
const TYPE_LEARNED = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +34,7 @@ class RuleSet implements \IteratorAggregate, \Countable
|
||||||
protected static $types = array(
|
protected static $types = array(
|
||||||
255 => 'UNKNOWN',
|
255 => 'UNKNOWN',
|
||||||
self::TYPE_PACKAGE => 'PACKAGE',
|
self::TYPE_PACKAGE => 'PACKAGE',
|
||||||
self::TYPE_JOB => 'JOB',
|
self::TYPE_REQUEST => 'REQUEST',
|
||||||
self::TYPE_LEARNED => 'LEARNED',
|
self::TYPE_LEARNED => 'LEARNED',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -155,13 +157,13 @@ class RuleSet implements \IteratorAggregate, \Countable
|
||||||
return array_keys($types);
|
return array_keys($types);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPrettyString(Pool $pool = null)
|
public function getPrettyString(RepositorySet $repositorySet = null, Request $request = null, Pool $pool = null)
|
||||||
{
|
{
|
||||||
$string = "\n";
|
$string = "\n";
|
||||||
foreach ($this->rules as $type => $rules) {
|
foreach ($this->rules as $type => $rules) {
|
||||||
$string .= str_pad(self::$types[$type], 8, ' ') . ": ";
|
$string .= str_pad(self::$types[$type], 8, ' ') . ": ";
|
||||||
foreach ($rules as $rule) {
|
foreach ($rules as $rule) {
|
||||||
$string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
|
$string .= ($repositorySet && $request && $pool ? $rule->getPrettyString($repositorySet, $request, $pool) : $rule)."\n";
|
||||||
}
|
}
|
||||||
$string .= "\n\n";
|
$string .= "\n\n";
|
||||||
}
|
}
|
||||||
|
@ -171,6 +173,6 @@ class RuleSet implements \IteratorAggregate, \Countable
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return $this->getPrettyString(null);
|
return $this->getPrettyString(null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
|
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
|
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Package\AliasPackage;
|
use Composer\Package\AliasPackage;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
@ -24,13 +26,11 @@ class RuleSetGenerator
|
||||||
protected $policy;
|
protected $policy;
|
||||||
protected $pool;
|
protected $pool;
|
||||||
protected $rules;
|
protected $rules;
|
||||||
protected $jobs;
|
|
||||||
protected $installedMap;
|
|
||||||
protected $whitelistedMap;
|
|
||||||
protected $addedMap;
|
protected $addedMap;
|
||||||
protected $conflictAddedMap;
|
protected $conflictAddedMap;
|
||||||
protected $addedPackages;
|
protected $addedPackages;
|
||||||
protected $addedPackagesByNames;
|
protected $addedPackagesByNames;
|
||||||
|
protected $conflictsForName;
|
||||||
|
|
||||||
public function __construct(PolicyInterface $policy, Pool $pool)
|
public function __construct(PolicyInterface $policy, Pool $pool)
|
||||||
{
|
{
|
||||||
|
@ -76,33 +76,17 @@ class RuleSetGenerator
|
||||||
* @param array $packages The set of packages to choose from
|
* @param array $packages The set of packages to choose from
|
||||||
* @param int $reason A RULE_* constant describing the reason for
|
* @param int $reason A RULE_* constant describing the reason for
|
||||||
* generating this rule
|
* generating this rule
|
||||||
* @param array $job The job this rule was created from
|
* @param array $reasonData Additional data like the root require or fix request info
|
||||||
* @return Rule The generated rule
|
* @return Rule The generated rule
|
||||||
*/
|
*/
|
||||||
protected function createInstallOneOfRule(array $packages, $reason, $job)
|
protected function createInstallOneOfRule(array $packages, $reason, $reasonData)
|
||||||
{
|
{
|
||||||
$literals = array();
|
$literals = array();
|
||||||
foreach ($packages as $package) {
|
foreach ($packages as $package) {
|
||||||
$literals[] = $package->id;
|
$literals[] = $package->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GenericRule($literals, $reason, $job['packageName'], $job);
|
return new GenericRule($literals, $reason, $reasonData);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a rule to remove a package
|
|
||||||
*
|
|
||||||
* The rule for a package A is (-A).
|
|
||||||
*
|
|
||||||
* @param PackageInterface $package The package to be removed
|
|
||||||
* @param int $reason A RULE_* constant describing the
|
|
||||||
* reason for generating this rule
|
|
||||||
* @param array $job The job this rule was created from
|
|
||||||
* @return Rule The generated rule
|
|
||||||
*/
|
|
||||||
protected function createRemoveRule(PackageInterface $package, $reason, $job)
|
|
||||||
{
|
|
||||||
return new GenericRule(array(-$package->id), $reason, $job['packageName'], $job);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,6 +113,20 @@ class RuleSetGenerator
|
||||||
return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
|
return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createMultiConflictRule(array $packages, $reason, $reasonData = null)
|
||||||
|
{
|
||||||
|
$literals = array();
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
$literals[] = -$package->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($literals) == 2) {
|
||||||
|
return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MultiConflictRule($literals, $reason, $reasonData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a rule unless it duplicates an existing one of any type
|
* Adds a rule unless it duplicates an existing one of any type
|
||||||
*
|
*
|
||||||
|
@ -147,41 +145,6 @@ 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;
|
||||||
|
@ -225,9 +188,16 @@ class RuleSetGenerator
|
||||||
|
|
||||||
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $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)) {
|
} else {
|
||||||
$reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
|
if (!isset($this->conflictsForName[$packageName])) {
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
|
$this->conflictsForName[$packageName] = array();
|
||||||
|
}
|
||||||
|
if (!$package instanceof AliasPackage) {
|
||||||
|
$this->conflictsForName[$packageName][$package->id] = $package;
|
||||||
|
}
|
||||||
|
if (!$provider instanceof AliasPackage) {
|
||||||
|
$this->conflictsForName[$packageName][$provider->id] = $provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +218,7 @@ class RuleSetGenerator
|
||||||
|
|
||||||
/** @var PackageInterface $possibleConflict */
|
/** @var PackageInterface $possibleConflict */
|
||||||
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
|
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
|
||||||
$conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
|
$conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint());
|
||||||
|
|
||||||
if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
|
if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
|
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
|
||||||
|
@ -258,8 +228,6 @@ class RuleSetGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// check obsoletes and implicit obsoletes of a package
|
// check obsoletes and implicit obsoletes of a package
|
||||||
$isInstalled = isset($this->installedMap[$package->id]);
|
|
||||||
|
|
||||||
foreach ($package->getReplaces() as $link) {
|
foreach ($package->getReplaces() as $link) {
|
||||||
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
|
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -272,12 +240,19 @@ class RuleSetGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
|
if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
|
||||||
$reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
|
$reason = Rule::RULE_PACKAGE_OBSOLETES;
|
||||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
|
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->conflictsForName as $name => $packages) {
|
||||||
|
if (count($packages) > 1) {
|
||||||
|
$reason = Rule::RULE_PACKAGE_SAME_NAME;
|
||||||
|
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function obsoleteImpossibleForAlias($package, $provider)
|
protected function obsoleteImpossibleForAlias($package, $provider)
|
||||||
|
@ -294,77 +269,61 @@ class RuleSetGenerator
|
||||||
return $impossible;
|
return $impossible;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function whitelistFromJobs()
|
protected function addRulesForRequest(Request $request, $ignorePlatformReqs)
|
||||||
{
|
{
|
||||||
foreach ($this->jobs as $job) {
|
$unlockableMap = $request->getUnlockableMap();
|
||||||
switch ($job['cmd']) {
|
|
||||||
case 'install':
|
foreach ($request->getFixedPackages() as $package) {
|
||||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
|
if ($package->id == -1) {
|
||||||
foreach ($packages as $package) {
|
// fixed package was not added to the pool as it did not pass the stability requirements, this is fine
|
||||||
$this->whitelistFromPackage($package);
|
if ($this->pool->isUnacceptableFixedPackage($package)) {
|
||||||
}
|
continue;
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
// otherwise, looks like a bug
|
||||||
|
throw new \LogicException("Fixed package ".$package->getName()." ".$package->getVersion().($package instanceof AliasPackage ? " (alias)" : "")." was not added to solver pool.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
||||||
|
|
||||||
|
$rule = $this->createInstallOneOfRule(array($package), Rule::RULE_FIXED, array(
|
||||||
|
'package' => $package,
|
||||||
|
'lockable' => !isset($unlockableMap[$package->id]),
|
||||||
|
));
|
||||||
|
$this->addRule(RuleSet::TYPE_REQUEST, $rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($request->getRequires() as $packageName => $constraint) {
|
||||||
|
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $packageName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packages = $this->pool->whatProvides($packageName, $constraint);
|
||||||
|
if ($packages) {
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, array(
|
||||||
|
'packageName' => $packageName,
|
||||||
|
'constraint' => $constraint,
|
||||||
|
));
|
||||||
|
$this->addRule(RuleSet::TYPE_REQUEST, $rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addRulesForJobs($ignorePlatformReqs)
|
public function getRulesFor(Request $request, $ignorePlatformReqs = false)
|
||||||
{
|
{
|
||||||
foreach ($this->jobs as $job) {
|
|
||||||
switch ($job['cmd']) {
|
|
||||||
case 'install':
|
|
||||||
if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
|
||||||
if ($packages) {
|
|
||||||
foreach ($packages as $package) {
|
|
||||||
if (!isset($this->installedMap[$package->id])) {
|
|
||||||
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job);
|
|
||||||
$this->addRule(RuleSet::TYPE_JOB, $rule);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
// remove all packages with this name including uninstalled
|
|
||||||
// ones to make sure none of them are picked as replacements
|
|
||||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
|
||||||
foreach ($packages as $package) {
|
|
||||||
$rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
|
|
||||||
$this->addRule(RuleSet::TYPE_JOB, $rule);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false)
|
|
||||||
{
|
|
||||||
$this->jobs = $jobs;
|
|
||||||
$this->rules = new RuleSet;
|
$this->rules = new RuleSet;
|
||||||
$this->installedMap = $installedMap;
|
|
||||||
|
|
||||||
$this->whitelistedMap = array();
|
|
||||||
foreach ($this->installedMap as $package) {
|
|
||||||
$this->whitelistFromPackage($package);
|
|
||||||
}
|
|
||||||
$this->whitelistFromJobs();
|
|
||||||
|
|
||||||
$this->pool->setWhitelist($this->whitelistedMap);
|
|
||||||
|
|
||||||
$this->addedMap = array();
|
$this->addedMap = array();
|
||||||
$this->conflictAddedMap = array();
|
$this->conflictAddedMap = array();
|
||||||
$this->addedPackages = array();
|
$this->addedPackages = array();
|
||||||
$this->addedPackagesByNames = array();
|
$this->addedPackagesByNames = array();
|
||||||
foreach ($this->installedMap as $package) {
|
$this->conflictsForName = array();
|
||||||
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addRulesForJobs($ignorePlatformReqs);
|
$this->addRulesForRequest($request, $ignorePlatformReqs);
|
||||||
|
|
||||||
$this->addConflictRules($ignorePlatformReqs);
|
$this->addConflictRules($ignorePlatformReqs);
|
||||||
|
|
||||||
|
|
|
@ -44,13 +44,24 @@ class RuleWatchGraph
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array($node->watch1, $node->watch2) as $literal) {
|
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||||
if (!isset($this->watchChains[$literal])) {
|
foreach (array($node->watch1, $node->watch2) as $literal) {
|
||||||
$this->watchChains[$literal] = new RuleWatchChain;
|
if (!isset($this->watchChains[$literal])) {
|
||||||
}
|
$this->watchChains[$literal] = new RuleWatchChain;
|
||||||
|
}
|
||||||
|
|
||||||
$this->watchChains[$literal]->unshift($node);
|
$this->watchChains[$literal]->unshift($node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($node->getRule()->getLiterals() as $literal) {
|
||||||
|
if (!isset($this->watchChains[$literal])) {
|
||||||
|
$this->watchChains[$literal] = new RuleWatchChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->watchChains[$literal]->unshift($node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,28 +103,40 @@ class RuleWatchGraph
|
||||||
$chain->rewind();
|
$chain->rewind();
|
||||||
while ($chain->valid()) {
|
while ($chain->valid()) {
|
||||||
$node = $chain->current();
|
$node = $chain->current();
|
||||||
$otherWatch = $node->getOtherWatch($literal);
|
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||||
|
$otherWatch = $node->getOtherWatch($literal);
|
||||||
|
|
||||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||||
$ruleLiterals = $node->getRule()->getLiterals();
|
$ruleLiterals = $node->getRule()->getLiterals();
|
||||||
|
|
||||||
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
||||||
return $literal !== $ruleLiteral &&
|
return $literal !== $ruleLiteral &&
|
||||||
$otherWatch !== $ruleLiteral &&
|
$otherWatch !== $ruleLiteral &&
|
||||||
!$decisions->conflict($ruleLiteral);
|
!$decisions->conflict($ruleLiteral);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($alternativeLiterals) {
|
if ($alternativeLiterals) {
|
||||||
reset($alternativeLiterals);
|
reset($alternativeLiterals);
|
||||||
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($decisions->conflict($otherWatch)) {
|
||||||
|
return $node->getRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
$decisions->decide($otherWatch, $level, $node->getRule());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($node->getRule()->getLiterals() as $otherLiteral) {
|
||||||
|
if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) {
|
||||||
|
if ($decisions->conflict($otherLiteral)) {
|
||||||
|
return $node->getRule();
|
||||||
|
}
|
||||||
|
|
||||||
if ($decisions->conflict($otherWatch)) {
|
$decisions->decide($otherLiteral, $level, $node->getRule());
|
||||||
return $node->getRule();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$decisions->decide($otherWatch, $level, $node->getRule());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$chain->next();
|
$chain->next();
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RuleWatchNode
|
||||||
$literals = $this->rule->getLiterals();
|
$literals = $this->rule->getLiterals();
|
||||||
|
|
||||||
// if there are only 2 elements, both are being watched anyway
|
// if there are only 2 elements, both are being watched anyway
|
||||||
if (count($literals) < 3) {
|
if (count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Repository\RepositoryInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,23 +28,18 @@ class Solver
|
||||||
protected $policy;
|
protected $policy;
|
||||||
/** @var Pool */
|
/** @var Pool */
|
||||||
protected $pool;
|
protected $pool;
|
||||||
/** @var RepositoryInterface */
|
|
||||||
protected $installed;
|
|
||||||
/** @var RuleSet */
|
/** @var RuleSet */
|
||||||
protected $rules;
|
protected $rules;
|
||||||
/** @var RuleSetGenerator */
|
/** @var RuleSetGenerator */
|
||||||
protected $ruleSetGenerator;
|
protected $ruleSetGenerator;
|
||||||
/** @var array */
|
|
||||||
protected $jobs;
|
|
||||||
|
|
||||||
/** @var int[] */
|
|
||||||
protected $updateMap = array();
|
|
||||||
/** @var RuleWatchGraph */
|
/** @var RuleWatchGraph */
|
||||||
protected $watchGraph;
|
protected $watchGraph;
|
||||||
/** @var Decisions */
|
/** @var Decisions */
|
||||||
protected $decisions;
|
protected $decisions;
|
||||||
/** @var int[] */
|
/** @var PackageInterface[] */
|
||||||
protected $installedMap;
|
protected $fixedMap;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $propagateIndex;
|
protected $propagateIndex;
|
||||||
|
@ -66,16 +61,13 @@ class Solver
|
||||||
/**
|
/**
|
||||||
* @param PolicyInterface $policy
|
* @param PolicyInterface $policy
|
||||||
* @param Pool $pool
|
* @param Pool $pool
|
||||||
* @param RepositoryInterface $installed
|
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
*/
|
*/
|
||||||
public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
|
public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io)
|
||||||
{
|
{
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
$this->policy = $policy;
|
$this->policy = $policy;
|
||||||
$this->pool = $pool;
|
$this->pool = $pool;
|
||||||
$this->installed = $installed;
|
|
||||||
$this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,6 +78,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()
|
||||||
|
@ -121,23 +118,23 @@ class Solver
|
||||||
$conflict = $this->decisions->decisionRule($literal);
|
$conflict = $this->decisions->decisionRule($literal);
|
||||||
|
|
||||||
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
|
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
|
||||||
$problem = new Problem($this->pool);
|
$problem = new Problem();
|
||||||
|
|
||||||
$problem->addRule($rule);
|
$problem->addRule($rule);
|
||||||
$problem->addRule($conflict);
|
$problem->addRule($conflict);
|
||||||
$this->disableProblem($rule);
|
$rule->disable();
|
||||||
$this->problems[] = $problem;
|
$this->problems[] = $problem;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// conflict with another job
|
// conflict with another root require/fixed package
|
||||||
$problem = new Problem($this->pool);
|
$problem = new Problem();
|
||||||
$problem->addRule($rule);
|
$problem->addRule($rule);
|
||||||
$problem->addRule($conflict);
|
$problem->addRule($conflict);
|
||||||
|
|
||||||
// push all of our rules (can only be job rules)
|
// push all of our rules (can only be root require/fixed package rules)
|
||||||
// asserting this literal on the problem stack
|
// asserting this literal on the problem stack
|
||||||
foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
|
foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) {
|
||||||
if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
|
if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -148,9 +145,8 @@ class Solver
|
||||||
if (abs($literal) !== abs($assertRuleLiteral)) {
|
if (abs($literal) !== abs($assertRuleLiteral)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$problem->addRule($assertRule);
|
$problem->addRule($assertRule);
|
||||||
$this->disableProblem($assertRule);
|
$assertRule->disable();
|
||||||
}
|
}
|
||||||
$this->problems[] = $problem;
|
$this->problems[] = $problem;
|
||||||
|
|
||||||
|
@ -159,47 +155,29 @@ class Solver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setupInstalledMap()
|
protected function setupFixedMap(Request $request)
|
||||||
{
|
{
|
||||||
$this->installedMap = array();
|
$this->fixedMap = array();
|
||||||
foreach ($this->installed->getPackages() as $package) {
|
foreach ($request->getFixedPackages() as $package) {
|
||||||
$this->installedMap[$package->id] = $package;
|
$this->fixedMap[$package->id] = $package;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Request $request
|
||||||
* @param bool $ignorePlatformReqs
|
* @param bool $ignorePlatformReqs
|
||||||
*/
|
*/
|
||||||
protected function checkForRootRequireProblems($ignorePlatformReqs)
|
protected function checkForRootRequireProblems($request, $ignorePlatformReqs)
|
||||||
{
|
{
|
||||||
foreach ($this->jobs as $job) {
|
foreach ($request->getRequires() as $packageName => $constraint) {
|
||||||
switch ($job['cmd']) {
|
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $packageName)) {
|
||||||
case 'update':
|
continue;
|
||||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
}
|
||||||
foreach ($packages as $package) {
|
|
||||||
if (isset($this->installedMap[$package->id])) {
|
|
||||||
$this->updateMap[$package->id] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'update-all':
|
if (!$this->pool->whatProvides($packageName, $constraint)) {
|
||||||
foreach ($this->installedMap as $package) {
|
$problem = new Problem();
|
||||||
$this->updateMap[$package->id] = true;
|
$problem->addRule(new GenericRule(array(), Rule::RULE_ROOT_REQUIRE, array('packageName' => $packageName, 'constraint' => $constraint)));
|
||||||
}
|
$this->problems[] = $problem;
|
||||||
break;
|
|
||||||
|
|
||||||
case 'install':
|
|
||||||
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->pool->whatProvides($job['packageName'], $job['constraint'])) {
|
|
||||||
$problem = new Problem($this->pool);
|
|
||||||
$problem->addRule(new GenericRule(array(), null, null, $job));
|
|
||||||
$this->problems[] = $problem;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,15 +185,16 @@ class Solver
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param bool $ignorePlatformReqs
|
* @param bool $ignorePlatformReqs
|
||||||
* @return array
|
* @return LockTransaction
|
||||||
*/
|
*/
|
||||||
public function solve(Request $request, $ignorePlatformReqs = false)
|
public function solve(Request $request, $ignorePlatformReqs = false)
|
||||||
{
|
{
|
||||||
$this->jobs = $request->getJobs();
|
$this->setupFixedMap($request);
|
||||||
|
|
||||||
$this->setupInstalledMap();
|
$this->io->writeError('Generating rules', true, IOInterface::DEBUG);
|
||||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
|
$this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
|
||||||
$this->checkForRootRequireProblems($ignorePlatformReqs);
|
$this->rules = $this->ruleSetGenerator->getRulesFor($request, $ignorePlatformReqs);
|
||||||
|
$this->checkForRootRequireProblems($request, $ignorePlatformReqs);
|
||||||
$this->decisions = new Decisions($this->pool);
|
$this->decisions = new Decisions($this->pool);
|
||||||
$this->watchGraph = new RuleWatchGraph;
|
$this->watchGraph = new RuleWatchGraph;
|
||||||
|
|
||||||
|
@ -223,29 +202,20 @@ class Solver
|
||||||
$this->watchGraph->insert(new RuleWatchNode($rule));
|
$this->watchGraph->insert(new RuleWatchNode($rule));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make decisions based on job/update assertions */
|
/* make decisions based on root require/fix assertions */
|
||||||
$this->makeAssertionRuleDecisions();
|
$this->makeAssertionRuleDecisions();
|
||||||
|
|
||||||
$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();
|
||||||
$this->io->writeError('', true, IOInterface::DEBUG);
|
$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
|
|
||||||
foreach ($this->installedMap as $packageId => $void) {
|
|
||||||
if ($this->decisions->undecided($packageId)) {
|
|
||||||
$this->decisions->decide(-$packageId, 1, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->problems) {
|
if ($this->problems) {
|
||||||
throw new SolverProblemsException($this->problems, $this->installedMap);
|
throw new SolverProblemsException($this->problems, $this->learnedPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
$transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);
|
return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
|
||||||
|
|
||||||
return $transaction->getOperations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,11 +292,10 @@ class Solver
|
||||||
*
|
*
|
||||||
* @param int $level
|
* @param int $level
|
||||||
* @param string|int $literal
|
* @param string|int $literal
|
||||||
* @param bool $disableRules
|
|
||||||
* @param Rule $rule
|
* @param Rule $rule
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function setPropagateLearn($level, $literal, $disableRules, Rule $rule)
|
private function setPropagateLearn($level, $literal, Rule $rule)
|
||||||
{
|
{
|
||||||
$level++;
|
$level++;
|
||||||
|
|
||||||
|
@ -340,7 +309,7 @@ class Solver
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($level == 1) {
|
if ($level == 1) {
|
||||||
return $this->analyzeUnsolvable($rule, $disableRules);
|
return $this->analyzeUnsolvable($rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
// conflict
|
// conflict
|
||||||
|
@ -377,14 +346,13 @@ class Solver
|
||||||
/**
|
/**
|
||||||
* @param int $level
|
* @param int $level
|
||||||
* @param array $decisionQueue
|
* @param array $decisionQueue
|
||||||
* @param bool $disableRules
|
|
||||||
* @param Rule $rule
|
* @param Rule $rule
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
|
private function selectAndInstall($level, array $decisionQueue, Rule $rule)
|
||||||
{
|
{
|
||||||
// choose best package to install from decisionQueue
|
// choose best package to install from decisionQueue
|
||||||
$literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage());
|
$literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage());
|
||||||
|
|
||||||
$selectedLiteral = array_shift($literals);
|
$selectedLiteral = array_shift($literals);
|
||||||
|
|
||||||
|
@ -393,7 +361,7 @@ class Solver
|
||||||
$this->branches[] = array($literals, $level);
|
$this->branches[] = array($literals, $level);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
|
return $this->setPropagateLearn($level, $selectedLiteral, $rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -539,12 +507,11 @@ class Solver
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Rule $conflictRule
|
* @param Rule $conflictRule
|
||||||
* @param bool $disableRules
|
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function analyzeUnsolvable(Rule $conflictRule, $disableRules)
|
private function analyzeUnsolvable(Rule $conflictRule)
|
||||||
{
|
{
|
||||||
$problem = new Problem($this->pool);
|
$problem = new Problem();
|
||||||
$problem->addRule($conflictRule);
|
$problem->addRule($conflictRule);
|
||||||
|
|
||||||
$this->analyzeUnsolvableRule($problem, $conflictRule);
|
$this->analyzeUnsolvableRule($problem, $conflictRule);
|
||||||
|
@ -586,41 +553,9 @@ class Solver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($disableRules) {
|
|
||||||
foreach ($this->problems[count($this->problems) - 1] as $reason) {
|
|
||||||
$this->disableProblem($reason['rule']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->resetSolver();
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Rule $why
|
|
||||||
*/
|
|
||||||
private function disableProblem(Rule $why)
|
|
||||||
{
|
|
||||||
$job = $why->getJob();
|
|
||||||
|
|
||||||
if (!$job) {
|
|
||||||
$why->disable();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable all rules of this job
|
|
||||||
foreach ($this->rules as $rule) {
|
|
||||||
/** @var Rule $rule */
|
|
||||||
if ($job === $rule->getJob()) {
|
|
||||||
$rule->disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resetSolver()
|
private function resetSolver()
|
||||||
{
|
{
|
||||||
$this->decisions->reset();
|
$this->decisions->reset();
|
||||||
|
@ -661,17 +596,14 @@ class Solver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function runSat()
|
||||||
* @param bool $disableRules
|
|
||||||
*/
|
|
||||||
private function runSat($disableRules = true)
|
|
||||||
{
|
{
|
||||||
$this->propagateIndex = 0;
|
$this->propagateIndex = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* here's the main loop:
|
* here's the main loop:
|
||||||
* 1) propagate new decisions (only needed once)
|
* 1) propagate new decisions (only needed once)
|
||||||
* 2) fulfill jobs
|
* 2) fulfill root requires/fixed packages
|
||||||
* 3) fulfill all unresolved rules
|
* 3) fulfill all unresolved rules
|
||||||
* 4) minimalize solution if we had choices
|
* 4) minimalize solution if we had choices
|
||||||
* if we encounter a problem, we rewind to a safe level and restart
|
* if we encounter a problem, we rewind to a safe level and restart
|
||||||
|
@ -679,10 +611,7 @@ class Solver
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$decisionQueue = array();
|
$decisionQueue = array();
|
||||||
/**
|
$decisionSupplementQueue = array();
|
||||||
* @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
|
|
||||||
*/
|
|
||||||
$disableRules = array();
|
|
||||||
|
|
||||||
$level = 1;
|
$level = 1;
|
||||||
$systemLevel = $level + 1;
|
$systemLevel = $level + 1;
|
||||||
|
@ -691,7 +620,7 @@ class Solver
|
||||||
if (1 === $level) {
|
if (1 === $level) {
|
||||||
$conflictRule = $this->propagate($level);
|
$conflictRule = $this->propagate($level);
|
||||||
if (null !== $conflictRule) {
|
if (null !== $conflictRule) {
|
||||||
if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
|
if ($this->analyzeUnsolvable($conflictRule)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,9 +628,9 @@ class Solver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle job rules
|
// handle root require/fixed package rules
|
||||||
if ($level < $systemLevel) {
|
if ($level < $systemLevel) {
|
||||||
$iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
|
$iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST);
|
||||||
foreach ($iterator as $rule) {
|
foreach ($iterator as $rule) {
|
||||||
if ($rule->isEnabled()) {
|
if ($rule->isEnabled()) {
|
||||||
$decisionQueue = array();
|
$decisionQueue = array();
|
||||||
|
@ -718,26 +647,21 @@ class Solver
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($noneSatisfied && count($decisionQueue)) {
|
if ($noneSatisfied && count($decisionQueue)) {
|
||||||
// prune all update packages until installed version
|
// if any of the options in the decision queue are fixed, only use those
|
||||||
// except for requested updates
|
$prunedQueue = array();
|
||||||
if (count($this->installed) != count($this->updateMap)) {
|
foreach ($decisionQueue as $literal) {
|
||||||
$prunedQueue = array();
|
if (isset($this->fixedMap[abs($literal)])) {
|
||||||
foreach ($decisionQueue as $literal) {
|
$prunedQueue[] = $literal;
|
||||||
if (isset($this->installedMap[abs($literal)])) {
|
|
||||||
$prunedQueue[] = $literal;
|
|
||||||
if (isset($this->updateMap[abs($literal)])) {
|
|
||||||
$prunedQueue = $decisionQueue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!empty($prunedQueue)) {
|
||||||
$decisionQueue = $prunedQueue;
|
$decisionQueue = $prunedQueue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($noneSatisfied && count($decisionQueue)) {
|
if ($noneSatisfied && count($decisionQueue)) {
|
||||||
$oLevel = $level;
|
$oLevel = $level;
|
||||||
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
|
$level = $this->selectAndInstall($level, $decisionQueue, $rule);
|
||||||
|
|
||||||
if (0 === $level) {
|
if (0 === $level) {
|
||||||
return;
|
return;
|
||||||
|
@ -751,7 +675,7 @@ class Solver
|
||||||
|
|
||||||
$systemLevel = $level + 1;
|
$systemLevel = $level + 1;
|
||||||
|
|
||||||
// jobs left
|
// root requires/fixed packages left
|
||||||
$iterator->next();
|
$iterator->next();
|
||||||
if ($iterator->valid()) {
|
if ($iterator->valid()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -813,7 +737,7 @@ class Solver
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
|
$level = $this->selectAndInstall($level, $decisionQueue, $rule);
|
||||||
|
|
||||||
if (0 === $level) {
|
if (0 === $level) {
|
||||||
return;
|
return;
|
||||||
|
@ -856,7 +780,7 @@ class Solver
|
||||||
|
|
||||||
$why = $this->decisions->lastReason();
|
$why = $this->decisions->lastReason();
|
||||||
|
|
||||||
$level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
|
$level = $this->setPropagateLearn($level, $lastLiteral, $why);
|
||||||
|
|
||||||
if ($level == 0) {
|
if ($level == 0) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
use Composer\Util\IniHelper;
|
use Composer\Util\IniHelper;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
|
@ -20,29 +21,33 @@ use Composer\Util\IniHelper;
|
||||||
class SolverProblemsException extends \RuntimeException
|
class SolverProblemsException extends \RuntimeException
|
||||||
{
|
{
|
||||||
protected $problems;
|
protected $problems;
|
||||||
protected $installedMap;
|
protected $learnedPool;
|
||||||
|
|
||||||
public function __construct(array $problems, array $installedMap)
|
public function __construct(array $problems, array $learnedPool)
|
||||||
{
|
{
|
||||||
$this->problems = $problems;
|
$this->problems = $problems;
|
||||||
$this->installedMap = $installedMap;
|
$this->learnedPool = $learnedPool;
|
||||||
|
|
||||||
parent::__construct($this->createMessage(), 2);
|
parent::__construct('Failed resolving dependencies with '.count($problems).' problems, call getPrettyString to get formatted details', 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createMessage()
|
public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, $isDevExtraction = false)
|
||||||
{
|
{
|
||||||
|
$installedMap = $request->getPresentMap(true);
|
||||||
$text = "\n";
|
$text = "\n";
|
||||||
$hasExtensionProblems = false;
|
$hasExtensionProblems = false;
|
||||||
|
$isCausedByLock = 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($repositorySet, $request, $pool, $installedMap, $this->learnedPool)."\n";
|
||||||
|
|
||||||
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
|
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
|
||||||
$hasExtensionProblems = true;
|
$hasExtensionProblems = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isCausedByLock |= $problem->isCausedByLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
|
if (!$isDevExtraction && (strpos($text, 'could not be found') || strpos($text, 'no matching package found'))) {
|
||||||
$text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
$text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +55,10 @@ class SolverProblemsException extends \RuntimeException
|
||||||
$text .= $this->createExtensionHint();
|
$text .= $this->createExtensionHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($isCausedByLock && !$isDevExtraction) {
|
||||||
|
$text .= "\nUse the option --with-all-dependencies to allow updates and removals for packages currently locked to specific versions.";
|
||||||
|
}
|
||||||
|
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,8 +85,8 @@ class SolverProblemsException extends \RuntimeException
|
||||||
private function hasExtensionProblems(array $reasonSets)
|
private function hasExtensionProblems(array $reasonSets)
|
||||||
{
|
{
|
||||||
foreach ($reasonSets as $reasonSet) {
|
foreach ($reasonSets as $reasonSet) {
|
||||||
foreach ($reasonSet as $reason) {
|
foreach ($reasonSet as $rule) {
|
||||||
if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) {
|
if (0 === strpos($rule->getRequiredPackage(), 'ext-')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,161 +13,205 @@
|
||||||
namespace Composer\DependencyResolver;
|
namespace Composer\DependencyResolver;
|
||||||
|
|
||||||
use Composer\Package\AliasPackage;
|
use Composer\Package\AliasPackage;
|
||||||
|
use Composer\Package\Link;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Repository\PlatformRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nils Adermann <naderman@naderman.de>
|
* @author Nils Adermann <naderman@naderman.de>
|
||||||
*/
|
*/
|
||||||
class Transaction
|
class Transaction
|
||||||
{
|
{
|
||||||
protected $policy;
|
/**
|
||||||
protected $pool;
|
* @var array
|
||||||
protected $installedMap;
|
*/
|
||||||
protected $decisions;
|
protected $operations;
|
||||||
protected $transaction;
|
|
||||||
|
|
||||||
public function __construct($policy, $pool, $installedMap, $decisions)
|
/**
|
||||||
|
* Packages present at the beginning of the transaction
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $presentPackages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package set resulting from this transaction
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $resultPackageMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $resultPackagesByName = array();
|
||||||
|
|
||||||
|
public function __construct($presentPackages, $resultPackages)
|
||||||
{
|
{
|
||||||
$this->policy = $policy;
|
$this->presentPackages = $presentPackages;
|
||||||
$this->pool = $pool;
|
$this->setResultPackageMaps($resultPackages);
|
||||||
$this->installedMap = $installedMap;
|
$this->operations = $this->calculateOperations();
|
||||||
$this->decisions = $decisions;
|
|
||||||
$this->transaction = array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOperations()
|
public function getOperations()
|
||||||
{
|
{
|
||||||
$installMeansUpdateMap = $this->findUpdates();
|
return $this->operations;
|
||||||
|
}
|
||||||
|
|
||||||
$updateMap = array();
|
private function setResultPackageMaps($resultPackages)
|
||||||
$installMap = array();
|
{
|
||||||
$uninstallMap = array();
|
$packageSort = function (PackageInterface $a, PackageInterface $b) {
|
||||||
|
// sort alias packages by the same name behind their non alias version
|
||||||
|
if ($a->getName() == $b->getName() && $a instanceof AliasPackage != $b instanceof AliasPackage) {
|
||||||
|
return $a instanceof AliasPackage ? -1 : 1;
|
||||||
|
}
|
||||||
|
return strcmp($b->getName(), $a->getName());
|
||||||
|
};
|
||||||
|
|
||||||
foreach ($this->decisions as $i => $decision) {
|
$this->resultPackageMap = array();
|
||||||
$literal = $decision[Decisions::DECISION_LITERAL];
|
foreach ($resultPackages as $package) {
|
||||||
$reason = $decision[Decisions::DECISION_REASON];
|
$this->resultPackageMap[spl_object_hash($package)] = $package;
|
||||||
|
foreach ($package->getNames() as $name) {
|
||||||
|
$this->resultPackagesByName[$name][] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$package = $this->pool->literalToPackage($literal);
|
uasort($this->resultPackageMap, $packageSort);
|
||||||
|
foreach ($this->resultPackagesByName as $name => $packages) {
|
||||||
|
uasort($this->resultPackagesByName[$name], $packageSort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// wanted & installed || !wanted & !installed
|
protected function calculateOperations()
|
||||||
if (($literal > 0) == isset($this->installedMap[$package->id])) {
|
{
|
||||||
|
$operations = array();
|
||||||
|
|
||||||
|
$presentPackageMap = array();
|
||||||
|
$removeMap = array();
|
||||||
|
$presentAliasMap = array();
|
||||||
|
$removeAliasMap = array();
|
||||||
|
foreach ($this->presentPackages as $package) {
|
||||||
|
if ($package instanceof AliasPackage) {
|
||||||
|
$presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
|
||||||
|
$removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
|
||||||
|
} else {
|
||||||
|
$presentPackageMap[$package->getName()] = $package;
|
||||||
|
$removeMap[$package->getName()] = $package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stack = $this->getRootPackages();
|
||||||
|
|
||||||
|
$visited = array();
|
||||||
|
$processed = array();
|
||||||
|
|
||||||
|
while (!empty($stack)) {
|
||||||
|
$package = array_pop($stack);
|
||||||
|
|
||||||
|
if (isset($processed[spl_object_hash($package)])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($literal > 0) {
|
if (!isset($visited[spl_object_hash($package)])) {
|
||||||
if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
|
$visited[spl_object_hash($package)] = true;
|
||||||
$source = $installMeansUpdateMap[abs($literal)];
|
|
||||||
|
|
||||||
$updateMap[$package->id] = array(
|
|
||||||
'package' => $package,
|
|
||||||
'source' => $source,
|
|
||||||
'reason' => $reason,
|
|
||||||
);
|
|
||||||
|
|
||||||
// avoid updates to one package from multiple origins
|
|
||||||
unset($installMeansUpdateMap[abs($literal)]);
|
|
||||||
$ignoreRemove[$source->id] = true;
|
|
||||||
} else {
|
|
||||||
$installMap[$package->id] = array(
|
|
||||||
'package' => $package,
|
|
||||||
'reason' => $reason,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->decisions as $i => $decision) {
|
|
||||||
$literal = $decision[Decisions::DECISION_LITERAL];
|
|
||||||
$reason = $decision[Decisions::DECISION_REASON];
|
|
||||||
$package = $this->pool->literalToPackage($literal);
|
|
||||||
|
|
||||||
if ($literal <= 0 &&
|
|
||||||
isset($this->installedMap[$package->id]) &&
|
|
||||||
!isset($ignoreRemove[$package->id])) {
|
|
||||||
$uninstallMap[$package->id] = array(
|
|
||||||
'package' => $package,
|
|
||||||
'reason' => $reason,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->transactionFromMaps($installMap, $updateMap, $uninstallMap);
|
|
||||||
|
|
||||||
return $this->transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function transactionFromMaps($installMap, $updateMap, $uninstallMap)
|
|
||||||
{
|
|
||||||
$queue = array_map(
|
|
||||||
function ($operation) {
|
|
||||||
return $operation['package'];
|
|
||||||
},
|
|
||||||
$this->findRootPackages($installMap, $updateMap)
|
|
||||||
);
|
|
||||||
|
|
||||||
$visited = array();
|
|
||||||
|
|
||||||
while (!empty($queue)) {
|
|
||||||
$package = array_pop($queue);
|
|
||||||
$packageId = $package->id;
|
|
||||||
|
|
||||||
if (!isset($visited[$packageId])) {
|
|
||||||
$queue[] = $package;
|
|
||||||
|
|
||||||
|
$stack[] = $package;
|
||||||
if ($package instanceof AliasPackage) {
|
if ($package instanceof AliasPackage) {
|
||||||
$queue[] = $package->getAliasOf();
|
$stack[] = $package->getAliasOf();
|
||||||
} else {
|
} else {
|
||||||
foreach ($package->getRequires() as $link) {
|
foreach ($package->getRequires() as $link) {
|
||||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
$possibleRequires = $this->getProvidersInResult($link);
|
||||||
|
|
||||||
foreach ($possibleRequires as $require) {
|
foreach ($possibleRequires as $require) {
|
||||||
$queue[] = $require;
|
$stack[] = $require;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif (!isset($processed[spl_object_hash($package)])) {
|
||||||
|
$processed[spl_object_hash($package)] = true;
|
||||||
|
|
||||||
$visited[$package->id] = true;
|
if ($package instanceof AliasPackage) {
|
||||||
} else {
|
$aliasKey = $package->getName().'::'.$package->getVersion();
|
||||||
if (isset($installMap[$packageId])) {
|
if (isset($presentAliasMap[$aliasKey])) {
|
||||||
$this->install(
|
unset($removeAliasMap[$aliasKey]);
|
||||||
$installMap[$packageId]['package'],
|
} else {
|
||||||
$installMap[$packageId]['reason']
|
$operations[] = new Operation\MarkAliasInstalledOperation($package);
|
||||||
);
|
}
|
||||||
unset($installMap[$packageId]);
|
} else {
|
||||||
}
|
if (isset($presentPackageMap[$package->getName()])) {
|
||||||
if (isset($updateMap[$packageId])) {
|
$source = $presentPackageMap[$package->getName()];
|
||||||
$this->update(
|
|
||||||
$updateMap[$packageId]['source'],
|
// do we need to update?
|
||||||
$updateMap[$packageId]['package'],
|
// TODO different for lock?
|
||||||
$updateMap[$packageId]['reason']
|
if ($package->getVersion() != $presentPackageMap[$package->getName()]->getVersion() ||
|
||||||
);
|
$package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() ||
|
||||||
unset($updateMap[$packageId]);
|
$package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference()
|
||||||
|
) {
|
||||||
|
$operations[] = new Operation\UpdateOperation($source, $package);
|
||||||
|
}
|
||||||
|
unset($removeMap[$package->getName()]);
|
||||||
|
} else {
|
||||||
|
$operations[] = new Operation\InstallOperation($package);
|
||||||
|
unset($removeMap[$package->getName()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($uninstallMap as $uninstall) {
|
foreach ($removeMap as $name => $package) {
|
||||||
$this->uninstall($uninstall['package'], $uninstall['reason']);
|
array_unshift($operations, new Operation\UninstallOperation($package, null));
|
||||||
}
|
}
|
||||||
|
foreach ($removeAliasMap as $nameVersion => $package) {
|
||||||
|
$operations[] = new Operation\MarkAliasUninstalledOperation($package, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$operations = $this->movePluginsToFront($operations);
|
||||||
|
// TODO fix this:
|
||||||
|
// we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls
|
||||||
|
$operations = $this->moveUninstallsToFront($operations);
|
||||||
|
|
||||||
|
// TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place?
|
||||||
|
/*
|
||||||
|
if ('update' === $opType) {
|
||||||
|
$targetPackage = $operation->getTargetPackage();
|
||||||
|
if ($targetPackage->isDev()) {
|
||||||
|
$initialPackage = $operation->getInitialPackage();
|
||||||
|
if ($targetPackage->getVersion() === $initialPackage->getVersion()
|
||||||
|
&& (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference())
|
||||||
|
&& (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference())
|
||||||
|
) {
|
||||||
|
$this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG);
|
||||||
|
$this->io->writeError('', true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return $this->operations = $operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function findRootPackages($installMap, $updateMap)
|
/**
|
||||||
|
* Determine which packages in the result are not required by any other packages in it.
|
||||||
|
*
|
||||||
|
* These serve as a starting point to enumerate packages in a topological order despite potential cycles.
|
||||||
|
* If there are packages with a cycle on the top level the package with the lowest name gets picked
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getRootPackages()
|
||||||
{
|
{
|
||||||
$packages = $installMap + $updateMap;
|
$roots = $this->resultPackageMap;
|
||||||
$roots = $packages;
|
|
||||||
|
|
||||||
foreach ($packages as $packageId => $operation) {
|
foreach ($this->resultPackageMap as $packageHash => $package) {
|
||||||
$package = $operation['package'];
|
if (!isset($roots[$packageHash])) {
|
||||||
|
|
||||||
if (!isset($roots[$packageId])) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($package->getRequires() as $link) {
|
foreach ($package->getRequires() as $link) {
|
||||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
$possibleRequires = $this->getProvidersInResult($link);
|
||||||
|
|
||||||
foreach ($possibleRequires as $require) {
|
foreach ($possibleRequires as $require) {
|
||||||
if ($require !== $package) {
|
if ($require !== $package) {
|
||||||
unset($roots[$require->id]);
|
unset($roots[spl_object_hash($require)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,69 +220,87 @@ class Transaction
|
||||||
return $roots;
|
return $roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function findUpdates()
|
protected function getProvidersInResult(Link $link)
|
||||||
{
|
{
|
||||||
$installMeansUpdateMap = array();
|
if (!isset($this->resultPackagesByName[$link->getTarget()])) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
return $this->resultPackagesByName[$link->getTarget()];
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->decisions as $i => $decision) {
|
/**
|
||||||
$literal = $decision[Decisions::DECISION_LITERAL];
|
* Workaround: if your packages depend on plugins, we must be sure
|
||||||
$package = $this->pool->literalToPackage($literal);
|
* that those are installed / updated first; else it would lead to packages
|
||||||
|
* being installed multiple times in different folders, when running Composer
|
||||||
|
* twice.
|
||||||
|
*
|
||||||
|
* While this does not fix the root-causes of https://github.com/composer/composer/issues/1147,
|
||||||
|
* it at least fixes the symptoms and makes usage of composer possible (again)
|
||||||
|
* in such scenarios.
|
||||||
|
*
|
||||||
|
* @param Operation\OperationInterface[] $operations
|
||||||
|
* @return Operation\OperationInterface[] reordered operation list
|
||||||
|
*/
|
||||||
|
private function movePluginsToFront(array $operations)
|
||||||
|
{
|
||||||
|
$pluginsNoDeps = array();
|
||||||
|
$pluginsWithDeps = array();
|
||||||
|
$pluginRequires = array();
|
||||||
|
|
||||||
if ($package instanceof AliasPackage) {
|
foreach (array_reverse($operations, true) as $idx => $op) {
|
||||||
|
if ($op instanceof Operation\InstallOperation) {
|
||||||
|
$package = $op->getPackage();
|
||||||
|
} elseif ($op instanceof Operation\UpdateOperation) {
|
||||||
|
$package = $op->getTargetPackage();
|
||||||
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// !wanted & installed
|
// is this package a plugin?
|
||||||
if ($literal <= 0 && isset($this->installedMap[$package->id])) {
|
$isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';
|
||||||
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
|
|
||||||
|
|
||||||
$literals = array($package->id);
|
// is this a plugin or a dependency of a plugin?
|
||||||
|
if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) {
|
||||||
|
// get the package's requires, but filter out any platform requirements or 'composer-plugin-api'
|
||||||
|
$requires = array_filter(array_keys($package->getRequires()), function ($req) {
|
||||||
|
return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req);
|
||||||
|
});
|
||||||
|
|
||||||
foreach ($updates as $update) {
|
// is this a plugin with no meaningful dependencies?
|
||||||
$literals[] = $update->id;
|
if ($isPlugin && !count($requires)) {
|
||||||
|
// plugins with no dependencies go to the very front
|
||||||
|
array_unshift($pluginsNoDeps, $op);
|
||||||
|
} else {
|
||||||
|
// capture the requirements for this package so those packages will be moved up as well
|
||||||
|
$pluginRequires = array_merge($pluginRequires, $requires);
|
||||||
|
// move the operation to the front
|
||||||
|
array_unshift($pluginsWithDeps, $op);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($literals as $updateLiteral) {
|
unset($operations[$idx]);
|
||||||
if ($updateLiteral !== $literal) {
|
|
||||||
$installMeansUpdateMap[abs($updateLiteral)] = $package;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $installMeansUpdateMap;
|
return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function install($package, $reason)
|
/**
|
||||||
|
* Removals of packages should be executed before installations in
|
||||||
|
* case two packages resolve to the same path (due to custom installers)
|
||||||
|
*
|
||||||
|
* @param Operation\OperationInterface[] $operations
|
||||||
|
* @return Operation\OperationInterface[] reordered operation list
|
||||||
|
*/
|
||||||
|
private function moveUninstallsToFront(array $operations)
|
||||||
{
|
{
|
||||||
if ($package instanceof AliasPackage) {
|
$uninstOps = array();
|
||||||
return $this->markAliasInstalled($package, $reason);
|
foreach ($operations as $idx => $op) {
|
||||||
|
if ($op instanceof Operation\UninstallOperation) {
|
||||||
|
$uninstOps[] = $op;
|
||||||
|
unset($operations[$idx]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->transaction[] = new Operation\InstallOperation($package, $reason);
|
return array_merge($uninstOps, $operations);
|
||||||
}
|
|
||||||
|
|
||||||
protected function update($from, $to, $reason)
|
|
||||||
{
|
|
||||||
$this->transaction[] = new Operation\UpdateOperation($from, $to, $reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function uninstall($package, $reason)
|
|
||||||
{
|
|
||||||
if ($package instanceof AliasPackage) {
|
|
||||||
return $this->markAliasUninstalled($package, $reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->transaction[] = new Operation\UninstallOperation($package, $reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function markAliasInstalled($package, $reason)
|
|
||||||
{
|
|
||||||
$this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function markAliasUninstalled($package, $reason)
|
|
||||||
{
|
|
||||||
$this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,33 +30,56 @@ abstract class ArchiveDownloader extends FileDownloader
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
* @throws \UnexpectedValueException
|
* @throws \UnexpectedValueException
|
||||||
*/
|
*/
|
||||||
public function download(PackageInterface $package, $path, $output = true)
|
public function install(PackageInterface $package, $path, $output = true)
|
||||||
{
|
{
|
||||||
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
|
if ($output) {
|
||||||
$retries = 3;
|
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): Extracting archive");
|
||||||
while ($retries--) {
|
} else {
|
||||||
$fileName = parent::download($package, $path, $output);
|
$this->io->writeError('Extracting archive', false);
|
||||||
|
}
|
||||||
|
|
||||||
if ($output) {
|
$this->filesystem->ensureDirectoryExists($path);
|
||||||
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
|
if (!$this->filesystem->isDirEmpty($path)) {
|
||||||
|
throw new \RuntimeException('Expected empty path to extract '.$package.' into but directory exists: '.$path);
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
|
||||||
|
} while (is_dir($temporaryDir));
|
||||||
|
|
||||||
|
$fileName = $this->getFileName($package, $path);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
||||||
|
try {
|
||||||
|
$this->extract($package, $fileName, $temporaryDir);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// remove cache if the file was corrupted
|
||||||
|
parent::clearLastCacheWrite($package);
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$this->filesystem->unlink($fileName);
|
||||||
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
|
||||||
try {
|
$renameAsOne = false;
|
||||||
$this->extract($fileName, $temporaryDir);
|
if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
|
||||||
} catch (\Exception $e) {
|
$renameAsOne = true;
|
||||||
// remove cache if the file was corrupted
|
}
|
||||||
parent::clearLastCacheWrite($package);
|
|
||||||
throw $e;
|
$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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,44 +88,16 @@ 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);
|
||||||
}
|
} catch (\Exception $e) {
|
||||||
}
|
// clean up
|
||||||
|
$this->filesystem->removeDirectory($path);
|
||||||
|
$this->filesystem->removeDirectory($temporaryDir);
|
||||||
|
|
||||||
/**
|
throw $e;
|
||||||
* {@inheritdoc}
|
}
|
||||||
*/
|
|
||||||
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), '.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,7 +108,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
|
||||||
|
|
|
@ -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,7 +140,7 @@ 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'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,63 +157,117 @@ class DownloadManager
|
||||||
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|null $prevPackage previous package instance in case of updates
|
||||||
*
|
*
|
||||||
|
* @return PromiseInterface
|
||||||
* @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 download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
|
||||||
{
|
{
|
||||||
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
$sourceType = $package->getSourceType();
|
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
|
||||||
$distType = $package->getDistType();
|
|
||||||
|
|
||||||
$sources = array();
|
$sources = $this->getAvailableSources($package, $prevPackage);
|
||||||
if ($sourceType) {
|
|
||||||
$sources[] = 'source';
|
|
||||||
}
|
|
||||||
if ($distType) {
|
|
||||||
$sources[] = 'dist';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($sources)) {
|
$io = $this->io;
|
||||||
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
|
$self = $this;
|
||||||
}
|
|
||||||
|
|
||||||
if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
|
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download, $prevPackage) {
|
||||||
$sources = array_reverse($sources);
|
$source = array_shift($sources);
|
||||||
}
|
if ($retry) {
|
||||||
|
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||||
$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);
|
$package->setInstallationSource($source);
|
||||||
try {
|
|
||||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
$downloader = $self->getDownloaderForPackage($package);
|
||||||
if ($downloader) {
|
if (!$downloader) {
|
||||||
$downloader->download($package, $targetDir);
|
return \React\Promise\resolve();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
} catch (\RuntimeException $e) {
|
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
|
||||||
if ($i === count($sources) - 1) {
|
if ($e instanceof \RuntimeException) {
|
||||||
throw $e;
|
if (!$sources) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->writeError(
|
||||||
|
' <warning>Failed to download '.
|
||||||
|
$package->getPrettyName().
|
||||||
|
' from ' . $source . ': '.
|
||||||
|
$e->getMessage().'</warning>'
|
||||||
|
);
|
||||||
|
|
||||||
|
return $download(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(
|
throw $e;
|
||||||
' <warning>Failed to download '.
|
};
|
||||||
$package->getPrettyName().
|
|
||||||
' from ' . $source . ': '.
|
try {
|
||||||
$e->getMessage().'</warning>'
|
$result = $downloader->download($package, $targetDir, $prevPackage);
|
||||||
);
|
} 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares an operation execution
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $targetDir target dir
|
||||||
|
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
|
$downloader = $this->getDownloaderForPackage($package);
|
||||||
|
if ($downloader) {
|
||||||
|
return $downloader->prepare($type, $package, $targetDir, $prevPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs package into target dir.
|
||||||
|
*
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $targetDir target dir
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
* @throws \InvalidArgumentException if package have no urls to download from
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
public function install(PackageInterface $package, $targetDir)
|
||||||
|
{
|
||||||
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
|
$downloader = $this->getDownloaderForPackage($package);
|
||||||
|
if ($downloader) {
|
||||||
|
return $downloader->install($package, $targetDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,39 +278,30 @@ class DownloadManager
|
||||||
* @param PackageInterface $target target package version
|
* @param PackageInterface $target target package version
|
||||||
* @param string $targetDir target dir
|
* @param string $targetDir target dir
|
||||||
*
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
* @throws \InvalidArgumentException if initial package is not installed
|
* @throws \InvalidArgumentException if initial package is not installed
|
||||||
*/
|
*/
|
||||||
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
|
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
|
||||||
{
|
{
|
||||||
$downloader = $this->getDownloaderForInstalledPackage($initial);
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
|
$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) {
|
||||||
return;
|
return $initialDownloader->remove($initial, $targetDir);
|
||||||
}
|
|
||||||
|
|
||||||
$installationSource = $initial->getInstallationSource();
|
|
||||||
|
|
||||||
if ('dist' === $installationSource) {
|
|
||||||
$initialType = $initial->getDistType();
|
|
||||||
$targetType = $target->getDistType();
|
|
||||||
} else {
|
|
||||||
$initialType = $initial->getSourceType();
|
|
||||||
$targetType = $target->getSourceType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// upgrading from a dist stable package to a dev package, force source reinstall
|
|
||||||
if ($target->isDev() && 'dist' === $installationSource) {
|
|
||||||
$downloader->remove($initial, $targetDir);
|
|
||||||
$this->download($target, $targetDir);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$initialType = $this->getDownloaderType($initialDownloader);
|
||||||
|
$targetType = $this->getDownloaderType($downloader);
|
||||||
if ($initialType === $targetType) {
|
if ($initialType === $targetType) {
|
||||||
$target->setInstallationSource($installationSource);
|
|
||||||
try {
|
try {
|
||||||
$downloader->update($initial, $target, $targetDir);
|
return $downloader->update($initial, $target, $targetDir);
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (\RuntimeException $e) {
|
} catch (\RuntimeException $e) {
|
||||||
if (!$this->io->isInteractive()) {
|
if (!$this->io->isInteractive()) {
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@ -282,8 +313,17 @@ 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
|
||||||
|
$promise = $initialDownloader->remove($initial, $targetDir);
|
||||||
|
if ($promise) {
|
||||||
|
$self = $this;
|
||||||
|
return $promise->then(function ($res) use ($self, $target, $targetDir) {
|
||||||
|
return $self->install($target, $targetDir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->install($target, $targetDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -291,12 +331,34 @@ class DownloadManager
|
||||||
*
|
*
|
||||||
* @param PackageInterface $package package instance
|
* @param PackageInterface $package package instance
|
||||||
* @param string $targetDir target dir
|
* @param string $targetDir target dir
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
public function remove(PackageInterface $package, $targetDir)
|
public function remove(PackageInterface $package, $targetDir)
|
||||||
{
|
{
|
||||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
|
$downloader = $this->getDownloaderForPackage($package);
|
||||||
if ($downloader) {
|
if ($downloader) {
|
||||||
$downloader->remove($package, $targetDir);
|
return $downloader->remove($package, $targetDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up a failed operation
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $targetDir target dir
|
||||||
|
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||||
|
$downloader = $this->getDownloaderForPackage($package);
|
||||||
|
if ($downloader) {
|
||||||
|
return $downloader->cleanup($type, $package, $targetDir, $prevPackage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,4 +384,64 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloaders expect a /path/to/dir without trailing slash
|
||||||
|
*
|
||||||
|
* If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function normalizeTargetDir($dir)
|
||||||
|
{
|
||||||
|
if ($dir === '\\' || $dir === '/') {
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($dir, '\\/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -30,12 +31,35 @@ interface DownloaderInterface
|
||||||
public function getInstallationSource();
|
public function getInstallationSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads specific package into specific folder.
|
* This should do any network-related tasks to prepare for an upcoming install/update
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do anything that needs to be done between all downloads have been completed and the actual operation is executed
|
||||||
|
*
|
||||||
|
* All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
|
||||||
|
* for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
|
||||||
|
* user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
|
||||||
|
* be undone as much as possible.
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $path download path
|
||||||
|
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs 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.
|
||||||
|
@ -55,10 +79,17 @@ interface DownloaderInterface
|
||||||
public function remove(PackageInterface $package, $path);
|
public function remove(PackageInterface $package, $path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether to output download progress information or not
|
* Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
|
||||||
*
|
*
|
||||||
* @param bool $outputProgress
|
* Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
|
||||||
* @return DownloaderInterface
|
* all installers a change to cleanup things they did previously, so you need to keep track of changes
|
||||||
|
* applied in the installer/downloader themselves.
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $path download path
|
||||||
|
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
public function setOutputProgress($outputProgress);
|
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,9 @@ 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\Util\Url as UrlUtil;
|
||||||
|
use Composer\Downloader\TransportException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base downloader for files
|
* Base downloader for files
|
||||||
|
@ -39,11 +40,13 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,17 +54,17 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
*
|
*
|
||||||
* @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;
|
||||||
|
|
||||||
|
@ -81,127 +84,191 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function download(PackageInterface $package, $path, $output = true)
|
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||||
{
|
{
|
||||||
if (!$package->getDistUrl()) {
|
if (!$package->getDistUrl()) {
|
||||||
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->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);
|
||||||
|
$this->filesystem->ensureDirectoryExists($path);
|
||||||
|
$this->filesystem->ensureDirectoryExists(dirname($fileName));
|
||||||
|
|
||||||
$processedUrl = $this->processUrl($package, $url);
|
$io = $this->io;
|
||||||
$origin = RemoteFilesystem::getOrigin($processedUrl);
|
$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, &$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'];
|
||||||
|
|
||||||
// use from cache if it is present and has a valid checksum or we have no checksum to check against
|
// 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)) {
|
||||||
$this->io->writeError('Loading from cache', false);
|
if ($output) {
|
||||||
|
$io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache", true, IOInterface::VERY_VERBOSE);
|
||||||
|
}
|
||||||
|
$result = \React\Promise\resolve($fileName);
|
||||||
} else {
|
} else {
|
||||||
// download if cache restore failed
|
if ($output) {
|
||||||
if (!$this->outputProgress) {
|
$io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||||
$this->io->writeError('Downloading', false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to download 3 times then fail hard
|
$result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
|
||||||
$retries = 3;
|
->then($accept, $reject);
|
||||||
while ($retries--) {
|
|
||||||
try {
|
|
||||||
$rfs->copy($origin, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
|
|
||||||
break;
|
|
||||||
} catch (TransportException $e) {
|
|
||||||
// if we got an http response with a proper code, then requesting again will probably not help, abort
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ($cache, $package, $fileName, $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, $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 prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
|
||||||
{
|
{
|
||||||
$this->outputProgress = $outputProgress;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function clearLastCacheWrite(PackageInterface $package)
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$fileName = $this->getFileName($package, $path);
|
||||||
|
if (file_exists($fileName)) {
|
||||||
|
$this->filesystem->unlink($fileName);
|
||||||
|
}
|
||||||
|
if (is_dir($path) && $this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
|
||||||
|
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
|
||||||
|
}
|
||||||
|
if (is_dir($path) && $this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
|
||||||
|
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
|
||||||
|
}
|
||||||
|
if (is_dir($path) && $this->filesystem->isDirEmpty($path)) {
|
||||||
|
$this->filesystem->removeDirectory($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function install(PackageInterface $package, $path, $output = true)
|
||||||
|
{
|
||||||
|
if ($output) {
|
||||||
|
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||||
|
}
|
||||||
|
|
||||||
|
$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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()]);
|
||||||
|
@ -218,11 +285,11 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
$from = $initial->getFullPrettyVersion();
|
$from = $initial->getFullPrettyVersion();
|
||||||
$to = $target->getFullPrettyVersion();
|
$to = $target->getFullPrettyVersion();
|
||||||
|
|
||||||
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
|
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Upgrading' : 'Downgrading';
|
||||||
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
|
$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('');
|
||||||
}
|
}
|
||||||
|
@ -249,7 +316,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
*/
|
*/
|
||||||
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 rtrim($this->config->get('vendor-dir').'/composer/'.md5($package.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -291,15 +358,15 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
public function getLocalChanges(PackageInterface $package, $targetDir)
|
public function getLocalChanges(PackageInterface $package, $targetDir)
|
||||||
{
|
{
|
||||||
$prevIO = $this->io;
|
$prevIO = $this->io;
|
||||||
$prevProgress = $this->outputProgress;
|
|
||||||
|
|
||||||
$this->io = new NullIO;
|
$this->io = new NullIO;
|
||||||
$this->io->loadConfiguration($this->config);
|
$this->io->loadConfiguration($this->config);
|
||||||
$this->outputProgress = false;
|
|
||||||
$e = null;
|
$e = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->download($package, $targetDir.'_compare', false);
|
$res = $this->download($package, $targetDir.'_compare', null, false);
|
||||||
|
$this->httpDownloader->wait();
|
||||||
|
$res = $this->install($package, $targetDir.'_compare', false);
|
||||||
|
|
||||||
$comparer = new Comparer();
|
$comparer = new Comparer();
|
||||||
$comparer->setSource($targetDir.'_compare');
|
$comparer->setSource($targetDir.'_compare');
|
||||||
|
@ -311,7 +378,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io = $prevIO;
|
$this->io = $prevIO;
|
||||||
$this->outputProgress = $prevProgress;
|
|
||||||
|
|
||||||
if ($e) {
|
if ($e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
|
|
|
@ -23,7 +23,15 @@ class FossilDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doDownload(PackageInterface $package, $path, $url)
|
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected 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);
|
||||||
|
@ -49,7 +57,7 @@ class FossilDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $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);
|
||||||
|
|
|
@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Composer\Util\Git as GitUtil;
|
use Composer\Util\Git as GitUtil;
|
||||||
|
use Composer\Util\Url;
|
||||||
use Composer\Util\Platform;
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Cache;
|
use Composer\Cache;
|
||||||
|
@ -29,6 +30,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
private $hasStashedChanges = false;
|
private $hasStashedChanges = false;
|
||||||
private $hasDiscardedChanges = false;
|
private $hasDiscardedChanges = false;
|
||||||
private $gitUtil;
|
private $gitUtil;
|
||||||
|
private $cachedPackages = array();
|
||||||
|
|
||||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
|
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
|
||||||
{
|
{
|
||||||
|
@ -39,7 +41,28 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doDownload(PackageInterface $package, $path, $url)
|
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
GitUtil::cleanEnv();
|
||||||
|
|
||||||
|
$cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/';
|
||||||
|
$gitVersion = $this->gitUtil->getVersion();
|
||||||
|
|
||||||
|
// --dissociate option is only available since git 2.3.0-rc0
|
||||||
|
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
|
||||||
|
$this->io->writeError(" - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
|
||||||
|
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
|
||||||
|
$ref = $package->getSourceReference();
|
||||||
|
if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref) && is_dir($cachePath)) {
|
||||||
|
$this->cachedPackages[$package->getId()][$ref] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected function doInstall(PackageInterface $package, $path, $url)
|
||||||
{
|
{
|
||||||
GitUtil::cleanEnv();
|
GitUtil::cleanEnv();
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
|
@ -47,31 +70,20 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
$flag = Platform::isWindows() ? '/D ' : '';
|
$flag = Platform::isWindows() ? '/D ' : '';
|
||||||
|
|
||||||
// --dissociate option is only available since git 2.3.0-rc0
|
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
|
||||||
$gitVersion = $this->gitUtil->getVersion();
|
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
|
||||||
$msg = "Cloning ".$this->getShortHash($ref);
|
$command =
|
||||||
|
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
|
||||||
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';
|
. '&& cd '.$flag.'%path% '
|
||||||
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
|
. '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
|
||||||
$this->io->writeError('', true, IOInterface::DEBUG);
|
} else {
|
||||||
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
|
$msg = "Cloning ".$this->getShortHash($ref);
|
||||||
try {
|
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';
|
||||||
if (!$this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref)) {
|
if (getenv('COMPOSER_DISABLE_NETWORK')) {
|
||||||
$this->io->writeError('<error>Failed to update '.$url.' in cache, package installation for '.$package->getPrettyName().' might fail.</error>');
|
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
|
||||||
}
|
|
||||||
if (is_dir($cachePath)) {
|
|
||||||
$command =
|
|
||||||
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
|
|
||||||
. '&& cd '.$flag.'%path% '
|
|
||||||
. '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
|
|
||||||
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
|
|
||||||
}
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
if (0 === strpos(get_class($e), 'PHPUnit')) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError($msg);
|
$this->io->writeError($msg);
|
||||||
|
|
||||||
$commandCallable = function ($url) use ($path, $command, $cachePath) {
|
$commandCallable = function ($url) use ($path, $command, $cachePath) {
|
||||||
|
@ -105,13 +117,52 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||||
{
|
{
|
||||||
GitUtil::cleanEnv();
|
GitUtil::cleanEnv();
|
||||||
|
$path = $this->normalizePath($path);
|
||||||
if (!$this->hasMetadataRepository($path)) {
|
if (!$this->hasMetadataRepository($path)) {
|
||||||
throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/';
|
||||||
|
$ref = $target->getSourceReference();
|
||||||
|
$flag = Platform::isWindows() ? '/D ' : '';
|
||||||
|
|
||||||
|
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
|
||||||
|
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
|
||||||
|
$command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
|
||||||
|
} else {
|
||||||
|
$msg = "Checking out ".$this->getShortHash($ref);
|
||||||
|
$command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
|
||||||
|
if (getenv('COMPOSER_DISABLE_NETWORK')) {
|
||||||
|
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->io->writeError($msg);
|
||||||
|
|
||||||
|
$commandCallable = function ($url) use ($ref, $command, $cachePath) {
|
||||||
|
return str_replace(
|
||||||
|
array('%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'),
|
||||||
|
array(
|
||||||
|
ProcessExecutor::escape($url),
|
||||||
|
ProcessExecutor::escape($ref.'^{commit}'),
|
||||||
|
ProcessExecutor::escape($cachePath),
|
||||||
|
ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
|
||||||
|
),
|
||||||
|
$command
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->gitUtil->runCommand($commandCallable, $url, $path);
|
||||||
|
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
|
||||||
|
if ($target->getDistReference() === $target->getSourceReference()) {
|
||||||
|
$target->setDistReference($newRef);
|
||||||
|
}
|
||||||
|
$target->setSourceReference($newRef);
|
||||||
|
}
|
||||||
|
|
||||||
$updateOriginUrl = false;
|
$updateOriginUrl = false;
|
||||||
if (
|
if (
|
||||||
0 === $this->process->execute('git remote -v', $output, $path)
|
0 === $this->process->execute('git remote -v', $output, $path)
|
||||||
|
@ -122,28 +173,6 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$updateOriginUrl = true;
|
$updateOriginUrl = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ref = $target->getSourceReference();
|
|
||||||
$this->io->writeError(" Checking out ".$this->getShortHash($ref));
|
|
||||||
$command = '(git remote set-url composer %s && git rev-parse --quiet --verify %s || (git fetch composer && git fetch --tags composer)) && git remote set-url composer %s';
|
|
||||||
|
|
||||||
$commandCallable = function ($url) use ($command, $ref) {
|
|
||||||
return sprintf(
|
|
||||||
$command,
|
|
||||||
ProcessExecutor::escape($url),
|
|
||||||
ProcessExecutor::escape($ref.'^{commit}'),
|
|
||||||
ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->gitUtil->runCommand($commandCallable, $url, $path);
|
|
||||||
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
|
|
||||||
if ($target->getDistReference() === $target->getSourceReference()) {
|
|
||||||
$target->setDistReference($newRef);
|
|
||||||
}
|
|
||||||
$target->setSourceReference($newRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($updateOriginUrl) {
|
if ($updateOriginUrl) {
|
||||||
$this->updateOriginUrl($path, $target->getSourceUrl());
|
$this->updateOriginUrl($path, $target->getSourceUrl());
|
||||||
}
|
}
|
||||||
|
@ -272,7 +301,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$changes = array_map(function ($elem) {
|
$changes = array_map(function ($elem) {
|
||||||
return ' '.$elem;
|
return ' '.$elem;
|
||||||
}, preg_split('{\s*\r?\n\s*}', $changes));
|
}, preg_split('{\s*\r?\n\s*}', $changes));
|
||||||
$this->io->writeError(' <error>The package has modified files:</error>');
|
$this->io->writeError(' <error>'.$package->getPrettyName().' has modified files:</error>');
|
||||||
$this->io->writeError(array_slice($changes, 0, 10));
|
$this->io->writeError(array_slice($changes, 0, 10));
|
||||||
if (count($changes) > 10) {
|
if (count($changes) > 10) {
|
||||||
$this->io->writeError(' <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
|
$this->io->writeError(' <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
|
||||||
|
@ -373,7 +402,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,14 +420,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")
|
||||||
|
@ -406,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
|
$this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \RuntimeException(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
|
throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateOriginUrl($path, $url)
|
protected function updateOriginUrl($path, $url)
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -24,7 +24,15 @@ class HgDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doDownload(PackageInterface $package, $path, $url)
|
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected function doInstall(PackageInterface $package, $path, $url)
|
||||||
{
|
{
|
||||||
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
||||||
|
|
||||||
|
@ -44,7 +52,7 @@ class HgDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||||
{
|
{
|
||||||
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function download(PackageInterface $package, $path, $output = true)
|
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||||
{
|
{
|
||||||
$url = $package->getDistUrl();
|
$url = $package->getDistUrl();
|
||||||
$realUrl = realpath($url);
|
$realUrl = realpath($url);
|
||||||
|
@ -50,14 +50,6 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (realpath($path) === $realUrl) {
|
if (realpath($path) === $realUrl) {
|
||||||
if ($output) {
|
|
||||||
$this->io->writeError(sprintf(
|
|
||||||
' - Installing <info>%s</info> (<comment>%s</comment>): Source already present',
|
|
||||||
$package->getName(),
|
|
||||||
$package->getFullPrettyVersion()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +65,29 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
||||||
$realUrl
|
$realUrl
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function install(PackageInterface $package, $path, $output = true)
|
||||||
|
{
|
||||||
|
$url = $package->getDistUrl();
|
||||||
|
$realUrl = realpath($url);
|
||||||
|
|
||||||
|
if (realpath($path) === $realUrl) {
|
||||||
|
if ($output) {
|
||||||
|
$this->io->writeError(sprintf(
|
||||||
|
' - Installing <info>%s</info> (<comment>%s</comment>): Source already present',
|
||||||
|
$package->getName(),
|
||||||
|
$package->getFullPrettyVersion()
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
$this->io->writeError('Source already present', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the transport options with default values
|
// Get the transport options with default values
|
||||||
$transportOptions = $package->getTransportOptions() + array('symlink' => null, 'relative' => true);
|
$transportOptions = $package->getTransportOptions() + array('symlink' => null, 'relative' => true);
|
||||||
|
@ -154,7 +169,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
||||||
$fileSystem->mirror($realUrl, $path, $iterator);
|
$fileSystem->mirror($realUrl, $path, $iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError('');
|
if ($output) {
|
||||||
|
$this->io->writeError('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,7 +181,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
||||||
{
|
{
|
||||||
$realUrl = realpath($package->getDistUrl());
|
$realUrl = realpath($package->getDistUrl());
|
||||||
|
|
||||||
if (realpath($path) === $realUrl) {
|
if ($path === $realUrl) {
|
||||||
if ($output) {
|
if ($output) {
|
||||||
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>), source is still present in $path");
|
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>), source is still present in $path");
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,15 @@ class PerforceDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doDownload(PackageInterface $package, $path, $url)
|
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function doInstall(PackageInterface $package, $path, $url)
|
||||||
{
|
{
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
$label = $this->getLabelFromSourceReference($ref);
|
$label = $this->getLabelFromSourceReference($ref);
|
||||||
|
@ -76,9 +84,9 @@ class PerforceDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||||
{
|
{
|
||||||
$this->doDownload($target, $path, $url);
|
$this->doInstall($target, $path, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace Composer\Downloader;
|
namespace Composer\Downloader;
|
||||||
|
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloader for phar files
|
* Downloader for phar files
|
||||||
*
|
*
|
||||||
|
@ -22,7 +24,7 @@ class PharDownloader extends ArchiveDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
protected function extract($file, $path)
|
protected function extract(PackageInterface $package, $file, $path)
|
||||||
{
|
{
|
||||||
// Can throw an UnexpectedValueException
|
// Can throw an UnexpectedValueException
|
||||||
$archive = new \Phar($file);
|
$archive = new \Phar($file);
|
||||||
|
|
|
@ -18,8 +18,9 @@ use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Util\IniHelper;
|
use Composer\Util\IniHelper;
|
||||||
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;
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
use RarArchive;
|
use RarArchive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,15 +32,16 @@ use RarArchive;
|
||||||
*/
|
*/
|
||||||
class RarDownloader extends ArchiveDownloader
|
class RarDownloader 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)
|
||||||
{
|
{
|
||||||
$processError = null;
|
$processError = null;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,15 @@ class SvnDownloader extends VcsDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doDownload(PackageInterface $package, $path, $url)
|
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected function doInstall(PackageInterface $package, $path, $url)
|
||||||
{
|
{
|
||||||
SvnUtil::cleanEnv();
|
SvnUtil::cleanEnv();
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
|
@ -42,13 +50,13 @@ class SvnDownloader extends VcsDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(" Checking out ".$package->getSourceReference());
|
$this->io->writeError(" Checking out ".$package->getSourceReference());
|
||||||
$this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
|
$this->execute($package, $url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||||
{
|
{
|
||||||
SvnUtil::cleanEnv();
|
SvnUtil::cleanEnv();
|
||||||
$ref = $target->getSourceReference();
|
$ref = $target->getSourceReference();
|
||||||
|
@ -64,7 +72,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(" Checking out " . $ref);
|
$this->io->writeError(" Checking out " . $ref);
|
||||||
$this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
|
$this->execute($target, $url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,7 +101,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function execute($baseUrl, $command, $url, $cwd = null, $path = null)
|
protected function execute(PackageInterface $package, $baseUrl, $command, $url, $cwd = null, $path = null)
|
||||||
{
|
{
|
||||||
$util = new SvnUtil($baseUrl, $this->io, $this->config);
|
$util = new SvnUtil($baseUrl, $this->io, $this->config);
|
||||||
$util->setCacheCredentials($this->cacheCredentials);
|
$util->setCacheCredentials($this->cacheCredentials);
|
||||||
|
@ -101,7 +109,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
|
return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
|
||||||
} catch (\RuntimeException $e) {
|
} catch (\RuntimeException $e) {
|
||||||
throw new \RuntimeException(
|
throw new \RuntimeException(
|
||||||
'Package could not be downloaded, '.$e->getMessage()
|
$package->getPrettyName().' could not be downloaded, '.$e->getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +135,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
return ' '.$elem;
|
return ' '.$elem;
|
||||||
}, preg_split('{\s*\r?\n\s*}', $changes));
|
}, preg_split('{\s*\r?\n\s*}', $changes));
|
||||||
$countChanges = count($changes);
|
$countChanges = count($changes);
|
||||||
$this->io->writeError(sprintf(' <error>The package has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
|
$this->io->writeError(sprintf(' <error>'.$package->getPrettyName().' has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
|
||||||
$this->io->writeError(array_slice($changes, 0, 10));
|
$this->io->writeError(array_slice($changes, 0, 10));
|
||||||
if ($countChanges > 10) {
|
if ($countChanges > 10) {
|
||||||
$remainingChanges = $countChanges - 10;
|
$remainingChanges = $countChanges - 10;
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace Composer\Downloader;
|
namespace Composer\Downloader;
|
||||||
|
|
||||||
|
use Composer\Package\PackageInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloader for tar files: tar, tar.gz or tar.bz2
|
* Downloader for tar files: tar, tar.gz or tar.bz2
|
||||||
*
|
*
|
||||||
|
@ -22,7 +24,7 @@ class TarDownloader extends ArchiveDownloader
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
protected function extract($file, $path)
|
protected function extract(PackageInterface $package, $file, $path)
|
||||||
{
|
{
|
||||||
// Can throw an UnexpectedValueException
|
// Can throw an UnexpectedValueException
|
||||||
$archive = new \PharData($file);
|
$archive = new \PharData($file);
|
||||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Package\Version\VersionParser;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
@ -54,44 +55,78 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function download(PackageInterface $package, $path)
|
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
if (!$package->getSourceReference()) {
|
||||||
|
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
|
||||||
|
}
|
||||||
|
|
||||||
|
$urls = $this->prepareUrls($package->getSourceUrls());
|
||||||
|
|
||||||
|
while ($url = array_shift($urls)) {
|
||||||
|
try {
|
||||||
|
return $this->doDownload($package, $path, $url, $prevPackage);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||||
|
if ($e instanceof \PHPUnit\Framework\Exception) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
if ($this->io->isDebug()) {
|
||||||
|
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
|
||||||
|
} elseif (count($urls)) {
|
||||||
|
$this->io->writeError(' Failed, trying the next URL');
|
||||||
|
}
|
||||||
|
if (!count($urls)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->cleanChanges($prevPackage, $path, true);
|
||||||
|
} elseif ($type === 'install') {
|
||||||
|
$this->filesystem->emptyDirectory($path);
|
||||||
|
} elseif ($type === 'uninstall') {
|
||||||
|
$this->cleanChanges($package, $path, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
// TODO keep track of whether prepare was called for this package
|
||||||
|
$this->reapplyChanges($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function install(PackageInterface $package, $path)
|
||||||
{
|
{
|
||||||
if (!$package->getSourceReference()) {
|
if (!$package->getSourceReference()) {
|
||||||
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
|
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
|
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
|
||||||
$this->filesystem->emptyDirectory($path);
|
|
||||||
|
|
||||||
$urls = $package->getSourceUrls();
|
$urls = $this->prepareUrls($package->getSourceUrls());
|
||||||
while ($url = array_shift($urls)) {
|
while ($url = array_shift($urls)) {
|
||||||
try {
|
try {
|
||||||
if (Filesystem::isLocalPath($url)) {
|
$this->doInstall($package, $path, $url);
|
||||||
// realpath() below will not understand
|
|
||||||
// url that starts with "file://"
|
|
||||||
$needle = 'file://';
|
|
||||||
$isFileProtocol = false;
|
|
||||||
if (0 === strpos($url, $needle)) {
|
|
||||||
$url = substr($url, strlen($needle));
|
|
||||||
$isFileProtocol = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// realpath() below will not understand %20 spaces etc.
|
|
||||||
if (false !== strpos($url, '%')) {
|
|
||||||
$url = rawurldecode($url);
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = realpath($url);
|
|
||||||
|
|
||||||
if ($isFileProtocol) {
|
|
||||||
$url = $needle . $url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->doDownload($package, $path, $url);
|
|
||||||
break;
|
break;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||||
if ($e instanceof \PHPUnit_Framework_Exception) {
|
if ($e instanceof \PHPUnit\Framework\Exception) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
if ($this->io->isDebug()) {
|
if ($this->io->isDebug()) {
|
||||||
|
@ -130,25 +165,21 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
$to = $target->getFullPrettyVersion();
|
$to = $target->getFullPrettyVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
|
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Upgrading' : 'Downgrading';
|
||||||
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
|
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
|
||||||
|
|
||||||
$this->cleanChanges($initial, $path, true);
|
$urls = $this->prepareUrls($target->getSourceUrls());
|
||||||
$urls = $target->getSourceUrls();
|
|
||||||
|
|
||||||
$exception = null;
|
$exception = null;
|
||||||
while ($url = array_shift($urls)) {
|
while ($url = array_shift($urls)) {
|
||||||
try {
|
try {
|
||||||
if (Filesystem::isLocalPath($url)) {
|
|
||||||
$url = realpath($url);
|
|
||||||
}
|
|
||||||
$this->doUpdate($initial, $target, $path, $url);
|
$this->doUpdate($initial, $target, $path, $url);
|
||||||
|
|
||||||
$exception = null;
|
$exception = null;
|
||||||
break;
|
break;
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||||
if ($exception instanceof \PHPUnit_Framework_Exception) {
|
if ($exception instanceof \PHPUnit\Framework\Exception) {
|
||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
if ($this->io->isDebug()) {
|
if ($this->io->isDebug()) {
|
||||||
|
@ -159,8 +190,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reapplyChanges($path);
|
|
||||||
|
|
||||||
// print the commit logs if in verbose mode and VCS metadata is present
|
// print the commit logs if in verbose mode and VCS metadata is present
|
||||||
// because in case of missing metadata code would trigger another exception
|
// because in case of missing metadata code would trigger another exception
|
||||||
if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
|
if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
|
||||||
|
@ -196,21 +225,11 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
public function remove(PackageInterface $package, $path)
|
public function remove(PackageInterface $package, $path)
|
||||||
{
|
{
|
||||||
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
|
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
|
||||||
$this->cleanChanges($package, $path, false);
|
|
||||||
if (!$this->filesystem->removeDirectory($path)) {
|
if (!$this->filesystem->removeDirectory($path)) {
|
||||||
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Download progress information is not available for all VCS downloaders.
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public function setOutputProgress($outputProgress)
|
|
||||||
{
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@ -244,7 +263,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guarantee that no changes have been made to the local copy
|
* Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not)
|
||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
|
* @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
|
||||||
|
@ -253,14 +272,28 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads data needed to run an install/update later
|
||||||
|
*
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param string $path download path
|
||||||
|
* @param string $url package url
|
||||||
|
* @param PackageInterface|null $prevPackage previous package (in case of an update)
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
abstract protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* @param string $url package url
|
* @param string $url package url
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
abstract protected function doDownload(PackageInterface $package, $path, $url);
|
abstract protected function doInstall(PackageInterface $package, $path, $url);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates specific package in specific folder from initial to target version.
|
* Updates specific package in specific folder from initial to target version.
|
||||||
|
@ -269,6 +302,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
* @param PackageInterface $target updated package
|
* @param PackageInterface $target updated package
|
||||||
* @param string $path download path
|
* @param string $path download path
|
||||||
* @param string $url package url
|
* @param string $url package url
|
||||||
|
*
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
|
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
|
||||||
|
|
||||||
|
@ -290,4 +325,33 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
abstract protected function hasMetadataRepository($path);
|
abstract protected function hasMetadataRepository($path);
|
||||||
|
|
||||||
|
private function prepareUrls(array $urls)
|
||||||
|
{
|
||||||
|
foreach ($urls as $index => $url) {
|
||||||
|
if (Filesystem::isLocalPath($url)) {
|
||||||
|
// realpath() below will not understand
|
||||||
|
// url that starts with "file://"
|
||||||
|
$fileProtocol = 'file://';
|
||||||
|
$isFileProtocol = false;
|
||||||
|
if (0 === strpos($url, $fileProtocol)) {
|
||||||
|
$url = substr($url, strlen($fileProtocol));
|
||||||
|
$isFileProtocol = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// realpath() below will not understand %20 spaces etc.
|
||||||
|
if (false !== strpos($url, '%')) {
|
||||||
|
$url = rawurldecode($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$urls[$index] = realpath($url);
|
||||||
|
|
||||||
|
if ($isFileProtocol) {
|
||||||
|
$urls[$index] = $fileProtocol . $urls[$index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $urls;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use Composer\Cache;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
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,16 +28,17 @@ use Composer\IO\IOInterface;
|
||||||
*/
|
*/
|
||||||
class XzDownloader extends ArchiveDownloader
|
class XzDownloader 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)
|
||||||
{
|
{
|
||||||
$command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);
|
$command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);
|
||||||
|
|
||||||
|
@ -49,12 +50,4 @@ class XzDownloader extends ArchiveDownloader
|
||||||
|
|
||||||
throw new \RuntimeException($processError);
|
throw new \RuntimeException($processError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getFileName(PackageInterface $package, $path)
|
|
||||||
{
|
|
||||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use Composer\Package\PackageInterface;
|
||||||
use Composer\Util\IniHelper;
|
use Composer\Util\IniHelper;
|
||||||
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;
|
||||||
use Symfony\Component\Process\ExecutableFinder;
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
use ZipArchive;
|
use ZipArchive;
|
||||||
|
@ -33,19 +33,21 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
private static $hasZipArchive;
|
private static $hasZipArchive;
|
||||||
private static $isWindows;
|
private static $isWindows;
|
||||||
|
|
||||||
|
/** @var ProcessExecutor */
|
||||||
protected $process;
|
protected $process;
|
||||||
|
/** @var ZipArchive|null */
|
||||||
private $zipArchiveObject;
|
private $zipArchiveObject;
|
||||||
|
|
||||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
|
public function __construct(IOInterface $io, Config $config, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function download(PackageInterface $package, $path, $output = true)
|
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||||
{
|
{
|
||||||
if (null === self::$hasSystemUnzip) {
|
if (null === self::$hasSystemUnzip) {
|
||||||
$finder = new ExecutableFinder;
|
$finder = new ExecutableFinder;
|
||||||
|
@ -74,7 +76,7 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::download($package, $path, $output);
|
return parent::download($package, $path, $prevPackage, $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,7 +187,7 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
* @param string $file File to extract
|
* @param string $file File to extract
|
||||||
* @param string $path Path where to extract file
|
* @param string $path Path where to extract file
|
||||||
*/
|
*/
|
||||||
public function extract($file, $path)
|
public function extract(PackageInterface $package, $file, $path)
|
||||||
{
|
{
|
||||||
// Each extract calls its alternative if not available or fails
|
// Each extract calls its alternative if not available or fails
|
||||||
if (self::$isWindows) {
|
if (self::$isWindows) {
|
||||||
|
|
|
@ -13,13 +13,16 @@
|
||||||
namespace Composer\EventDispatcher;
|
namespace Composer\EventDispatcher;
|
||||||
|
|
||||||
use Composer\DependencyResolver\PolicyInterface;
|
use Composer\DependencyResolver\PolicyInterface;
|
||||||
use Composer\DependencyResolver\Pool;
|
|
||||||
use Composer\DependencyResolver\Request;
|
use Composer\DependencyResolver\Request;
|
||||||
|
use Composer\DependencyResolver\Pool;
|
||||||
|
use Composer\DependencyResolver\Transaction;
|
||||||
use Composer\Installer\InstallerEvent;
|
use Composer\Installer\InstallerEvent;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Composer;
|
use Composer\Composer;
|
||||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\CompositeRepository;
|
||||||
|
use Composer\Repository\RepositoryInterface;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
use Composer\Script;
|
use Composer\Script;
|
||||||
use Composer\Installer\PackageEvent;
|
use Composer\Installer\PackageEvent;
|
||||||
use Composer\Installer\BinaryInstaller;
|
use Composer\Installer\BinaryInstaller;
|
||||||
|
@ -46,7 +49,7 @@ class EventDispatcher
|
||||||
protected $io;
|
protected $io;
|
||||||
protected $loader;
|
protected $loader;
|
||||||
protected $process;
|
protected $process;
|
||||||
protected $listeners;
|
protected $listeners = array();
|
||||||
private $eventStack;
|
private $eventStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,40 +102,34 @@ class EventDispatcher
|
||||||
/**
|
/**
|
||||||
* Dispatch a package event.
|
* Dispatch a package event.
|
||||||
*
|
*
|
||||||
* @param string $eventName The constant in PackageEvents
|
* @param string $eventName The constant in PackageEvents
|
||||||
* @param bool $devMode Whether or not we are in dev mode
|
* @param bool $devMode Whether or not we are in dev mode
|
||||||
* @param PolicyInterface $policy The policy
|
* @param RepositoryInterface $localRepo The installed repository
|
||||||
* @param Pool $pool The pool
|
* @param array $operations The list of operations
|
||||||
* @param CompositeRepository $installedRepo The installed repository
|
* @param OperationInterface $operation The package being installed/updated/removed
|
||||||
* @param Request $request The request
|
|
||||||
* @param array $operations The list of operations
|
|
||||||
* @param OperationInterface $operation The package being installed/updated/removed
|
|
||||||
*
|
*
|
||||||
* @return int return code of the executed script if any, for php scripts a false return
|
* @return int return code of the executed script if any, for php scripts a false return
|
||||||
* value is changed to 1, anything else to 0
|
* value is changed to 1, anything else to 0
|
||||||
*/
|
*/
|
||||||
public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
|
public function dispatchPackageEvent($eventName, $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation)
|
||||||
{
|
{
|
||||||
return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations, $operation));
|
return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a installer event.
|
* Dispatch a installer event.
|
||||||
*
|
*
|
||||||
* @param string $eventName The constant in InstallerEvents
|
* @param string $eventName The constant in InstallerEvents
|
||||||
* @param bool $devMode Whether or not we are in dev mode
|
* @param bool $devMode Whether or not we are in dev mode
|
||||||
* @param PolicyInterface $policy The policy
|
* @param bool $executeOperations True if operations will be executed, false in --dry-run
|
||||||
* @param Pool $pool The pool
|
* @param Transaction $transaction The transaction contains the list of operations
|
||||||
* @param CompositeRepository $installedRepo The installed repository
|
|
||||||
* @param Request $request The request
|
|
||||||
* @param array $operations The list of operations
|
|
||||||
*
|
*
|
||||||
* @return int return code of the executed script if any, for php scripts a false return
|
* @return int return code of the executed script if any, for php scripts a false return
|
||||||
* value is changed to 1, anything else to 0
|
* value is changed to 1, anything else to 0
|
||||||
*/
|
*/
|
||||||
public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
|
public function dispatchInstallerEvent($eventName, $devMode, $executeOperations, Transaction $transaction)
|
||||||
{
|
{
|
||||||
return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations));
|
return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,6 +157,9 @@ class EventDispatcher
|
||||||
|
|
||||||
throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
|
throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
|
||||||
}
|
}
|
||||||
|
if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
|
||||||
|
$this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1] ), true, IOInterface::VERBOSE);
|
||||||
|
}
|
||||||
$event = $this->checkListenerExpectedEvent($callable, $event);
|
$event = $this->checkListenerExpectedEvent($callable, $event);
|
||||||
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
||||||
} elseif ($this->isComposerScript($callable)) {
|
} elseif ($this->isComposerScript($callable)) {
|
||||||
|
@ -172,8 +172,8 @@ class EventDispatcher
|
||||||
$args = array_merge($script, $event->getArguments());
|
$args = array_merge($script, $event->getArguments());
|
||||||
$flags = $event->getFlags();
|
$flags = $event->getFlags();
|
||||||
if (substr($callable, 0, 10) === '@composer ') {
|
if (substr($callable, 0, 10) === '@composer ') {
|
||||||
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9);
|
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . ' ' . implode(' ', $args);
|
||||||
if (0 !== ($exitCode = $this->process->execute($exec))) {
|
if (0 !== ($exitCode = $this->executeTty($exec))) {
|
||||||
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);
|
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);
|
||||||
|
|
||||||
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
||||||
|
@ -184,6 +184,7 @@ class EventDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
/** @var InstallerEvent $event */
|
||||||
$scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
|
$scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
|
||||||
$scriptEvent->setOriginatingEvent($event);
|
$scriptEvent->setOriginatingEvent($event);
|
||||||
$return = $this->dispatch($scriptName, $scriptEvent);
|
$return = $this->dispatch($scriptName, $scriptEvent);
|
||||||
|
@ -247,7 +248,7 @@ class EventDispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 !== ($exitCode = $this->process->execute($exec))) {
|
if (0 !== ($exitCode = $this->executeTty($exec))) {
|
||||||
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);
|
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);
|
||||||
|
|
||||||
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
||||||
|
@ -264,6 +265,15 @@ class EventDispatcher
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function executeTty($exec)
|
||||||
|
{
|
||||||
|
if ($this->io->isInteractive()) {
|
||||||
|
return $this->process->executeTty($exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->process->execute($exec);
|
||||||
|
}
|
||||||
|
|
||||||
protected function getPhpExecCommand()
|
protected function getPhpExecCommand()
|
||||||
{
|
{
|
||||||
$finder = new PhpExecutableFinder();
|
$finder = new PhpExecutableFinder();
|
||||||
|
@ -327,44 +337,6 @@ class EventDispatcher
|
||||||
|
|
||||||
$expected = $typehint->getName();
|
$expected = $typehint->getName();
|
||||||
|
|
||||||
// BC support
|
|
||||||
if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
|
|
||||||
trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
|
|
||||||
$event = new \Composer\Script\CommandEvent(
|
|
||||||
$event->getName(),
|
|
||||||
$event->getComposer(),
|
|
||||||
$event->getIO(),
|
|
||||||
$event->isDevMode(),
|
|
||||||
$event->getArguments()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') {
|
|
||||||
trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
|
|
||||||
$event = new \Composer\Script\PackageEvent(
|
|
||||||
$event->getName(),
|
|
||||||
$event->getComposer(),
|
|
||||||
$event->getIO(),
|
|
||||||
$event->isDevMode(),
|
|
||||||
$event->getPolicy(),
|
|
||||||
$event->getPool(),
|
|
||||||
$event->getInstalledRepo(),
|
|
||||||
$event->getRequest(),
|
|
||||||
$event->getOperations(),
|
|
||||||
$event->getOperation()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!$event instanceof $expected && $expected === 'Composer\Script\Event') {
|
|
||||||
trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
|
|
||||||
$event = new \Composer\Script\Event(
|
|
||||||
$event->getName(),
|
|
||||||
$event->getComposer(),
|
|
||||||
$event->getIO(),
|
|
||||||
$event->isDevMode(),
|
|
||||||
$event->getArguments(),
|
|
||||||
$event->getFlags()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $event;
|
return $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,6 +369,22 @@ class EventDispatcher
|
||||||
$this->listeners[$eventName][$priority][] = $listener;
|
$this->listeners[$eventName][$priority][] = $listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable|object $listener A callable or an object instance for which all listeners should be removed
|
||||||
|
*/
|
||||||
|
public function removeListener($listener)
|
||||||
|
{
|
||||||
|
foreach ($this->listeners as $eventName => $priorities) {
|
||||||
|
foreach ($priorities as $priority => $listeners) {
|
||||||
|
foreach ($listeners as $index => $candidate) {
|
||||||
|
if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
|
||||||
|
unset($this->listeners[$eventName][$priority][$index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds object methods as listeners for the events in getSubscribedEvents
|
* Adds object methods as listeners for the events in getSubscribedEvents
|
||||||
*
|
*
|
||||||
|
@ -513,7 +501,7 @@ class EventDispatcher
|
||||||
*
|
*
|
||||||
* @param Event $event
|
* @param Event $event
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
* @return number
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function pushEvent(Event $event)
|
protected function pushEvent(Event $event)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,8 @@ use Composer\Repository\WritableRepositoryInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
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\Util\Loop;
|
||||||
use Composer\Util\Silencer;
|
use Composer\Util\Silencer;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
use Composer\EventDispatcher\Event;
|
use Composer\EventDispatcher\Event;
|
||||||
|
@ -222,6 +223,13 @@ class Factory
|
||||||
return trim(getenv('COMPOSER')) ?: './composer.json';
|
return trim(getenv('COMPOSER')) ?: './composer.json';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getLockFile($composerFile)
|
||||||
|
{
|
||||||
|
return "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
|
||||||
|
? substr($composerFile, 0, -4).'lock'
|
||||||
|
: $composerFile . '.lock';
|
||||||
|
}
|
||||||
|
|
||||||
public static function createAdditionalStyles()
|
public static function createAdditionalStyles()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
@ -325,14 +333,15 @@ class Factory
|
||||||
$io->loadConfiguration($config);
|
$io->loadConfiguration($config);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rfs = self::createRemoteFilesystem($io, $config);
|
$httpDownloader = self::createHttpDownloader($io, $config);
|
||||||
|
$loop = new Loop($httpDownloader);
|
||||||
|
|
||||||
// initialize event dispatcher
|
// initialize event dispatcher
|
||||||
$dispatcher = new EventDispatcher($composer, $io);
|
$dispatcher = new EventDispatcher($composer, $io);
|
||||||
$composer->setEventDispatcher($dispatcher);
|
$composer->setEventDispatcher($dispatcher);
|
||||||
|
|
||||||
// initialize repository manager
|
// initialize repository manager
|
||||||
$rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
|
$rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher);
|
||||||
$composer->setRepositoryManager($rm);
|
$composer->setRepositoryManager($rm);
|
||||||
|
|
||||||
// load local repository
|
// load local repository
|
||||||
|
@ -352,12 +361,12 @@ class Factory
|
||||||
$composer->setPackage($package);
|
$composer->setPackage($package);
|
||||||
|
|
||||||
// initialize installation manager
|
// initialize installation manager
|
||||||
$im = $this->createInstallationManager();
|
$im = $this->createInstallationManager($loop, $io, $dispatcher);
|
||||||
$composer->setInstallationManager($im);
|
$composer->setInstallationManager($im);
|
||||||
|
|
||||||
if ($fullLoad) {
|
if ($fullLoad) {
|
||||||
// initialize download manager
|
// initialize download manager
|
||||||
$dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
|
$dm = $this->createDownloadManager($io, $config, $httpDownloader, $dispatcher);
|
||||||
$composer->setDownloadManager($dm);
|
$composer->setDownloadManager($dm);
|
||||||
|
|
||||||
// initialize autoload generator
|
// initialize autoload generator
|
||||||
|
@ -365,7 +374,7 @@ class Factory
|
||||||
$composer->setAutoloadGenerator($generator);
|
$composer->setAutoloadGenerator($generator);
|
||||||
|
|
||||||
// initialize archive manager
|
// initialize archive manager
|
||||||
$am = $this->createArchiveManager($config, $dm);
|
$am = $this->createArchiveManager($config, $dm, $loop);
|
||||||
$composer->setArchiveManager($am);
|
$composer->setArchiveManager($am);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,11 +395,9 @@ class Factory
|
||||||
|
|
||||||
// init locker if possible
|
// init locker if possible
|
||||||
if ($fullLoad && isset($composerFile)) {
|
if ($fullLoad && isset($composerFile)) {
|
||||||
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
|
$lockFile = self::getLockFile($composerFile);
|
||||||
? substr($composerFile, 0, -4).'lock'
|
|
||||||
: $composerFile . '.lock';
|
|
||||||
|
|
||||||
$locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
|
$locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile));
|
||||||
$composer->setLocker($locker);
|
$composer->setLocker($locker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +418,7 @@ class Factory
|
||||||
/**
|
/**
|
||||||
* @param IOInterface $io IO instance
|
* @param IOInterface $io IO instance
|
||||||
* @param bool $disablePlugins Whether plugins should not be loaded
|
* @param bool $disablePlugins Whether plugins should not be loaded
|
||||||
* @return Composer
|
* @return Composer|null
|
||||||
*/
|
*/
|
||||||
public static function createGlobal(IOInterface $io, $disablePlugins = false)
|
public static function createGlobal(IOInterface $io, $disablePlugins = false)
|
||||||
{
|
{
|
||||||
|
@ -451,7 +458,7 @@ class Factory
|
||||||
* @param EventDispatcher $eventDispatcher
|
* @param EventDispatcher $eventDispatcher
|
||||||
* @return Downloader\DownloadManager
|
* @return Downloader\DownloadManager
|
||||||
*/
|
*/
|
||||||
public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
|
||||||
{
|
{
|
||||||
$cache = null;
|
$cache = null;
|
||||||
if ($config->get('cache-files-ttl') > 0) {
|
if ($config->get('cache-files-ttl') > 0) {
|
||||||
|
@ -484,14 +491,14 @@ class Factory
|
||||||
$dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs));
|
$dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs));
|
||||||
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
|
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
|
||||||
$dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
|
$dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
|
||||||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||||
|
|
||||||
return $dm;
|
return $dm;
|
||||||
}
|
}
|
||||||
|
@ -501,15 +508,9 @@ class Factory
|
||||||
* @param Downloader\DownloadManager $dm Manager use to download sources
|
* @param Downloader\DownloadManager $dm Manager use to download sources
|
||||||
* @return Archiver\ArchiveManager
|
* @return Archiver\ArchiveManager
|
||||||
*/
|
*/
|
||||||
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
|
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
|
||||||
{
|
{
|
||||||
if (null === $dm) {
|
$am = new Archiver\ArchiveManager($dm, $loop);
|
||||||
$io = new IO\NullIO();
|
|
||||||
$io->loadConfiguration($config);
|
|
||||||
$dm = $this->createDownloadManager($io, $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
$am = new Archiver\ArchiveManager($dm);
|
|
||||||
$am->addArchiver(new Archiver\ZipArchiver);
|
$am->addArchiver(new Archiver\ZipArchiver);
|
||||||
$am->addArchiver(new Archiver\PharArchiver);
|
$am->addArchiver(new Archiver\PharArchiver);
|
||||||
|
|
||||||
|
@ -531,9 +532,9 @@ class Factory
|
||||||
/**
|
/**
|
||||||
* @return Installer\InstallationManager
|
* @return Installer\InstallationManager
|
||||||
*/
|
*/
|
||||||
protected function createInstallationManager()
|
public function createInstallationManager(Loop $loop, IOInterface $io, EventDispatcher $eventDispatcher = null)
|
||||||
{
|
{
|
||||||
return new Installer\InstallationManager();
|
return new Installer\InstallationManager($loop, $io, $eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -579,10 +580,10 @@ class Factory
|
||||||
/**
|
/**
|
||||||
* @param IOInterface $io IO instance
|
* @param IOInterface $io IO instance
|
||||||
* @param Config $config Config instance
|
* @param Config $config Config instance
|
||||||
* @param array $options Array of options passed directly to RemoteFilesystem constructor
|
* @param array $options Array of options passed directly to HttpDownloader constructor
|
||||||
* @return RemoteFilesystem
|
* @return HttpDownloader
|
||||||
*/
|
*/
|
||||||
public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
|
public static function createHttpDownloader(IOInterface $io, Config $config = null, $options = array())
|
||||||
{
|
{
|
||||||
static $warned = false;
|
static $warned = false;
|
||||||
$disableTls = false;
|
$disableTls = false;
|
||||||
|
@ -596,18 +597,18 @@ class Factory
|
||||||
throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
|
throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
|
||||||
. 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
|
. 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
|
||||||
}
|
}
|
||||||
$remoteFilesystemOptions = array();
|
$httpDownloaderOptions = array();
|
||||||
if ($disableTls === false) {
|
if ($disableTls === false) {
|
||||||
if ($config && $config->get('cafile')) {
|
if ($config && $config->get('cafile')) {
|
||||||
$remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
|
$httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
|
||||||
}
|
}
|
||||||
if ($config && $config->get('capath')) {
|
if ($config && $config->get('capath')) {
|
||||||
$remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
|
$httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
|
||||||
}
|
}
|
||||||
$remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
|
$httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
|
$httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls);
|
||||||
} catch (TransportException $e) {
|
} catch (TransportException $e) {
|
||||||
if (false !== strpos($e->getMessage(), 'cafile')) {
|
if (false !== strpos($e->getMessage(), 'cafile')) {
|
||||||
$io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
|
$io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
|
||||||
|
@ -620,7 +621,7 @@ class Factory
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $remoteFilesystem;
|
return $httpDownloader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,10 +14,9 @@ namespace Composer\IO;
|
||||||
|
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
|
||||||
abstract class BaseIO implements IOInterface, LoggerInterface
|
abstract class BaseIO implements IOInterface
|
||||||
{
|
{
|
||||||
protected $authentications = array();
|
protected $authentications = array();
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,14 @@
|
||||||
namespace Composer\IO;
|
namespace Composer\IO;
|
||||||
|
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Input/Output helper interface.
|
* The Input/Output helper interface.
|
||||||
*
|
*
|
||||||
* @author François Pluchino <francois.pluchino@opendisplay.com>
|
* @author François Pluchino <francois.pluchino@opendisplay.com>
|
||||||
*/
|
*/
|
||||||
interface IOInterface
|
interface IOInterface extends LoggerInterface
|
||||||
{
|
{
|
||||||
const QUIET = 1;
|
const QUIET = 1;
|
||||||
const NORMAL = 2;
|
const NORMAL = 2;
|
||||||
|
@ -80,6 +81,24 @@ interface IOInterface
|
||||||
*/
|
*/
|
||||||
public function writeError($messages, $newline = true, $verbosity = self::NORMAL);
|
public function writeError($messages, $newline = true, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a message to the output, without formatting it.
|
||||||
|
*
|
||||||
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
|
* @param bool $newline Whether to add a newline or not
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
|
*/
|
||||||
|
public function writeRaw($messages, $newline = true, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a message to the error output, without formatting it.
|
||||||
|
*
|
||||||
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
|
* @param bool $newline Whether to add a newline or not
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
|
*/
|
||||||
|
public function writeErrorRaw($messages, $newline = true, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwrites a previous message to the output.
|
* Overwrites a previous message to the output.
|
||||||
*
|
*
|
||||||
|
@ -107,7 +126,7 @@ interface IOInterface
|
||||||
* @param string $default The default answer if none is given by the user
|
* @param string $default The default answer if none is given by the user
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException If there is no data to read in the input stream
|
* @throws \RuntimeException If there is no data to read in the input stream
|
||||||
* @return string The user answer
|
* @return string|null The user answer
|
||||||
*/
|
*/
|
||||||
public function ask($question, $default = null);
|
public function ask($question, $default = null);
|
||||||
|
|
||||||
|
@ -145,7 +164,7 @@ interface IOInterface
|
||||||
*
|
*
|
||||||
* @param string $question The question to ask
|
* @param string $question The question to ask
|
||||||
*
|
*
|
||||||
* @return string The answer
|
* @return string|null The answer
|
||||||
*/
|
*/
|
||||||
public function askAndHideAnswer($question);
|
public function askAndHideAnswer($question);
|
||||||
|
|
||||||
|
@ -160,7 +179,7 @@ interface IOInterface
|
||||||
* @param bool $multiselect Select more than one value separated by comma
|
* @param bool $multiselect Select more than one value separated by comma
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
* @return int|string|array The selected value or values (the key of the choices array)
|
* @return int|string|array|bool The selected value or values (the key of the choices array)
|
||||||
*/
|
*/
|
||||||
public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);
|
public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,7 +23,9 @@ use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||||
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
|
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
|
||||||
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
|
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
|
||||||
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Util\StreamContextFactory;
|
use Composer\Util\StreamContextFactory;
|
||||||
|
use Composer\Util\Loop;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package operation manager.
|
* Package operation manager.
|
||||||
|
@ -37,6 +39,16 @@ class InstallationManager
|
||||||
private $installers = array();
|
private $installers = array();
|
||||||
private $cache = array();
|
private $cache = array();
|
||||||
private $notifiablePackages = array();
|
private $notifiablePackages = array();
|
||||||
|
private $loop;
|
||||||
|
private $io;
|
||||||
|
private $eventDispatcher;
|
||||||
|
|
||||||
|
public function __construct(Loop $loop, IOInterface $io, EventDispatcher $eventDispatcher = null)
|
||||||
|
{
|
||||||
|
$this->loop = $loop;
|
||||||
|
$this->io = $io;
|
||||||
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
public function reset()
|
public function reset()
|
||||||
{
|
{
|
||||||
|
@ -151,13 +163,105 @@ class InstallationManager
|
||||||
/**
|
/**
|
||||||
* Executes solver operation.
|
* Executes solver operation.
|
||||||
*
|
*
|
||||||
* @param RepositoryInterface $repo repository in which to check
|
* @param RepositoryInterface $repo repository in which to add/remove/update packages
|
||||||
* @param OperationInterface $operation operation instance
|
* @param OperationInterface[] $operations operations to execute
|
||||||
|
* @param bool $devMode whether the install is being run in dev mode
|
||||||
|
* @param bool $operation whether to dispatch script events
|
||||||
*/
|
*/
|
||||||
public function execute(RepositoryInterface $repo, OperationInterface $operation)
|
public function execute(RepositoryInterface $repo, array $operations, $devMode = true, $runScripts = true)
|
||||||
{
|
{
|
||||||
$method = $operation->getJobType();
|
$promises = array();
|
||||||
$this->$method($repo, $operation);
|
|
||||||
|
foreach ($operations as $operation) {
|
||||||
|
$opType = $operation->getOperationType();
|
||||||
|
$promise = null;
|
||||||
|
|
||||||
|
if ($opType === 'install') {
|
||||||
|
$package = $operation->getPackage();
|
||||||
|
$installer = $this->getInstaller($package->getType());
|
||||||
|
$promise = $installer->download($package);
|
||||||
|
} elseif ($opType === 'update') {
|
||||||
|
$target = $operation->getTargetPackage();
|
||||||
|
$targetType = $target->getType();
|
||||||
|
$installer = $this->getInstaller($targetType);
|
||||||
|
$promise = $installer->download($target, $operation->getInitialPackage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($promise) {
|
||||||
|
$promises[] = $promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($promises)) {
|
||||||
|
$this->loop->wait($promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($operations as $operation) {
|
||||||
|
$opType = $operation->getOperationType();
|
||||||
|
|
||||||
|
// ignoring alias ops as they don't need to execute anything
|
||||||
|
if (!in_array($opType, array('update', 'install', 'uninstall'))) {
|
||||||
|
// output alias ops in debug verbosity as they have no output otherwise
|
||||||
|
if ($this->io->isDebug()) {
|
||||||
|
$this->io->writeError(' - ' . $operation->show(false));
|
||||||
|
}
|
||||||
|
$this->$opType($repo, $operation);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($opType === 'install' || $opType === 'uninstall') {
|
||||||
|
$package = $operation->getPackage();
|
||||||
|
$initialPackage = null;
|
||||||
|
} elseif ($opType === 'update') {
|
||||||
|
$package = $operation->getTargetPackage();
|
||||||
|
$initialPackage = $operation->getInitialPackage();
|
||||||
|
}
|
||||||
|
$installer = $this->getInstaller($package->getType());
|
||||||
|
|
||||||
|
$event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($opType);
|
||||||
|
if (defined($event) && $runScripts && $this->eventDispatcher) {
|
||||||
|
$this->eventDispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dispatcher = $this->eventDispatcher;
|
||||||
|
$installManager = $this;
|
||||||
|
$loop = $this->loop;
|
||||||
|
$io = $this->io;
|
||||||
|
|
||||||
|
$promise = $installer->prepare($opType, $package, $initialPackage);
|
||||||
|
if (null === $promise) {
|
||||||
|
$promise = new \React\Promise\Promise(function ($resolve, $reject) { $resolve(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
$promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) {
|
||||||
|
return $installManager->$opType($repo, $operation);
|
||||||
|
})->then(function () use ($opType, $installer, $package, $initialPackage) {
|
||||||
|
return $installer->cleanup($opType, $package, $initialPackage);
|
||||||
|
})->then(function () use ($opType, $runScripts, $dispatcher, $installManager, $devMode, $repo, $operations, $operation) {
|
||||||
|
$repo->write($devMode, $installManager);
|
||||||
|
|
||||||
|
$event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($opType);
|
||||||
|
if (defined($event) && $runScripts && $dispatcher) {
|
||||||
|
$dispatcher->dispatchPackageEvent(constant($event), $devMode, $repo, $operations, $operation);
|
||||||
|
}
|
||||||
|
}, function ($e) use ($opType, $installer, $package, $initialPackage, $loop, $io) {
|
||||||
|
$io->writeError(' <error>' . ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>');
|
||||||
|
|
||||||
|
$promise = $installer->cleanup($opType, $package, $initialPackage);
|
||||||
|
if ($promise) {
|
||||||
|
$loop->wait(array($promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
});
|
||||||
|
|
||||||
|
$promises[] = $promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($promises)) {
|
||||||
|
$this->loop->wait($promises);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,8 +274,10 @@ class InstallationManager
|
||||||
{
|
{
|
||||||
$package = $operation->getPackage();
|
$package = $operation->getPackage();
|
||||||
$installer = $this->getInstaller($package->getType());
|
$installer = $this->getInstaller($package->getType());
|
||||||
$installer->install($repo, $package);
|
$promise = $installer->install($repo, $package);
|
||||||
$this->markForNotification($package);
|
$this->markForNotification($package);
|
||||||
|
|
||||||
|
return $promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,12 +296,15 @@ class InstallationManager
|
||||||
|
|
||||||
if ($initialType === $targetType) {
|
if ($initialType === $targetType) {
|
||||||
$installer = $this->getInstaller($initialType);
|
$installer = $this->getInstaller($initialType);
|
||||||
$installer->update($repo, $initial, $target);
|
$promise = $installer->update($repo, $initial, $target);
|
||||||
$this->markForNotification($target);
|
$this->markForNotification($target);
|
||||||
} else {
|
} else {
|
||||||
$this->getInstaller($initialType)->uninstall($repo, $initial);
|
$this->getInstaller($initialType)->uninstall($repo, $initial);
|
||||||
$this->getInstaller($targetType)->install($repo, $target);
|
$installer = $this->getInstaller($targetType);
|
||||||
|
$promise = $installer->install($repo, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +317,8 @@ class InstallationManager
|
||||||
{
|
{
|
||||||
$package = $operation->getPackage();
|
$package = $operation->getPackage();
|
||||||
$installer = $this->getInstaller($package->getType());
|
$installer = $this->getInstaller($package->getType());
|
||||||
$installer->uninstall($repo, $package);
|
|
||||||
|
return $installer->uninstall($repo, $package);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,18 +14,13 @@ namespace Composer\Installer;
|
||||||
|
|
||||||
use Composer\Composer;
|
use Composer\Composer;
|
||||||
use Composer\DependencyResolver\PolicyInterface;
|
use Composer\DependencyResolver\PolicyInterface;
|
||||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
|
||||||
use Composer\DependencyResolver\Pool;
|
|
||||||
use Composer\DependencyResolver\Request;
|
use Composer\DependencyResolver\Request;
|
||||||
|
use Composer\DependencyResolver\Pool;
|
||||||
|
use Composer\DependencyResolver\Transaction;
|
||||||
use Composer\EventDispatcher\Event;
|
use Composer\EventDispatcher\Event;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\RepositorySet;
|
||||||
|
|
||||||
/**
|
|
||||||
* An event for all installer.
|
|
||||||
*
|
|
||||||
* @author François Pluchino <francois.pluchino@gmail.com>
|
|
||||||
*/
|
|
||||||
class InstallerEvent extends Event
|
class InstallerEvent extends Event
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -44,29 +39,14 @@ class InstallerEvent extends Event
|
||||||
private $devMode;
|
private $devMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var PolicyInterface
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $policy;
|
private $executeOperations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Pool
|
* @var Transaction
|
||||||
*/
|
*/
|
||||||
private $pool;
|
private $transaction;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var CompositeRepository
|
|
||||||
*/
|
|
||||||
private $installedRepo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Request
|
|
||||||
*/
|
|
||||||
private $request;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var OperationInterface[]
|
|
||||||
*/
|
|
||||||
private $operations;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -75,24 +55,18 @@ class InstallerEvent extends Event
|
||||||
* @param Composer $composer
|
* @param Composer $composer
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
* @param bool $devMode
|
* @param bool $devMode
|
||||||
* @param PolicyInterface $policy
|
* @param bool $executeOperations
|
||||||
* @param Pool $pool
|
* @param Transaction $transaction
|
||||||
* @param CompositeRepository $installedRepo
|
|
||||||
* @param Request $request
|
|
||||||
* @param OperationInterface[] $operations
|
|
||||||
*/
|
*/
|
||||||
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
|
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, $executeOperations, Transaction $transaction)
|
||||||
{
|
{
|
||||||
parent::__construct($eventName);
|
parent::__construct($eventName);
|
||||||
|
|
||||||
$this->composer = $composer;
|
$this->composer = $composer;
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
$this->devMode = $devMode;
|
$this->devMode = $devMode;
|
||||||
$this->policy = $policy;
|
$this->executeOperations = $executeOperations;
|
||||||
$this->pool = $pool;
|
$this->transaction = $transaction;
|
||||||
$this->installedRepo = $installedRepo;
|
|
||||||
$this->request = $request;
|
|
||||||
$this->operations = $operations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,42 +94,18 @@ class InstallerEvent extends Event
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return PolicyInterface
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function getPolicy()
|
public function isExecutingOperations()
|
||||||
{
|
{
|
||||||
return $this->policy;
|
return $this->executeOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Pool
|
* @return Transaction|null
|
||||||
*/
|
*/
|
||||||
public function getPool()
|
public function getTransaction()
|
||||||
{
|
{
|
||||||
return $this->pool;
|
return $this->transaction;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return CompositeRepository
|
|
||||||
*/
|
|
||||||
public function getInstalledRepo()
|
|
||||||
{
|
|
||||||
return $this->installedRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Request
|
|
||||||
*/
|
|
||||||
public function getRequest()
|
|
||||||
{
|
|
||||||
return $this->request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return OperationInterface[]
|
|
||||||
*/
|
|
||||||
public function getOperations()
|
|
||||||
{
|
|
||||||
return $this->operations;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,32 +12,15 @@
|
||||||
|
|
||||||
namespace Composer\Installer;
|
namespace Composer\Installer;
|
||||||
|
|
||||||
/**
|
|
||||||
* The Installer Events.
|
|
||||||
*
|
|
||||||
* @author François Pluchino <francois.pluchino@gmail.com>
|
|
||||||
*/
|
|
||||||
class InstallerEvents
|
class InstallerEvents
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins
|
* The PRE_OPERATIONS_EXEC event occurs before the lock file gets
|
||||||
* resolve operations.
|
* installed and operations are executed.
|
||||||
*
|
*
|
||||||
* The event listener method receives a
|
* The event listener method receives an Composer\Installer\InstallerEvent instance.
|
||||||
* Composer\Installer\InstallerEvent instance.
|
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving';
|
const PRE_OPERATIONS_EXEC = 'pre-operations-exec';
|
||||||
|
|
||||||
/**
|
|
||||||
* The POST_DEPENDENCIES_SOLVING event occurs as a installer after
|
|
||||||
* resolve operations.
|
|
||||||
*
|
|
||||||
* The event listener method receives a
|
|
||||||
* Composer\Installer\InstallerEvent instance.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
const POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving';
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Installer;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Repository\InstalledRepositoryInterface;
|
use Composer\Repository\InstalledRepositoryInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for the package installation manager.
|
* Interface for the package installation manager.
|
||||||
|
@ -42,20 +43,46 @@ interface InstallerInterface
|
||||||
*/
|
*/
|
||||||
public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
|
public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the files needed to later install the given package.
|
||||||
|
*
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do anything that needs to be done between all downloads have been completed and the actual operation is executed
|
||||||
|
*
|
||||||
|
* All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
|
||||||
|
* for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
|
||||||
|
* user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
|
||||||
|
* be undone as much as possible.
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs specific package.
|
* Installs specific package.
|
||||||
*
|
*
|
||||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||||
* @param PackageInterface $package package instance
|
* @param PackageInterface $package package instance
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package);
|
public function install(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates specific package.
|
* Updates specific package.
|
||||||
*
|
*
|
||||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||||
* @param PackageInterface $initial already installed package version
|
* @param PackageInterface $initial already installed package version
|
||||||
* @param PackageInterface $target updated version
|
* @param PackageInterface $target updated version
|
||||||
|
* @return PromiseInterface|null
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException if $initial package is not installed
|
* @throws InvalidArgumentException if $initial package is not installed
|
||||||
*/
|
*/
|
||||||
|
@ -64,11 +91,26 @@ interface InstallerInterface
|
||||||
/**
|
/**
|
||||||
* Uninstalls specific package.
|
* Uninstalls specific package.
|
||||||
*
|
*
|
||||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||||
* @param PackageInterface $package package instance
|
* @param PackageInterface $package package instance
|
||||||
|
* @return PromiseInterface|null
|
||||||
*/
|
*/
|
||||||
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);
|
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
|
||||||
|
*
|
||||||
|
* Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
|
||||||
|
* all installers a change to cleanup things they did previously, so you need to keep track of changes
|
||||||
|
* applied in the installer/downloader themselves.
|
||||||
|
*
|
||||||
|
* @param string $type one of install/update/uninstall
|
||||||
|
* @param PackageInterface $package package instance
|
||||||
|
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||||
|
* @return PromiseInterface|null
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the installation path of a package
|
* Returns the installation path of a package
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,7 +43,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
||||||
*
|
*
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
* @param Composer $composer
|
* @param Composer $composer
|
||||||
* @param string $type
|
* @param string|null $type
|
||||||
* @param Filesystem $filesystem
|
* @param Filesystem $filesystem
|
||||||
* @param BinaryInstaller $binaryInstaller
|
* @param BinaryInstaller $binaryInstaller
|
||||||
*/
|
*/
|
||||||
|
@ -85,6 +85,39 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
||||||
return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
|
return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$this->initializeVendorDir();
|
||||||
|
$downloadPath = $this->getInstallPath($package);
|
||||||
|
|
||||||
|
return $this->downloadManager->download($package, $downloadPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$this->initializeVendorDir();
|
||||||
|
$downloadPath = $this->getInstallPath($package);
|
||||||
|
|
||||||
|
return $this->downloadManager->prepare($type, $package, $downloadPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$this->initializeVendorDir();
|
||||||
|
$downloadPath = $this->getInstallPath($package);
|
||||||
|
|
||||||
|
return $this->downloadManager->cleanup($type, $package, $downloadPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@ -194,7 +227,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
||||||
protected function installCode(PackageInterface $package)
|
protected function installCode(PackageInterface $package)
|
||||||
{
|
{
|
||||||
$downloadPath = $this->getInstallPath($package);
|
$downloadPath = $this->getInstallPath($package);
|
||||||
$this->downloadManager->download($package, $downloadPath);
|
$this->downloadManager->install($package, $downloadPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateCode(PackageInterface $initial, PackageInterface $target)
|
protected function updateCode(PackageInterface $initial, PackageInterface $target)
|
||||||
|
|
|
@ -47,6 +47,30 @@ class MetapackageInstaller implements InstallerInterface
|
||||||
return $repo->hasPackage($package);
|
return $repo->hasPackage($package);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
@ -69,7 +93,7 @@ class MetapackageInstaller implements InstallerInterface
|
||||||
$name = $target->getName();
|
$name = $target->getName();
|
||||||
$from = $initial->getFullPrettyVersion();
|
$from = $initial->getFullPrettyVersion();
|
||||||
$to = $target->getFullPrettyVersion();
|
$to = $target->getFullPrettyVersion();
|
||||||
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
|
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Upgrading' : 'Downgrading';
|
||||||
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
|
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
|
||||||
|
|
||||||
$repo->removePackage($initial);
|
$repo->removePackage($initial);
|
||||||
|
|
|
@ -40,6 +40,27 @@ class NoopInstaller implements InstallerInterface
|
||||||
return $repo->hasPackage($package);
|
return $repo->hasPackage($package);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,19 +16,45 @@ use Composer\Composer;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||||
use Composer\DependencyResolver\PolicyInterface;
|
use Composer\DependencyResolver\PolicyInterface;
|
||||||
use Composer\DependencyResolver\Pool;
|
|
||||||
use Composer\DependencyResolver\Request;
|
use Composer\DependencyResolver\Request;
|
||||||
use Composer\Repository\CompositeRepository;
|
use Composer\Repository\RepositoryInterface;
|
||||||
|
use Composer\Repository\RepositorySet;
|
||||||
|
use Composer\EventDispatcher\Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Package Event.
|
* The Package Event.
|
||||||
*
|
*
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
*/
|
*/
|
||||||
class PackageEvent extends InstallerEvent
|
class PackageEvent extends Event
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var OperationInterface The package instance
|
* @var Composer
|
||||||
|
*/
|
||||||
|
private $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var IOInterface
|
||||||
|
*/
|
||||||
|
private $io;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $devMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RepositoryInterface
|
||||||
|
*/
|
||||||
|
private $localRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var OperationInterface[]
|
||||||
|
*/
|
||||||
|
private $operations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var OperationInterface The operation instance which is being executed
|
||||||
*/
|
*/
|
||||||
private $operation;
|
private $operation;
|
||||||
|
|
||||||
|
@ -39,20 +65,63 @@ class PackageEvent extends InstallerEvent
|
||||||
* @param Composer $composer
|
* @param Composer $composer
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
* @param bool $devMode
|
* @param bool $devMode
|
||||||
* @param PolicyInterface $policy
|
* @param RepositoryInterface $localRepo
|
||||||
* @param Pool $pool
|
|
||||||
* @param CompositeRepository $installedRepo
|
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param OperationInterface[] $operations
|
* @param OperationInterface[] $operations
|
||||||
* @param OperationInterface $operation
|
* @param OperationInterface $operation
|
||||||
*/
|
*/
|
||||||
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
|
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, RepositoryInterface $localRepo, array $operations = array(), OperationInterface $operation)
|
||||||
{
|
{
|
||||||
parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations);
|
parent::__construct($eventName);
|
||||||
|
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->io = $io;
|
||||||
|
$this->devMode = $devMode;
|
||||||
|
$this->localRepo = $localRepo;
|
||||||
|
$this->operations = $operations;
|
||||||
$this->operation = $operation;
|
$this->operation = $operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Composer
|
||||||
|
*/
|
||||||
|
public function getComposer()
|
||||||
|
{
|
||||||
|
return $this->composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return IOInterface
|
||||||
|
*/
|
||||||
|
public function getIO()
|
||||||
|
{
|
||||||
|
return $this->io;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isDevMode()
|
||||||
|
{
|
||||||
|
return $this->devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RepositoryInterface
|
||||||
|
*/
|
||||||
|
public function getLocalRepo()
|
||||||
|
{
|
||||||
|
return $this->localRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return OperationInterface[]
|
||||||
|
*/
|
||||||
|
public function getOperations()
|
||||||
|
{
|
||||||
|
return $this->operations;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the package instance.
|
* Returns the package instance.
|
||||||
*
|
*
|
||||||
|
|
|
@ -50,19 +50,27 @@ class PluginInstaller extends LibraryInstaller
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
{
|
{
|
||||||
$extra = $package->getExtra();
|
$extra = $package->getExtra();
|
||||||
if (empty($extra['class'])) {
|
if (empty($extra['class'])) {
|
||||||
throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parent::download($package, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||||
|
{
|
||||||
parent::install($repo, $package);
|
parent::install($repo, $package);
|
||||||
try {
|
try {
|
||||||
$this->composer->getPluginManager()->registerPackage($package, true);
|
$this->composer->getPluginManager()->registerPackage($package, true);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// Rollback installation
|
// Rollback installation
|
||||||
$this->io->writeError('Plugin installation failed, rolling back');
|
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||||
parent::uninstall($repo, $package);
|
parent::uninstall($repo, $package);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
@ -73,12 +81,22 @@ class PluginInstaller extends LibraryInstaller
|
||||||
*/
|
*/
|
||||||
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
|
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
|
||||||
{
|
{
|
||||||
$extra = $target->getExtra();
|
|
||||||
if (empty($extra['class'])) {
|
|
||||||
throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::update($repo, $initial, $target);
|
parent::update($repo, $initial, $target);
|
||||||
$this->composer->getPluginManager()->registerPackage($target, true);
|
|
||||||
|
try {
|
||||||
|
$this->composer->getPluginManager()->deactivatePackage($initial, true);
|
||||||
|
$this->composer->getPluginManager()->registerPackage($target, true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Rollback installation
|
||||||
|
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||||
|
parent::uninstall($repo, $target);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||||
|
{
|
||||||
|
$this->composer->getPluginManager()->uninstallPackage($package, true);
|
||||||
|
parent::uninstall($repo, $package);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
{
|
{
|
||||||
$installPath = $this->installPath;
|
$installPath = $this->installPath;
|
||||||
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
|
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
|
||||||
|
@ -67,7 +67,32 @@ class ProjectInstaller implements InstallerInterface
|
||||||
if (!is_dir($installPath)) {
|
if (!is_dir($installPath)) {
|
||||||
mkdir($installPath, 0777, true);
|
mkdir($installPath, 0777, true);
|
||||||
}
|
}
|
||||||
$this->downloadManager->download($package, $installPath);
|
|
||||||
|
return $this->downloadManager->download($package, $installPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
|
||||||
|
{
|
||||||
|
$this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||||
|
{
|
||||||
|
$this->downloadManager->install($package, $this->installPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,6 +24,10 @@ use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
*/
|
*/
|
||||||
class SuggestedPackagesReporter
|
class SuggestedPackagesReporter
|
||||||
{
|
{
|
||||||
|
const MODE_LIST = 1;
|
||||||
|
const MODE_BY_PACKAGE = 2;
|
||||||
|
const MODE_BY_SUGGESTION = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
|
@ -91,38 +95,105 @@ class SuggestedPackagesReporter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output suggested packages.
|
* Output suggested packages.
|
||||||
|
*
|
||||||
* Do not list the ones already installed if installed repository provided.
|
* Do not list the ones already installed if installed repository provided.
|
||||||
*
|
*
|
||||||
* @param RepositoryInterface $installedRepo Installed packages
|
* @param int $mode One of the MODE_* constants from this class
|
||||||
* @return SuggestedPackagesReporter
|
* @return SuggestedPackagesReporter
|
||||||
*/
|
*/
|
||||||
public function output(RepositoryInterface $installedRepo = null)
|
public function output($mode, RepositoryInterface $installedRepo = null)
|
||||||
|
{
|
||||||
|
$suggestedPackages = $this->getFilteredSuggestions($installedRepo);
|
||||||
|
|
||||||
|
$suggesters = array();
|
||||||
|
$suggested = array();
|
||||||
|
foreach ($suggestedPackages as $suggestion) {
|
||||||
|
$suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason'];
|
||||||
|
$suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason'];
|
||||||
|
}
|
||||||
|
ksort($suggesters);
|
||||||
|
ksort($suggested);
|
||||||
|
|
||||||
|
// Simple mode
|
||||||
|
if ($mode & self::MODE_LIST) {
|
||||||
|
foreach (array_keys($suggested) as $name) {
|
||||||
|
$this->io->write(sprintf('<info>%s</info>', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grouped by package
|
||||||
|
if ($mode & self::MODE_BY_PACKAGE) {
|
||||||
|
foreach ($suggesters as $suggester => $suggestions) {
|
||||||
|
$this->io->write(sprintf('<comment>%s</comment> suggests:', $suggester));
|
||||||
|
|
||||||
|
foreach ($suggestions as $suggestion => $reason) {
|
||||||
|
$this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason)));
|
||||||
|
}
|
||||||
|
$this->io->write('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grouped by suggestion
|
||||||
|
if ($mode & self::MODE_BY_SUGGESTION) {
|
||||||
|
// Improve readability in full mode
|
||||||
|
if ($mode & self::MODE_BY_PACKAGE) {
|
||||||
|
$this->io->write(str_repeat('-', 78));
|
||||||
|
}
|
||||||
|
foreach ($suggested as $suggestion => $suggesters) {
|
||||||
|
$this->io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));
|
||||||
|
|
||||||
|
foreach ($suggesters as $suggester => $reason) {
|
||||||
|
$this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason)));
|
||||||
|
}
|
||||||
|
$this->io->write('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output number of new suggested packages and a hint to use suggest command.
|
||||||
|
**
|
||||||
|
* Do not list the ones already installed if installed repository provided.
|
||||||
|
*
|
||||||
|
* @return SuggestedPackagesReporter
|
||||||
|
*/
|
||||||
|
public function outputMinimalistic(RepositoryInterface $installedRepo = null)
|
||||||
|
{
|
||||||
|
$suggestedPackages = $this->getFilteredSuggestions($installedRepo);
|
||||||
|
if ($suggestedPackages) {
|
||||||
|
$this->io->writeError('<info>'.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.</info>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFilteredSuggestions(RepositoryInterface $installedRepo = null)
|
||||||
{
|
{
|
||||||
$suggestedPackages = $this->getPackages();
|
$suggestedPackages = $this->getPackages();
|
||||||
$installedPackages = array();
|
$installedNames = array();
|
||||||
if (null !== $installedRepo && ! empty($suggestedPackages)) {
|
if (null !== $installedRepo && !empty($suggestedPackages)) {
|
||||||
foreach ($installedRepo->getPackages() as $package) {
|
foreach ($installedRepo->getPackages() as $package) {
|
||||||
$installedPackages = array_merge(
|
$installedNames = array_merge(
|
||||||
$installedPackages,
|
$installedNames,
|
||||||
$package->getNames()
|
$package->getNames()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$suggestions = array();
|
||||||
foreach ($suggestedPackages as $suggestion) {
|
foreach ($suggestedPackages as $suggestion) {
|
||||||
if (in_array($suggestion['target'], $installedPackages)) {
|
if (in_array($suggestion['target'], $installedNames)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(sprintf(
|
$suggestions[] = $suggestion;
|
||||||
'%s suggests installing %s%s',
|
|
||||||
$suggestion['source'],
|
|
||||||
$this->escapeOutput($suggestion['target']),
|
|
||||||
$this->escapeOutput('' !== $suggestion['reason'] ? ' ('.$suggestion['reason'].')' : '')
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Composer\Json;
|
||||||
use JsonSchema\Validator;
|
use JsonSchema\Validator;
|
||||||
use Seld\JsonLint\JsonParser;
|
use Seld\JsonLint\JsonParser;
|
||||||
use Seld\JsonLint\ParsingException;
|
use Seld\JsonLint\ParsingException;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\HttpDownloader;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Downloader\TransportException;
|
use Composer\Downloader\TransportException;
|
||||||
|
|
||||||
|
@ -37,25 +37,25 @@ class JsonFile
|
||||||
const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json';
|
const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json';
|
||||||
|
|
||||||
private $path;
|
private $path;
|
||||||
private $rfs;
|
private $httpDownloader;
|
||||||
private $io;
|
private $io;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes json file reader/parser.
|
* Initializes json file reader/parser.
|
||||||
*
|
*
|
||||||
* @param string $path path to a lockfile
|
* @param string $path path to a lockfile
|
||||||
* @param RemoteFilesystem $rfs required for loading http/https json files
|
* @param HttpDownloader $httpDownloader required for loading http/https json files
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
|
public function __construct($path, HttpDownloader $httpDownloader = null, IOInterface $io = null)
|
||||||
{
|
{
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
|
|
||||||
if (null === $rfs && preg_match('{^https?://}i', $path)) {
|
if (null === $httpDownloader && preg_match('{^https?://}i', $path)) {
|
||||||
throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
|
throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
|
||||||
}
|
}
|
||||||
$this->rfs = $rfs;
|
$this->httpDownloader = $httpDownloader;
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +86,8 @@ class JsonFile
|
||||||
public function read()
|
public function read()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->rfs) {
|
if ($this->httpDownloader) {
|
||||||
$json = $this->rfs->getContents($this->path, $this->path, false);
|
$json = $this->httpDownloader->get($this->path)->getBody();
|
||||||
} else {
|
} else {
|
||||||
if ($this->io && $this->io->isDebug()) {
|
if ($this->io && $this->io->isDebug()) {
|
||||||
$this->io->writeError('Reading ' . $this->path);
|
$this->io->writeError('Reading ' . $this->path);
|
||||||
|
|
|
@ -416,4 +416,9 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
|
||||||
{
|
{
|
||||||
return $this->aliasOf->setDistType($type);
|
return $this->aliasOf->setDistType($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setSourceDistReferences($reference)
|
||||||
|
{
|
||||||
|
return $this->aliasOf->setSourceDistReferences($reference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Package\RootPackageInterface;
|
use Composer\Package\RootPackageInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Loop;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +26,7 @@ use Composer\Json\JsonFile;
|
||||||
class ArchiveManager
|
class ArchiveManager
|
||||||
{
|
{
|
||||||
protected $downloadManager;
|
protected $downloadManager;
|
||||||
|
protected $loop;
|
||||||
|
|
||||||
protected $archivers = array();
|
protected $archivers = array();
|
||||||
|
|
||||||
|
@ -36,9 +38,10 @@ class ArchiveManager
|
||||||
/**
|
/**
|
||||||
* @param DownloadManager $downloadManager A manager used to download package sources
|
* @param DownloadManager $downloadManager A manager used to download package sources
|
||||||
*/
|
*/
|
||||||
public function __construct(DownloadManager $downloadManager)
|
public function __construct(DownloadManager $downloadManager, Loop $loop)
|
||||||
{
|
{
|
||||||
$this->downloadManager = $downloadManager;
|
$this->downloadManager = $downloadManager;
|
||||||
|
$this->loop = $loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,7 +152,9 @@ class ArchiveManager
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Download sources
|
// Download sources
|
||||||
$this->downloadManager->download($package, $sourcePath);
|
$promise = $this->downloadManager->download($package, $sourcePath);
|
||||||
|
$this->loop->wait(array($promise));
|
||||||
|
$this->downloadManager->install($package, $sourcePath);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$filesystem->removeDirectory($sourcePath);
|
$filesystem->removeDirectory($sourcePath);
|
||||||
throw $e;
|
throw $e;
|
||||||
|
|
|
@ -210,18 +210,30 @@ abstract class BasePackage implements PackageInterface
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function getFullPrettyVersion($truncate = true)
|
public function getFullPrettyVersion($truncate = true, $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV)
|
||||||
{
|
{
|
||||||
if (!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git'))) {
|
if ($displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV &&
|
||||||
|
(!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git')))
|
||||||
|
) {
|
||||||
return $this->getPrettyVersion();
|
return $this->getPrettyVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if source reference is a sha1 hash -- truncate
|
switch ($displayMode) {
|
||||||
if ($truncate && strlen($this->getSourceReference()) === 40) {
|
case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV:
|
||||||
return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7);
|
case PackageInterface::DISPLAY_SOURCE_REF:
|
||||||
|
$reference = $this->getSourceReference();
|
||||||
|
break;
|
||||||
|
case PackageInterface::DISPLAY_DIST_REF:
|
||||||
|
$reference = $this->getDistReference();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
|
// if source reference is a sha1 hash -- truncate
|
||||||
|
if ($truncate && strlen($reference) === 40) {
|
||||||
|
return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getPrettyVersion() . ' ' . $reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStabilityPriority()
|
public function getStabilityPriority()
|
||||||
|
@ -238,14 +250,14 @@ abstract class BasePackage implements PackageInterface
|
||||||
/**
|
/**
|
||||||
* Build a regexp from a package name, expanding * globs as required
|
* Build a regexp from a package name, expanding * globs as required
|
||||||
*
|
*
|
||||||
* @param string $whiteListedPattern
|
* @param string $allowPattern
|
||||||
* @param string $wrap Wrap the cleaned string by the given string
|
* @param string $wrap Wrap the cleaned string by the given string
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i')
|
public static function packageNameToRegexp($allowPattern, $wrap = '{^%s$}i')
|
||||||
{
|
{
|
||||||
$cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern));
|
$cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern));
|
||||||
|
|
||||||
return sprintf($wrap, $cleanedWhiteListedPattern);
|
return sprintf($wrap, $cleanedAllowPattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue