Merge branch '2.0'
commit
87757de6bc
|
@ -15,3 +15,4 @@
|
|||
.travis.yml export-ignore
|
||||
appveyor.yml export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
/phpstan/ export-ignore
|
||||
|
|
43
.travis.yml
43
.travis.yml
|
@ -1,6 +1,6 @@
|
|||
language: php
|
||||
|
||||
dist: trusty
|
||||
dist: bionic
|
||||
|
||||
git:
|
||||
depth: 5
|
||||
|
@ -9,27 +9,40 @@ cache:
|
|||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- parallel
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
- php: 5.4
|
||||
dist: trusty
|
||||
- php: 5.5
|
||||
dist: trusty
|
||||
- php: 5.6
|
||||
dist: xenial
|
||||
- php: 7.0
|
||||
dist: xenial
|
||||
- php: 7.1
|
||||
dist: xenial
|
||||
- php: 7.2
|
||||
dist: xenial
|
||||
- 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
|
||||
env:
|
||||
- deps=high
|
||||
- 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
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
|
@ -44,9 +57,9 @@ before_install:
|
|||
|
||||
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
|
||||
- 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
|
||||
- composer install $flags
|
||||
# install dependencies using composer from source
|
||||
|
@ -58,9 +71,13 @@ before_script:
|
|||
- git config --global user.email travis@example.com
|
||||
|
||||
script:
|
||||
- ./vendor/bin/simple-phpunit
|
||||
# run test suite directories in parallel using GNU parallel
|
||||
# - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
|
||||
- if [[ $PHPSTAN == "1" ]]; then
|
||||
bin/composer require --dev phpstan/phpstan:^0.12 phpunit/phpunit:^7.5 --no-update &&
|
||||
bin/composer update phpstan/* phpunit/* sebastian/* --with-dependencies &&
|
||||
vendor/bin/phpstan analyse --configuration=phpstan/config.neon;
|
||||
else
|
||||
vendor/bin/simple-phpunit;
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- php -d phar.readonly=0 bin/compile
|
||||
|
@ -73,4 +90,4 @@ deploy:
|
|||
on:
|
||||
tags: true
|
||||
repo: composer/composer
|
||||
php: '7.2'
|
||||
php: '7.3'
|
||||
|
|
11
appveyor.yml
11
appveyor.yml
|
@ -3,7 +3,7 @@ clone_depth: 5
|
|||
|
||||
environment:
|
||||
# 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_PHP: C:\tools\phpci\php
|
||||
PHPCI_COMPOSER: C:\tools\phpci\composer
|
||||
|
@ -25,6 +25,15 @@ install:
|
|||
- IF %PHP%==0 cinst composer -i -y --ia "/DEV=%PHPCI_COMPOSER%"
|
||||
- php -v
|
||||
- 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%
|
||||
- composer install --prefer-dist --no-progress
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"require": {
|
||||
"php": "^5.3.2 || ^7.0",
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"composer/semver": "^1.0",
|
||||
"composer/semver": "^2.0@dev",
|
||||
"composer/spdx-licenses": "^1.2",
|
||||
"composer/xdebug-handler": "^1.1",
|
||||
"justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
|
||||
|
@ -34,7 +34,8 @@
|
|||
"symfony/console": "^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/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": {
|
||||
"symfony/console": "2.8.38"
|
||||
|
@ -55,7 +56,7 @@
|
|||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.10-dev"
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -65,8 +66,13 @@
|
|||
},
|
||||
"autoload-dev": {
|
||||
"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/composer"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cc6f9640996dfad00a5b03a8be01a571",
|
||||
"content-hash": "a0a9399315ac0b612d4296b8df745112",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -60,20 +60,25 @@
|
|||
"ssl",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "1.5.1",
|
||||
"version": "2.0.x-dev",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
|
||||
"reference": "4df5ff3249f01018504939d66040d8d2b783d820"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/4df5ff3249f01018504939d66040d8d2b783d820",
|
||||
"reference": "4df5ff3249f01018504939d66040d8d2b783d820",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -85,7 +90,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -121,7 +126,22 @@
|
|||
"validation",
|
||||
"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",
|
||||
|
@ -181,6 +201,11 @@
|
|||
"spdx",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -225,6 +250,11 @@
|
|||
"Xdebug",
|
||||
"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": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
|
@ -353,6 +383,44 @@
|
|||
},
|
||||
"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",
|
||||
"version": "1.7.2",
|
||||
|
@ -448,6 +516,10 @@
|
|||
"keywords": [
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -735,6 +807,9 @@
|
|||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/master"
|
||||
},
|
||||
"time": "2020-01-13T11:15:53+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -794,6 +869,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/master"
|
||||
},
|
||||
"time": "2020-01-13T11:15:53+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -949,6 +1027,16 @@
|
|||
"license": [
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1012,6 +1100,10 @@
|
|||
"spy",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1076,6 +1168,10 @@
|
|||
"compare",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1128,6 +1224,10 @@
|
|||
"keywords": [
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1195,6 +1295,10 @@
|
|||
"export",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1248,6 +1352,10 @@
|
|||
],
|
||||
"description": "Provides functionality to recursively process PHP variables",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1313,6 +1421,9 @@
|
|||
],
|
||||
"description": "Symfony PHPUnit Bridge",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/phpunit-bridge/tree/v3.4.38"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -1332,7 +1443,9 @@
|
|||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"composer/semver": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
@ -1342,5 +1455,5 @@
|
|||
"platform-overrides": {
|
||||
"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
|
||||
> `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
|
||||
php composer.phar update monolog/monolog [...]
|
||||
|
|
|
@ -106,7 +106,6 @@ resolution.
|
|||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
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
|
||||
autoloader. This is recommended especially for production, but can take
|
||||
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-progress:** Removes the progress display that can mess with some
|
||||
terminals or scripts which don't handle backspace characters.
|
||||
* **--no-suggest:** Skips suggested packages in the output.
|
||||
* **--with-dependencies:** Add also dependencies of whitelisted packages to the whitelist, except those that are root requirements.
|
||||
* **--with-all-dependencies:** Add also all dependencies of whitelisted packages to the whitelist, including those that are root requirements.
|
||||
* **--with-dependencies:** Update also dependencies of packages in the argument list, except those which are root requirements.
|
||||
* **--with-all-dependencies:** Update also dependencies of packages in the argument list, including those which are root requirements.
|
||||
* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
|
||||
autoloader. This is recommended especially for production, but can take
|
||||
a bit of time to run so it is currently not done by default.
|
||||
|
@ -198,11 +196,11 @@ If you do not specify a package, composer will prompt you to search for a packag
|
|||
### Options
|
||||
|
||||
* **--dev:** Add packages to `require-dev`.
|
||||
* **--dry-run:** Simulate the command without actually doing anything.
|
||||
* **--prefer-source:** Install packages from `source` when available.
|
||||
* **--prefer-dist:** Install packages from `dist` when available.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
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-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||
* **--update-no-dev:** Run the dependency update with the `--no-dev` option.
|
||||
|
@ -236,6 +234,7 @@ uninstalled.
|
|||
|
||||
### Options
|
||||
* **--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
|
||||
terminals or scripts which don't handle backspace characters.
|
||||
* **--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`
|
||||
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.
|
||||
|
||||
Use the `--verbose (-v)` flag to display the suggesting package and the suggestion reason.
|
||||
This implies `--by-package --by-suggestion`, showing both lists.
|
||||
If you only want a list of suggested package names, use `--list`.
|
||||
|
||||
### Options
|
||||
|
||||
* **--by-package:** Groups output by suggesting package.
|
||||
* **--by-package:** Groups output by suggesting package (default).
|
||||
* **--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.
|
||||
|
||||
## 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
|
||||
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) →
|
||||
|
|
|
@ -176,8 +176,8 @@ class AwsPlugin implements PluginInterface, EventSubscriberInterface
|
|||
|
||||
if ($protocol === 's3') {
|
||||
$awsClient = new AwsClient($this->io, $this->composer->getConfig());
|
||||
$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient);
|
||||
$event->setRemoteFilesystem($s3RemoteFilesystem);
|
||||
$s3Downloader = new S3Downloader($this->io, $event->getHttpDownloader()->getOptions(), $awsClient);
|
||||
$event->setHttpdownloader($s3Downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,8 +43,8 @@ Composer fires the following named events during its execution process:
|
|||
|
||||
### Installer Events
|
||||
|
||||
- **pre-dependencies-solving**: occurs before the dependencies are resolved.
|
||||
- **post-dependencies-solving**: occurs after the dependencies have been resolved.
|
||||
- **pre-operations-exec**: occurs before the install/upgrade/.. operations
|
||||
are executed when installing a lock file.
|
||||
|
||||
### 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
|
||||
provides you with access to the input and output objects of the program.
|
||||
- **pre-file-download**: occurs before files are downloaded and allows
|
||||
you to manipulate the `RemoteFilesystem` object prior to downloading files
|
||||
you to manipulate the `HttpDownloader` object prior to downloading files
|
||||
based on the URL to be downloaded.
|
||||
- **pre-command-run**: occurs before a command is executed and allows you to
|
||||
manipulate the `InputInterface` object's options and arguments to tweak
|
||||
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
|
||||
> 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);
|
||||
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 ($validClasses) {
|
||||
if ($classes) {
|
||||
$scannedFiles[$realPath] = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -126,8 +126,7 @@ class ClassMapGenerator
|
|||
|
||||
foreach ($classes as $class) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -196,19 +195,15 @@ class ClassMapGenerator
|
|||
// warn only if no valid classes, else silently skip invalid
|
||||
if (empty($validClasses)) {
|
||||
foreach ($rejectedClasses as $class) {
|
||||
trigger_error(
|
||||
"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.",
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
if ($io) {
|
||||
$io->writeError("<warning>Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. Skipping.</warning>");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO enable in Composer 2.0
|
||||
//return array();
|
||||
return array();
|
||||
}
|
||||
|
||||
// TODO enable in Composer 2.0 & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
|
||||
//return $validClasses;
|
||||
return array($classes, $validClasses);
|
||||
return $validClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,20 +28,20 @@ class Cache
|
|||
private $io;
|
||||
private $root;
|
||||
private $enabled = true;
|
||||
private $whitelist;
|
||||
private $allowlist;
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* @param IOInterface $io
|
||||
* @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
|
||||
*/
|
||||
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->root = rtrim($cacheDir, '/\\') . '/';
|
||||
$this->whitelist = $whitelist;
|
||||
$this->allowlist = $allowlist;
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
|
||||
if (!self::isUsable($cacheDir)) {
|
||||
|
@ -77,7 +77,7 @@ class Cache
|
|||
public function read($file)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
if (file_exists($this->root . $file)) {
|
||||
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
|
||||
|
||||
|
@ -91,7 +91,7 @@ class Cache
|
|||
public function write($file, $contents)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -129,7 +129,7 @@ class Cache
|
|||
public function copyFrom($file, $source)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file));
|
||||
|
||||
if (!file_exists($source)) {
|
||||
|
@ -150,7 +150,7 @@ class Cache
|
|||
public function copyTo($file, $target)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
if (file_exists($this->root . $file)) {
|
||||
try {
|
||||
touch($this->root . $file, filemtime($this->root . $file), time());
|
||||
|
@ -177,7 +177,7 @@ class Cache
|
|||
public function remove($file)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
if (file_exists($this->root . $file)) {
|
||||
return $this->filesystem->unlink($this->root . $file);
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ class Cache
|
|||
public function sha1($file)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
if (file_exists($this->root . $file)) {
|
||||
return sha1_file($this->root . $file);
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ class Cache
|
|||
public function sha256($file)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
|
||||
if (file_exists($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\PluginEvents;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -111,8 +112,9 @@ EOT
|
|||
$archiveManager = $composer->getArchiveManager();
|
||||
} else {
|
||||
$factory = new Factory;
|
||||
$downloadManager = $factory->createDownloadManager($io, $config);
|
||||
$archiveManager = $factory->createArchiveManager($config, $downloadManager);
|
||||
$httpDownloader = $factory->createHttpDownloader($io, $config);
|
||||
$downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader);
|
||||
$archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader));
|
||||
}
|
||||
|
||||
if ($packageName) {
|
||||
|
|
|
@ -27,6 +27,8 @@ use Symfony\Component\Console\Command\Command;
|
|||
/**
|
||||
* Base class for Composer commands
|
||||
*
|
||||
* @method Application getApplication()
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knplabs.com>
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
|
@ -46,7 +48,7 @@ abstract class BaseCommand extends Command
|
|||
* @param bool $required
|
||||
* @param bool|null $disablePlugins
|
||||
* @throws \RuntimeException
|
||||
* @return Composer
|
||||
* @return Composer|null
|
||||
*/
|
||||
public function getComposer($required = true, $disablePlugins = null)
|
||||
{
|
||||
|
@ -173,7 +175,7 @@ abstract class BaseCommand extends Command
|
|||
|
||||
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
|
||||
$preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
|
||||
$preferDist = $input->getOption('prefer-dist');
|
||||
$preferDist = (bool) $input->getOption('prefer-dist');
|
||||
}
|
||||
|
||||
return array($preferSource, $preferDist);
|
||||
|
|
|
@ -12,11 +12,12 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Repository\InstalledArrayRepository;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RootPackageRepository;
|
||||
use Composer\Repository\InstalledRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
|
@ -71,15 +72,12 @@ class BaseDependencyCommand extends BaseCommand
|
|||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
// Prepare repositories and set up a pool
|
||||
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
||||
$repository = new CompositeRepository(array(
|
||||
new ArrayRepository(array($composer->getPackage())),
|
||||
$installedRepo = new InstalledRepository(array(
|
||||
new RootPackageRepository($composer->getPackage()),
|
||||
$composer->getRepositoryManager()->getLocalRepository(),
|
||||
new PlatformRepository(array(), $platformOverrides),
|
||||
));
|
||||
$pool = new Pool();
|
||||
$pool->addRepository($repository);
|
||||
|
||||
// Parse package name and constraint
|
||||
list($needle, $textConstraint) = array_pad(
|
||||
|
@ -89,17 +87,17 @@ class BaseDependencyCommand extends BaseCommand
|
|||
);
|
||||
|
||||
// Find packages that are or provide the requested package first
|
||||
$packages = $pool->whatProvides(strtolower($needle));
|
||||
$packages = $installedRepo->findPackagesWithReplacersAndProviders($needle);
|
||||
if (empty($packages)) {
|
||||
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.
|
||||
// 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()));
|
||||
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);
|
||||
|
||||
// Resolve dependencies
|
||||
$results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
|
||||
$results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive);
|
||||
if (empty($results)) {
|
||||
$extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
|
||||
$this->getIO()->writeError(sprintf(
|
||||
|
|
|
@ -20,6 +20,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Repository\InstalledRepository;
|
||||
|
||||
class CheckPlatformReqsCommand extends BaseCommand
|
||||
{
|
||||
|
@ -48,12 +49,13 @@ EOT
|
|||
|
||||
$requires = $composer->getPackage()->getRequires();
|
||||
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 {
|
||||
$dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
|
||||
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||
// fallback to lockfile if installed repo is empty
|
||||
if (!$dependencies) {
|
||||
$dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
|
||||
if (!$installedRepo->getPackages()) {
|
||||
$installedRepo = $composer->getLocker()->getLockedRepository(true);
|
||||
}
|
||||
$requires += $composer->getPackage()->getDevRequires();
|
||||
}
|
||||
|
@ -61,7 +63,8 @@ EOT
|
|||
$requires[$require] = array($link);
|
||||
}
|
||||
|
||||
foreach ($dependencies as $package) {
|
||||
$installedRepo = new InstalledRepository(array($installedRepo));
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
foreach ($package->getRequires() as $require => $link) {
|
||||
$requires[$require][] = $link;
|
||||
}
|
||||
|
@ -69,19 +72,9 @@ EOT
|
|||
|
||||
ksort($requires);
|
||||
|
||||
$platformRepo = new PlatformRepository(array(), array());
|
||||
$currentPlatformPackages = $platformRepo->getPackages();
|
||||
$currentPlatformPackageMap = array();
|
||||
|
||||
/**
|
||||
* @var PackageInterface $currentPlatformPackage
|
||||
*/
|
||||
foreach ($currentPlatformPackages as $currentPlatformPackage) {
|
||||
$currentPlatformPackageMap[$currentPlatformPackage->getName()] = $currentPlatformPackage;
|
||||
}
|
||||
$installedRepo->addRepository(new PlatformRepository(array(), array()));
|
||||
|
||||
$results = array();
|
||||
|
||||
$exitCode = 0;
|
||||
|
||||
/**
|
||||
|
@ -89,42 +82,62 @@ EOT
|
|||
*/
|
||||
foreach ($requires as $require => $links) {
|
||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) {
|
||||
if (isset($currentPlatformPackageMap[$require])) {
|
||||
$pass = true;
|
||||
$version = $currentPlatformPackageMap[$require]->getVersion();
|
||||
|
||||
foreach ($links as $link) {
|
||||
if (!$link->getConstraint()->matches(new Constraint('=', $version))) {
|
||||
$results[] = array(
|
||||
$currentPlatformPackageMap[$require]->getPrettyName(),
|
||||
$currentPlatformPackageMap[$require]->getPrettyVersion(),
|
||||
$link,
|
||||
'<error>failed</error>',
|
||||
);
|
||||
$pass = false;
|
||||
|
||||
$exitCode = max($exitCode, 1);
|
||||
$candidates = $installedRepo->findPackagesWithReplacersAndProviders($require);
|
||||
if ($candidates) {
|
||||
$reqResults = array();
|
||||
foreach ($candidates as $candidate) {
|
||||
if ($candidate->getName() === $require) {
|
||||
$candidateConstraint = new Constraint('=', $candidate->getVersion());
|
||||
$candidateConstraint->setPrettyString($candidate->getPrettyVersion());
|
||||
} else {
|
||||
foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
|
||||
if ($link->getTarget() === $require) {
|
||||
$candidateConstraint = $link->getConstraint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
$currentPlatformPackageMap[$require]->getPrettyName(),
|
||||
$currentPlatformPackageMap[$require]->getPrettyVersion(),
|
||||
$candidate->getName() === $require ? $candidate->getPrettyName() : $require,
|
||||
$candidateConstraint->getPrettyString(),
|
||||
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');
|
||||
if (!$settingKey) {
|
||||
if (!$settingKey || !is_string($settingKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ use Composer\Installer\InstallationManager;
|
|||
use Composer\Installer\SuggestedPackagesReporter;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Package\AliasPackage;
|
||||
|
@ -28,6 +27,7 @@ use Composer\Repository\RepositoryFactory;
|
|||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Repository\InstalledFilesystemRepository;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Util\Silencer;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder;
|
|||
use Composer\Json\JsonFile;
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
||||
/**
|
||||
|
@ -182,8 +183,6 @@ EOT
|
|||
$composer = Factory::create($io, null, $disablePlugins);
|
||||
}
|
||||
|
||||
$composer->getDownloadManager()->setOutputProgress(!$noProgress);
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
if ($noScripts === false) {
|
||||
|
@ -334,8 +333,8 @@ EOT
|
|||
throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
|
||||
}
|
||||
|
||||
$pool = new Pool($stability);
|
||||
$pool->addRepository($sourceRepo);
|
||||
$repositorySet = new RepositorySet($stability);
|
||||
$repositorySet->addRepository($sourceRepo);
|
||||
|
||||
$phpVersion = null;
|
||||
$prettyPhpVersion = null;
|
||||
|
@ -349,7 +348,7 @@ EOT
|
|||
}
|
||||
|
||||
// find the latest version if there are multiple
|
||||
$versionSelector = new VersionSelector($pool);
|
||||
$versionSelector = new VersionSelector($repositorySet);
|
||||
$package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability);
|
||||
|
||||
if (!$package) {
|
||||
|
@ -384,15 +383,17 @@ EOT
|
|||
$package = $package->getAliasOf();
|
||||
}
|
||||
|
||||
$dm = $this->createDownloadManager($io, $config);
|
||||
$factory = new Factory();
|
||||
|
||||
$httpDownloader = $factory->createHttpDownloader($io, $config);
|
||||
$dm = $factory->createDownloadManager($io, $config, $httpDownloader);
|
||||
$dm->setPreferSource($preferSource)
|
||||
->setPreferDist($preferDist)
|
||||
->setOutputProgress(!$noProgress);
|
||||
->setPreferDist($preferDist);
|
||||
|
||||
$projectInstaller = new ProjectInstaller($directory, $dm);
|
||||
$im = $this->createInstallationManager();
|
||||
$im = $factory->createInstallationManager(new Loop($httpDownloader), $io);
|
||||
$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);
|
||||
|
||||
// collect suggestions
|
||||
|
@ -408,16 +409,4 @@ EOT
|
|||
|
||||
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\IniHelper;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
use Composer\SelfUpdate\Keys;
|
||||
use Composer\SelfUpdate\Versions;
|
||||
|
@ -35,8 +35,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class DiagnoseCommand extends BaseCommand
|
||||
{
|
||||
/** @var RemoteFilesystem */
|
||||
protected $rfs;
|
||||
/** @var HttpDownloader */
|
||||
protected $httpDownloader;
|
||||
|
||||
/** @var ProcessExecutor */
|
||||
protected $process;
|
||||
|
@ -86,7 +86,7 @@ EOT
|
|||
$config->merge(array('config' => array('secure-http' => false)));
|
||||
$config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);
|
||||
|
||||
$this->rfs = Factory::createRemoteFilesystem($io, $config);
|
||||
$this->httpDownloader = Factory::createHttpDownloader($io, $config);
|
||||
$this->process = new ProcessExecutor($io);
|
||||
|
||||
$io->write('Checking platform settings: ', false);
|
||||
|
@ -156,7 +156,7 @@ EOT
|
|||
$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();
|
||||
$platformRepo = new PlatformRepository(array(), $platformOverrides);
|
||||
|
@ -229,7 +229,7 @@ EOT
|
|||
}
|
||||
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false);
|
||||
$this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
|
||||
} catch (TransportException $e) {
|
||||
if (false !== strpos($e->getMessage(), 'cafile')) {
|
||||
$result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
|
||||
|
@ -256,11 +256,11 @@ EOT
|
|||
|
||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||
try {
|
||||
$json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true);
|
||||
$json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson();
|
||||
$hash = reset($json['provider-includes']);
|
||||
$hash = $hash['sha256'];
|
||||
$path = str_replace('%hash%', $hash, key($json['provider-includes']));
|
||||
$provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false);
|
||||
$provider = $this->httpDownloader->get($protocol . '://repo.packagist.org/'.$path)->getBody();
|
||||
|
||||
if (hash('sha256', $provider) !== $hash) {
|
||||
return 'It seems that your proxy is modifying http traffic on the fly';
|
||||
|
@ -288,10 +288,10 @@ EOT
|
|||
|
||||
$url = 'http://repo.packagist.org/packages.json';
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $url, false);
|
||||
$this->httpDownloader->get($url);
|
||||
} catch (TransportException $e) {
|
||||
try {
|
||||
$this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
$this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
@ -322,10 +322,10 @@ EOT
|
|||
|
||||
$url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
|
||||
try {
|
||||
$this->rfs->getContents('github.com', $url, false);
|
||||
$this->httpDownloader->get($url);
|
||||
} catch (TransportException $e) {
|
||||
try {
|
||||
$this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
$this->httpDownloader->get($url, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
@ -347,7 +347,7 @@ EOT
|
|||
try {
|
||||
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';
|
||||
|
||||
return $this->rfs->getContents($domain, $url, false, array(
|
||||
return $this->httpDownloader->get($url, array(
|
||||
'retry-auth-failure' => false,
|
||||
)) ? true : 'Unexpected error';
|
||||
} catch (\Exception $e) {
|
||||
|
@ -377,8 +377,7 @@ EOT
|
|||
}
|
||||
|
||||
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
|
||||
$json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false));
|
||||
$data = json_decode($json, true);
|
||||
$data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->decodeJson();
|
||||
|
||||
return $data['resources']['core'];
|
||||
}
|
||||
|
@ -431,7 +430,7 @@ EOT
|
|||
return $result;
|
||||
}
|
||||
|
||||
$versionsUtil = new Versions($config, $this->rfs);
|
||||
$versionsUtil = new Versions($config, $this->httpDownloader);
|
||||
$latest = $versionsUtil->getLatest();
|
||||
|
||||
if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
|
||||
|
@ -613,20 +612,6 @@ EOT
|
|||
$text .= "Install either of them or recompile php without --disable-iconv";
|
||||
break;
|
||||
|
||||
case 'unicode':
|
||||
$text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
|
||||
$text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
|
||||
$text .= " detect_unicode = Off";
|
||||
$displayIniMessage = true;
|
||||
break;
|
||||
|
||||
case 'suhosin':
|
||||
$text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
|
||||
$text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
|
||||
$text .= " suhosin.executor.include.whitelist = phar ".$current;
|
||||
$displayIniMessage = true;
|
||||
break;
|
||||
|
||||
case 'php':
|
||||
$text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
|
||||
break;
|
||||
|
@ -729,7 +714,7 @@ EOT
|
|||
/**
|
||||
* Check if allow_url_fopen is ON
|
||||
*
|
||||
* @return bool|string
|
||||
* @return true|string
|
||||
*/
|
||||
private function checkConnectivity()
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Composer\Command;
|
|||
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Repository\RootPackageRepository;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
@ -157,7 +157,7 @@ EOT
|
|||
|
||||
if ($composer) {
|
||||
return array_merge(
|
||||
array(new ArrayRepository(array($composer->getPackage()))), // root package
|
||||
array(new RootPackageRepository($composer->getPackage())), // root package
|
||||
array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
|
||||
$composer->getRepositoryManager()->getRepositories() // remotes
|
||||
);
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Factory;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Package\BasePackage;
|
||||
|
@ -22,6 +21,7 @@ use Composer\Package\Version\VersionSelector;
|
|||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
@ -42,8 +42,8 @@ class InitCommand extends BaseCommand
|
|||
/** @var array */
|
||||
private $gitConfig;
|
||||
|
||||
/** @var Pool[] */
|
||||
private $pools;
|
||||
/** @var RepositorySet[] */
|
||||
private $repositorySets;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -86,8 +86,8 @@ EOT
|
|||
{
|
||||
$io = $this->getIO();
|
||||
|
||||
$whitelist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
|
||||
$options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist)));
|
||||
$allowlist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
|
||||
$options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)));
|
||||
|
||||
if (isset($options['author'])) {
|
||||
$options['authors'] = $this->formatAuthors($options['author']);
|
||||
|
@ -688,16 +688,16 @@ EOT
|
|||
return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
private function getPool(InputInterface $input, $minimumStability = null)
|
||||
private function getRepositorySet(InputInterface $input, $minimumStability = null)
|
||||
{
|
||||
$key = $minimumStability ?: 'default';
|
||||
|
||||
if (!isset($this->pools[$key])) {
|
||||
$this->pools[$key] = $pool = new Pool($minimumStability ?: $this->getMinimumStability($input));
|
||||
$pool->addRepository($this->getRepos());
|
||||
if (!isset($this->repositorySets[$key])) {
|
||||
$this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?: $this->getMinimumStability($input));
|
||||
$repositorySet->addRepository($this->getRepos());
|
||||
}
|
||||
|
||||
return $this->pools[$key];
|
||||
return $this->repositorySets[$key];
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// find the latest version allowed in this pool
|
||||
$versionSelector = new VersionSelector($this->getPool($input, $minimumStability));
|
||||
// find the latest version allowed in this repo set
|
||||
$versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
|
||||
$ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');
|
||||
|
||||
// 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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
|
||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
|
||||
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
||||
|
@ -86,7 +85,6 @@ EOT
|
|||
}
|
||||
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
@ -108,7 +106,6 @@ EOT
|
|||
->setDevMode(!$input->getOption('no-dev'))
|
||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setSkipSuggest($input->getOption('no-suggest'))
|
||||
->setOptimizeAutoloader($optimize)
|
||||
->setClassMapAuthoritative($authoritative)
|
||||
->setApcuAutoloader($apcu)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Command;
|
||||
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Installer;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
|
@ -38,6 +39,7 @@ class RemoveCommand extends BaseCommand
|
|||
->setDefinition(array(
|
||||
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('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-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.'),
|
||||
|
@ -92,26 +94,44 @@ EOT
|
|||
}
|
||||
}
|
||||
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$toRemove = array();
|
||||
foreach ($packages as $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])) {
|
||||
$io->writeError('<warning>' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
|
||||
if ($io->isInteractive()) {
|
||||
if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
|
||||
$json->removeLink($altType, $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]))) {
|
||||
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]))) {
|
||||
foreach ($matches as $matchedPackage) {
|
||||
$io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
|
||||
if ($io->isInteractive()) {
|
||||
if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ', true)) {
|
||||
$json->removeLink($altType, $matchedPackage);
|
||||
if ($dryRun) {
|
||||
$toRemove[$altType][] = $matchedPackage;
|
||||
} else {
|
||||
$json->removeLink($altType, $matchedPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +147,21 @@ EOT
|
|||
// Update packages
|
||||
$this->resetComposer();
|
||||
$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);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
@ -146,10 +180,11 @@ EOT
|
|||
->setClassMapAuthoritative($authoritative)
|
||||
->setApcuAutoloader($apcu)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($packages)
|
||||
->setWhitelistTransitiveDependencies(!$input->getOption('no-update-with-dependencies'))
|
||||
->setUpdateAllowList($packages)
|
||||
->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'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setDryRun($dryRun)
|
||||
;
|
||||
|
||||
$status = $install->run();
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -21,6 +22,8 @@ use Composer\Installer;
|
|||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
|
@ -35,9 +38,14 @@ use Composer\Util\Silencer;
|
|||
class RequireCommand extends InitCommand
|
||||
{
|
||||
private $newlyCreated;
|
||||
private $firstRequire;
|
||||
private $json;
|
||||
private $file;
|
||||
private $composerBackup;
|
||||
/** @var string file name */
|
||||
private $lock;
|
||||
/** @var ?string contents before modification if the lock file exists */
|
||||
private $lockBackup;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
|
@ -47,16 +55,19 @@ class RequireCommand extends InitCommand
|
|||
->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 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-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('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-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-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('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('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable 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->lock = Factory::getLockFile($this->file);
|
||||
$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
|
||||
// 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');
|
||||
|
||||
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();
|
||||
foreach ($requirements as $package => $version) {
|
||||
$composerDefinition[$requireKey][$package] = $version;
|
||||
|
@ -202,51 +223,78 @@ EOT
|
|||
}
|
||||
|
||||
try {
|
||||
return $this->doUpdate($input, $output, $io, $requirements);
|
||||
return $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey);
|
||||
} catch (\Exception $e) {
|
||||
$this->revertComposerFile(false);
|
||||
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
|
||||
$this->resetComposer();
|
||||
$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');
|
||||
$optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
|
||||
$authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
|
||||
$apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
|
||||
|
||||
$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);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
$install = Installer::create($io, $composer);
|
||||
|
||||
$install
|
||||
->setDryRun($input->getOption('dry-run'))
|
||||
->setVerbose($input->getOption('verbose'))
|
||||
->setPreferSource($input->getOption('prefer-source'))
|
||||
->setPreferDist($input->getOption('prefer-dist'))
|
||||
->setDevMode($updateDevMode)
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setSkipSuggest($input->getOption('no-suggest'))
|
||||
->setOptimizeAutoloader($optimize)
|
||||
->setClassMapAuthoritative($authoritative)
|
||||
->setApcuAutoloader($apcu)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist(array_keys($requirements))
|
||||
->setWhitelistTransitiveDependencies($input->getOption('update-with-dependencies'))
|
||||
->setWhitelistAllDependencies($input->getOption('update-with-all-dependencies'))
|
||||
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
->setPreferStable($input->getOption('prefer-stable'))
|
||||
->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();
|
||||
if ($status !== 0) {
|
||||
if ($status !== 0 || $input->getOption('dry-run')) {
|
||||
$this->revertComposerFile(false);
|
||||
}
|
||||
|
||||
|
@ -285,9 +333,19 @@ EOT
|
|||
if ($this->newlyCreated) {
|
||||
$io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
|
||||
unlink($this->json->getPath());
|
||||
if (file_exists($this->lock)) {
|
||||
unlink($this->lock);
|
||||
}
|
||||
} 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);
|
||||
if ($this->lockBackup) {
|
||||
file_put_contents($this->lock, $this->lockBackup);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hardExit) {
|
||||
|
|
|
@ -77,9 +77,9 @@ EOT
|
|||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
$remoteFilesystem = Factory::createRemoteFilesystem($io, $config);
|
||||
$httpDownloader = Factory::createHttpDownloader($io, $config);
|
||||
|
||||
$versionsUtil = new Versions($config, $remoteFilesystem);
|
||||
$versionsUtil = new Versions($config, $httpDownloader);
|
||||
|
||||
// switch channel if requested
|
||||
foreach (array('stable', 'preview', 'snapshot') as $channel) {
|
||||
|
@ -154,11 +154,11 @@ EOT
|
|||
|
||||
$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');
|
||||
$signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false);
|
||||
$signature = $httpDownloader->get($remoteFilename.'.sig')->getBody();
|
||||
$io->writeError(' ', false);
|
||||
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
|
||||
$httpDownloader->copy($remoteFilename, $tempFilename);
|
||||
$io->writeError('');
|
||||
|
||||
if (!file_exists($tempFilename) || !$signature) {
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Composer\Command;
|
|||
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\DefaultPolicy;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
|
@ -23,12 +22,14 @@ use Composer\Package\Version\VersionParser;
|
|||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Repository\ComposerRepository;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Repository\InstalledRepository;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Repository\RootPackageRepository;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Semver\Semver;
|
||||
use Composer\Spdx\SpdxLicenses;
|
||||
|
@ -52,8 +53,8 @@ class ShowCommand extends BaseCommand
|
|||
protected $versionParser;
|
||||
protected $colors;
|
||||
|
||||
/** @var Pool */
|
||||
private $pool;
|
||||
/** @var RepositorySet */
|
||||
private $repositorySet;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
|
@ -152,13 +153,14 @@ EOT
|
|||
|
||||
if ($input->getOption('self')) {
|
||||
$package = $this->getComposer()->getPackage();
|
||||
$repos = $installedRepo = new ArrayRepository(array($package));
|
||||
$repos = $installedRepo = new InstalledRepository(array(new RootPackageRepository($package)));
|
||||
} elseif ($input->getOption('platform')) {
|
||||
$repos = $installedRepo = $platformRepo;
|
||||
$repos = $installedRepo = new InstalledRepository(array($platformRepo));
|
||||
} elseif ($input->getOption('available')) {
|
||||
$installedRepo = $platformRepo;
|
||||
$installedRepo = new InstalledRepository(array($platformRepo));
|
||||
if ($composer) {
|
||||
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
|
||||
$installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
|
||||
} else {
|
||||
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
||||
$repos = new CompositeRepository($defaultRepos);
|
||||
|
@ -166,15 +168,15 @@ EOT
|
|||
}
|
||||
} elseif ($input->getOption('all') && $composer) {
|
||||
$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()));
|
||||
} elseif ($input->getOption('all')) {
|
||||
$defaultRepos = RepositoryFactory::defaultRepos($io);
|
||||
$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));
|
||||
} else {
|
||||
$repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
$repos = $installedRepo = new InstalledRepository(array($this->getComposer()->getRepositoryManager()->getLocalRepository()));
|
||||
$rootPkg = $this->getComposer()->getPackage();
|
||||
if (!$installedRepo->getPackages() && ($rootPkg->getRequires() || $rootPkg->getDevRequires())) {
|
||||
$io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
|
||||
|
@ -313,16 +315,13 @@ EOT
|
|||
foreach ($repos as $repo) {
|
||||
if ($repo === $platformRepo) {
|
||||
$type = 'platform';
|
||||
} elseif (
|
||||
$repo === $installedRepo
|
||||
|| ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
|
||||
) {
|
||||
} elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) {
|
||||
$type = 'installed';
|
||||
} else {
|
||||
$type = 'available';
|
||||
}
|
||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||
foreach ($repo->getProviderNames() as $name) {
|
||||
if ($repo instanceof ComposerRepository) {
|
||||
foreach ($repo->getPackageNames() as $name) {
|
||||
if (!$packageFilter || preg_match($packageFilter, $name)) {
|
||||
$packages[$type][$name] = $name;
|
||||
}
|
||||
|
@ -528,32 +527,27 @@ EOT
|
|||
/**
|
||||
* finds a package by name and version if provided
|
||||
*
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @param RepositoryInterface $repos
|
||||
* @param string $name
|
||||
* @param ConstraintInterface|string $version
|
||||
* @throws \InvalidArgumentException
|
||||
* @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);
|
||||
$constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;
|
||||
|
||||
$policy = new DefaultPolicy();
|
||||
$pool = new Pool('dev');
|
||||
$pool->addRepository($repos);
|
||||
$repositorySet = new RepositorySet('dev');
|
||||
$repositorySet->allowInstalledRepositories();
|
||||
$repositorySet->addRepository($repos);
|
||||
|
||||
$matchedPackage = null;
|
||||
$versions = array();
|
||||
$matches = $pool->whatProvides($name, $constraint);
|
||||
$matches = $repositorySet->findPackages($name, $constraint);
|
||||
foreach ($matches as $index => $package) {
|
||||
// skip providers/replacers
|
||||
if ($package->getName() !== $name) {
|
||||
unset($matches[$index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// select an exact match if it is in the installed repo and no specific version was required
|
||||
if (null === $version && $installedRepo->hasPackage($package)) {
|
||||
$matchedPackage = $package;
|
||||
|
@ -563,8 +557,10 @@ EOT
|
|||
$matches[$index] = $package->getId();
|
||||
}
|
||||
|
||||
$pool = $repositorySet->createPoolForPackage($name);
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
|
@ -576,10 +572,10 @@ EOT
|
|||
*
|
||||
* @param CompletePackageInterface $package
|
||||
* @param array $versions
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @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();
|
||||
|
||||
|
@ -604,10 +600,10 @@ EOT
|
|||
*
|
||||
* @param CompletePackageInterface $package
|
||||
* @param array $versions
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @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->write('<info>name</info> : ' . $package->getPrettyName());
|
||||
|
@ -676,19 +672,21 @@ EOT
|
|||
*
|
||||
* @param CompletePackageInterface $package
|
||||
* @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(array_reverse($versions));
|
||||
$versions = array_keys($versions);
|
||||
$versions = Semver::rsort($versions);
|
||||
|
||||
// highlight installed version
|
||||
if ($installedRepo->hasPackage($package)) {
|
||||
$installedVersion = $package->getPrettyVersion();
|
||||
$key = array_search($installedVersion, $versions);
|
||||
if (false !== $key) {
|
||||
$versions[$key] = '<info>* ' . $installedVersion . '</info>';
|
||||
if ($installedPackages = $installedRepo->findPackages($package->getName())) {
|
||||
foreach ($installedPackages as $installedPackage) {
|
||||
$installedVersion = $installedPackage->getPrettyVersion();
|
||||
$key = array_search($installedVersion, $versions);
|
||||
if (false !== $key) {
|
||||
$versions[$key] = '<info>* ' . $installedVersion . '</info>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -752,10 +750,10 @@ EOT
|
|||
*
|
||||
* @param CompletePackageInterface $package
|
||||
* @param array $versions
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @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(
|
||||
'name' => $package->getPrettyName(),
|
||||
|
@ -975,15 +973,15 @@ EOT
|
|||
/**
|
||||
* Generate the package tree
|
||||
*
|
||||
* @param PackageInterface $package
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param RepositoryInterface $distantRepos
|
||||
* @param PackageInterface $package
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @param RepositoryInterface $remoteRepos
|
||||
* @return array
|
||||
*/
|
||||
protected function generatePackageTree(
|
||||
PackageInterface $package,
|
||||
RepositoryInterface $installedRepo,
|
||||
RepositoryInterface $distantRepos
|
||||
InstalledRepository $installedRepo,
|
||||
RepositoryInterface $remoteRepos
|
||||
) {
|
||||
$requires = $package->getRequires();
|
||||
ksort($requires);
|
||||
|
@ -996,7 +994,7 @@ EOT
|
|||
'version' => $require->getPrettyConstraint(),
|
||||
);
|
||||
|
||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree);
|
||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree);
|
||||
|
||||
if ($deepChildren) {
|
||||
$treeChildDesc['requires'] = $deepChildren;
|
||||
|
@ -1020,10 +1018,10 @@ EOT
|
|||
/**
|
||||
* Display a package tree
|
||||
*
|
||||
* @param PackageInterface|string $package
|
||||
* @param array $packagesInTree
|
||||
* @param string $previousTreeBar
|
||||
* @param int $level
|
||||
* @param array|string $package
|
||||
* @param array $packagesInTree
|
||||
* @param string $previousTreeBar
|
||||
* @param int $level
|
||||
*/
|
||||
protected function displayTree(
|
||||
$package,
|
||||
|
@ -1032,7 +1030,7 @@ EOT
|
|||
$level = 1
|
||||
) {
|
||||
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
|
||||
if (isset($package['requires'])) {
|
||||
if (is_array($package) && isset($package['requires'])) {
|
||||
$requires = $package['requires'];
|
||||
$treeBar = $previousTreeBar . ' ├';
|
||||
$i = 0;
|
||||
|
@ -1075,22 +1073,22 @@ EOT
|
|||
*
|
||||
* @param string $name
|
||||
* @param PackageInterface|string $package
|
||||
* @param RepositoryInterface $installedRepo
|
||||
* @param RepositoryInterface $distantRepos
|
||||
* @param InstalledRepository $installedRepo
|
||||
* @param RepositoryInterface $remoteRepos
|
||||
* @param array $packagesInTree
|
||||
* @return array
|
||||
*/
|
||||
protected function addTree(
|
||||
$name,
|
||||
$package,
|
||||
RepositoryInterface $installedRepo,
|
||||
RepositoryInterface $distantRepos,
|
||||
InstalledRepository $installedRepo,
|
||||
RepositoryInterface $remoteRepos,
|
||||
array $packagesInTree
|
||||
) {
|
||||
$children = array();
|
||||
list($package, $versions) = $this->getPackage(
|
||||
$installedRepo,
|
||||
$distantRepos,
|
||||
$remoteRepos,
|
||||
$name,
|
||||
$package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->getPrettyConstraint()
|
||||
);
|
||||
|
@ -1107,7 +1105,7 @@ EOT
|
|||
|
||||
if (!in_array($requireName, $currentTree, true)) {
|
||||
$currentTree[] = $requireName;
|
||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree);
|
||||
$deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree);
|
||||
if ($deepChildren) {
|
||||
$treeChildDesc['requires'] = $deepChildren;
|
||||
}
|
||||
|
@ -1165,13 +1163,13 @@ EOT
|
|||
* @param string $phpVersion
|
||||
* @param bool $minorOnly
|
||||
*
|
||||
* @return PackageInterface|null
|
||||
* @return PackageInterface|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();
|
||||
$versionSelector = new VersionSelector($this->getPool($composer));
|
||||
$versionSelector = new VersionSelector($this->getRepositorySet($composer));
|
||||
$stability = $composer->getPackage()->getMinimumStability();
|
||||
$flags = $composer->getPackage()->getStabilityFlags();
|
||||
if (isset($flags[$name])) {
|
||||
|
@ -1195,13 +1193,13 @@ EOT
|
|||
return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability);
|
||||
}
|
||||
|
||||
private function getPool(Composer $composer)
|
||||
private function getRepositorySet(Composer $composer)
|
||||
{
|
||||
if (!$this->pool) {
|
||||
$this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
|
||||
$this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
|
||||
if (!$this->repositorySet) {
|
||||
$this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
|
||||
$this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
|
||||
}
|
||||
|
||||
return $this->pool;
|
||||
return $this->repositorySet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ EOT
|
|||
|
||||
// list packages
|
||||
foreach ($installedRepo->getCanonicalPackages() as $package) {
|
||||
$downloader = $dm->getDownloaderForInstalledPackage($package);
|
||||
$downloader = $dm->getDownloaderForPackage($package);
|
||||
$targetDir = $im->getInstallPath($package);
|
||||
|
||||
if ($downloader instanceof ChangeReportInterface) {
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
namespace Composer\Command;
|
||||
|
||||
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\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -26,8 +29,10 @@ class SuggestsCommand extends BaseCommand
|
|||
->setName('suggests')
|
||||
->setDescription('Shows package suggestions.')
|
||||
->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('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 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.
|
||||
|
||||
Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.
|
||||
|
||||
Read more at https://getcomposer.org/doc/03-cli.md#suggests
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$lock = $this->getComposer()->getLocker()->getLockData();
|
||||
$composer = $this->getComposer();
|
||||
|
||||
if (empty($lock)) {
|
||||
throw new \RuntimeException('Lockfile seems to be empty?');
|
||||
$installedRepos = array(
|
||||
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'];
|
||||
|
||||
if (!$input->getOption('no-dev')) {
|
||||
$packages += $lock['packages-dev'];
|
||||
}
|
||||
$installedRepo = new CompositeRepository($installedRepos);
|
||||
$reporter = new SuggestedPackagesReporter($this->getIO());
|
||||
|
||||
$filter = $input->getArgument('packages');
|
||||
|
||||
// First assemble lookup list of packages that are installed, replaced or provided
|
||||
$installed = array();
|
||||
foreach ($packages as $package) {
|
||||
$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']));
|
||||
}
|
||||
if (empty($filter) && !$input->getOption('all')) {
|
||||
$filter = array_map(function ($link) {
|
||||
return $link->getTarget();
|
||||
}, array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()));
|
||||
}
|
||||
|
||||
// Undub and sort the install list into a sorted lookup array
|
||||
$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'])) {
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
if (!empty($filter) && !in_array($package->getName(), $filter)) {
|
||||
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
|
||||
$mode = 0;
|
||||
$reporter->addSuggestionsFromPackage($package);
|
||||
}
|
||||
|
||||
// Determine output mode, default is by-package
|
||||
$mode = SuggestedPackagesReporter::MODE_BY_PACKAGE;
|
||||
$io = $this->getIO();
|
||||
if ($input->getOption('by-package') || $io->isVerbose()) {
|
||||
$mode |= 1;
|
||||
}
|
||||
// if by-suggestion is given we override the default
|
||||
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
|
||||
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('');
|
||||
}
|
||||
}
|
||||
$reporter->output($mode, $installedRepo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Command;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Installer;
|
||||
use Composer\IO\IOInterface;
|
||||
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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
|
||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
|
||||
new InputOption('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, 'Add also all dependencies of whitelisted packages to the whitelist, including those defined in root package.'),
|
||||
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-all-dependencies', null, InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements.'),
|
||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
||||
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
|
||||
|
@ -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);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
@ -135,6 +146,13 @@ EOT
|
|||
$authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
|
||||
$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
|
||||
->setDryRun($input->getOption('dry-run'))
|
||||
->setVerbose($input->getOption('verbose'))
|
||||
|
@ -143,14 +161,13 @@ EOT
|
|||
->setDevMode(!$input->getOption('no-dev'))
|
||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setSkipSuggest($input->getOption('no-suggest'))
|
||||
->setOptimizeAutoloader($optimize)
|
||||
->setClassMapAuthoritative($authoritative)
|
||||
->setApcuAutoloader($apcu)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages)
|
||||
->setWhitelistTransitiveDependencies($input->getOption('with-dependencies'))
|
||||
->setWhitelistAllDependencies($input->getOption('with-all-dependencies'))
|
||||
->setUpdateMirrors($updateMirrors)
|
||||
->setUpdateAllowList($packages)
|
||||
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
->setPreferStable($input->getOption('prefer-stable'))
|
||||
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||
|
|
|
@ -124,6 +124,7 @@ class Compiler
|
|||
->in(__DIR__.'/../../vendor/composer/ca-bundle/')
|
||||
->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
|
||||
->in(__DIR__.'/../../vendor/psr/')
|
||||
->in(__DIR__.'/../../vendor/react/')
|
||||
->sort($finderSort)
|
||||
;
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class Composer
|
|||
const VERSION = '@package_version@';
|
||||
const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
|
||||
const RELEASE_DATE = '@release_date@';
|
||||
const SOURCE_VERSION = '1.10-dev+source';
|
||||
const SOURCE_VERSION = '2.0-dev+source';
|
||||
|
||||
public static function getVersion()
|
||||
{
|
||||
|
|
|
@ -265,7 +265,7 @@ class JsonConfigSource implements ConfigSourceInterface
|
|||
*
|
||||
* @param array $array
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
* @return int
|
||||
*/
|
||||
private function arrayUnshiftRef(&$array, &$value)
|
||||
{
|
||||
|
|
|
@ -230,6 +230,12 @@ class Application extends BaseApplication
|
|||
if (function_exists('posix_getuid') && posix_getuid() === 0) {
|
||||
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>');
|
||||
|
||||
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')) {
|
||||
// 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;
|
||||
} catch (ScriptExecutionException $e) {
|
||||
return $e->getCode();
|
||||
return (int) $e->getCode();
|
||||
} catch (\Exception $e) {
|
||||
$this->hintCommonErrors($e);
|
||||
restore_error_handler();
|
||||
|
|
|
@ -44,54 +44,33 @@ class DefaultPolicy implements PolicyInterface
|
|||
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) {
|
||||
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) {
|
||||
foreach ($packages as &$nameLiterals) {
|
||||
$policy = $this;
|
||||
usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
|
||||
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
|
||||
usort($nameLiterals, function ($a, $b) use ($policy, $pool, $requiredPackage) {
|
||||
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
|
||||
});
|
||||
}
|
||||
|
||||
foreach ($packages as &$literals) {
|
||||
$literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
|
||||
|
||||
$literals = $this->pruneToBestVersion($pool, $literals);
|
||||
|
||||
$literals = $this->pruneRemoteAliases($pool, $literals);
|
||||
foreach ($packages as &$sortedLiterals) {
|
||||
$sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals);
|
||||
$sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
|
||||
}
|
||||
|
||||
$selected = call_user_func_array('array_merge', $packages);
|
||||
|
||||
// now sort the result across all packages to respect replaces across packages
|
||||
usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
|
||||
return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
|
||||
usort($selected, function ($a, $b) use ($policy, $pool, $requiredPackage) {
|
||||
return $policy->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
|
||||
});
|
||||
|
||||
return $selected;
|
||||
}
|
||||
|
||||
protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
|
||||
protected function groupLiteralsByName(Pool $pool, $literals)
|
||||
{
|
||||
$packages = array();
|
||||
foreach ($literals as $literal) {
|
||||
|
@ -100,12 +79,7 @@ class DefaultPolicy implements PolicyInterface
|
|||
if (!isset($packages[$packageName])) {
|
||||
$packages[$packageName] = array();
|
||||
}
|
||||
|
||||
if (isset($installedMap[abs($literal)])) {
|
||||
array_unshift($packages[$packageName], $literal);
|
||||
} else {
|
||||
$packages[$packageName][] = $literal;
|
||||
}
|
||||
$packages[$packageName][] = $literal;
|
||||
}
|
||||
|
||||
return $packages;
|
||||
|
@ -114,61 +88,49 @@ class DefaultPolicy implements PolicyInterface
|
|||
/**
|
||||
* @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
|
||||
if ($a->getName() === $b->getName()) {
|
||||
$aAliased = $a instanceof AliasPackage;
|
||||
$bAliased = $b instanceof AliasPackage;
|
||||
if ($aAliased && !$bAliased) {
|
||||
return -1; // use a
|
||||
}
|
||||
if (!$aAliased && $bAliased) {
|
||||
return 1; // use b
|
||||
}
|
||||
// prefer aliases to the original package
|
||||
if ($a->getName() === $b->getName()) {
|
||||
$aAliased = $a instanceof AliasPackage;
|
||||
$bAliased = $b instanceof AliasPackage;
|
||||
if ($aAliased && !$bAliased) {
|
||||
return -1; // use a
|
||||
}
|
||||
|
||||
if (!$ignoreReplace) {
|
||||
// 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 (!$aAliased && $bAliased) {
|
||||
return 1; // use b
|
||||
}
|
||||
|
||||
// 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])) {
|
||||
return -1;
|
||||
if (!$ignoreReplace) {
|
||||
// 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])) {
|
||||
return 1;
|
||||
// priority equal, sort by package id to make reproducible
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
|
|
@ -23,14 +23,13 @@ class GenericRule extends Rule
|
|||
protected $literals;
|
||||
|
||||
/**
|
||||
* @param array $literals
|
||||
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||
* @param Link|PackageInterface $reasonData
|
||||
* @param array $job The job this rule was created from
|
||||
* @param array $literals
|
||||
* @param int|null $reason A RULE_* constant describing the reason for generating this rule
|
||||
* @param Link|PackageInterface|int|null $reasonData
|
||||
*/
|
||||
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($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
|
||||
*/
|
||||
public function getJobType()
|
||||
public function getOperationType()
|
||||
{
|
||||
return 'install';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function show($lock)
|
||||
{
|
||||
return ($lock ? 'Locking ' : 'Installing ').$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function getJobType()
|
||||
public function getOperationType()
|
||||
{
|
||||
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}
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function getJobType()
|
||||
public function getOperationType()
|
||||
{
|
||||
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}
|
||||
*/
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Returns job type.
|
||||
* Returns operation type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJobType();
|
||||
public function getOperationType();
|
||||
|
||||
/**
|
||||
* Returns operation reason.
|
||||
|
@ -33,6 +33,14 @@ interface OperationInterface
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -43,8 +43,9 @@ abstract class SolverOperation implements OperationInterface
|
|||
return $this->reason;
|
||||
}
|
||||
|
||||
protected function formatVersion(PackageInterface $package)
|
||||
{
|
||||
return $package->getFullPrettyVersion();
|
||||
}
|
||||
/**
|
||||
* @param $lock bool Whether this is an operation on the lock file
|
||||
* @return string
|
||||
*/
|
||||
abstract public function show($lock);
|
||||
}
|
||||
|
|
|
@ -47,20 +47,28 @@ class UninstallOperation extends SolverOperation
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns job type.
|
||||
* Returns operation type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJobType()
|
||||
public function getOperationType()
|
||||
{
|
||||
return 'uninstall';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function show($lock)
|
||||
{
|
||||
return 'Removing '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function getJobType()
|
||||
public function getOperationType()
|
||||
{
|
||||
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}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$actionName = VersionParser::isUpgrade($this->initialPackage->getVersion(), $this->targetPackage->getVersion()) ? 'Updating' : 'Downgrading';
|
||||
|
||||
return $actionName.' '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '.
|
||||
$this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')';
|
||||
return $this->show(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,5 @@ use Composer\Package\PackageInterface;
|
|||
interface PolicyInterface
|
||||
{
|
||||
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
|
||||
|
||||
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);
|
||||
|
||||
public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null);
|
||||
public function selectPreferredPackages(Pool $pool, array $literals, $requiredPackage = null);
|
||||
}
|
||||
|
|
|
@ -12,143 +12,56 @@
|
|||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Constraint\EmptyConstraint;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\ComposerRepository;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* A package pool contains repositories that provide packages.
|
||||
* A package pool contains all packages for dependency resolution
|
||||
*
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class Pool implements \Countable
|
||||
{
|
||||
const MATCH_NAME = -1;
|
||||
const MATCH_NONE = 0;
|
||||
const MATCH = 1;
|
||||
const MATCH_PROVIDE = 2;
|
||||
const MATCH_REPLACE = 3;
|
||||
const MATCH_FILTERED = 4;
|
||||
|
||||
protected $repositories = array();
|
||||
protected $providerRepos = array();
|
||||
protected $packages = array();
|
||||
protected $packageByName = array();
|
||||
protected $packageByExactName = array();
|
||||
protected $acceptableStabilities;
|
||||
protected $stabilityFlags;
|
||||
protected $versionParser;
|
||||
protected $providerCache = array();
|
||||
protected $filterRequires;
|
||||
protected $whitelist = null;
|
||||
protected $id = 1;
|
||||
protected $unacceptableFixedPackages;
|
||||
|
||||
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->acceptableStabilities = array();
|
||||
foreach (BasePackage::$stabilities as $stability => $value) {
|
||||
if ($value <= BasePackage::$stabilities[$minimumStability]) {
|
||||
$this->acceptableStabilities[$stability] = $value;
|
||||
}
|
||||
}
|
||||
$this->stabilityFlags = $stabilityFlags;
|
||||
$this->filterRequires = $filterRequires;
|
||||
foreach ($filterRequires as $name => $constraint) {
|
||||
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
|
||||
unset($this->filterRequires[$name]);
|
||||
}
|
||||
}
|
||||
$this->setPackages($packages);
|
||||
$this->unacceptableFixedPackages = $unacceptableFixedPackages;
|
||||
}
|
||||
|
||||
public function setWhitelist($whitelist)
|
||||
private function setPackages(array $packages)
|
||||
{
|
||||
$this->whitelist = $whitelist;
|
||||
$this->providerCache = array();
|
||||
}
|
||||
$id = 1;
|
||||
|
||||
/**
|
||||
* Adds a repository and its packages to this package pool
|
||||
*
|
||||
* @param RepositoryInterface $repo A package repository
|
||||
* @param array $rootAliases
|
||||
*/
|
||||
public function addRepository(RepositoryInterface $repo, $rootAliases = array())
|
||||
{
|
||||
if ($repo instanceof CompositeRepository) {
|
||||
$repos = $repo->getRepositories();
|
||||
} else {
|
||||
$repos = array($repo);
|
||||
}
|
||||
foreach ($packages as $package) {
|
||||
$this->packages[] = $package;
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$this->repositories[] = $repo;
|
||||
$package->id = $id++;
|
||||
$this->packageByExactName[$package->getName()][$package->id] = $package;
|
||||
|
||||
$exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
|
||||
|
||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||
$this->providerRepos[] = $repo;
|
||||
$repo->setRootAliases($rootAliases);
|
||||
$repo->resetPackageIds();
|
||||
} else {
|
||||
foreach ($repo->getPackages() as $package) {
|
||||
$names = $package->getNames();
|
||||
$stability = $package->getStability();
|
||||
if ($exempt || $this->isPackageAcceptable($names, $stability)) {
|
||||
$package->setId($this->id++);
|
||||
$this->packages[] = $package;
|
||||
$this->packageByExactName[$package->getName()][$package->id] = $package;
|
||||
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][] = $package;
|
||||
}
|
||||
|
||||
// handle root package aliases
|
||||
$name = $package->getName();
|
||||
if (isset($rootAliases[$name][$package->getVersion()])) {
|
||||
$alias = $rootAliases[$name][$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$package = $package->getAliasOf();
|
||||
}
|
||||
$aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
|
||||
$aliasPackage->setRootPackageAlias(true);
|
||||
$aliasPackage->setId($this->id++);
|
||||
|
||||
$package->getRepository()->addPackage($aliasPackage);
|
||||
$this->packages[] = $aliasPackage;
|
||||
$this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
|
||||
|
||||
foreach ($aliasPackage->getNames() as $name) {
|
||||
$this->packageByName[$name][] = $aliasPackage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($package->getNames() as $provided) {
|
||||
$this->packageByName[$provided][] = $package;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -176,104 +89,52 @@ class Pool implements \Countable
|
|||
* packages must match or null to return all
|
||||
* @param bool $mustMatchName Whether the name of returned packages
|
||||
* must match the given name
|
||||
* @param bool $bypassFilters If enabled, filterRequires and stability matching is ignored
|
||||
* @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;
|
||||
if (isset($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
|
||||
*/
|
||||
private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false)
|
||||
private function computeWhatProvides($name, $constraint, $mustMatchName = false)
|
||||
{
|
||||
$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) {
|
||||
$candidates = array_filter($candidates, function ($candidate) use ($name) {
|
||||
return $candidate->getName() == $name;
|
||||
});
|
||||
if (isset($this->packageByExactName[$name])) {
|
||||
$candidates = array_merge($candidates, $this->packageByExactName[$name]);
|
||||
$candidates = $this->packageByExactName[$name];
|
||||
}
|
||||
} elseif (isset($this->packageByName[$name])) {
|
||||
$candidates = array_merge($candidates, $this->packageByName[$name]);
|
||||
$candidates = $this->packageByName[$name];
|
||||
}
|
||||
|
||||
$matches = $provideMatches = array();
|
||||
$nameMatch = false;
|
||||
$matches = array();
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
$aliasOfCandidate = null;
|
||||
|
||||
// alias packages are not white listed, make sure that the package
|
||||
// being aliased is white listed
|
||||
if ($candidate instanceof AliasPackage) {
|
||||
$aliasOfCandidate = $candidate->getAliasOf();
|
||||
}
|
||||
|
||||
if ($this->whitelist !== null && !$bypassFilters && (
|
||||
(!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
|
||||
($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
switch ($this->match($candidate, $name, $constraint, $bypassFilters)) {
|
||||
switch ($this->match($candidate, $name, $constraint)) {
|
||||
case self::MATCH_NONE:
|
||||
break;
|
||||
|
||||
case self::MATCH_NAME:
|
||||
$nameMatch = true;
|
||||
break;
|
||||
|
||||
case self::MATCH:
|
||||
$nameMatch = true;
|
||||
$matches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_PROVIDE:
|
||||
$provideMatches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_REPLACE:
|
||||
$matches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_FILTERED:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \UnexpectedValueException('Unexpected match type');
|
||||
}
|
||||
}
|
||||
|
||||
// if a package with the required name exists, we ignore providers
|
||||
if ($nameMatch) {
|
||||
return $matches;
|
||||
}
|
||||
|
||||
return array_merge($matches, $provideMatches);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function literalToPackage($literal)
|
||||
|
@ -296,23 +157,6 @@ class Pool implements \Countable
|
|||
return $prefix.' '.$package->getPrettyString();
|
||||
}
|
||||
|
||||
public function isPackageAcceptable($name, $stability)
|
||||
{
|
||||
foreach ((array) $name as $n) {
|
||||
// allow if package matches the global stability requirement and has no exception
|
||||
if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// allow if package matches the package-specific stability flag
|
||||
if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the package matches the given constraint directly or through
|
||||
* provided or replaced packages
|
||||
|
@ -322,27 +166,19 @@ class Pool implements \Countable
|
|||
* @param ConstraintInterface $constraint The constraint to verify
|
||||
* @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();
|
||||
$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) {
|
||||
$pkgConstraint = new Constraint('==', $candidateVersion);
|
||||
|
||||
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();
|
||||
|
@ -352,13 +188,13 @@ class Pool implements \Countable
|
|||
if (isset($replaces[0]) || isset($provides[0])) {
|
||||
foreach ($provides as $link) {
|
||||
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) {
|
||||
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()))) {
|
||||
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()))) {
|
||||
return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
|
||||
return self::MATCH_REPLACE;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
/**
|
||||
* Represents a problem detected while solving dependencies
|
||||
|
@ -28,20 +30,13 @@ class Problem
|
|||
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
|
||||
*/
|
||||
protected $reasons = array();
|
||||
|
||||
protected $section = 0;
|
||||
|
||||
protected $pool;
|
||||
|
||||
public function __construct(Pool $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a rule as a reason
|
||||
*
|
||||
|
@ -49,10 +44,7 @@ class Problem
|
|||
*/
|
||||
public function addRule(Rule $rule)
|
||||
{
|
||||
$this->addReason(spl_object_hash($rule), array(
|
||||
'rule' => $rule,
|
||||
'job' => $rule->getJob(),
|
||||
));
|
||||
$this->addReason(spl_object_hash($rule), $rule);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,124 +60,67 @@ class Problem
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
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));
|
||||
|
||||
if (count($reasons) === 1) {
|
||||
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'];
|
||||
$constraint = $job['constraint'];
|
||||
$reasonData = $rule->getReasonData();
|
||||
$packageName = $reasonData['packageName'];
|
||||
$constraint = $reasonData['constraint'];
|
||||
|
||||
if (isset($constraint)) {
|
||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
||||
$packages = $pool->whatProvides($packageName, $constraint);
|
||||
} else {
|
||||
$packages = array();
|
||||
}
|
||||
|
||||
if ($job && $job['cmd'] === 'install' && empty($packages)) {
|
||||
|
||||
// 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.';
|
||||
if (empty($packages)) {
|
||||
return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $packageName, $constraint));
|
||||
}
|
||||
}
|
||||
|
||||
$messages = array();
|
||||
|
||||
foreach ($reasons as $reason) {
|
||||
$rule = $reason['rule'];
|
||||
$job = $reason['job'];
|
||||
|
||||
if ($job) {
|
||||
$messages[] = $this->jobToText($job);
|
||||
} elseif ($rule) {
|
||||
if ($rule instanceof Rule) {
|
||||
$messages[] = $rule->getPrettyString($this->pool, $installedMap);
|
||||
}
|
||||
}
|
||||
foreach ($reasons as $rule) {
|
||||
$messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @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])) {
|
||||
$this->reasonSeen[$id] = true;
|
||||
$this->reasons[$this->section][] = $reason;
|
||||
|
@ -198,39 +133,150 @@ class Problem
|
|||
}
|
||||
|
||||
/**
|
||||
* Turns a job into a human readable description
|
||||
*
|
||||
* @param array $job
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
protected function jobToText($job)
|
||||
public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, $packageName, $constraint = null)
|
||||
{
|
||||
$packageName = $job['packageName'];
|
||||
$constraint = $job['constraint'];
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
||||
if (!$packages) {
|
||||
return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
|
||||
// handle php/hhvm
|
||||
if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
|
||||
$version = phpversion();
|
||||
$available = $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 .= '; ' . 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).'.';
|
||||
case 'update':
|
||||
return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
|
||||
case 'remove':
|
||||
return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
|
||||
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.');
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
if (isset($constraint)) {
|
||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
||||
} else {
|
||||
$packages = array();
|
||||
if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
|
||||
$illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
|
||||
|
||||
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();
|
||||
foreach ($packages as $package) {
|
||||
|
@ -238,19 +284,37 @@ class Problem
|
|||
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
|
||||
}
|
||||
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']).']';
|
||||
}
|
||||
|
||||
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
|
||||
* @return string
|
||||
*/
|
||||
protected function constraintToText($constraint)
|
||||
protected static function constraintToText($constraint)
|
||||
{
|
||||
return $constraint ? ' '.$constraint->getPrettyString() : '';
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootAliasPackage;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
|
||||
/**
|
||||
|
@ -19,60 +23,129 @@ use Composer\Semver\Constraint\ConstraintInterface;
|
|||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public function update($packageName, ConstraintInterface $constraint = null)
|
||||
{
|
||||
$this->addJob($packageName, 'update', $constraint);
|
||||
}
|
||||
|
||||
public function remove($packageName, ConstraintInterface $constraint = null)
|
||||
{
|
||||
$this->addJob($packageName, 'remove', $constraint);
|
||||
$packageName = strtolower($packageName);
|
||||
$this->requires[$packageName] = $constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an existing package as being installed and having to remain installed
|
||||
*
|
||||
* These jobs will not be tempered with by the solver
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param ConstraintInterface|null $constraint
|
||||
* @param bool $lockable if set to false, the package will not be written to the lock file
|
||||
*/
|
||||
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);
|
||||
|
||||
$this->jobs[] = array(
|
||||
'cmd' => $cmd,
|
||||
'packageName' => $packageName,
|
||||
'constraint' => $constraint,
|
||||
'fixed' => $fixed,
|
||||
);
|
||||
unset($this->fixedPackages[spl_object_hash($package)]);
|
||||
unset($this->unlockables[spl_object_hash($package)]);
|
||||
}
|
||||
|
||||
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\Link;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\RepositorySet;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -24,8 +25,8 @@ abstract class Rule
|
|||
{
|
||||
// reason constants
|
||||
const RULE_INTERNAL_ALLOW_UPDATE = 1;
|
||||
const RULE_JOB_INSTALL = 2;
|
||||
const RULE_JOB_REMOVE = 3;
|
||||
const RULE_ROOT_REQUIRE = 2;
|
||||
const RULE_FIXED = 3;
|
||||
const RULE_PACKAGE_CONFLICT = 6;
|
||||
const RULE_PACKAGE_REQUIRES = 7;
|
||||
const RULE_PACKAGE_OBSOLETES = 8;
|
||||
|
@ -41,22 +42,17 @@ abstract class Rule
|
|||
const BITFIELD_DISABLED = 16;
|
||||
|
||||
protected $bitfield;
|
||||
protected $job;
|
||||
protected $request;
|
||||
protected $reasonData;
|
||||
|
||||
/**
|
||||
* @param int $reason A RULE_* constant describing the reason for generating this rule
|
||||
* @param Link|PackageInterface $reasonData
|
||||
* @param array $job The job this rule was created from
|
||||
*/
|
||||
public function __construct($reason, $reasonData, $job = null)
|
||||
public function __construct($reason, $reasonData)
|
||||
{
|
||||
$this->reasonData = $reasonData;
|
||||
|
||||
if ($job) {
|
||||
$this->job = $job;
|
||||
}
|
||||
|
||||
$this->bitfield = (0 << self::BITFIELD_DISABLED) |
|
||||
($reason << self::BITFIELD_REASON) |
|
||||
(255 << self::BITFIELD_TYPE);
|
||||
|
@ -66,11 +62,6 @@ abstract class Rule
|
|||
|
||||
abstract public function getHash();
|
||||
|
||||
public function getJob()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
abstract public function equals(Rule $rule);
|
||||
|
||||
public function getReason()
|
||||
|
@ -85,11 +76,17 @@ abstract class Rule
|
|||
|
||||
public function getRequiredPackage()
|
||||
{
|
||||
if ($this->getReason() === self::RULE_JOB_INSTALL) {
|
||||
return $this->reasonData;
|
||||
$reason = $this->getReason();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +123,12 @@ abstract class Rule
|
|||
|
||||
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();
|
||||
|
||||
|
@ -142,17 +144,30 @@ abstract class Rule
|
|||
case self::RULE_INTERNAL_ALLOW_UPDATE:
|
||||
return $ruleText;
|
||||
|
||||
case self::RULE_JOB_INSTALL:
|
||||
return "Install command rule ($ruleText)";
|
||||
case self::RULE_ROOT_REQUIRE:
|
||||
$packageName = $this->reasonData['packageName'];
|
||||
$constraint = $this->reasonData['constraint'];
|
||||
|
||||
case self::RULE_JOB_REMOVE:
|
||||
return "Remove command rule ($ruleText)";
|
||||
$packages = $pool->whatProvides($packageName, $constraint);
|
||||
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:
|
||||
$package1 = $pool->literalToPackage($literals[0]);
|
||||
$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:
|
||||
$sourceLiteral = array_shift($literals);
|
||||
|
@ -169,73 +184,103 @@ abstract class Rule
|
|||
} else {
|
||||
$targetName = $this->reasonData->getTarget();
|
||||
|
||||
if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') {
|
||||
// handle php/hhvm
|
||||
if (defined('HHVM_VERSION')) {
|
||||
return $text . ' -> your HHVM version does not satisfy that requirement.';
|
||||
}
|
||||
$reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $targetName, $this->reasonData->getConstraint());
|
||||
|
||||
$packages = $pool->whatProvides($targetName);
|
||||
$package = count($packages) ? current($packages) : phpversion();
|
||||
|
||||
if ($targetName === 'hhvm') {
|
||||
if ($package instanceof CompletePackage) {
|
||||
return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
|
||||
} else {
|
||||
return $text . ' -> you are running this with PHP and not HHVM.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!($package instanceof CompletePackage)) {
|
||||
return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
|
||||
}
|
||||
|
||||
$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 . ' -> ' . $reason[1];
|
||||
}
|
||||
|
||||
return $text;
|
||||
|
||||
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;
|
||||
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
|
||||
return $ruleText;
|
||||
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:
|
||||
return $ruleText;
|
||||
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:
|
||||
return $ruleText;
|
||||
default:
|
||||
|
@ -252,17 +297,22 @@ abstract class Rule
|
|||
protected function formatPackagesUnique($pool, array $packages)
|
||||
{
|
||||
$prepared = array();
|
||||
foreach ($packages as $package) {
|
||||
foreach ($packages as $index => $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 $reason A RULE_* constant describing the reason for generating this rule
|
||||
* @param Link|PackageInterface $reasonData
|
||||
* @param array $job The job this rule was created from
|
||||
*/
|
||||
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) {
|
||||
$this->literal1 = $literal1;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Repository\RepositorySet;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
*/
|
||||
|
@ -19,7 +21,7 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
{
|
||||
// highest priority => lowest number
|
||||
const TYPE_PACKAGE = 0;
|
||||
const TYPE_JOB = 1;
|
||||
const TYPE_REQUEST = 1;
|
||||
const TYPE_LEARNED = 4;
|
||||
|
||||
/**
|
||||
|
@ -32,7 +34,7 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
protected static $types = array(
|
||||
255 => 'UNKNOWN',
|
||||
self::TYPE_PACKAGE => 'PACKAGE',
|
||||
self::TYPE_JOB => 'JOB',
|
||||
self::TYPE_REQUEST => 'REQUEST',
|
||||
self::TYPE_LEARNED => 'LEARNED',
|
||||
);
|
||||
|
||||
|
@ -155,13 +157,13 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
return array_keys($types);
|
||||
}
|
||||
|
||||
public function getPrettyString(Pool $pool = null)
|
||||
public function getPrettyString(RepositorySet $repositorySet = null, Request $request = null, Pool $pool = null)
|
||||
{
|
||||
$string = "\n";
|
||||
foreach ($this->rules as $type => $rules) {
|
||||
$string .= str_pad(self::$types[$type], 8, ' ') . ": ";
|
||||
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";
|
||||
}
|
||||
|
@ -171,6 +173,6 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getPrettyString(null);
|
||||
return $this->getPrettyString(null, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -24,13 +26,11 @@ class RuleSetGenerator
|
|||
protected $policy;
|
||||
protected $pool;
|
||||
protected $rules;
|
||||
protected $jobs;
|
||||
protected $installedMap;
|
||||
protected $whitelistedMap;
|
||||
protected $addedMap;
|
||||
protected $conflictAddedMap;
|
||||
protected $addedPackages;
|
||||
protected $addedPackagesByNames;
|
||||
protected $conflictsForName;
|
||||
|
||||
public function __construct(PolicyInterface $policy, Pool $pool)
|
||||
{
|
||||
|
@ -76,33 +76,17 @@ class RuleSetGenerator
|
|||
* @param array $packages The set of packages to choose from
|
||||
* @param int $reason A RULE_* constant describing the reason for
|
||||
* 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
|
||||
*/
|
||||
protected function createInstallOneOfRule(array $packages, $reason, $job)
|
||||
protected function createInstallOneOfRule(array $packages, $reason, $reasonData)
|
||||
{
|
||||
$literals = array();
|
||||
foreach ($packages as $package) {
|
||||
$literals[] = $package->id;
|
||||
}
|
||||
|
||||
return new GenericRule($literals, $reason, $job['packageName'], $job);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return new GenericRule($literals, $reason, $reasonData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,6 +113,20 @@ class RuleSetGenerator
|
|||
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
|
||||
*
|
||||
|
@ -147,41 +145,6 @@ class RuleSetGenerator
|
|||
$this->rules->add($newRule, $type);
|
||||
}
|
||||
|
||||
protected function whitelistFromPackage(PackageInterface $package)
|
||||
{
|
||||
$workQueue = new \SplQueue;
|
||||
$workQueue->enqueue($package);
|
||||
|
||||
while (!$workQueue->isEmpty()) {
|
||||
$package = $workQueue->dequeue();
|
||||
if (isset($this->whitelistedMap[$package->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->whitelistedMap[$package->id] = true;
|
||||
|
||||
foreach ($package->getRequires() as $link) {
|
||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);
|
||||
|
||||
foreach ($possibleRequires as $require) {
|
||||
$workQueue->enqueue($require);
|
||||
}
|
||||
}
|
||||
|
||||
$obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);
|
||||
|
||||
foreach ($obsoleteProviders as $provider) {
|
||||
if ($provider === $package) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
||||
$workQueue->enqueue($provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs)
|
||||
{
|
||||
$workQueue = new \SplQueue;
|
||||
|
@ -225,9 +188,16 @@ class RuleSetGenerator
|
|||
|
||||
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
|
||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
|
||||
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
|
||||
$reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
|
||||
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
|
||||
} else {
|
||||
if (!isset($this->conflictsForName[$packageName])) {
|
||||
$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 */
|
||||
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) {
|
||||
$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
|
||||
$isInstalled = isset($this->installedMap[$package->id]);
|
||||
|
||||
foreach ($package->getReplaces() as $link) {
|
||||
if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
|
||||
continue;
|
||||
|
@ -272,12 +240,19 @@ class RuleSetGenerator
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -294,77 +269,61 @@ class RuleSetGenerator
|
|||
return $impossible;
|
||||
}
|
||||
|
||||
protected function whitelistFromJobs()
|
||||
protected function addRulesForRequest(Request $request, $ignorePlatformReqs)
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
|
||||
foreach ($packages as $package) {
|
||||
$this->whitelistFromPackage($package);
|
||||
}
|
||||
break;
|
||||
$unlockableMap = $request->getUnlockableMap();
|
||||
|
||||
foreach ($request->getFixedPackages() as $package) {
|
||||
if ($package->id == -1) {
|
||||
// fixed package was not added to the pool as it did not pass the stability requirements, this is fine
|
||||
if ($this->pool->isUnacceptableFixedPackage($package)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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->installedMap = $installedMap;
|
||||
|
||||
$this->whitelistedMap = array();
|
||||
foreach ($this->installedMap as $package) {
|
||||
$this->whitelistFromPackage($package);
|
||||
}
|
||||
$this->whitelistFromJobs();
|
||||
|
||||
$this->pool->setWhitelist($this->whitelistedMap);
|
||||
|
||||
$this->addedMap = array();
|
||||
$this->conflictAddedMap = array();
|
||||
$this->addedPackages = array();
|
||||
$this->addedPackagesByNames = array();
|
||||
foreach ($this->installedMap as $package) {
|
||||
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
||||
}
|
||||
$this->conflictsForName = array();
|
||||
|
||||
$this->addRulesForJobs($ignorePlatformReqs);
|
||||
$this->addRulesForRequest($request, $ignorePlatformReqs);
|
||||
|
||||
$this->addConflictRules($ignorePlatformReqs);
|
||||
|
||||
|
|
|
@ -44,13 +44,24 @@ class RuleWatchGraph
|
|||
return;
|
||||
}
|
||||
|
||||
foreach (array($node->watch1, $node->watch2) as $literal) {
|
||||
if (!isset($this->watchChains[$literal])) {
|
||||
$this->watchChains[$literal] = new RuleWatchChain;
|
||||
}
|
||||
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||
foreach (array($node->watch1, $node->watch2) as $literal) {
|
||||
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();
|
||||
while ($chain->valid()) {
|
||||
$node = $chain->current();
|
||||
$otherWatch = $node->getOtherWatch($literal);
|
||||
if (!$node->getRule() instanceof MultiConflictRule) {
|
||||
$otherWatch = $node->getOtherWatch($literal);
|
||||
|
||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||
$ruleLiterals = $node->getRule()->getLiterals();
|
||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||
$ruleLiterals = $node->getRule()->getLiterals();
|
||||
|
||||
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
||||
return $literal !== $ruleLiteral &&
|
||||
$otherWatch !== $ruleLiteral &&
|
||||
!$decisions->conflict($ruleLiteral);
|
||||
});
|
||||
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
||||
return $literal !== $ruleLiteral &&
|
||||
$otherWatch !== $ruleLiteral &&
|
||||
!$decisions->conflict($ruleLiteral);
|
||||
});
|
||||
|
||||
if ($alternativeLiterals) {
|
||||
reset($alternativeLiterals);
|
||||
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
||||
continue;
|
||||
if ($alternativeLiterals) {
|
||||
reset($alternativeLiterals);
|
||||
$this->moveWatch($literal, current($alternativeLiterals), $node);
|
||||
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)) {
|
||||
return $node->getRule();
|
||||
$decisions->decide($otherLiteral, $level, $node->getRule());
|
||||
}
|
||||
}
|
||||
|
||||
$decisions->decide($otherWatch, $level, $node->getRule());
|
||||
}
|
||||
|
||||
$chain->next();
|
||||
|
|
|
@ -55,7 +55,7 @@ class RuleWatchNode
|
|||
$literals = $this->rule->getLiterals();
|
||||
|
||||
// if there are only 2 elements, both are being watched anyway
|
||||
if (count($literals) < 3) {
|
||||
if (count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
|
@ -28,23 +28,18 @@ class Solver
|
|||
protected $policy;
|
||||
/** @var Pool */
|
||||
protected $pool;
|
||||
/** @var RepositoryInterface */
|
||||
protected $installed;
|
||||
|
||||
/** @var RuleSet */
|
||||
protected $rules;
|
||||
/** @var RuleSetGenerator */
|
||||
protected $ruleSetGenerator;
|
||||
/** @var array */
|
||||
protected $jobs;
|
||||
|
||||
/** @var int[] */
|
||||
protected $updateMap = array();
|
||||
/** @var RuleWatchGraph */
|
||||
protected $watchGraph;
|
||||
/** @var Decisions */
|
||||
protected $decisions;
|
||||
/** @var int[] */
|
||||
protected $installedMap;
|
||||
/** @var PackageInterface[] */
|
||||
protected $fixedMap;
|
||||
|
||||
/** @var int */
|
||||
protected $propagateIndex;
|
||||
|
@ -66,16 +61,13 @@ class Solver
|
|||
/**
|
||||
* @param PolicyInterface $policy
|
||||
* @param Pool $pool
|
||||
* @param RepositoryInterface $installed
|
||||
* @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->policy = $policy;
|
||||
$this->pool = $pool;
|
||||
$this->installed = $installed;
|
||||
$this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,6 +78,11 @@ class Solver
|
|||
return count($this->rules);
|
||||
}
|
||||
|
||||
public function getPool()
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
// aka solver_makeruledecisions
|
||||
|
||||
private function makeAssertionRuleDecisions()
|
||||
|
@ -121,23 +118,23 @@ class Solver
|
|||
$conflict = $this->decisions->decisionRule($literal);
|
||||
|
||||
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
|
||||
$problem = new Problem($this->pool);
|
||||
$problem = new Problem();
|
||||
|
||||
$problem->addRule($rule);
|
||||
$problem->addRule($conflict);
|
||||
$this->disableProblem($rule);
|
||||
$rule->disable();
|
||||
$this->problems[] = $problem;
|
||||
continue;
|
||||
}
|
||||
|
||||
// conflict with another job
|
||||
$problem = new Problem($this->pool);
|
||||
// conflict with another root require/fixed package
|
||||
$problem = new Problem();
|
||||
$problem->addRule($rule);
|
||||
$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
|
||||
foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
|
||||
foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) {
|
||||
if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -148,9 +145,8 @@ class Solver
|
|||
if (abs($literal) !== abs($assertRuleLiteral)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$problem->addRule($assertRule);
|
||||
$this->disableProblem($assertRule);
|
||||
$assertRule->disable();
|
||||
}
|
||||
$this->problems[] = $problem;
|
||||
|
||||
|
@ -159,47 +155,29 @@ class Solver
|
|||
}
|
||||
}
|
||||
|
||||
protected function setupInstalledMap()
|
||||
protected function setupFixedMap(Request $request)
|
||||
{
|
||||
$this->installedMap = array();
|
||||
foreach ($this->installed->getPackages() as $package) {
|
||||
$this->installedMap[$package->id] = $package;
|
||||
$this->fixedMap = array();
|
||||
foreach ($request->getFixedPackages() as $package) {
|
||||
$this->fixedMap[$package->id] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param bool $ignorePlatformReqs
|
||||
*/
|
||||
protected function checkForRootRequireProblems($ignorePlatformReqs)
|
||||
protected function checkForRootRequireProblems($request, $ignorePlatformReqs)
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'update':
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
||||
foreach ($packages as $package) {
|
||||
if (isset($this->installedMap[$package->id])) {
|
||||
$this->updateMap[$package->id] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
foreach ($request->getRequires() as $packageName => $constraint) {
|
||||
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $packageName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'update-all':
|
||||
foreach ($this->installedMap as $package) {
|
||||
$this->updateMap[$package->id] = true;
|
||||
}
|
||||
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;
|
||||
if (!$this->pool->whatProvides($packageName, $constraint)) {
|
||||
$problem = new Problem();
|
||||
$problem->addRule(new GenericRule(array(), Rule::RULE_ROOT_REQUIRE, array('packageName' => $packageName, 'constraint' => $constraint)));
|
||||
$this->problems[] = $problem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,15 +185,16 @@ class Solver
|
|||
/**
|
||||
* @param Request $request
|
||||
* @param bool $ignorePlatformReqs
|
||||
* @return array
|
||||
* @return LockTransaction
|
||||
*/
|
||||
public function solve(Request $request, $ignorePlatformReqs = false)
|
||||
{
|
||||
$this->jobs = $request->getJobs();
|
||||
$this->setupFixedMap($request);
|
||||
|
||||
$this->setupInstalledMap();
|
||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
|
||||
$this->checkForRootRequireProblems($ignorePlatformReqs);
|
||||
$this->io->writeError('Generating rules', true, IOInterface::DEBUG);
|
||||
$this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
|
||||
$this->rules = $this->ruleSetGenerator->getRulesFor($request, $ignorePlatformReqs);
|
||||
$this->checkForRootRequireProblems($request, $ignorePlatformReqs);
|
||||
$this->decisions = new Decisions($this->pool);
|
||||
$this->watchGraph = new RuleWatchGraph;
|
||||
|
||||
|
@ -223,29 +202,20 @@ class Solver
|
|||
$this->watchGraph->insert(new RuleWatchNode($rule));
|
||||
}
|
||||
|
||||
/* make decisions based on job/update assertions */
|
||||
/* make decisions based on root require/fix assertions */
|
||||
$this->makeAssertionRuleDecisions();
|
||||
|
||||
$this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
|
||||
$before = microtime(true);
|
||||
$this->runSat(true);
|
||||
$this->runSat();
|
||||
$this->io->writeError('', true, IOInterface::DEBUG);
|
||||
$this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
|
||||
|
||||
// decide to remove everything that's installed and undecided
|
||||
foreach ($this->installedMap as $packageId => $void) {
|
||||
if ($this->decisions->undecided($packageId)) {
|
||||
$this->decisions->decide(-$packageId, 1, null);
|
||||
}
|
||||
}
|
||||
|
||||
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 $transaction->getOperations();
|
||||
return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,11 +292,10 @@ class Solver
|
|||
*
|
||||
* @param int $level
|
||||
* @param string|int $literal
|
||||
* @param bool $disableRules
|
||||
* @param Rule $rule
|
||||
* @return int
|
||||
*/
|
||||
private function setPropagateLearn($level, $literal, $disableRules, Rule $rule)
|
||||
private function setPropagateLearn($level, $literal, Rule $rule)
|
||||
{
|
||||
$level++;
|
||||
|
||||
|
@ -340,7 +309,7 @@ class Solver
|
|||
}
|
||||
|
||||
if ($level == 1) {
|
||||
return $this->analyzeUnsolvable($rule, $disableRules);
|
||||
return $this->analyzeUnsolvable($rule);
|
||||
}
|
||||
|
||||
// conflict
|
||||
|
@ -377,14 +346,13 @@ class Solver
|
|||
/**
|
||||
* @param int $level
|
||||
* @param array $decisionQueue
|
||||
* @param bool $disableRules
|
||||
* @param Rule $rule
|
||||
* @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
|
||||
$literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage());
|
||||
$literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage());
|
||||
|
||||
$selectedLiteral = array_shift($literals);
|
||||
|
||||
|
@ -393,7 +361,7 @@ class Solver
|
|||
$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 bool $disableRules
|
||||
* @return int
|
||||
*/
|
||||
private function analyzeUnsolvable(Rule $conflictRule, $disableRules)
|
||||
private function analyzeUnsolvable(Rule $conflictRule)
|
||||
{
|
||||
$problem = new Problem($this->pool);
|
||||
$problem = new Problem();
|
||||
$problem->addRule($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()
|
||||
{
|
||||
$this->decisions->reset();
|
||||
|
@ -661,17 +596,14 @@ class Solver
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $disableRules
|
||||
*/
|
||||
private function runSat($disableRules = true)
|
||||
private function runSat()
|
||||
{
|
||||
$this->propagateIndex = 0;
|
||||
|
||||
/*
|
||||
* here's the main loop:
|
||||
* 1) propagate new decisions (only needed once)
|
||||
* 2) fulfill jobs
|
||||
* 2) fulfill root requires/fixed packages
|
||||
* 3) fulfill all unresolved rules
|
||||
* 4) minimalize solution if we had choices
|
||||
* if we encounter a problem, we rewind to a safe level and restart
|
||||
|
@ -679,10 +611,7 @@ class Solver
|
|||
*/
|
||||
|
||||
$decisionQueue = array();
|
||||
/**
|
||||
* @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
|
||||
*/
|
||||
$disableRules = array();
|
||||
$decisionSupplementQueue = array();
|
||||
|
||||
$level = 1;
|
||||
$systemLevel = $level + 1;
|
||||
|
@ -691,7 +620,7 @@ class Solver
|
|||
if (1 === $level) {
|
||||
$conflictRule = $this->propagate($level);
|
||||
if (null !== $conflictRule) {
|
||||
if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
|
||||
if ($this->analyzeUnsolvable($conflictRule)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -699,9 +628,9 @@ class Solver
|
|||
}
|
||||
}
|
||||
|
||||
// handle job rules
|
||||
// handle root require/fixed package rules
|
||||
if ($level < $systemLevel) {
|
||||
$iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
|
||||
$iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST);
|
||||
foreach ($iterator as $rule) {
|
||||
if ($rule->isEnabled()) {
|
||||
$decisionQueue = array();
|
||||
|
@ -718,26 +647,21 @@ class Solver
|
|||
}
|
||||
|
||||
if ($noneSatisfied && count($decisionQueue)) {
|
||||
// prune all update packages until installed version
|
||||
// except for requested updates
|
||||
if (count($this->installed) != count($this->updateMap)) {
|
||||
$prunedQueue = array();
|
||||
foreach ($decisionQueue as $literal) {
|
||||
if (isset($this->installedMap[abs($literal)])) {
|
||||
$prunedQueue[] = $literal;
|
||||
if (isset($this->updateMap[abs($literal)])) {
|
||||
$prunedQueue = $decisionQueue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if any of the options in the decision queue are fixed, only use those
|
||||
$prunedQueue = array();
|
||||
foreach ($decisionQueue as $literal) {
|
||||
if (isset($this->fixedMap[abs($literal)])) {
|
||||
$prunedQueue[] = $literal;
|
||||
}
|
||||
}
|
||||
if (!empty($prunedQueue)) {
|
||||
$decisionQueue = $prunedQueue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($noneSatisfied && count($decisionQueue)) {
|
||||
$oLevel = $level;
|
||||
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
|
||||
$level = $this->selectAndInstall($level, $decisionQueue, $rule);
|
||||
|
||||
if (0 === $level) {
|
||||
return;
|
||||
|
@ -751,7 +675,7 @@ class Solver
|
|||
|
||||
$systemLevel = $level + 1;
|
||||
|
||||
// jobs left
|
||||
// root requires/fixed packages left
|
||||
$iterator->next();
|
||||
if ($iterator->valid()) {
|
||||
continue;
|
||||
|
@ -813,7 +737,7 @@ class Solver
|
|||
continue;
|
||||
}
|
||||
|
||||
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
|
||||
$level = $this->selectAndInstall($level, $decisionQueue, $rule);
|
||||
|
||||
if (0 === $level) {
|
||||
return;
|
||||
|
@ -856,7 +780,7 @@ class Solver
|
|||
|
||||
$why = $this->decisions->lastReason();
|
||||
|
||||
$level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
|
||||
$level = $this->setPropagateLearn($level, $lastLiteral, $why);
|
||||
|
||||
if ($level == 0) {
|
||||
return;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Util\IniHelper;
|
||||
use Composer\Repository\RepositorySet;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -20,29 +21,33 @@ use Composer\Util\IniHelper;
|
|||
class SolverProblemsException extends \RuntimeException
|
||||
{
|
||||
protected $problems;
|
||||
protected $installedMap;
|
||||
protected $learnedPool;
|
||||
|
||||
public function __construct(array $problems, array $installedMap)
|
||||
public function __construct(array $problems, array $learnedPool)
|
||||
{
|
||||
$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";
|
||||
$hasExtensionProblems = false;
|
||||
$isCausedByLock = false;
|
||||
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())) {
|
||||
$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.";
|
||||
}
|
||||
|
||||
|
@ -50,6 +55,10 @@ class SolverProblemsException extends \RuntimeException
|
|||
$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;
|
||||
}
|
||||
|
||||
|
@ -76,8 +85,8 @@ class SolverProblemsException extends \RuntimeException
|
|||
private function hasExtensionProblems(array $reasonSets)
|
||||
{
|
||||
foreach ($reasonSets as $reasonSet) {
|
||||
foreach ($reasonSet as $reason) {
|
||||
if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) {
|
||||
foreach ($reasonSet as $rule) {
|
||||
if (0 === strpos($rule->getRequiredPackage(), 'ext-')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,161 +13,205 @@
|
|||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
*/
|
||||
class Transaction
|
||||
{
|
||||
protected $policy;
|
||||
protected $pool;
|
||||
protected $installedMap;
|
||||
protected $decisions;
|
||||
protected $transaction;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $operations;
|
||||
|
||||
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->pool = $pool;
|
||||
$this->installedMap = $installedMap;
|
||||
$this->decisions = $decisions;
|
||||
$this->transaction = array();
|
||||
$this->presentPackages = $presentPackages;
|
||||
$this->setResultPackageMaps($resultPackages);
|
||||
$this->operations = $this->calculateOperations();
|
||||
}
|
||||
|
||||
public function getOperations()
|
||||
{
|
||||
$installMeansUpdateMap = $this->findUpdates();
|
||||
return $this->operations;
|
||||
}
|
||||
|
||||
$updateMap = array();
|
||||
$installMap = array();
|
||||
$uninstallMap = array();
|
||||
private function setResultPackageMaps($resultPackages)
|
||||
{
|
||||
$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) {
|
||||
$literal = $decision[Decisions::DECISION_LITERAL];
|
||||
$reason = $decision[Decisions::DECISION_REASON];
|
||||
$this->resultPackageMap = array();
|
||||
foreach ($resultPackages as $package) {
|
||||
$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
|
||||
if (($literal > 0) == isset($this->installedMap[$package->id])) {
|
||||
protected function calculateOperations()
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
if ($literal > 0) {
|
||||
if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
|
||||
$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;
|
||||
if (!isset($visited[spl_object_hash($package)])) {
|
||||
$visited[spl_object_hash($package)] = true;
|
||||
|
||||
$stack[] = $package;
|
||||
if ($package instanceof AliasPackage) {
|
||||
$queue[] = $package->getAliasOf();
|
||||
$stack[] = $package->getAliasOf();
|
||||
} else {
|
||||
foreach ($package->getRequires() as $link) {
|
||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
||||
$possibleRequires = $this->getProvidersInResult($link);
|
||||
|
||||
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;
|
||||
} else {
|
||||
if (isset($installMap[$packageId])) {
|
||||
$this->install(
|
||||
$installMap[$packageId]['package'],
|
||||
$installMap[$packageId]['reason']
|
||||
);
|
||||
unset($installMap[$packageId]);
|
||||
}
|
||||
if (isset($updateMap[$packageId])) {
|
||||
$this->update(
|
||||
$updateMap[$packageId]['source'],
|
||||
$updateMap[$packageId]['package'],
|
||||
$updateMap[$packageId]['reason']
|
||||
);
|
||||
unset($updateMap[$packageId]);
|
||||
if ($package instanceof AliasPackage) {
|
||||
$aliasKey = $package->getName().'::'.$package->getVersion();
|
||||
if (isset($presentAliasMap[$aliasKey])) {
|
||||
unset($removeAliasMap[$aliasKey]);
|
||||
} else {
|
||||
$operations[] = new Operation\MarkAliasInstalledOperation($package);
|
||||
}
|
||||
} else {
|
||||
if (isset($presentPackageMap[$package->getName()])) {
|
||||
$source = $presentPackageMap[$package->getName()];
|
||||
|
||||
// do we need to update?
|
||||
// TODO different for lock?
|
||||
if ($package->getVersion() != $presentPackageMap[$package->getName()]->getVersion() ||
|
||||
$package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() ||
|
||||
$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) {
|
||||
$this->uninstall($uninstall['package'], $uninstall['reason']);
|
||||
foreach ($removeMap as $name => $package) {
|
||||
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 = $packages;
|
||||
$roots = $this->resultPackageMap;
|
||||
|
||||
foreach ($packages as $packageId => $operation) {
|
||||
$package = $operation['package'];
|
||||
|
||||
if (!isset($roots[$packageId])) {
|
||||
foreach ($this->resultPackageMap as $packageHash => $package) {
|
||||
if (!isset($roots[$packageHash])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($package->getRequires() as $link) {
|
||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
||||
$possibleRequires = $this->getProvidersInResult($link);
|
||||
|
||||
foreach ($possibleRequires as $require) {
|
||||
if ($require !== $package) {
|
||||
unset($roots[$require->id]);
|
||||
unset($roots[spl_object_hash($require)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,69 +220,87 @@ class Transaction
|
|||
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];
|
||||
$package = $this->pool->literalToPackage($literal);
|
||||
/**
|
||||
* Workaround: if your packages depend on plugins, we must be sure
|
||||
* 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;
|
||||
}
|
||||
|
||||
// !wanted & installed
|
||||
if ($literal <= 0 && isset($this->installedMap[$package->id])) {
|
||||
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
|
||||
// is this package a plugin?
|
||||
$isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';
|
||||
|
||||
$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) {
|
||||
$literals[] = $update->id;
|
||||
// is this a plugin with no meaningful dependencies?
|
||||
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) {
|
||||
if ($updateLiteral !== $literal) {
|
||||
$installMeansUpdateMap[abs($updateLiteral)] = $package;
|
||||
}
|
||||
}
|
||||
unset($operations[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return $this->markAliasInstalled($package, $reason);
|
||||
$uninstOps = array();
|
||||
foreach ($operations as $idx => $op) {
|
||||
if ($op instanceof Operation\UninstallOperation) {
|
||||
$uninstOps[] = $op;
|
||||
unset($operations[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->transaction[] = new Operation\InstallOperation($package, $reason);
|
||||
}
|
||||
|
||||
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);
|
||||
return array_merge($uninstOps, $operations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,33 +30,56 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
* @throws \RuntimeException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function download(PackageInterface $package, $path, $output = true)
|
||||
public function install(PackageInterface $package, $path, $output = true)
|
||||
{
|
||||
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
|
||||
$retries = 3;
|
||||
while ($retries--) {
|
||||
$fileName = parent::download($package, $path, $output);
|
||||
if ($output) {
|
||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): Extracting archive");
|
||||
} else {
|
||||
$this->io->writeError('Extracting archive', false);
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
|
||||
$this->filesystem->ensureDirectoryExists($path);
|
||||
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->ensureDirectoryExists($temporaryDir);
|
||||
try {
|
||||
$this->extract($fileName, $temporaryDir);
|
||||
} catch (\Exception $e) {
|
||||
// remove cache if the file was corrupted
|
||||
parent::clearLastCacheWrite($package);
|
||||
throw $e;
|
||||
$this->filesystem->unlink($fileName);
|
||||
|
||||
$renameAsOne = false;
|
||||
if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
|
||||
$renameAsOne = true;
|
||||
}
|
||||
|
||||
$contentDir = $this->getFolderContent($temporaryDir);
|
||||
$singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir));
|
||||
|
||||
if ($renameAsOne) {
|
||||
// if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
|
||||
if ($singleDirAtTopLevel) {
|
||||
$extractedDir = (string) reset($contentDir);
|
||||
} else {
|
||||
$extractedDir = $temporaryDir;
|
||||
}
|
||||
|
||||
$this->filesystem->unlink($fileName);
|
||||
|
||||
$contentDir = $this->getFolderContent($temporaryDir);
|
||||
|
||||
$this->filesystem->rename($extractedDir, $path);
|
||||
} else {
|
||||
// only one dir in the archive, extract its contents out of it
|
||||
if (1 === count($contentDir) && is_dir(reset($contentDir))) {
|
||||
if ($singleDirAtTopLevel) {
|
||||
$contentDir = $this->getFolderContent((string) reset($contentDir));
|
||||
}
|
||||
|
||||
|
@ -65,44 +88,16 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
$file = (string) $file;
|
||||
$this->filesystem->rename($file, $path . '/' . basename($file));
|
||||
}
|
||||
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
|
||||
}
|
||||
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
|
||||
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
|
||||
// retry downloading if we have an invalid zip file
|
||||
if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
|
||||
$this->io->writeError('');
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...');
|
||||
} else {
|
||||
$this->io->writeError(' Invalid zip file, retrying...');
|
||||
}
|
||||
usleep(500000);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
} catch (\Exception $e) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->filesystem->removeDirectory($temporaryDir);
|
||||
|
||||
/**
|
||||
* {@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), '.');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
*
|
||||
* @throws \UnexpectedValueException If can not extract downloaded file to path
|
||||
*/
|
||||
abstract protected function extract($file, $path);
|
||||
abstract protected function extract(PackageInterface $package, $file, $path);
|
||||
|
||||
/**
|
||||
* Returns the folder content, excluding dotfiles
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Downloader;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Downloaders manager.
|
||||
|
@ -24,6 +25,7 @@ use Composer\Util\Filesystem;
|
|||
class DownloadManager
|
||||
{
|
||||
private $io;
|
||||
private $httpDownloader;
|
||||
private $preferDist = false;
|
||||
private $preferSource = false;
|
||||
private $packagePreferences = array();
|
||||
|
@ -33,9 +35,9 @@ class DownloadManager
|
|||
/**
|
||||
* Initializes download manager.
|
||||
*
|
||||
* @param IOInterface $io The Input Output Interface
|
||||
* @param bool $preferSource prefer downloading from source
|
||||
* @param Filesystem|null $filesystem custom Filesystem object
|
||||
* @param IOInterface $io The Input Output Interface
|
||||
* @param bool $preferSource prefer downloading from source
|
||||
* @param Filesystem|null $filesystem custom Filesystem object
|
||||
*/
|
||||
public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
|
||||
{
|
||||
|
@ -83,22 +85,6 @@ class DownloadManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to output download progress information for all registered
|
||||
* downloaders
|
||||
*
|
||||
* @param bool $outputProgress
|
||||
* @return DownloadManager
|
||||
*/
|
||||
public function setOutputProgress($outputProgress)
|
||||
{
|
||||
foreach ($this->downloaders as $downloader) {
|
||||
$downloader->setOutputProgress($outputProgress);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets installer downloader for a specific installation type.
|
||||
*
|
||||
|
@ -140,7 +126,7 @@ class DownloadManager
|
|||
* wrong type
|
||||
* @return DownloaderInterface|null
|
||||
*/
|
||||
public function getDownloaderForInstalledPackage(PackageInterface $package)
|
||||
public function getDownloaderForPackage(PackageInterface $package)
|
||||
{
|
||||
$installationSource = $package->getInstallationSource();
|
||||
|
||||
|
@ -154,7 +140,7 @@ class DownloadManager
|
|||
$downloader = $this->getDownloader($package->getSourceType());
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
'Package '.$package.' seems not been installed properly'
|
||||
'Package '.$package.' does not have an installation source set'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -171,63 +157,117 @@ class DownloadManager
|
|||
return $downloader;
|
||||
}
|
||||
|
||||
public function getDownloaderType(DownloaderInterface $downloader)
|
||||
{
|
||||
return array_search($downloader, $this->downloaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads package into target dir.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param bool $preferSource prefer installation from source
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $targetDir target dir
|
||||
* @param PackageInterface|null $prevPackage previous package instance in case of updates
|
||||
*
|
||||
* @return PromiseInterface
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function download(PackageInterface $package, $targetDir, $preferSource = null)
|
||||
public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
|
||||
$sourceType = $package->getSourceType();
|
||||
$distType = $package->getDistType();
|
||||
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
|
||||
|
||||
$sources = array();
|
||||
if ($sourceType) {
|
||||
$sources[] = 'source';
|
||||
}
|
||||
if ($distType) {
|
||||
$sources[] = 'dist';
|
||||
}
|
||||
$sources = $this->getAvailableSources($package, $prevPackage);
|
||||
|
||||
if (empty($sources)) {
|
||||
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
|
||||
}
|
||||
$io = $this->io;
|
||||
$self = $this;
|
||||
|
||||
if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
|
||||
$sources = array_reverse($sources);
|
||||
}
|
||||
|
||||
$this->filesystem->ensureDirectoryExists($targetDir);
|
||||
|
||||
foreach ($sources as $i => $source) {
|
||||
if (isset($e)) {
|
||||
$this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download, $prevPackage) {
|
||||
$source = array_shift($sources);
|
||||
if ($retry) {
|
||||
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||
}
|
||||
$package->setInstallationSource($source);
|
||||
try {
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
if ($downloader) {
|
||||
$downloader->download($package, $targetDir);
|
||||
}
|
||||
break;
|
||||
} catch (\RuntimeException $e) {
|
||||
if ($i === count($sources) - 1) {
|
||||
throw $e;
|
||||
|
||||
$downloader = $self->getDownloaderForPackage($package);
|
||||
if (!$downloader) {
|
||||
return \React\Promise\resolve();
|
||||
}
|
||||
|
||||
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
if (!$sources) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$io->writeError(
|
||||
' <warning>Failed to download '.
|
||||
$package->getPrettyName().
|
||||
' from ' . $source . ': '.
|
||||
$e->getMessage().'</warning>'
|
||||
);
|
||||
|
||||
return $download(true);
|
||||
}
|
||||
|
||||
$this->io->writeError(
|
||||
' <warning>Failed to download '.
|
||||
$package->getPrettyName().
|
||||
' from ' . $source . ': '.
|
||||
$e->getMessage().'</warning>'
|
||||
);
|
||||
throw $e;
|
||||
};
|
||||
|
||||
try {
|
||||
$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 string $targetDir target dir
|
||||
*
|
||||
* @return PromiseInterface|null
|
||||
* @throws \InvalidArgumentException if initial package is not installed
|
||||
*/
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
$installationSource = $initial->getInstallationSource();
|
||||
|
||||
if ('dist' === $installationSource) {
|
||||
$initialType = $initial->getDistType();
|
||||
$targetType = $target->getDistType();
|
||||
} else {
|
||||
$initialType = $initial->getSourceType();
|
||||
$targetType = $target->getSourceType();
|
||||
}
|
||||
|
||||
// upgrading from a dist stable package to a dev package, force source reinstall
|
||||
if ($target->isDev() && 'dist' === $installationSource) {
|
||||
$downloader->remove($initial, $targetDir);
|
||||
$this->download($target, $targetDir);
|
||||
|
||||
return;
|
||||
return $initialDownloader->remove($initial, $targetDir);
|
||||
}
|
||||
|
||||
$initialType = $this->getDownloaderType($initialDownloader);
|
||||
$targetType = $this->getDownloaderType($downloader);
|
||||
if ($initialType === $targetType) {
|
||||
$target->setInstallationSource($installationSource);
|
||||
try {
|
||||
$downloader->update($initial, $target, $targetDir);
|
||||
|
||||
return;
|
||||
return $downloader->update($initial, $target, $targetDir);
|
||||
} catch (\RuntimeException $e) {
|
||||
if (!$this->io->isInteractive()) {
|
||||
throw $e;
|
||||
|
@ -282,8 +313,17 @@ class DownloadManager
|
|||
}
|
||||
}
|
||||
|
||||
$downloader->remove($initial, $targetDir);
|
||||
$this->download($target, $targetDir, 'source' === $installationSource);
|
||||
// if downloader type changed, or update failed and user asks for reinstall,
|
||||
// we wipe the dir and do a new install instead of updating it
|
||||
$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 string $targetDir target dir
|
||||
*
|
||||
* @return PromiseInterface|null
|
||||
*/
|
||||
public function remove(PackageInterface $package, $targetDir)
|
||||
{
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
$targetDir = $this->normalizeTargetDir($targetDir);
|
||||
$downloader = $this->getDownloaderForPackage($package);
|
||||
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 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;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Downloader interface.
|
||||
|
@ -30,12 +31,35 @@ interface DownloaderInterface
|
|||
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 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.
|
||||
|
@ -55,10 +79,17 @@ interface DownloaderInterface
|
|||
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
|
||||
* @return DownloaderInterface
|
||||
* 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 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\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Url as UrlUtil;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* Base downloader for files
|
||||
|
@ -39,11 +40,13 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
{
|
||||
protected $io;
|
||||
protected $config;
|
||||
protected $rfs;
|
||||
protected $httpDownloader;
|
||||
protected $filesystem;
|
||||
protected $cache;
|
||||
protected $outputProgress = true;
|
||||
private $lastCacheWrites = array();
|
||||
/**
|
||||
* @private this is only public for php 5.3 support in closures
|
||||
*/
|
||||
public $lastCacheWrites = array();
|
||||
private $eventDispatcher;
|
||||
|
||||
/**
|
||||
|
@ -51,17 +54,17 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
*
|
||||
* @param IOInterface $io The IO instance
|
||||
* @param Config $config The config
|
||||
* @param HttpDownloader $httpDownloader The remote filesystem
|
||||
* @param EventDispatcher $eventDispatcher The event dispatcher
|
||||
* @param Cache $cache Optional cache instance
|
||||
* @param RemoteFilesystem $rfs The remote filesystem
|
||||
* @param Cache $cache Cache instance
|
||||
* @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->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
$this->cache = $cache;
|
||||
|
||||
|
@ -81,127 +84,191 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, $path, $output = true)
|
||||
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||
{
|
||||
if (!$package->getDistUrl()) {
|
||||
throw new \InvalidArgumentException('The given package is missing url information');
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
|
||||
}
|
||||
|
||||
$retries = 3;
|
||||
$urls = $package->getDistUrls();
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
$fileName = $this->doDownload($package, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
|
||||
} elseif (count($urls)) {
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
|
||||
}
|
||||
|
||||
if (!count($urls)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
foreach ($urls as $index => $url) {
|
||||
$processedUrl = $this->processUrl($package, $url);
|
||||
$urls[$index] = array(
|
||||
'base' => $url,
|
||||
'processed' => $processedUrl,
|
||||
'cacheKey' => $this->getCacheKey($package, $processedUrl)
|
||||
);
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
protected function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$this->filesystem->emptyDirectory($path);
|
||||
|
||||
$fileName = $this->getFileName($package, $path);
|
||||
$this->filesystem->ensureDirectoryExists($path);
|
||||
$this->filesystem->ensureDirectoryExists(dirname($fileName));
|
||||
|
||||
$processedUrl = $this->processUrl($package, $url);
|
||||
$origin = RemoteFilesystem::getOrigin($processedUrl);
|
||||
$io = $this->io;
|
||||
$cache = $this->cache;
|
||||
$httpDownloader = $this->httpDownloader;
|
||||
$eventDispatcher = $this->eventDispatcher;
|
||||
$filesystem = $this->filesystem;
|
||||
$self = $this;
|
||||
|
||||
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl);
|
||||
if ($this->eventDispatcher) {
|
||||
$this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
|
||||
}
|
||||
$rfs = $preFileDownloadEvent->getRemoteFilesystem();
|
||||
$accept = null;
|
||||
$reject = null;
|
||||
$download = function () use ($io, $output, $httpDownloader, $cache, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) {
|
||||
$url = reset($urls);
|
||||
|
||||
if ($eventDispatcher) {
|
||||
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']);
|
||||
$eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
|
||||
}
|
||||
|
||||
try {
|
||||
$checksum = $package->getDistSha1Checksum();
|
||||
$cacheKey = $this->getCacheKey($package, $processedUrl);
|
||||
$cacheKey = $url['cacheKey'];
|
||||
|
||||
// use from cache if it is present and has a valid checksum or we have no checksum to check against
|
||||
if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
|
||||
$this->io->writeError('Loading from cache', false);
|
||||
if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) {
|
||||
if ($output) {
|
||||
$io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache", true, IOInterface::VERY_VERBOSE);
|
||||
}
|
||||
$result = \React\Promise\resolve($fileName);
|
||||
} else {
|
||||
// download if cache restore failed
|
||||
if (!$this->outputProgress) {
|
||||
$this->io->writeError('Downloading', false);
|
||||
if ($output) {
|
||||
$io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
|
||||
}
|
||||
|
||||
// try to download 3 times then fail hard
|
||||
$retries = 3;
|
||||
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);
|
||||
}
|
||||
$result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
|
||||
->then($accept, $reject);
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
|
||||
.' directory is writable and you have internet connectivity');
|
||||
return $result->then(function ($result) use ($fileName, $checksum, $url) {
|
||||
// in case of retry, the first call's Promise chain finally calls this twice at the end,
|
||||
// once with $result being the returned $fileName from $accept, and then once for every
|
||||
// failed request with a null result, which can be skipped.
|
||||
if (null === $result) {
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
|
||||
.' directory is writable and you have internet connectivity');
|
||||
}
|
||||
|
||||
if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
|
||||
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
});
|
||||
};
|
||||
|
||||
$accept = function ($response) use ($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) {
|
||||
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$response->collect();
|
||||
|
||||
return $fileName;
|
||||
};
|
||||
|
||||
$reject = function ($e) use ($io, &$urls, $download, $fileName, $package, &$retries, $filesystem, $self) {
|
||||
// clean up
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->clearLastCacheWrite($package);
|
||||
throw $e;
|
||||
}
|
||||
if (file_exists($fileName)) {
|
||||
$filesystem->unlink($fileName);
|
||||
}
|
||||
$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}
|
||||
*/
|
||||
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()])) {
|
||||
$this->cache->remove($this->lastCacheWrites[$package->getName()]);
|
||||
|
@ -218,11 +285,11 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
$from = $initial->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->remove($initial, $path, false);
|
||||
$this->download($target, $path, false);
|
||||
$this->install($target, $path, false);
|
||||
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
@ -249,7 +316,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
return 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)
|
||||
{
|
||||
$prevIO = $this->io;
|
||||
$prevProgress = $this->outputProgress;
|
||||
|
||||
$this->io = new NullIO;
|
||||
$this->io->loadConfiguration($this->config);
|
||||
$this->outputProgress = false;
|
||||
$e = null;
|
||||
|
||||
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->setSource($targetDir.'_compare');
|
||||
|
@ -311,7 +378,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
|||
}
|
||||
|
||||
$this->io = $prevIO;
|
||||
$this->outputProgress = $prevProgress;
|
||||
|
||||
if ($e) {
|
||||
throw $e;
|
||||
|
|
|
@ -23,7 +23,15 @@ class FossilDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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
|
||||
$this->config->prohibitUrlByConfig($url, $this->io);
|
||||
|
@ -49,7 +57,7 @@ class FossilDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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
|
||||
$this->config->prohibitUrlByConfig($url, $this->io);
|
||||
|
|
|
@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Git as GitUtil;
|
||||
use Composer\Util\Url;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Cache;
|
||||
|
@ -29,6 +30,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
private $hasStashedChanges = false;
|
||||
private $hasDiscardedChanges = false;
|
||||
private $gitUtil;
|
||||
private $cachedPackages = array();
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
|
||||
{
|
||||
|
@ -39,7 +41,28 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
/**
|
||||
* {@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();
|
||||
$path = $this->normalizePath($path);
|
||||
|
@ -47,31 +70,20 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
$ref = $package->getSourceReference();
|
||||
$flag = Platform::isWindows() ? '/D ' : '';
|
||||
|
||||
// --dissociate option is only available since git 2.3.0-rc0
|
||||
$gitVersion = $this->gitUtil->getVersion();
|
||||
$msg = "Cloning ".$this->getShortHash($ref);
|
||||
|
||||
$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 ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
|
||||
$this->io->writeError('', true, IOInterface::DEBUG);
|
||||
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
|
||||
try {
|
||||
if (!$this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref)) {
|
||||
$this->io->writeError('<error>Failed to update '.$url.' in cache, package installation for '.$package->getPrettyName().' might fail.</error>');
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
|
||||
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
|
||||
$command =
|
||||
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
|
||||
. '&& cd '.$flag.'%path% '
|
||||
. '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
|
||||
} else {
|
||||
$msg = "Cloning ".$this->getShortHash($ref);
|
||||
$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 (getenv('COMPOSER_DISABLE_NETWORK')) {
|
||||
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->writeError($msg);
|
||||
|
||||
$commandCallable = function ($url) use ($path, $command, $cachePath) {
|
||||
|
@ -105,13 +117,52 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
if (!$this->hasMetadataRepository($path)) {
|
||||
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;
|
||||
if (
|
||||
0 === $this->process->execute('git remote -v', $output, $path)
|
||||
|
@ -122,28 +173,6 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
$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) {
|
||||
$this->updateOriginUrl($path, $target->getSourceUrl());
|
||||
}
|
||||
|
@ -272,7 +301,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
$changes = array_map(function ($elem) {
|
||||
return ' '.$elem;
|
||||
}, 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));
|
||||
if (count($changes) > 10) {
|
||||
$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));
|
||||
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));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$command = sprintf($template, ProcessExecutor::escape($gitRef));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// reference was not found (prints "fatal: reference is not a tree: $ref")
|
||||
|
@ -406,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
|||
$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)
|
||||
|
|
|
@ -18,7 +18,7 @@ use Composer\EventDispatcher\EventDispatcher;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -28,17 +28,19 @@ use Composer\IO\IOInterface;
|
|||
*/
|
||||
class GzipDownloader extends ArchiveDownloader
|
||||
{
|
||||
/** @var ProcessExecutor */
|
||||
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);
|
||||
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
|
||||
if (!Platform::isWindows()) {
|
||||
|
@ -63,14 +65,6 @@ class GzipDownloader extends ArchiveDownloader
|
|||
$this->extractUsingExt($file, $targetFilepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
private function extractUsingExt($file, $targetFilepath)
|
||||
{
|
||||
$archiveFile = gzopen($file, 'rb');
|
||||
|
|
|
@ -24,7 +24,15 @@ class HgDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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);
|
||||
|
||||
|
@ -44,7 +52,7 @@ class HgDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, $path, $output = true)
|
||||
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||
{
|
||||
$url = $package->getDistUrl();
|
||||
$realUrl = realpath($url);
|
||||
|
@ -50,14 +50,6 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -73,6 +65,29 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
|||
$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
|
||||
$transportOptions = $package->getTransportOptions() + array('symlink' => null, 'relative' => true);
|
||||
|
@ -154,7 +169,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
|
|||
$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());
|
||||
|
||||
if (realpath($path) === $realUrl) {
|
||||
if ($path === $realUrl) {
|
||||
if ($output) {
|
||||
$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}
|
||||
*/
|
||||
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();
|
||||
$label = $this->getLabelFromSourceReference($ref);
|
||||
|
@ -76,9 +84,9 @@ class PerforceDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Downloader for phar files
|
||||
*
|
||||
|
@ -22,7 +24,7 @@ class PharDownloader extends ArchiveDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Can throw an UnexpectedValueException
|
||||
$archive = new \Phar($file);
|
||||
|
|
|
@ -18,8 +18,9 @@ use Composer\EventDispatcher\EventDispatcher;
|
|||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use RarArchive;
|
||||
|
||||
/**
|
||||
|
@ -31,15 +32,16 @@ use RarArchive;
|
|||
*/
|
||||
class RarDownloader extends ArchiveDownloader
|
||||
{
|
||||
/** @var ProcessExecutor */
|
||||
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);
|
||||
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;
|
||||
|
||||
|
|
|
@ -28,7 +28,15 @@ class SvnDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@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();
|
||||
$ref = $package->getSourceReference();
|
||||
|
@ -42,13 +50,13 @@ class SvnDownloader extends VcsDownloader
|
|||
}
|
||||
|
||||
$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}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
SvnUtil::cleanEnv();
|
||||
$ref = $target->getSourceReference();
|
||||
|
@ -64,7 +72,7 @@ class SvnDownloader extends VcsDownloader
|
|||
}
|
||||
|
||||
$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
|
||||
* @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->setCacheCredentials($this->cacheCredentials);
|
||||
|
@ -101,7 +109,7 @@ class SvnDownloader extends VcsDownloader
|
|||
return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
|
||||
} catch (\RuntimeException $e) {
|
||||
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;
|
||||
}, preg_split('{\s*\r?\n\s*}', $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));
|
||||
if ($countChanges > 10) {
|
||||
$remainingChanges = $countChanges - 10;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Downloader for tar files: tar, tar.gz or tar.bz2
|
||||
*
|
||||
|
@ -22,7 +24,7 @@ class TarDownloader extends ArchiveDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function extract($file, $path)
|
||||
protected function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Can throw an UnexpectedValueException
|
||||
$archive = new \PharData($file);
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Package\Version\VersionParser;
|
|||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -54,44 +55,78 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
/**
|
||||
* {@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()) {
|
||||
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
|
||||
}
|
||||
|
||||
$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)) {
|
||||
try {
|
||||
if (Filesystem::isLocalPath($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);
|
||||
$this->doInstall($package, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||
if ($e instanceof \PHPUnit_Framework_Exception) {
|
||||
if ($e instanceof \PHPUnit\Framework\Exception) {
|
||||
throw $e;
|
||||
}
|
||||
if ($this->io->isDebug()) {
|
||||
|
@ -130,25 +165,21 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
$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->cleanChanges($initial, $path, true);
|
||||
$urls = $target->getSourceUrls();
|
||||
$urls = $this->prepareUrls($target->getSourceUrls());
|
||||
|
||||
$exception = null;
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
if (Filesystem::isLocalPath($url)) {
|
||||
$url = realpath($url);
|
||||
}
|
||||
$this->doUpdate($initial, $target, $path, $url);
|
||||
|
||||
$exception = null;
|
||||
break;
|
||||
} catch (\Exception $exception) {
|
||||
// rethrow phpunit exceptions to avoid hard to debug bug failures
|
||||
if ($exception instanceof \PHPUnit_Framework_Exception) {
|
||||
if ($exception instanceof \PHPUnit\Framework\Exception) {
|
||||
throw $exception;
|
||||
}
|
||||
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
|
||||
// because in case of missing metadata code would trigger another exception
|
||||
if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
|
||||
|
@ -196,21 +225,11 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
public function remove(PackageInterface $package, $path)
|
||||
{
|
||||
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
|
||||
$this->cleanChanges($package, $path, false);
|
||||
if (!$this->filesystem->removeDirectory($path)) {
|
||||
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}
|
||||
*/
|
||||
|
@ -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
|
||||
* @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.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @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.
|
||||
|
@ -269,6 +302,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param PackageInterface $target updated package
|
||||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
*
|
||||
* @return PromiseInterface|null
|
||||
*/
|
||||
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
|
||||
|
||||
|
@ -290,4 +325,33 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @return bool
|
||||
*/
|
||||
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\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
|
@ -28,16 +28,17 @@ use Composer\IO\IOInterface;
|
|||
*/
|
||||
class XzDownloader extends ArchiveDownloader
|
||||
{
|
||||
/** @var ProcessExecutor */
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -49,12 +50,4 @@ class XzDownloader extends ArchiveDownloader
|
|||
|
||||
throw new \RuntimeException($processError);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use Composer\Package\PackageInterface;
|
|||
use Composer\Util\IniHelper;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
use ZipArchive;
|
||||
|
@ -33,19 +33,21 @@ class ZipDownloader extends ArchiveDownloader
|
|||
private static $hasZipArchive;
|
||||
private static $isWindows;
|
||||
|
||||
/** @var ProcessExecutor */
|
||||
protected $process;
|
||||
/** @var ZipArchive|null */
|
||||
private $zipArchiveObject;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, 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);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
|
||||
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function download(PackageInterface $package, $path, $output = true)
|
||||
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
|
||||
{
|
||||
if (null === self::$hasSystemUnzip) {
|
||||
$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 $path Path where to extract file
|
||||
*/
|
||||
public function extract($file, $path)
|
||||
public function extract(PackageInterface $package, $file, $path)
|
||||
{
|
||||
// Each extract calls its alternative if not available or fails
|
||||
if (self::$isWindows) {
|
||||
|
|
|
@ -13,13 +13,16 @@
|
|||
namespace Composer\EventDispatcher;
|
||||
|
||||
use Composer\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Transaction;
|
||||
use Composer\Installer\InstallerEvent;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Script;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Installer\BinaryInstaller;
|
||||
|
@ -46,7 +49,7 @@ class EventDispatcher
|
|||
protected $io;
|
||||
protected $loader;
|
||||
protected $process;
|
||||
protected $listeners;
|
||||
protected $listeners = array();
|
||||
private $eventStack;
|
||||
|
||||
/**
|
||||
|
@ -99,40 +102,34 @@ class EventDispatcher
|
|||
/**
|
||||
* Dispatch a package event.
|
||||
*
|
||||
* @param string $eventName The constant in PackageEvents
|
||||
* @param bool $devMode Whether or not we are in dev mode
|
||||
* @param PolicyInterface $policy The policy
|
||||
* @param Pool $pool The pool
|
||||
* @param CompositeRepository $installedRepo The installed repository
|
||||
* @param Request $request The request
|
||||
* @param array $operations The list of operations
|
||||
* @param OperationInterface $operation The package being installed/updated/removed
|
||||
* @param string $eventName The constant in PackageEvents
|
||||
* @param bool $devMode Whether or not we are in dev mode
|
||||
* @param RepositoryInterface $localRepo The installed repository
|
||||
* @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
|
||||
* 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.
|
||||
*
|
||||
* @param string $eventName The constant in InstallerEvents
|
||||
* @param bool $devMode Whether or not we are in dev mode
|
||||
* @param PolicyInterface $policy The policy
|
||||
* @param Pool $pool The pool
|
||||
* @param CompositeRepository $installedRepo The installed repository
|
||||
* @param Request $request The request
|
||||
* @param array $operations The list of operations
|
||||
* @param string $eventName The constant in InstallerEvents
|
||||
* @param bool $devMode Whether or not we are in dev mode
|
||||
* @param bool $executeOperations True if operations will be executed, false in --dry-run
|
||||
* @param Transaction $transaction The transaction contains the list of operations
|
||||
*
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
*/
|
||||
public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
|
||||
public function dispatchInstallerEvent($eventName, $devMode, $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');
|
||||
}
|
||||
if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
|
||||
$this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1] ), true, IOInterface::VERBOSE);
|
||||
}
|
||||
$event = $this->checkListenerExpectedEvent($callable, $event);
|
||||
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
||||
} elseif ($this->isComposerScript($callable)) {
|
||||
|
@ -172,8 +172,8 @@ class EventDispatcher
|
|||
$args = array_merge($script, $event->getArguments());
|
||||
$flags = $event->getFlags();
|
||||
if (substr($callable, 0, 10) === '@composer ') {
|
||||
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9);
|
||||
if (0 !== ($exitCode = $this->process->execute($exec))) {
|
||||
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . ' ' . implode(' ', $args);
|
||||
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);
|
||||
|
||||
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
||||
|
@ -184,6 +184,7 @@ class EventDispatcher
|
|||
}
|
||||
|
||||
try {
|
||||
/** @var InstallerEvent $event */
|
||||
$scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
|
||||
$scriptEvent->setOriginatingEvent($event);
|
||||
$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);
|
||||
|
||||
throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
||||
|
@ -264,6 +265,15 @@ class EventDispatcher
|
|||
return $return;
|
||||
}
|
||||
|
||||
protected function executeTty($exec)
|
||||
{
|
||||
if ($this->io->isInteractive()) {
|
||||
return $this->process->executeTty($exec);
|
||||
}
|
||||
|
||||
return $this->process->execute($exec);
|
||||
}
|
||||
|
||||
protected function getPhpExecCommand()
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
|
@ -327,44 +337,6 @@ class EventDispatcher
|
|||
|
||||
$expected = $typehint->getName();
|
||||
|
||||
// BC support
|
||||
if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
|
||||
trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
|
||||
$event = new \Composer\Script\CommandEvent(
|
||||
$event->getName(),
|
||||
$event->getComposer(),
|
||||
$event->getIO(),
|
||||
$event->isDevMode(),
|
||||
$event->getArguments()
|
||||
);
|
||||
}
|
||||
if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') {
|
||||
trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
|
||||
$event = new \Composer\Script\PackageEvent(
|
||||
$event->getName(),
|
||||
$event->getComposer(),
|
||||
$event->getIO(),
|
||||
$event->isDevMode(),
|
||||
$event->getPolicy(),
|
||||
$event->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;
|
||||
}
|
||||
|
||||
|
@ -397,6 +369,22 @@ class EventDispatcher
|
|||
$this->listeners[$eventName][$priority][] = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|object $listener A callable or an object instance for which all listeners should be removed
|
||||
*/
|
||||
public function removeListener($listener)
|
||||
{
|
||||
foreach ($this->listeners as $eventName => $priorities) {
|
||||
foreach ($priorities as $priority => $listeners) {
|
||||
foreach ($listeners as $index => $candidate) {
|
||||
if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
|
||||
unset($this->listeners[$eventName][$priority][$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds object methods as listeners for the events in getSubscribedEvents
|
||||
*
|
||||
|
@ -513,7 +501,7 @@ class EventDispatcher
|
|||
*
|
||||
* @param Event $event
|
||||
* @throws \RuntimeException
|
||||
* @return number
|
||||
* @return int
|
||||
*/
|
||||
protected function pushEvent(Event $event)
|
||||
{
|
||||
|
|
|
@ -23,7 +23,8 @@ use Composer\Repository\WritableRepositoryInterface;
|
|||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Util\Silencer;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\EventDispatcher\Event;
|
||||
|
@ -222,6 +223,13 @@ class Factory
|
|||
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()
|
||||
{
|
||||
return array(
|
||||
|
@ -325,14 +333,15 @@ class Factory
|
|||
$io->loadConfiguration($config);
|
||||
}
|
||||
|
||||
$rfs = self::createRemoteFilesystem($io, $config);
|
||||
$httpDownloader = self::createHttpDownloader($io, $config);
|
||||
$loop = new Loop($httpDownloader);
|
||||
|
||||
// initialize event dispatcher
|
||||
$dispatcher = new EventDispatcher($composer, $io);
|
||||
$composer->setEventDispatcher($dispatcher);
|
||||
|
||||
// initialize repository manager
|
||||
$rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
|
||||
$rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher);
|
||||
$composer->setRepositoryManager($rm);
|
||||
|
||||
// load local repository
|
||||
|
@ -352,12 +361,12 @@ class Factory
|
|||
$composer->setPackage($package);
|
||||
|
||||
// initialize installation manager
|
||||
$im = $this->createInstallationManager();
|
||||
$im = $this->createInstallationManager($loop, $io, $dispatcher);
|
||||
$composer->setInstallationManager($im);
|
||||
|
||||
if ($fullLoad) {
|
||||
// initialize download manager
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
|
||||
$dm = $this->createDownloadManager($io, $config, $httpDownloader, $dispatcher);
|
||||
$composer->setDownloadManager($dm);
|
||||
|
||||
// initialize autoload generator
|
||||
|
@ -365,7 +374,7 @@ class Factory
|
|||
$composer->setAutoloadGenerator($generator);
|
||||
|
||||
// initialize archive manager
|
||||
$am = $this->createArchiveManager($config, $dm);
|
||||
$am = $this->createArchiveManager($config, $dm, $loop);
|
||||
$composer->setArchiveManager($am);
|
||||
}
|
||||
|
||||
|
@ -386,11 +395,9 @@ class Factory
|
|||
|
||||
// init locker if possible
|
||||
if ($fullLoad && isset($composerFile)) {
|
||||
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
|
||||
? substr($composerFile, 0, -4).'lock'
|
||||
: $composerFile . '.lock';
|
||||
$lockFile = self::getLockFile($composerFile);
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -411,7 +418,7 @@ class Factory
|
|||
/**
|
||||
* @param IOInterface $io IO instance
|
||||
* @param bool $disablePlugins Whether plugins should not be loaded
|
||||
* @return Composer
|
||||
* @return Composer|null
|
||||
*/
|
||||
public static function createGlobal(IOInterface $io, $disablePlugins = false)
|
||||
{
|
||||
|
@ -451,7 +458,7 @@ class Factory
|
|||
* @param EventDispatcher $eventDispatcher
|
||||
* @return Downloader\DownloadManager
|
||||
*/
|
||||
public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null)
|
||||
{
|
||||
$cache = null;
|
||||
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('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
|
||||
$dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
|
||||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs));
|
||||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache));
|
||||
|
||||
return $dm;
|
||||
}
|
||||
|
@ -501,15 +508,9 @@ class Factory
|
|||
* @param Downloader\DownloadManager $dm Manager use to download sources
|
||||
* @return Archiver\ArchiveManager
|
||||
*/
|
||||
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
|
||||
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
|
||||
{
|
||||
if (null === $dm) {
|
||||
$io = new IO\NullIO();
|
||||
$io->loadConfiguration($config);
|
||||
$dm = $this->createDownloadManager($io, $config);
|
||||
}
|
||||
|
||||
$am = new Archiver\ArchiveManager($dm);
|
||||
$am = new Archiver\ArchiveManager($dm, $loop);
|
||||
$am->addArchiver(new Archiver\ZipArchiver);
|
||||
$am->addArchiver(new Archiver\PharArchiver);
|
||||
|
||||
|
@ -531,9 +532,9 @@ class Factory
|
|||
/**
|
||||
* @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 Config $config Config instance
|
||||
* @param array $options Array of options passed directly to RemoteFilesystem constructor
|
||||
* @return RemoteFilesystem
|
||||
* @param array $options Array of options passed directly to HttpDownloader constructor
|
||||
* @return HttpDownloader
|
||||
*/
|
||||
public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
|
||||
public static function createHttpDownloader(IOInterface $io, Config $config = null, $options = array())
|
||||
{
|
||||
static $warned = false;
|
||||
$disableTls = false;
|
||||
|
@ -596,18 +597,18 @@ class Factory
|
|||
throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
|
||||
. 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
|
||||
}
|
||||
$remoteFilesystemOptions = array();
|
||||
$httpDownloaderOptions = array();
|
||||
if ($disableTls === false) {
|
||||
if ($config && $config->get('cafile')) {
|
||||
$remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
|
||||
$httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
|
||||
}
|
||||
if ($config && $config->get('capath')) {
|
||||
$remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
|
||||
$httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
|
||||
}
|
||||
$remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
|
||||
$httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
|
||||
}
|
||||
try {
|
||||
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
|
||||
$httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls);
|
||||
} catch (TransportException $e) {
|
||||
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>');
|
||||
|
@ -620,7 +621,7 @@ class Factory
|
|||
throw $e;
|
||||
}
|
||||
|
||||
return $remoteFilesystem;
|
||||
return $httpDownloader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,10 +14,9 @@ namespace Composer\IO;
|
|||
|
||||
use Composer\Config;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
abstract class BaseIO implements IOInterface, LoggerInterface
|
||||
abstract class BaseIO implements IOInterface
|
||||
{
|
||||
protected $authentications = array();
|
||||
|
||||
|
|
|
@ -13,13 +13,14 @@
|
|||
namespace Composer\IO;
|
||||
|
||||
use Composer\Config;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* The Input/Output helper interface.
|
||||
*
|
||||
* @author François Pluchino <francois.pluchino@opendisplay.com>
|
||||
*/
|
||||
interface IOInterface
|
||||
interface IOInterface extends LoggerInterface
|
||||
{
|
||||
const QUIET = 1;
|
||||
const NORMAL = 2;
|
||||
|
@ -80,6 +81,24 @@ interface IOInterface
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -107,7 +126,7 @@ interface IOInterface
|
|||
* @param string $default The default answer if none is given by the user
|
||||
*
|
||||
* @throws \RuntimeException If there is no data to read in the input stream
|
||||
* @return string The user answer
|
||||
* @return string|null The user answer
|
||||
*/
|
||||
public function ask($question, $default = null);
|
||||
|
||||
|
@ -145,7 +164,7 @@ interface IOInterface
|
|||
*
|
||||
* @param string $question The question to ask
|
||||
*
|
||||
* @return string The answer
|
||||
* @return string|null The answer
|
||||
*/
|
||||
public function askAndHideAnswer($question);
|
||||
|
||||
|
@ -160,7 +179,7 @@ interface IOInterface
|
|||
* @param bool $multiselect Select more than one value separated by comma
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return int|string|array The selected value or values (the key of the choices array)
|
||||
* @return int|string|array|bool The selected value or values (the key of the choices array)
|
||||
*/
|
||||
public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false);
|
||||
|
||||
|
|
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\MarkAliasInstalledOperation;
|
||||
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\StreamContextFactory;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
/**
|
||||
* Package operation manager.
|
||||
|
@ -37,6 +39,16 @@ class InstallationManager
|
|||
private $installers = array();
|
||||
private $cache = 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()
|
||||
{
|
||||
|
@ -151,13 +163,105 @@ class InstallationManager
|
|||
/**
|
||||
* Executes solver operation.
|
||||
*
|
||||
* @param RepositoryInterface $repo repository in which to check
|
||||
* @param OperationInterface $operation operation instance
|
||||
* @param RepositoryInterface $repo repository in which to add/remove/update packages
|
||||
* @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();
|
||||
$this->$method($repo, $operation);
|
||||
$promises = array();
|
||||
|
||||
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();
|
||||
$installer = $this->getInstaller($package->getType());
|
||||
$installer->install($repo, $package);
|
||||
$promise = $installer->install($repo, $package);
|
||||
$this->markForNotification($package);
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -190,12 +296,15 @@ class InstallationManager
|
|||
|
||||
if ($initialType === $targetType) {
|
||||
$installer = $this->getInstaller($initialType);
|
||||
$installer->update($repo, $initial, $target);
|
||||
$promise = $installer->update($repo, $initial, $target);
|
||||
$this->markForNotification($target);
|
||||
} else {
|
||||
$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();
|
||||
$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\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Transaction;
|
||||
use Composer\EventDispatcher\Event;
|
||||
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
|
||||
{
|
||||
/**
|
||||
|
@ -44,29 +39,14 @@ class InstallerEvent extends Event
|
|||
private $devMode;
|
||||
|
||||
/**
|
||||
* @var PolicyInterface
|
||||
* @var bool
|
||||
*/
|
||||
private $policy;
|
||||
private $executeOperations;
|
||||
|
||||
/**
|
||||
* @var Pool
|
||||
* @var Transaction
|
||||
*/
|
||||
private $pool;
|
||||
|
||||
/**
|
||||
* @var CompositeRepository
|
||||
*/
|
||||
private $installedRepo;
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var OperationInterface[]
|
||||
*/
|
||||
private $operations;
|
||||
private $transaction;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -75,24 +55,18 @@ class InstallerEvent extends Event
|
|||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
* @param bool $devMode
|
||||
* @param PolicyInterface $policy
|
||||
* @param Pool $pool
|
||||
* @param CompositeRepository $installedRepo
|
||||
* @param Request $request
|
||||
* @param OperationInterface[] $operations
|
||||
* @param bool $executeOperations
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
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);
|
||||
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->devMode = $devMode;
|
||||
$this->policy = $policy;
|
||||
$this->pool = $pool;
|
||||
$this->installedRepo = $installedRepo;
|
||||
$this->request = $request;
|
||||
$this->operations = $operations;
|
||||
$this->executeOperations = $executeOperations;
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 CompositeRepository
|
||||
*/
|
||||
public function getInstalledRepo()
|
||||
{
|
||||
return $this->installedRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OperationInterface[]
|
||||
*/
|
||||
public function getOperations()
|
||||
{
|
||||
return $this->operations;
|
||||
return $this->transaction;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,32 +12,15 @@
|
|||
|
||||
namespace Composer\Installer;
|
||||
|
||||
/**
|
||||
* The Installer Events.
|
||||
*
|
||||
* @author François Pluchino <francois.pluchino@gmail.com>
|
||||
*/
|
||||
class InstallerEvents
|
||||
{
|
||||
/**
|
||||
* The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins
|
||||
* resolve operations.
|
||||
* The PRE_OPERATIONS_EXEC event occurs before the lock file gets
|
||||
* installed and operations are executed.
|
||||
*
|
||||
* The event listener method receives a
|
||||
* Composer\Installer\InstallerEvent instance.
|
||||
* The event listener method receives an Composer\Installer\InstallerEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
const PRE_OPERATIONS_EXEC = 'pre-operations-exec';
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Installer;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use InvalidArgumentException;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Interface for the package installation manager.
|
||||
|
@ -42,20 +43,46 @@ interface InstallerInterface
|
|||
*/
|
||||
public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||
|
||||
/**
|
||||
* Downloads the files needed to later install the given package.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $prevPackage previous package instance in case of an update
|
||||
* @return PromiseInterface|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.
|
||||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @return PromiseInterface|null
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package);
|
||||
|
||||
/**
|
||||
* Updates specific package.
|
||||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $initial already installed package version
|
||||
* @param PackageInterface $target updated version
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $initial already installed package version
|
||||
* @param PackageInterface $target updated version
|
||||
* @return PromiseInterface|null
|
||||
*
|
||||
* @throws InvalidArgumentException if $initial package is not installed
|
||||
*/
|
||||
|
@ -64,11 +91,26 @@ interface InstallerInterface
|
|||
/**
|
||||
* Uninstalls specific package.
|
||||
*
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @param InstalledRepositoryInterface $repo repository in which to check
|
||||
* @param PackageInterface $package package instance
|
||||
* @return PromiseInterface|null
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -43,7 +43,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
*
|
||||
* @param IOInterface $io
|
||||
* @param Composer $composer
|
||||
* @param string $type
|
||||
* @param string|null $type
|
||||
* @param Filesystem $filesystem
|
||||
* @param BinaryInstaller $binaryInstaller
|
||||
*/
|
||||
|
@ -85,6 +85,39 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
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}
|
||||
*/
|
||||
|
@ -194,7 +227,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
|
|||
protected function installCode(PackageInterface $package)
|
||||
{
|
||||
$downloadPath = $this->getInstallPath($package);
|
||||
$this->downloadManager->download($package, $downloadPath);
|
||||
$this->downloadManager->install($package, $downloadPath);
|
||||
}
|
||||
|
||||
protected function updateCode(PackageInterface $initial, PackageInterface $target)
|
||||
|
|
|
@ -47,6 +47,30 @@ class MetapackageInstaller implements InstallerInterface
|
|||
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}
|
||||
*/
|
||||
|
@ -69,7 +93,7 @@ class MetapackageInstaller implements InstallerInterface
|
|||
$name = $target->getName();
|
||||
$from = $initial->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>)");
|
||||
|
||||
$repo->removePackage($initial);
|
||||
|
|
|
@ -40,6 +40,27 @@ class NoopInstaller implements InstallerInterface
|
|||
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}
|
||||
*/
|
||||
|
|
|
@ -16,19 +16,45 @@ use Composer\Composer;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* The Package Event.
|
||||
*
|
||||
* @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;
|
||||
|
||||
|
@ -39,20 +65,63 @@ class PackageEvent extends InstallerEvent
|
|||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
* @param bool $devMode
|
||||
* @param PolicyInterface $policy
|
||||
* @param Pool $pool
|
||||
* @param CompositeRepository $installedRepo
|
||||
* @param RepositoryInterface $localRepo
|
||||
* @param Request $request
|
||||
* @param OperationInterface[] $operations
|
||||
* @param OperationInterface $operation
|
||||
*/
|
||||
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
|
||||
public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
|
|
|
@ -50,19 +50,27 @@ class PluginInstaller extends LibraryInstaller
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$extra = $package->getExtra();
|
||||
if (empty($extra['class'])) {
|
||||
throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
||||
}
|
||||
|
||||
return parent::download($package, $prevPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
{
|
||||
parent::install($repo, $package);
|
||||
try {
|
||||
$this->composer->getPluginManager()->registerPackage($package, true);
|
||||
} catch (\Exception $e) {
|
||||
// Rollback installation
|
||||
$this->io->writeError('Plugin installation failed, rolling back');
|
||||
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||
parent::uninstall($repo, $package);
|
||||
throw $e;
|
||||
}
|
||||
|
@ -73,12 +81,22 @@ class PluginInstaller extends LibraryInstaller
|
|||
*/
|
||||
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
|
||||
{
|
||||
$extra = $target->getExtra();
|
||||
if (empty($extra['class'])) {
|
||||
throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
|
||||
}
|
||||
|
||||
parent::update($repo, $initial, $target);
|
||||
$this->composer->getPluginManager()->registerPackage($target, true);
|
||||
|
||||
try {
|
||||
$this->composer->getPluginManager()->deactivatePackage($initial, true);
|
||||
$this->composer->getPluginManager()->registerPackage($target, true);
|
||||
} catch (\Exception $e) {
|
||||
// Rollback installation
|
||||
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
|
||||
parent::uninstall($repo, $target);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
{
|
||||
$this->composer->getPluginManager()->uninstallPackage($package, true);
|
||||
parent::uninstall($repo, $package);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
|
||||
{
|
||||
$installPath = $this->installPath;
|
||||
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
|
||||
|
@ -67,7 +67,32 @@ class ProjectInstaller implements InstallerInterface
|
|||
if (!is_dir($installPath)) {
|
||||
mkdir($installPath, 0777, true);
|
||||
}
|
||||
$this->downloadManager->download($package, $installPath);
|
||||
|
||||
return $this->downloadManager->download($package, $installPath, $prevPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function 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
|
||||
{
|
||||
const MODE_LIST = 1;
|
||||
const MODE_BY_PACKAGE = 2;
|
||||
const MODE_BY_SUGGESTION = 4;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
|
@ -91,38 +95,105 @@ class SuggestedPackagesReporter
|
|||
|
||||
/**
|
||||
* Output suggested packages.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
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();
|
||||
$installedPackages = array();
|
||||
if (null !== $installedRepo && ! empty($suggestedPackages)) {
|
||||
$installedNames = array();
|
||||
if (null !== $installedRepo && !empty($suggestedPackages)) {
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
$installedPackages = array_merge(
|
||||
$installedPackages,
|
||||
$installedNames = array_merge(
|
||||
$installedNames,
|
||||
$package->getNames()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$suggestions = array();
|
||||
foreach ($suggestedPackages as $suggestion) {
|
||||
if (in_array($suggestion['target'], $installedPackages)) {
|
||||
if (in_array($suggestion['target'], $installedNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->io->writeError(sprintf(
|
||||
'%s suggests installing %s%s',
|
||||
$suggestion['source'],
|
||||
$this->escapeOutput($suggestion['target']),
|
||||
$this->escapeOutput('' !== $suggestion['reason'] ? ' ('.$suggestion['reason'].')' : '')
|
||||
));
|
||||
$suggestions[] = $suggestion;
|
||||
}
|
||||
|
||||
return $this;
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Composer\Json;
|
|||
use JsonSchema\Validator;
|
||||
use Seld\JsonLint\JsonParser;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
|
@ -37,25 +37,25 @@ class JsonFile
|
|||
const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json';
|
||||
|
||||
private $path;
|
||||
private $rfs;
|
||||
private $httpDownloader;
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* Initializes json file reader/parser.
|
||||
*
|
||||
* @param string $path path to a lockfile
|
||||
* @param RemoteFilesystem $rfs required for loading http/https json files
|
||||
* @param string $path path to a lockfile
|
||||
* @param HttpDownloader $httpDownloader required for loading http/https json files
|
||||
* @param IOInterface $io
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
|
||||
public function __construct($path, HttpDownloader $httpDownloader = null, IOInterface $io = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
if (null === $rfs && preg_match('{^https?://}i', $path)) {
|
||||
throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
|
||||
if (null === $httpDownloader && preg_match('{^https?://}i', $path)) {
|
||||
throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
|
||||
}
|
||||
$this->rfs = $rfs;
|
||||
$this->httpDownloader = $httpDownloader;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
|
@ -86,8 +86,8 @@ class JsonFile
|
|||
public function read()
|
||||
{
|
||||
try {
|
||||
if ($this->rfs) {
|
||||
$json = $this->rfs->getContents($this->path, $this->path, false);
|
||||
if ($this->httpDownloader) {
|
||||
$json = $this->httpDownloader->get($this->path)->getBody();
|
||||
} else {
|
||||
if ($this->io && $this->io->isDebug()) {
|
||||
$this->io->writeError('Reading ' . $this->path);
|
||||
|
|
|
@ -416,4 +416,9 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
|
|||
{
|
||||
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\RootPackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Loop;
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
/**
|
||||
|
@ -25,6 +26,7 @@ use Composer\Json\JsonFile;
|
|||
class ArchiveManager
|
||||
{
|
||||
protected $downloadManager;
|
||||
protected $loop;
|
||||
|
||||
protected $archivers = array();
|
||||
|
||||
|
@ -36,9 +38,10 @@ class ArchiveManager
|
|||
/**
|
||||
* @param DownloadManager $downloadManager A manager used to download package sources
|
||||
*/
|
||||
public function __construct(DownloadManager $downloadManager)
|
||||
public function __construct(DownloadManager $downloadManager, Loop $loop)
|
||||
{
|
||||
$this->downloadManager = $downloadManager;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,7 +152,9 @@ class ArchiveManager
|
|||
|
||||
try {
|
||||
// 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) {
|
||||
$filesystem->removeDirectory($sourcePath);
|
||||
throw $e;
|
||||
|
|
|
@ -210,18 +210,30 @@ abstract class BasePackage implements PackageInterface
|
|||
/**
|
||||
* {@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();
|
||||
}
|
||||
|
||||
// if source reference is a sha1 hash -- truncate
|
||||
if ($truncate && strlen($this->getSourceReference()) === 40) {
|
||||
return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7);
|
||||
switch ($displayMode) {
|
||||
case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV:
|
||||
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()
|
||||
|
@ -238,14 +250,14 @@ abstract class BasePackage implements PackageInterface
|
|||
/**
|
||||
* 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
|
||||
* @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