Merge branches 'add_exclude' and 'master' of https://github.com/trivago/composer into add_exclude
* 'add_exclude' of https://github.com/trivago/composer: # By Jordi Boggiano (239) and others # Via Jordi Boggiano (184) and others * 'master' of https://github.com/trivago/composer: (638 commits) Simplified syntax github deprecation changes fix bug in GitDriver::supports for remote repo strict check, testcase(s) Fix regex matching and add more tests for addSubNode, refs #3721, fixes #3716 solve edge case for `composer remove vendor/pkg` chmod 644 src/Composer/Command/RemoveCommand.php Avoid failing on composer show of lazy providers Show more info when a download fails Add notion of autoloader skipping autoload-dev rules Satis grammar fix. remove unused statements removed needless output param + limit git ls-remote to heads + escape repo url add check for remote Repository in GitDriver::supports suppress the prefix Improve notice about /usr/local/bin Reuse current file permissions Add the P character to the regex pattern Added deprecated warning for the dev option ... Conflicts: src/Composer/Autoload/AutoloadGenerator.php src/Composer/Autoload/ClassMapGenerator.phppull/1607/head
commit
f28785a49d
14
.travis.yml
14
.travis.yml
|
@ -5,19 +5,19 @@ php:
|
|||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
before_script:
|
||||
- sudo apt-get install parallel
|
||||
- rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
|
||||
- composer install --dev --prefer-source
|
||||
- bin/composer install --dev --prefer-source
|
||||
- composer install --prefer-source
|
||||
- bin/composer install --prefer-source
|
||||
- git config --global user.name travis-ci
|
||||
- git config --global user.email travis@example.com
|
||||
|
||||
script:
|
||||
- ls -d tests/Composer/Test/* | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml {};' || exit 1
|
||||
- ls -d tests/Composer/Test/* | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml {};'
|
||||
|
||||
git:
|
||||
depth: 5
|
||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,3 +1,33 @@
|
|||
### 1.0.0-alpha9 (2014-12-07)
|
||||
|
||||
* Added `remove` command to do the reverse of `require`
|
||||
* Added --ignore-platform-reqs to `install`/`update` commands to install even if you are missing a php extension or have an invalid php version
|
||||
* Added a warning when abandoned packages are being installed
|
||||
* Added auto-selection of the version constraint in the `require` command, which can now be used simply as `composer require foo/bar`
|
||||
* Added ability to define custom composer commands using scripts
|
||||
* Added `browse` command to open a browser to the given package's repo URL (or homepage with `-H`)
|
||||
* Added an `autoload-dev` section to declare dev-only autoload rules + a --no-dev flag to dump-autoload
|
||||
* Added an `auth.json` file, with `store-auths` config option
|
||||
* Added a `http-basic` config option to store login/pwds to hosts
|
||||
* Added failover to source/dist and vice-versa in case a download method fails
|
||||
* Added --path (-P) flag to the show command to see the install path of packages
|
||||
* Added --update-with-dependencies and --update-no-dev flags to the require command
|
||||
* Added `optimize-autoloader` config option to force the `-o` flag from the config
|
||||
* Added `clear-cache` command
|
||||
* Added a GzipDownloader to download single gzipped files
|
||||
* Added `ssh` support in the `github-protocols` config option
|
||||
* Added `pre-dependencies-solving` and `post-dependencies-solving` events
|
||||
* Added `pre-archive-cmd` and `post-archive-cmd` script events to the `archive` command
|
||||
* Added a `no-api` flag to GitHub VCS repos to skip the API but still get zip downloads
|
||||
* Added http-basic auth support for private git repos not on github
|
||||
* Added support for autoloading `.hh` files when running HHVM
|
||||
* Added support for PHP 5.6
|
||||
* Added support for OTP auth when retrieving a GitHub API key
|
||||
* Fixed isolation of `files` autoloaded scripts to ensure they can not affect anything
|
||||
* Improved performance of solving dependencies
|
||||
* Improved SVN and Perforce support
|
||||
* A boatload of minor fixes, documentation additions and UX improvements
|
||||
|
||||
### 1.0.0-alpha8 (2014-01-06)
|
||||
|
||||
* Break: The `install` command now has --dev enabled by default. --no-dev can be used to install without dev requirements
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
Contributing to Composer
|
||||
========================
|
||||
|
||||
Installation from Source
|
||||
------------------------
|
||||
|
||||
Prior to contributing to Composer, you must use be able to run the tests.
|
||||
To achieve this, you must use the sources and not the phar file.
|
||||
|
||||
1. Run `git clone https://github.com/composer/composer.git`
|
||||
2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
|
||||
3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
|
||||
|
||||
You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
|
||||
|
||||
Contributing policy
|
||||
-------------------
|
||||
|
||||
All code contributions - including those of people having commit access -
|
||||
must go through a pull request and approved by a core developer before being
|
||||
merged. This is to ensure proper review of all the code.
|
||||
|
||||
Fork the project, create a feature branch, and send us a pull request.
|
||||
|
||||
To ensure a consistent code base, you should make sure the code follows
|
||||
the [Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html)
|
||||
which we borrowed from Symfony.
|
||||
|
||||
If you would like to help, take a look at the [list of issues](http://github.com/composer/composer/issues).
|
30
README.md
30
README.md
|
@ -1,11 +1,11 @@
|
|||
Composer - Dependency Management for PHP
|
||||
========================================
|
||||
|
||||
Composer is a dependency manager tracking local dependencies of your projects and libraries.
|
||||
Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.
|
||||
|
||||
See [https://getcomposer.org/](https://getcomposer.org/) for more information and documentation.
|
||||
|
||||
[![Build Status](https://secure.travis-ci.org/composer/composer.png?branch=master)](http://travis-ci.org/composer/composer)
|
||||
[![Build Status](https://travis-ci.org/composer/composer.svg?branch=master)](https://travis-ci.org/composer/composer)
|
||||
|
||||
Installation / Usage
|
||||
--------------------
|
||||
|
@ -32,18 +32,6 @@ themselves. To create libraries/packages please read the
|
|||
3. Run Composer: `php composer.phar install`
|
||||
4. Browse for more packages on [Packagist](https://packagist.org).
|
||||
|
||||
Installation from Source
|
||||
------------------------
|
||||
|
||||
To run tests, or develop Composer itself, you must use the sources and not the phar
|
||||
file as described above.
|
||||
|
||||
1. Run `git clone https://github.com/composer/composer.git`
|
||||
2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
|
||||
3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
|
||||
|
||||
You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
|
||||
|
||||
Global installation of Composer (manual)
|
||||
----------------------------------------
|
||||
|
||||
|
@ -55,20 +43,6 @@ Updating Composer
|
|||
Running `php composer.phar self-update` or equivalent will update a phar
|
||||
install with the latest version.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
All code contributions - including those of people having commit access -
|
||||
must go through a pull request and approved by a core developer before being
|
||||
merged. This is to ensure proper review of all the code.
|
||||
|
||||
Fork the project, create a feature branch, and send us a pull request.
|
||||
|
||||
To ensure a consistent code base, you should make sure the code follows
|
||||
the [Coding Standards](http://symfony.com/doc/2.0/contributing/code/standards.html)
|
||||
which we borrowed from Symfony.
|
||||
|
||||
If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues).
|
||||
|
||||
Community
|
||||
---------
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "composer/composer",
|
||||
"description": "Dependency Manager",
|
||||
"description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
|
||||
"keywords": ["package", "dependency", "autoload"],
|
||||
"homepage": "http://getcomposer.org/",
|
||||
"type": "library",
|
||||
|
@ -23,14 +23,14 @@
|
|||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"justinrainbow/json-schema": "1.1.*",
|
||||
"seld/jsonlint": "1.*",
|
||||
"justinrainbow/json-schema": "~1.3",
|
||||
"seld/jsonlint": "~1.0",
|
||||
"symfony/console": "~2.3",
|
||||
"symfony/finder": "~2.2",
|
||||
"symfony/process": "~2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~3.7.10"
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic",
|
||||
|
@ -39,10 +39,16 @@
|
|||
"autoload": {
|
||||
"psr-0": { "Composer": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-0": { "Composer\\Test": "tests/" }
|
||||
},
|
||||
"bin": ["bin/composer"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,11 +33,13 @@ You decide to use [monolog](https://github.com/Seldaek/monolog). In order to
|
|||
add it to your project, all you need to do is create a `composer.json` file
|
||||
which describes the project's dependencies.
|
||||
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.2.*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.2.*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are simply stating that our project requires some `monolog/monolog` package,
|
||||
any version beginning with `1.2`.
|
||||
|
@ -45,7 +47,7 @@ any version beginning with `1.2`.
|
|||
## System Requirements
|
||||
|
||||
Composer requires PHP 5.3.2+ to run. A few sensitive php settings and compile
|
||||
flags are also required, but the installer will warn you about any
|
||||
flags are also required, but when using the installer you will be warned about any
|
||||
incompatibilities.
|
||||
|
||||
To install packages from sources instead of simple zip archives, you will need
|
||||
|
@ -54,26 +56,40 @@ git, svn or hg depending on how the package is version-controlled.
|
|||
Composer is multi-platform and we strive to make it run equally well on Windows,
|
||||
Linux and OSX.
|
||||
|
||||
## Installation - *nix
|
||||
## Installation - Linux / Unix / OSX
|
||||
|
||||
### Downloading the Composer Executable
|
||||
|
||||
There are in short, two ways to install Composer. Locally as part of your
|
||||
project, or globally as a system wide executable.
|
||||
|
||||
#### Locally
|
||||
|
||||
To actually get Composer, we need to do two things. The first one is installing
|
||||
Composer (again, this means downloading it into your project):
|
||||
Installing Composer locally is a matter of just running the installer in your
|
||||
project directory:
|
||||
|
||||
$ curl -sS https://getcomposer.org/installer | php
|
||||
```sh
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
This will just check a few PHP settings and then download `composer.phar` to
|
||||
your working directory. This file is the Composer binary. It is a PHAR (PHP
|
||||
> **Note:** If the above fails for some reason, you can download the installer
|
||||
> with `php` instead:
|
||||
|
||||
```sh
|
||||
php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
```
|
||||
|
||||
The installer will just check a few PHP settings and then download `composer.phar`
|
||||
to your working directory. This file is the Composer binary. It is a PHAR (PHP
|
||||
archive), which is an archive format for PHP which can be run on the command
|
||||
line, amongst other things.
|
||||
|
||||
You can install Composer to a specific directory by using the `--install-dir`
|
||||
option and providing a target directory (it can be an absolute or relative path):
|
||||
|
||||
$ curl -sS https://getcomposer.org/installer | php -- --install-dir=bin
|
||||
```sh
|
||||
curl -sS https://getcomposer.org/installer | php -- --install-dir=bin
|
||||
```
|
||||
|
||||
#### Globally
|
||||
|
||||
|
@ -83,26 +99,18 @@ executable and invoke it without `php`.
|
|||
|
||||
You can run these commands to easily access `composer` from anywhere on your system:
|
||||
|
||||
$ curl -sS https://getcomposer.org/installer | php
|
||||
$ mv composer.phar /usr/local/bin/composer
|
||||
```sh
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
mv composer.phar /usr/local/bin/composer
|
||||
```
|
||||
|
||||
> **Note:** If the above fails due to permissions, run the `mv` line
|
||||
> again with sudo.
|
||||
|
||||
> **Note:** In OSX Yosemite the `/usr` directory does not exist by default. If you receive the error "/usr/local/bin/composer: No such file or directory" then you must create `/usr/local/bin/` manually before proceeding.
|
||||
|
||||
Then, just run `composer` in order to run Composer instead of `php composer.phar`.
|
||||
|
||||
#### Globally (on OSX via homebrew)
|
||||
|
||||
Composer is part of the homebrew-php project.
|
||||
|
||||
1. Tap the homebrew-php repository into your brew installation if you haven't done
|
||||
so yet: `brew tap josegonzalez/homebrew-php`
|
||||
2. Run `brew install josegonzalez/php/composer`.
|
||||
3. Use Composer with the `composer` command.
|
||||
|
||||
> **Note:** If you receive an error saying PHP53 or higher is missing use this command to install php
|
||||
> `brew install php53-intl`
|
||||
|
||||
## Installation - Windows
|
||||
|
||||
### Using the Installer
|
||||
|
@ -113,26 +121,33 @@ Download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe
|
|||
it will install the latest Composer version and set up your PATH so that you can
|
||||
just call `composer` from any directory in your command line.
|
||||
|
||||
> **Note:** Close your current terminal. Test usage with a new terminal:
|
||||
> That is important since the PATH only gets loaded when the terminal starts.
|
||||
|
||||
### Manual Installation
|
||||
|
||||
Change to a directory on your `PATH` and run the install snippet to download
|
||||
composer.phar:
|
||||
|
||||
C:\Users\username>cd C:\bin
|
||||
C:\bin>php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
|
||||
```sh
|
||||
C:\Users\username>cd C:\bin
|
||||
C:\bin>php -r "readfile('https://getcomposer.org/installer');" | php
|
||||
```
|
||||
|
||||
> **Note:** If the above fails due to file_get_contents, use the `http` url or enable php_openssl.dll in php.ini
|
||||
> **Note:** If the above fails due to readfile, use the `http` url or enable php_openssl.dll in php.ini
|
||||
|
||||
Create a new `composer.bat` file alongside `composer.phar`:
|
||||
|
||||
C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
|
||||
```sh
|
||||
C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
|
||||
```
|
||||
|
||||
Close your current terminal. Test usage with a new terminal:
|
||||
|
||||
C:\Users\username>composer -V
|
||||
Composer version 27d8904
|
||||
|
||||
C:\Users\username>
|
||||
```sh
|
||||
C:\Users\username>composer -V
|
||||
Composer version 27d8904
|
||||
```
|
||||
|
||||
## Using Composer
|
||||
|
||||
|
@ -142,12 +157,16 @@ don't have a `composer.json` file in the current directory please skip to the
|
|||
|
||||
To resolve and download dependencies, run the `install` command:
|
||||
|
||||
$ php composer.phar install
|
||||
```sh
|
||||
php composer.phar install
|
||||
```
|
||||
|
||||
If you did a global install and do not have the phar in that directory
|
||||
run this instead:
|
||||
|
||||
$ composer install
|
||||
```sh
|
||||
composer install
|
||||
```
|
||||
|
||||
Following the [example above](#declaring-dependencies), this will download
|
||||
monolog into the `vendor/monolog/monolog` directory.
|
||||
|
@ -159,7 +178,9 @@ capable of autoloading all of the classes in any of the libraries that it
|
|||
downloads. To use it, just add the following line to your code's bootstrap
|
||||
process:
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
Woah! Now start using monolog! To keep learning more about Composer, keep
|
||||
reading the "Basic Usage" chapter.
|
||||
|
|
|
@ -1,23 +1,8 @@
|
|||
# Basic usage
|
||||
|
||||
## Installation
|
||||
## Installing
|
||||
|
||||
To install Composer, you just need to download the `composer.phar` executable.
|
||||
|
||||
$ curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
For the details, see the [Introduction](00-intro.md) chapter.
|
||||
|
||||
To check if Composer is working, just run the PHAR through `php`:
|
||||
|
||||
$ php composer.phar
|
||||
|
||||
This should give you a list of available commands.
|
||||
|
||||
> **Note:** You can also perform the checks only without downloading Composer
|
||||
> by using the `--check` option. For more information, just use `--help`.
|
||||
>
|
||||
> $ curl -sS https://getcomposer.org/installer | php -- --help
|
||||
If you have not yet installed Composer, refer to the [Intro](00-intro.md) chapter.
|
||||
|
||||
## `composer.json`: Project Setup
|
||||
|
||||
|
@ -34,11 +19,13 @@ The first (and often only) thing you specify in `composer.json` is the
|
|||
`require` key. You're simply telling Composer which packages your project
|
||||
depends on.
|
||||
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, `require` takes an object that maps **package names** (e.g. `monolog/monolog`)
|
||||
to **package versions** (e.g. `1.0.*`).
|
||||
|
@ -64,17 +51,19 @@ means any version in the `1.0` development branch. It would match `1.0.0`,
|
|||
|
||||
Version constraints can be specified in a few different ways.
|
||||
|
||||
Name | Example | Description
|
||||
-------------- | --------------------- | -----------
|
||||
Exact version | `1.0.2` | You can specify the exact version of a package.
|
||||
Range | `>=1.0` `>=1.0,<2.0` `>=1.0,<1.1 | >=1.2` | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. <br />You can define multiple ranges, separated by a comma, which will be treated as a **logical AND**. A pipe symbol `|` will be treated as a **logical OR**. <br />AND has higher precedence than OR.
|
||||
Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1`.
|
||||
Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2,<2.0`. For more details, read the next section below.
|
||||
Name | Example | Description
|
||||
-------------- | ------------------------------------------------------------------------ | -----------
|
||||
Exact version | `1.0.2` | You can specify the exact version of a package.
|
||||
Range | `>=1.0` `>=1.0 <2.0` <code>>=1.0 <1.1 || >=1.2</code> | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. <br />You can define multiple ranges. Ranges separated by a space (<code> </code>) or comma (`,`) will be treated as a **logical AND**. A double pipe (<code>||</code>) will be treated as a **logical OR**. AND has higher precedence than OR.
|
||||
Hyphen Range | `1.0 - 2.0` | Inclusive set of versions. Partial versions on the right include are completed with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the `2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to `>=1.0.0 <=2.1.0`.
|
||||
Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0 <1.1`.
|
||||
Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2 <2.0`. For more details, read the next section below.
|
||||
Caret Operator | `^1.2.3` | Very useful for projects that follow semantic versioning. `^1.2.3` is equivalent to `>=1.2.3 <2.0`. For more details, read the next section below.
|
||||
|
||||
### Next Significant Release (Tilde Operator)
|
||||
### Next Significant Release (Tilde and Caret Operators)
|
||||
|
||||
The `~` operator is best explained by example: `~1.2` is equivalent to
|
||||
`>=1.2,<2.0`, while `~1.2.3` is equivalent to `>=1.2.3,<1.3`. As you can see
|
||||
`>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see
|
||||
it is mostly useful for projects respecting [semantic
|
||||
versioning](http://semver.org/). A common usage would be to mark the minimum
|
||||
minor version you depend on, like `~1.2` (which allows anything up to, but not
|
||||
|
@ -82,6 +71,21 @@ including, 2.0). Since in theory there should be no backwards compatibility
|
|||
breaks until 2.0, that works well. Another way of looking at it is that using
|
||||
`~` specifies a minimum version, but allows the last digit specified to go up.
|
||||
|
||||
The `^` operator behaves very similarly but it sticks closer to semantic
|
||||
versioning, and will always allow non-breaking updates. For example `^1.2.3`
|
||||
is equivalent to `>=1.2.3 <2.0.0` as none of the releases until 2.0 should
|
||||
break backwards compatibility. For pre-1.0 versions it also acts with safety
|
||||
in mind and treats `^0.3` as `>=0.3.0 <0.4.0`
|
||||
|
||||
> **Note:** Though `2.0-beta.1` is strictly before `2.0`, a version constraint
|
||||
> like `~1.2` would not install it. As said above `~1.2` only means the `.2`
|
||||
> can change but the `1.` part is fixed.
|
||||
|
||||
> **Note:** The `~` operator has an exception on its behavior for the major
|
||||
> release number. This means for example that `~1` is the same as `~1.0` as
|
||||
> it will not allow the major number to increase trying to keep backwards
|
||||
> compatibility.
|
||||
|
||||
### Stability
|
||||
|
||||
By default only stable releases are taken into consideration. If you would like
|
||||
|
@ -95,7 +99,9 @@ packages instead of doing per dependency you can also use the
|
|||
To fetch the defined dependencies into your local project, just run the
|
||||
`install` command of `composer.phar`.
|
||||
|
||||
$ php composer.phar install
|
||||
```sh
|
||||
php composer.phar install
|
||||
```
|
||||
|
||||
This will find the latest version of `monolog/monolog` that matches the
|
||||
supplied version constraint and download it into the `vendor` directory.
|
||||
|
@ -130,18 +136,25 @@ dependencies installed are still working even if your dependencies released
|
|||
many new versions since then.
|
||||
|
||||
If no `composer.lock` file exists, Composer will read the dependencies and
|
||||
versions from `composer.json` and create the lock file.
|
||||
versions from `composer.json` and create the lock file after executing the `update` or the `install`
|
||||
command.
|
||||
|
||||
This means that if any of the dependencies get a new version, you won't get the updates
|
||||
automatically. To update to the new version, use `update` command. This will fetch
|
||||
the latest matching versions (according to your `composer.json` file) and also update
|
||||
the lock file with the new version.
|
||||
|
||||
$ php composer.phar update
|
||||
|
||||
```sh
|
||||
php composer.phar update
|
||||
```
|
||||
> **Note:** Composer will display a Warning when executing an `install` command if
|
||||
`composer.lock` and `composer.json` are not synchronized.
|
||||
|
||||
If you only want to install or update one dependency, you can whitelist them:
|
||||
|
||||
$ php composer.phar update monolog/monolog [...]
|
||||
```sh
|
||||
php composer.phar update monolog/monolog [...]
|
||||
```
|
||||
|
||||
> **Note:** For libraries it is not necessarily recommended to commit the lock file,
|
||||
> see also: [Libraries - Lock file](02-libraries.md#lock-file).
|
||||
|
@ -167,25 +180,31 @@ For libraries that specify autoload information, Composer generates a
|
|||
`vendor/autoload.php` file. You can simply include this file and you
|
||||
will get autoloading for free.
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
This makes it really easy to use third party code. For example: If your
|
||||
project depends on monolog, you can just start using classes from it, and they
|
||||
will be autoloaded.
|
||||
|
||||
$log = new Monolog\Logger('name');
|
||||
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
|
||||
```php
|
||||
$log = new Monolog\Logger('name');
|
||||
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
|
||||
|
||||
$log->addWarning('Foo');
|
||||
$log->addWarning('Foo');
|
||||
```
|
||||
|
||||
You can even add your own code to the autoloader by adding an `autoload` field
|
||||
to `composer.json`.
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {"Acme\\": "src/"}
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {"Acme\\": "src/"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Composer will register a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader
|
||||
for the `Acme` namespace.
|
||||
|
@ -194,15 +213,17 @@ You define a mapping from namespaces to directories. The `src` directory would
|
|||
be in your project root, on the same level as `vendor` directory is. An example
|
||||
filename would be `src/Foo.php` containing an `Acme\Foo` class.
|
||||
|
||||
After adding the `autoload` field, you have to re-run `install` to re-generate
|
||||
After adding the `autoload` field, you have to re-run `dump-autoload` to re-generate
|
||||
the `vendor/autoload.php` file.
|
||||
|
||||
Including that file will also return the autoloader instance, so you can store
|
||||
the return value of the include call in a variable and add more namespaces.
|
||||
This can be useful for autoloading classes in a test suite, for example.
|
||||
|
||||
$loader = require 'vendor/autoload.php';
|
||||
$loader->add('Acme\\Test\\', __DIR__);
|
||||
```php
|
||||
$loader = require 'vendor/autoload.php';
|
||||
$loader->add('Acme\\Test\\', __DIR__);
|
||||
```
|
||||
|
||||
In addition to PSR-4 autoloading, classmap is also supported. This allows
|
||||
classes to be autoloaded even if they do not conform to PSR-4. See the
|
||||
|
|
|
@ -12,12 +12,14 @@ libraries is that your project is a package without a name.
|
|||
In order to make that package installable you need to give it a name. You do
|
||||
this by adding a `name` to `composer.json`:
|
||||
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the project name is `acme/hello-world`, where `acme` is the
|
||||
vendor name. Supplying a vendor name is mandatory.
|
||||
|
@ -33,8 +35,11 @@ installed on the system but are not actually installable by Composer. This
|
|||
includes PHP itself, PHP extensions and some system libraries.
|
||||
|
||||
* `php` represents the PHP version of the user, allowing you to apply
|
||||
constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can
|
||||
require the `php-64bit` package.
|
||||
constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can
|
||||
require the `php-64bit` package.
|
||||
|
||||
* `hhvm` represents the version of the HHVM runtime (aka HipHop Virtual
|
||||
Machine) and allows you to apply a constraint, e.g., '>=2.3.3'.
|
||||
|
||||
* `ext-<name>` allows you to require PHP extensions (includes core
|
||||
extensions). Versioning can be quite inconsistent here, so it's often
|
||||
|
@ -42,8 +47,8 @@ includes PHP itself, PHP extensions and some system libraries.
|
|||
package name is `ext-gd`.
|
||||
|
||||
* `lib-<name>` allows constraints to be made on versions of libraries used by
|
||||
PHP. The following are available: `curl`, `iconv`, `libxml`, `openssl`,
|
||||
`pcre`, `uuid`, `xsl`.
|
||||
PHP. The following are available: `curl`, `iconv`, `icu`, `libxml`,
|
||||
`openssl`, `pcre`, `uuid`, `xsl`.
|
||||
|
||||
You can use `composer show --platform` to get a list of your locally available
|
||||
platform packages.
|
||||
|
@ -59,9 +64,11 @@ version numbers are extracted from these.
|
|||
If you are creating packages by hand and really have to specify it explicitly,
|
||||
you can just add a `version` field:
|
||||
|
||||
{
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** You should avoid specifying the version field explicitly, because
|
||||
> for tags the value must match the tag name.
|
||||
|
@ -70,17 +77,17 @@ you can just add a `version` field:
|
|||
|
||||
For every tag that looks like a version, a package version of that tag will be
|
||||
created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix
|
||||
of `-patch`, `-alpha`, `-beta` or `-RC`. The suffixes can also be followed by
|
||||
a number.
|
||||
of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes
|
||||
can also be followed by a number.
|
||||
|
||||
Here are a few examples of valid tag names:
|
||||
|
||||
1.0.0
|
||||
v1.0.0
|
||||
1.10.5-RC1
|
||||
v4.4.4beta2
|
||||
v2.0.0-alpha
|
||||
v2.0.4-p1
|
||||
- 1.0.0
|
||||
- v1.0.0
|
||||
- 1.10.5-RC1
|
||||
- v4.4.4-beta2
|
||||
- v2.0.0-alpha
|
||||
- v2.0.4-p1
|
||||
|
||||
> **Note:** Even if your tag is prefixed with `v`, a [version constraint](01-basic-usage.md#package-versions)
|
||||
> in a `require` statement has to be specified without prefix
|
||||
|
@ -98,9 +105,9 @@ like a version, it will be `dev-{branchname}`. `master` results in a
|
|||
|
||||
Here are some examples of version branch names:
|
||||
|
||||
1.x
|
||||
1.0 (equals 1.0.x)
|
||||
1.1.x
|
||||
- 1.x
|
||||
- 1.0 (equals 1.0.x)
|
||||
- 1.1.x
|
||||
|
||||
> **Note:** When you install a development version, it will be automatically
|
||||
> pulled from its `source`. See the [`install`](03-cli.md#install) command
|
||||
|
@ -137,12 +144,14 @@ project locally. We will call it `acme/blog`. This blog will depend on
|
|||
accomplish this by creating a new `blog` directory somewhere, containing a
|
||||
`composer.json`:
|
||||
|
||||
{
|
||||
"name": "acme/blog",
|
||||
"require": {
|
||||
"acme/hello-world": "dev-master"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "acme/blog",
|
||||
"require": {
|
||||
"acme/hello-world": "dev-master"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The name is not needed in this case, since we don't want to publish the blog
|
||||
as a library. It is added here to clarify which `composer.json` is being
|
||||
|
@ -152,18 +161,20 @@ Now we need to tell the blog app where to find the `hello-world` dependency.
|
|||
We do this by adding a package repository specification to the blog's
|
||||
`composer.json`:
|
||||
|
||||
{
|
||||
"name": "acme/blog",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/username/hello-world"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"acme/hello-world": "dev-master"
|
||||
```json
|
||||
{
|
||||
"name": "acme/blog",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/username/hello-world"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"acme/hello-world": "dev-master"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more details on how package repositories work and what other types are
|
||||
available, see [Repositories](05-repositories.md).
|
||||
|
|
238
doc/03-cli.md
238
doc/03-cli.md
|
@ -1,4 +1,4 @@
|
|||
# Command-line interface
|
||||
# Command-line interface / Commands
|
||||
|
||||
You've already learned how to use the command-line interface to do some
|
||||
things. This chapter documents all the available commands.
|
||||
|
@ -36,7 +36,9 @@ it a bit easier to do this.
|
|||
When you run the command it will interactively ask you to fill in the fields,
|
||||
while using some smart defaults.
|
||||
|
||||
$ php composer.phar init
|
||||
```sh
|
||||
php composer.phar init
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -54,7 +56,9 @@ while using some smart defaults.
|
|||
The `install` command reads the `composer.json` file from the current
|
||||
directory, resolves the dependencies, and installs them into `vendor`.
|
||||
|
||||
$ php composer.phar install
|
||||
```sh
|
||||
php composer.phar install
|
||||
```
|
||||
|
||||
If there is a `composer.lock` file in the current directory, it will use the
|
||||
exact versions from there instead of resolving them. This ensures that
|
||||
|
@ -76,11 +80,15 @@ resolution.
|
|||
servers and other use cases where you typically do not run updates of the
|
||||
vendors. It is also a way to circumvent problems with git if you do not
|
||||
have a proper setup.
|
||||
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
|
||||
requirements and force the installation even if the local machine does not
|
||||
fulfill these.
|
||||
* **--dry-run:** If you want to run through an installation without actually
|
||||
installing a package, you can use `--dry-run`. This will simulate the
|
||||
installation and show you what would happen.
|
||||
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
|
||||
* **--no-dev:** Skip installing packages listed in `require-dev`.
|
||||
* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
|
||||
* **--no-autoloader:** Skips autoloader generation.
|
||||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||
* **--no-plugins:** Disables plugins.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
|
@ -94,26 +102,36 @@ resolution.
|
|||
In order to get the latest versions of the dependencies and to update the
|
||||
`composer.lock` file, you should use the `update` command.
|
||||
|
||||
$ php composer.phar update
|
||||
```sh
|
||||
php composer.phar update
|
||||
```
|
||||
|
||||
This will resolve all dependencies of the project and write the exact versions
|
||||
into `composer.lock`.
|
||||
|
||||
If you just want to update a few packages and not all, you can list them as such:
|
||||
|
||||
$ php composer.phar update vendor/package vendor/package2
|
||||
```sh
|
||||
php composer.phar update vendor/package vendor/package2
|
||||
```
|
||||
|
||||
You can also use wildcards to update a bunch of packages at once:
|
||||
|
||||
$ php composer.phar update vendor/*
|
||||
```sh
|
||||
php composer.phar update vendor/*
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
* **--prefer-source:** Install packages from `source` when available.
|
||||
* **--prefer-dist:** Install packages from `dist` when available.
|
||||
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
|
||||
requirements and force the installation even if the local machine does not
|
||||
fulfill these.
|
||||
* **--dry-run:** Simulate the command without actually doing anything.
|
||||
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
|
||||
* **--no-dev:** Skip installing packages listed in `require-dev`.
|
||||
* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
|
||||
* **--no-autoloader:** Skips autoloader generation.
|
||||
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
|
||||
* **--no-plugins:** Disables plugins.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
|
@ -124,14 +142,18 @@ You can also use wildcards to update a bunch of packages at once:
|
|||
* **--lock:** Only updates the lock file hash to suppress warning about the
|
||||
lock file being out of date.
|
||||
* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
|
||||
So all packages with their dependencies are updated recursively.
|
||||
* **--prefer-stable:** Prefer stable versions of dependencies.
|
||||
* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
|
||||
versions of requirements, generally used with `--prefer-stable`.
|
||||
|
||||
## require
|
||||
|
||||
The `require` command adds new packages to the `composer.json` file from
|
||||
the current directory.
|
||||
the current directory. If no file exists one will be created on the fly.
|
||||
|
||||
$ php composer.phar require
|
||||
```sh
|
||||
php composer.phar require
|
||||
```
|
||||
|
||||
After adding/changing the requirements, the modified requirements will be
|
||||
installed or updated.
|
||||
|
@ -139,16 +161,47 @@ installed or updated.
|
|||
If you do not want to choose requirements interactively, you can just pass them
|
||||
to the command.
|
||||
|
||||
$ php composer.phar require vendor/package:2.* vendor/package2:dev-master
|
||||
```sh
|
||||
php composer.phar require vendor/package:2.* vendor/package2:dev-master
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
* **--prefer-source:** Install packages from `source` when available.
|
||||
* **--prefer-dist:** Install packages from `dist` when available.
|
||||
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
|
||||
requirements and force the installation even if the local machine does not
|
||||
fulfill these.
|
||||
* **--dev:** Add packages to `require-dev`.
|
||||
* **--no-update:** Disables the automatic update of the dependencies.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
terminals or scripts which don't handle backspace characters.
|
||||
* **--update-no-dev** Run the dependency update with the --no-dev option.
|
||||
* **--update-with-dependencies** Also update dependencies of the newly
|
||||
required packages.
|
||||
|
||||
## remove
|
||||
|
||||
The `remove` command removes packages from the `composer.json` file from
|
||||
the current directory.
|
||||
|
||||
```sh
|
||||
php composer.phar remove vendor/package vendor/package2
|
||||
```
|
||||
|
||||
After removing the requirements, the modified requirements will be
|
||||
uninstalled.
|
||||
|
||||
### Options
|
||||
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
|
||||
requirements and force the installation even if the local machine does not
|
||||
fulfill these.
|
||||
* **--dev:** Remove packages from `require-dev`.
|
||||
* **--no-update:** Disables the automatic update of the dependencies.
|
||||
* **--no-progress:** Removes the progress display that can mess with some
|
||||
terminals or scripts which don't handle backspace characters.
|
||||
* **--update-no-dev** Run the dependency update with the --no-dev option.
|
||||
* **--update-with-dependencies** Also update dependencies of the removed packages.
|
||||
|
||||
## global
|
||||
|
||||
|
@ -160,13 +213,17 @@ This can be used to install CLI utilities globally and if you add
|
|||
`$COMPOSER_HOME/vendor/bin` to your `$PATH` environment variable. Here is an
|
||||
example:
|
||||
|
||||
$ php composer.phar global require fabpot/php-cs-fixer:dev-master
|
||||
```sh
|
||||
php composer.phar global require fabpot/php-cs-fixer:dev-master
|
||||
```
|
||||
|
||||
Now the `php-cs-fixer` binary is available globally (assuming you adjusted
|
||||
your PATH). If you wish to update the binary later on you can just run a
|
||||
global update:
|
||||
|
||||
$ php composer.phar global update
|
||||
```sh
|
||||
php composer.phar global update
|
||||
```
|
||||
|
||||
## search
|
||||
|
||||
|
@ -174,7 +231,9 @@ The search command allows you to search through the current project's package
|
|||
repositories. Usually this will be just packagist. You simply pass it the
|
||||
terms you want to search for.
|
||||
|
||||
$ php composer.phar search monolog
|
||||
```sh
|
||||
php composer.phar search monolog
|
||||
```
|
||||
|
||||
You can also search for more than one term by passing multiple arguments.
|
||||
|
||||
|
@ -186,32 +245,38 @@ You can also search for more than one term by passing multiple arguments.
|
|||
|
||||
To list all of the available packages, you can use the `show` command.
|
||||
|
||||
$ php composer.phar show
|
||||
```sh
|
||||
php composer.phar show
|
||||
```
|
||||
|
||||
If you want to see the details of a certain package, you can pass the package
|
||||
name.
|
||||
|
||||
$ php composer.phar show monolog/monolog
|
||||
```sh
|
||||
php composer.phar show monolog/monolog
|
||||
|
||||
name : monolog/monolog
|
||||
versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1
|
||||
type : library
|
||||
names : monolog/monolog
|
||||
source : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da
|
||||
dist : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da
|
||||
license : MIT
|
||||
name : monolog/monolog
|
||||
versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1
|
||||
type : library
|
||||
names : monolog/monolog
|
||||
source : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da
|
||||
dist : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da
|
||||
license : MIT
|
||||
|
||||
autoload
|
||||
psr-0
|
||||
Monolog : src/
|
||||
autoload
|
||||
psr-0
|
||||
Monolog : src/
|
||||
|
||||
requires
|
||||
php >=5.3.0
|
||||
requires
|
||||
php >=5.3.0
|
||||
```
|
||||
|
||||
You can even pass the package version, which will tell you the details of that
|
||||
specific version.
|
||||
|
||||
$ php composer.phar show monolog/monolog 1.0.2
|
||||
```sh
|
||||
php composer.phar show monolog/monolog 1.0.2
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -219,19 +284,30 @@ specific version.
|
|||
* **--platform (-p):** List only platform packages (php & extensions).
|
||||
* **--self (-s):** List the root package info.
|
||||
|
||||
## browse / home
|
||||
|
||||
The `browse` (aliased to `home`) opens a package's repository URL or homepage
|
||||
in your browser.
|
||||
|
||||
### Options
|
||||
|
||||
* **--homepage (-H):** Open the homepage instead of the repository URL.
|
||||
|
||||
## depends
|
||||
|
||||
The `depends` command tells you which other packages depend on a certain
|
||||
package. You can specify which link types (`require`, `require-dev`)
|
||||
should be included in the listing. By default both are used.
|
||||
|
||||
$ php composer.phar depends --link-type=require monolog/monolog
|
||||
```sh
|
||||
php composer.phar depends --link-type=require monolog/monolog
|
||||
|
||||
nrk/monolog-fluent
|
||||
poc/poc
|
||||
propel/propel
|
||||
symfony/monolog-bridge
|
||||
symfony/symfony
|
||||
nrk/monolog-fluent
|
||||
poc/poc
|
||||
propel/propel
|
||||
symfony/monolog-bridge
|
||||
symfony/symfony
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -244,7 +320,13 @@ You should always run the `validate` command before you commit your
|
|||
`composer.json` file, and before you tag a release. It will check if your
|
||||
`composer.json` is valid.
|
||||
|
||||
$ php composer.phar validate
|
||||
```sh
|
||||
php composer.phar validate
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
* **--no-check-all:** Whether or not composer do a complete validation.
|
||||
|
||||
## status
|
||||
|
||||
|
@ -252,31 +334,42 @@ If you often need to modify the code of your dependencies and they are
|
|||
installed from source, the `status` command allows you to check if you have
|
||||
local changes in any of them.
|
||||
|
||||
$ php composer.phar status
|
||||
```sh
|
||||
php composer.phar status
|
||||
```
|
||||
|
||||
With the `--verbose` option you get some more information about what was
|
||||
changed:
|
||||
|
||||
$ php composer.phar status -v
|
||||
You have changes in the following dependencies:
|
||||
vendor/seld/jsonlint:
|
||||
M README.mdown
|
||||
```sh
|
||||
php composer.phar status -v
|
||||
|
||||
You have changes in the following dependencies:
|
||||
vendor/seld/jsonlint:
|
||||
M README.mdown
|
||||
```
|
||||
|
||||
## self-update
|
||||
|
||||
To update composer itself to the latest version, just run the `self-update`
|
||||
command. It will replace your `composer.phar` with the latest version.
|
||||
|
||||
$ php composer.phar self-update
|
||||
```sh
|
||||
php composer.phar self-update
|
||||
```
|
||||
|
||||
If you would like to instead update to a specific release simply specify it:
|
||||
|
||||
$ composer self-update 1.0.0-alpha7
|
||||
```sh
|
||||
php composer.phar self-update 1.0.0-alpha7
|
||||
```
|
||||
|
||||
If you have installed composer for your entire system (see [global installation](00-intro.md#globally)),
|
||||
you may have to run the command with `root` privileges
|
||||
|
||||
$ sudo composer self-update
|
||||
```sh
|
||||
sudo composer self-update
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -288,7 +381,9 @@ you may have to run the command with `root` privileges
|
|||
The `config` command allows you to edit some basic composer settings in either
|
||||
the local composer.json file or the global config.json file.
|
||||
|
||||
$ php composer.phar config --list
|
||||
```sh
|
||||
php composer.phar config --list
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -304,23 +399,27 @@ options.
|
|||
### Options
|
||||
|
||||
* **--global (-g):** Operate on the global config file located at
|
||||
`$COMPOSER_HOME/config.json` by default. Without this option, this command
|
||||
affects the local composer.json file or a file specified by `--file`.
|
||||
`$COMPOSER_HOME/config.json` by default. Without this option, this command
|
||||
affects the local composer.json file or a file specified by `--file`.
|
||||
* **--editor (-e):** Open the local composer.json file using in a text editor as
|
||||
defined by the `EDITOR` env variable. With the `--global` option, this opens
|
||||
the global config file.
|
||||
defined by the `EDITOR` env variable. With the `--global` option, this opens
|
||||
the global config file.
|
||||
* **--unset:** Remove the configuration element named by `setting-key`.
|
||||
* **--list (-l):** Show the list of current config variables. With the `--global`
|
||||
option this lists the global configuration only.
|
||||
option this lists the global configuration only.
|
||||
* **--file="..." (-f):** Operate on a specific file instead of composer.json. Note
|
||||
that this cannot be used in conjunction with the `--global` option.
|
||||
that this cannot be used in conjunction with the `--global` option.
|
||||
* **--absolute:** Returns absolute paths when fetching *-dir config values
|
||||
instead of relative.
|
||||
|
||||
### Modifying Repositories
|
||||
|
||||
In addition to modifying the config section, the `config` command also supports making
|
||||
changes to the repositories section by using it the following way:
|
||||
|
||||
$ php composer.phar config repositories.foo vcs http://github.com/foo/bar
|
||||
```sh
|
||||
php composer.phar config repositories.foo vcs http://github.com/foo/bar
|
||||
```
|
||||
|
||||
## create-project
|
||||
|
||||
|
@ -341,7 +440,9 @@ provide a version as third argument, otherwise the latest version is used.
|
|||
|
||||
If the directory does not currently exist, it will be created during installation.
|
||||
|
||||
php composer.phar create-project doctrine/orm path 2.2.*
|
||||
```sh
|
||||
php composer.phar create-project doctrine/orm path 2.2.*
|
||||
```
|
||||
|
||||
It is also possible to run the command without params in a directory with an
|
||||
existing `composer.json` file to bootstrap a project.
|
||||
|
@ -366,6 +467,9 @@ By default the command checks for the packages on packagist.org.
|
|||
* **--keep-vcs:** Skip the deletion of the VCS metadata for the created
|
||||
project. This is mostly useful if you run the command in non-interactive
|
||||
mode.
|
||||
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
|
||||
requirements and force the installation even if the local machine does not
|
||||
fulfill these.
|
||||
|
||||
## dump-autoload
|
||||
|
||||
|
@ -385,12 +489,22 @@ performance.
|
|||
* **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
|
||||
autoloader. This is recommended especially for production, but can take
|
||||
a bit of time to run so it is currently not done by default.
|
||||
* **--no-dev:** Disables autoload-dev rules.
|
||||
|
||||
## clear-cache
|
||||
|
||||
Deletes all content from Composer's cache directories.
|
||||
|
||||
## licenses
|
||||
|
||||
Lists the name, version and license of every package installed. Use
|
||||
`--format=json` to get machine readable output.
|
||||
|
||||
### Options
|
||||
|
||||
* **--no-dev:** Remove dev dependencies from the output
|
||||
* **--format:** Format of the output: text or json (default: "text")
|
||||
|
||||
## run-script
|
||||
|
||||
To run [scripts](articles/scripts.md) manually you can use this command,
|
||||
|
@ -402,7 +516,9 @@ If you think you found a bug, or something is behaving strangely, you might
|
|||
want to run the `diagnose` command to perform automated checks for many common
|
||||
problems.
|
||||
|
||||
$ php composer.phar diagnose
|
||||
```sh
|
||||
php composer.phar diagnose
|
||||
```
|
||||
|
||||
## archive
|
||||
|
||||
|
@ -410,7 +526,9 @@ This command is used to generate a zip/tar archive for a given package in a
|
|||
given version. It can also be used to archive your entire project without
|
||||
excluded/ignored files.
|
||||
|
||||
$ php composer.phar archive vendor/package 2.0.21 --format=zip
|
||||
```sh
|
||||
php composer.phar archive vendor/package 2.0.21 --format=zip
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -422,7 +540,9 @@ excluded/ignored files.
|
|||
|
||||
To get more information about a certain command, just use `help`.
|
||||
|
||||
$ php composer.phar help install
|
||||
```sh
|
||||
php composer.phar help install
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
|
@ -438,7 +558,9 @@ By setting the `COMPOSER` env variable it is possible to set the filename of
|
|||
|
||||
For example:
|
||||
|
||||
$ COMPOSER=composer-other.json php composer.phar install
|
||||
```sh
|
||||
COMPOSER=composer-other.json php composer.phar install
|
||||
```
|
||||
|
||||
### COMPOSER_ROOT_VERSION
|
||||
|
||||
|
|
481
doc/04-schema.md
481
doc/04-schema.md
|
@ -1,4 +1,4 @@
|
|||
# composer.json
|
||||
# The composer.json Schema
|
||||
|
||||
This chapter will explain all of the fields available in `composer.json`.
|
||||
|
||||
|
@ -54,19 +54,20 @@ The version of the package. In most cases this is not required and should
|
|||
be omitted (see below).
|
||||
|
||||
This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix
|
||||
of `-dev`, `-patch`, `-alpha`, `-beta` or `-RC`. The patch, alpha, beta and
|
||||
RC suffixes can also be followed by a number.
|
||||
of `-dev`, `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`.
|
||||
The patch, alpha, beta and RC suffixes can also be followed by a number.
|
||||
|
||||
Examples:
|
||||
|
||||
1.0.0
|
||||
1.0.2
|
||||
1.1.0
|
||||
0.2.5
|
||||
1.0.0-dev
|
||||
1.0.0-alpha3
|
||||
1.0.0-beta2
|
||||
1.0.0-RC5
|
||||
- 1.0.0
|
||||
- 1.0.2
|
||||
- 1.1.0
|
||||
- 0.2.5
|
||||
- 1.0.0-dev
|
||||
- 1.0.0-alpha3
|
||||
- 1.0.0-beta2
|
||||
- 1.0.0-RC5
|
||||
- v2.0.4-p1
|
||||
|
||||
Optional if the package repository can infer the version from somewhere, such
|
||||
as the VCS tag name in the VCS repository. In that case it is also recommended
|
||||
|
@ -113,11 +114,11 @@ searching and filtering.
|
|||
|
||||
Examples:
|
||||
|
||||
logging
|
||||
events
|
||||
database
|
||||
redis
|
||||
templating
|
||||
- logging
|
||||
- events
|
||||
- database
|
||||
- redis
|
||||
- templating
|
||||
|
||||
Optional.
|
||||
|
||||
|
@ -141,19 +142,19 @@ The license of the package. This can be either a string or an array of strings.
|
|||
|
||||
The recommended notation for the most common licenses is (alphabetical):
|
||||
|
||||
Apache-2.0
|
||||
BSD-2-Clause
|
||||
BSD-3-Clause
|
||||
BSD-4-Clause
|
||||
GPL-2.0
|
||||
GPL-2.0+
|
||||
GPL-3.0
|
||||
GPL-3.0+
|
||||
LGPL-2.1
|
||||
LGPL-2.1+
|
||||
LGPL-3.0
|
||||
LGPL-3.0+
|
||||
MIT
|
||||
- Apache-2.0
|
||||
- BSD-2-Clause
|
||||
- BSD-3-Clause
|
||||
- BSD-4-Clause
|
||||
- GPL-2.0
|
||||
- GPL-2.0+
|
||||
- GPL-3.0
|
||||
- GPL-3.0+
|
||||
- LGPL-2.1
|
||||
- LGPL-2.1+
|
||||
- LGPL-3.0
|
||||
- LGPL-3.0+
|
||||
- MIT
|
||||
|
||||
Optional, but it is highly recommended to supply this. More identifiers are
|
||||
listed at the [SPDX Open Source License Registry](http://www.spdx.org/licenses/).
|
||||
|
@ -162,28 +163,33 @@ For closed-source software, you may use `"proprietary"` as the license identifie
|
|||
|
||||
An Example:
|
||||
|
||||
{
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "MIT"
|
||||
}
|
||||
```
|
||||
|
||||
For a package, when there is a choice between licenses ("disjunctive license"),
|
||||
multiple can be specified as array.
|
||||
|
||||
An Example for disjunctive licenses:
|
||||
|
||||
{
|
||||
"license": [
|
||||
"LGPL-2.1",
|
||||
"GPL-3.0+"
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"license": [
|
||||
"LGPL-2.1",
|
||||
"GPL-3.0+"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively they can be separated with "or" and enclosed in parenthesis;
|
||||
|
||||
{
|
||||
"license": "(LGPL-2.1 or GPL-3.0+)"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"license": "(LGPL-2.1 or GPL-3.0+)"
|
||||
}
|
||||
```
|
||||
|
||||
Similarly when multiple licenses need to be applied ("conjunctive license"),
|
||||
they should be separated with "and" and enclosed in parenthesis.
|
||||
|
@ -201,22 +207,24 @@ Each author object can have following properties:
|
|||
|
||||
An example:
|
||||
|
||||
{
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Optional, but highly recommended.
|
||||
|
||||
|
@ -235,12 +243,14 @@ Support information includes the following:
|
|||
|
||||
An example:
|
||||
|
||||
{
|
||||
"support": {
|
||||
"email": "support@example.org",
|
||||
"irc": "irc://irc.freenode.org/composer"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"support": {
|
||||
"email": "support@example.org",
|
||||
"irc": "irc://irc.freenode.org/composer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Optional.
|
||||
|
||||
|
@ -251,11 +261,13 @@ All of the following take an object which maps package names to
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All links are optional fields.
|
||||
|
||||
|
@ -267,24 +279,28 @@ allow unstable packages of a dependency for example.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*@beta",
|
||||
"acme/foo": "@dev"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "1.0.*@beta",
|
||||
"acme/foo": "@dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If one of your dependencies has a dependency on an unstable package you need to
|
||||
explicitly require it as well, along with its sufficient stability flag.
|
||||
|
||||
Example:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/doctrine-fixtures-bundle": "dev-master",
|
||||
"doctrine/data-fixtures": "@dev"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"doctrine/doctrine-fixtures-bundle": "dev-master",
|
||||
"doctrine/data-fixtures": "@dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`require` and `require-dev` additionally support explicit references (i.e.
|
||||
commit) for dev versions to make sure they are locked to a given state, even
|
||||
|
@ -293,12 +309,14 @@ and append the reference with `#<ref>`.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7",
|
||||
"acme/foo": "1.0.x-dev#abc123"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7",
|
||||
"acme/foo": "1.0.x-dev#abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** While this is convenient at times, it should not be how you use
|
||||
> packages in the long term because it comes with a technical limitation. The
|
||||
|
@ -328,10 +346,10 @@ dependencies from being installed.
|
|||
Lists packages that conflict with this version of this package. They
|
||||
will not be allowed to be installed together with your package.
|
||||
|
||||
Note that when specifying ranges like `<1.0, >= 1.1` in a `conflict` link,
|
||||
Note that when specifying ranges like `<1.0 >=1.1` in a `conflict` link,
|
||||
this will state a conflict with all versions that are less than 1.0 *and* equal
|
||||
or newer than 1.1 at the same time, which is probably not what you want. You
|
||||
probably want to go for `<1.0 | >= 1.1` in this case.
|
||||
probably want to go for `<1.0 | >=1.1` in this case.
|
||||
|
||||
#### replace
|
||||
|
||||
|
@ -358,7 +376,7 @@ useful for common interfaces. A package could depend on some virtual
|
|||
`logger` package, any library that implements this logger interface would
|
||||
simply list it in `provide`.
|
||||
|
||||
### suggest
|
||||
#### suggest
|
||||
|
||||
Suggested packages that can enhance or work well with this package. These are
|
||||
just informational and are displayed after the package is installed, to give
|
||||
|
@ -370,11 +388,13 @@ and not version constraints.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"suggest": {
|
||||
"monolog/monolog": "Allows more advanced logging of the application flow"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"suggest": {
|
||||
"monolog/monolog": "Allows more advanced logging of the application flow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### autoload
|
||||
|
||||
|
@ -403,32 +423,38 @@ key => value array which may be found in the generated file
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/",
|
||||
"Vendor\\Namespace\\": "",
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/",
|
||||
"Vendor\\Namespace\\": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you need to search for a same prefix in multiple directories,
|
||||
you can specify them as an array as such:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": { "Monolog\\": ["src/", "lib/"] }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": { "Monolog\\": ["src/", "lib/"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to have a fallback directory where any namespace will be looked for,
|
||||
you can use an empty prefix like:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": { "": "src/" }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": { "": "src/" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### PSR-0
|
||||
|
||||
|
@ -444,44 +470,52 @@ array which may be found in the generated file `vendor/composer/autoload_namespa
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Monolog\\": "src/",
|
||||
"Vendor\\Namespace\\": "src/",
|
||||
"Vendor_Namespace_": "src/"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Monolog\\": "src/",
|
||||
"Vendor\\Namespace\\": "src/",
|
||||
"Vendor_Namespace_": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you need to search for a same prefix in multiple directories,
|
||||
you can specify them as an array as such:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "Monolog\\": ["src/", "lib/"] }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "Monolog\\": ["src/", "lib/"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The PSR-0 style is not limited to namespace declarations only but may be
|
||||
specified right down to the class level. This can be useful for libraries with
|
||||
only one class in the global namespace. If the php source file is also located
|
||||
in the root of the package, for example, it may be declared like this:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "UniqueGlobalClass": "" }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "UniqueGlobalClass": "" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to have a fallback directory where any namespace can be, you can
|
||||
use an empty prefix like:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "": "src/" }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "": "src/" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Classmap
|
||||
|
||||
|
@ -496,11 +530,13 @@ to search for classes.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"classmap": ["src/", "lib/", "Something.php"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"classmap": ["src/", "lib/", "Something.php"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Files
|
||||
|
||||
|
@ -510,11 +546,37 @@ that cannot be autoloaded by PHP.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"files": ["src/MyLibrary/functions.php"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"files": ["src/MyLibrary/functions.php"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### autoload-dev <span>(root-only)</span>
|
||||
|
||||
This section allows to define autoload rules for development purposes.
|
||||
|
||||
Classes needed to run the test suite should not be included in the main autoload
|
||||
rules to avoid polluting the autoloader in production and when other people use
|
||||
your package as a dependency.
|
||||
|
||||
Therefore, it is a good idea to rely on a dedicated path for your unit tests
|
||||
and to add it within the autoload-dev section.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": { "MyLibrary\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "MyLibrary\\Tests\\": "tests/" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### include-path
|
||||
|
||||
|
@ -526,9 +588,11 @@ A list of paths which should get appended to PHP's `include_path`.
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"include-path": ["lib/"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"include-path": ["lib/"]
|
||||
}
|
||||
```
|
||||
|
||||
Optional.
|
||||
|
||||
|
@ -552,12 +616,14 @@ it from `vendor/symfony/yaml`.
|
|||
|
||||
To do that, `autoload` and `target-dir` are defined as follows:
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Component\\Yaml\\": "" }
|
||||
},
|
||||
"target-dir": "Symfony/Component/Yaml"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Component\\Yaml\\": "" }
|
||||
},
|
||||
"target-dir": "Symfony/Component/Yaml"
|
||||
}
|
||||
```
|
||||
|
||||
Optional.
|
||||
|
||||
|
@ -615,47 +681,49 @@ For more information on any of these, see [Repositories](05-repositories.md).
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "http://packages.example.com"
|
||||
},
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.example.com",
|
||||
"options": {
|
||||
"ssl": {
|
||||
"verify_peer": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/Seldaek/monolog"
|
||||
},
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear2.php.net"
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "http://smarty-php.googlecode.com/svn/",
|
||||
"type": "svn",
|
||||
"reference": "tags/Smarty_3_1_7/distribution/"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "http://packages.example.com"
|
||||
},
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.example.com",
|
||||
"options": {
|
||||
"ssl": {
|
||||
"verify_peer": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/Seldaek/monolog"
|
||||
},
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear2.php.net"
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "http://smarty-php.googlecode.com/svn/",
|
||||
"type": "svn",
|
||||
"reference": "tags/Smarty_3_1_7/distribution/"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** Order is significant here. When looking for a package, Composer
|
||||
will look from the first to the last repository, and pick the first match.
|
||||
|
@ -676,21 +744,29 @@ The following options are supported:
|
|||
* **preferred-install:** Defaults to `auto` and can be any of `source`, `dist` or
|
||||
`auto`. This option allows you to set the install method Composer will prefer to
|
||||
use.
|
||||
* **github-protocols:** Defaults to `["git", "https"]`. A list of protocols to
|
||||
* **store-auths:** What to do after prompting for authentication, one of:
|
||||
`true` (always store), `false` (do not store) and `"prompt"` (ask every
|
||||
time), defaults to `"prompt"`.
|
||||
* **github-protocols:** Defaults to `["git", "https", "ssh"]`. A list of protocols to
|
||||
use when cloning from github.com, in priority order. You can reconfigure it to
|
||||
prioritize the https protocol if you are behind a proxy or have somehow bad
|
||||
performances with the git protocol.
|
||||
for example prioritize the https protocol if you are behind a proxy or have somehow
|
||||
bad performances with the git protocol.
|
||||
* **github-oauth:** A list of domain names and oauth keys. For example using
|
||||
`{"github.com": "oauthtoken"}` as the value of this option will use `oauthtoken`
|
||||
to access private repositories on github and to circumvent the low IP-based
|
||||
rate limiting of their API.
|
||||
[Read more](articles/troubleshooting.md#api-rate-limit-and-two-factor-authentication)
|
||||
on how to get an oauth token for GitHub.
|
||||
[Read more](articles/troubleshooting.md#api-rate-limit-and-oauth-tokens)
|
||||
on how to get an OAuth token for GitHub.
|
||||
* **http-basic:** A list of domain names and username/passwords to authenticate
|
||||
against them. For example using
|
||||
`{"example.org": {"username": "alice", "password": "foo"}` as the value of this
|
||||
option will let composer authenticate against example.org.
|
||||
* **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
|
||||
different directory if you want to.
|
||||
different directory if you want to. `$HOME` and `~` will be replaced by your
|
||||
home directory's path in vendor-dir and all `*-dir` options below.
|
||||
* **bin-dir:** Defaults to `vendor/bin`. If a project includes binaries, they
|
||||
will be symlinked into this directory.
|
||||
* **cache-dir:** Defaults to `$home/cache` on unix systems and
|
||||
* **cache-dir:** Defaults to `$COMPOSER_HOME/cache` on unix systems and
|
||||
`C:\Users\<user>\AppData\Local\Composer` on Windows. Stores all the caches
|
||||
used by composer. See also [COMPOSER_HOME](03-cli.md#composer-home).
|
||||
* **cache-files-dir:** Defaults to `$cache-dir/files`. Stores the zip archives
|
||||
|
@ -714,8 +790,14 @@ The following options are supported:
|
|||
the generated Composer autoloader. When null a random one will be generated.
|
||||
* **optimize-autoloader** Defaults to `false`. Always optimize when dumping
|
||||
the autoloader.
|
||||
* **classmap-authoritative:** Defaults to `false`. If true, the composer
|
||||
autoloader will not scan the filesystem for classes that are not found in
|
||||
the class map. Implies 'optimize-autoloader'.
|
||||
* **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
|
||||
github mode. This is used for GitHub Enterprise setups.
|
||||
* **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth
|
||||
tokens created to access the github API will have a date instead of the
|
||||
machine hostname.
|
||||
* **notify-on-install:** Defaults to `true`. Composer allows repositories to
|
||||
define a notification URL, so that they get notified whenever a package from
|
||||
that repository is installed. This option allows you to disable that behaviour.
|
||||
|
@ -727,11 +809,18 @@ The following options are supported:
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"config": {
|
||||
"bin-dir": "bin"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"bin-dir": "bin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** Authentication-related config options like `http-basic` and
|
||||
> `github-oauth` can also be specified inside a `auth.json` file that goes
|
||||
> besides your `composer.json`. That way you can gitignore it and every
|
||||
> developer can place their own credentials in there.
|
||||
|
||||
### scripts <span>(root-only)</span>
|
||||
|
||||
|
@ -747,7 +836,9 @@ Arbitrary extra data for consumption by `scripts`.
|
|||
This can be virtually anything. To access it from within a script event
|
||||
handler, you can do:
|
||||
|
||||
$extra = $event->getComposer()->getPackage()->getExtra();
|
||||
```php
|
||||
$extra = $event->getComposer()->getPackage()->getExtra();
|
||||
```
|
||||
|
||||
Optional.
|
||||
|
||||
|
@ -774,11 +865,13 @@ The following options are supported:
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"archive": {
|
||||
"exclude": ["/foo/bar", "baz", "/*.test", "!/foo/bar/baz"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"archive": {
|
||||
"exclude": ["/foo/bar", "baz", "/*.test", "!/foo/bar/baz"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`,
|
||||
`/foo/my.test` but it will exclude `/foo/bar/any`, `/foo/baz`, and `/my.test`.
|
||||
|
|
|
@ -66,16 +66,18 @@ repository URL would be `example.org`.
|
|||
|
||||
The only required field is `packages`. The JSON structure is as follows:
|
||||
|
||||
{
|
||||
"packages": {
|
||||
"vendor/package-name": {
|
||||
"dev-master": { @composer.json },
|
||||
"1.0.x-dev": { @composer.json },
|
||||
"0.0.1": { @composer.json },
|
||||
"1.0.0": { @composer.json }
|
||||
}
|
||||
```json
|
||||
{
|
||||
"packages": {
|
||||
"vendor/package-name": {
|
||||
"dev-master": { @composer.json },
|
||||
"1.0.x-dev": { @composer.json },
|
||||
"0.0.1": { @composer.json },
|
||||
"1.0.0": { @composer.json }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `@composer.json` marker would be the contents of the `composer.json` from
|
||||
that package version including as a minimum:
|
||||
|
@ -86,14 +88,16 @@ that package version including as a minimum:
|
|||
|
||||
Here is a minimal package definition:
|
||||
|
||||
{
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It may include any of the other fields specified in the [schema](04-schema.md).
|
||||
|
||||
|
@ -105,19 +109,23 @@ every time a user installs a package. The URL can be either an absolute path
|
|||
|
||||
An example value:
|
||||
|
||||
{
|
||||
"notify-batch": "/downloads/"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"notify-batch": "/downloads/"
|
||||
}
|
||||
```
|
||||
|
||||
For `example.org/packages.json` containing a `monolog/monolog` package, this
|
||||
would send a `POST` request to `example.org/downloads/` with following
|
||||
JSON request body:
|
||||
|
||||
{
|
||||
"downloads": [
|
||||
{"name": "monolog/monolog", "version": "1.2.1.0"},
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"downloads": [
|
||||
{"name": "monolog/monolog", "version": "1.2.1.0"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The version field will contain the normalized representation of the version
|
||||
number.
|
||||
|
@ -132,19 +140,21 @@ files.
|
|||
|
||||
An example:
|
||||
|
||||
{
|
||||
"includes": {
|
||||
"packages-2011.json": {
|
||||
"sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17"
|
||||
},
|
||||
"packages-2012-01.json": {
|
||||
"sha1": "897cde726f8a3918faf27c803b336da223d400dd"
|
||||
},
|
||||
"packages-2012-02.json": {
|
||||
"sha1": "26f911ad717da26bbcac3f8f435280d13917efa5"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"includes": {
|
||||
"packages-2011.json": {
|
||||
"sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17"
|
||||
},
|
||||
"packages-2012-01.json": {
|
||||
"sha1": "897cde726f8a3918faf27c803b336da223d400dd"
|
||||
},
|
||||
"packages-2012-02.json": {
|
||||
"sha1": "26f911ad717da26bbcac3f8f435280d13917efa5"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The SHA-1 sum of the file allows it to be cached and only re-requested if the
|
||||
hash changed.
|
||||
|
@ -164,35 +174,39 @@ is an absolute path from the repository root.
|
|||
|
||||
An example:
|
||||
|
||||
{
|
||||
"provider-includes": {
|
||||
"providers-a.json": {
|
||||
"sha256": "f5b4bc0b354108ef08614e569c1ed01a2782e67641744864a74e788982886f4c"
|
||||
},
|
||||
"providers-b.json": {
|
||||
"sha256": "b38372163fac0573053536f5b8ef11b86f804ea8b016d239e706191203f6efac"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"provider-includes": {
|
||||
"providers-a.json": {
|
||||
"sha256": "f5b4bc0b354108ef08614e569c1ed01a2782e67641744864a74e788982886f4c"
|
||||
},
|
||||
"providers-url": "/p/%package%$%hash%.json"
|
||||
}
|
||||
"providers-b.json": {
|
||||
"sha256": "b38372163fac0573053536f5b8ef11b86f804ea8b016d239e706191203f6efac"
|
||||
}
|
||||
},
|
||||
"providers-url": "/p/%package%$%hash%.json"
|
||||
}
|
||||
```
|
||||
|
||||
Those files contain lists of package names and hashes to verify the file
|
||||
integrity, for example:
|
||||
|
||||
{
|
||||
"providers": {
|
||||
"acme/foo": {
|
||||
"sha256": "38968de1305c2e17f4de33aea164515bc787c42c7e2d6e25948539a14268bb82"
|
||||
},
|
||||
"acme/bar": {
|
||||
"sha256": "4dd24c930bd6e1103251306d6336ac813b563a220d9ca14f4743c032fb047233"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"acme/foo": {
|
||||
"sha256": "38968de1305c2e17f4de33aea164515bc787c42c7e2d6e25948539a14268bb82"
|
||||
},
|
||||
"acme/bar": {
|
||||
"sha256": "4dd24c930bd6e1103251306d6336ac813b563a220d9ca14f4743c032fb047233"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The file above declares that acme/foo and acme/bar can be found in this
|
||||
repository, by loading the file referenced by `providers-url`, replacing
|
||||
`%name%` by the package name and `%hash%` by the sha256 field. Those files
|
||||
`%package%` by the package name and `%hash%` by the sha256 field. Those files
|
||||
themselves just contain package definitions as described [above](#packages).
|
||||
|
||||
This field is optional. You probably don't need it for your own custom
|
||||
|
@ -225,17 +239,19 @@ point to your custom branch. For version constraint naming conventions see
|
|||
|
||||
Example assuming you patched monolog to fix a bug in the `bugfix` branch:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/igorw/monolog"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"monolog/monolog": "dev-bugfix"
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/igorw/monolog"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"monolog/monolog": "dev-bugfix"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When you run `php composer.phar update`, you should get your modified version
|
||||
of `monolog/monolog` instead of the one from packagist.
|
||||
|
@ -256,17 +272,19 @@ For more information [see the aliases article](articles/aliases.md).
|
|||
Exactly the same solution allows you to work with your private repositories at
|
||||
GitHub and BitBucket:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"vendor/my-private-repo": "dev-master"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git@bitbucket.org:vendor/my-private-repo.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"vendor/my-private-repo": "dev-master"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "git@bitbucket.org:vendor/my-private-repo.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The only requirement is the installation of SSH keys for a git client.
|
||||
|
||||
|
@ -292,6 +310,11 @@ The VCS driver to be used is detected automatically based on the URL. However,
|
|||
should you need to specify one for whatever reason, you can use `git`, `svn` or
|
||||
`hg` as the repository type instead of `vcs`.
|
||||
|
||||
If you set the `no-api` key to `true` on a github repository it will clone the
|
||||
repository as it would with any other git repository instead of using the
|
||||
GitHub API. But unlike using the `git` driver directly, composer will still
|
||||
attempt to use github's zip files.
|
||||
|
||||
#### Subversion Options
|
||||
|
||||
Since Subversion has no native concept of branches and tags, Composer assumes
|
||||
|
@ -300,17 +323,19 @@ by default that code is located in `$url/trunk`, `$url/branches` and
|
|||
values. For example if you used capitalized names you could configure the
|
||||
repository like this:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "http://svn.example.org/projectA/",
|
||||
"trunk-path": "Trunk",
|
||||
"branches-path": "Branches",
|
||||
"tags-path": "Tags"
|
||||
}
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "http://svn.example.org/projectA/",
|
||||
"trunk-path": "Trunk",
|
||||
"branches-path": "Branches",
|
||||
"tags-path": "Tags"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If you have no branches or tags directory you can disable them entirely by
|
||||
setting the `branches-path` or `tags-path` to `false`.
|
||||
|
@ -320,6 +345,37 @@ If the package is in a sub-directory, e.g. `/trunk/foo/bar/composer.json` and
|
|||
setting the `"package-path"` option to the sub-directory, in this example it
|
||||
would be `"package-path": "foo/bar/"`.
|
||||
|
||||
If you have a private Subversion repository you can save credentials in the
|
||||
http-basic section of your config (See [Schema](04-schema.md)):
|
||||
|
||||
```json
|
||||
{
|
||||
"http-basic": {
|
||||
"svn.example.org": {
|
||||
"username": "username",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your Subversion client is configured to store credentials by default these
|
||||
credentials will be saved for the current user and existing saved credentials
|
||||
for this server will be overwritten. To change this behavior by setting the
|
||||
`"svn-cache-credentials"` option in your repository configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "http://svn.example.org/projectA/",
|
||||
"svn-cache-credentials": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### PEAR
|
||||
|
||||
It is possible to install packages from any PEAR channel by using the `pear`
|
||||
|
@ -328,18 +384,20 @@ avoid conflicts. All packages are also aliased with prefix `pear-{channelAlias}/
|
|||
|
||||
Example using `pear2.php.net`:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear2.php.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"pear-pear2.php.net/PEAR2_Text_Markdown": "*",
|
||||
"pear-pear2/PEAR2_HTTP_Request": "*"
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear2.php.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"pear-pear2.php.net/PEAR2_Text_Markdown": "*",
|
||||
"pear-pear2/PEAR2_HTTP_Request": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case the short name of the channel is `pear2`, so the
|
||||
`PEAR2_HTTP_Request` package name becomes `pear-pear2/PEAR2_HTTP_Request`.
|
||||
|
@ -382,23 +440,25 @@ To illustrate, the following example would get the `BasePackage`,
|
|||
`TopLevelPackage1`, and `TopLevelPackage2` packages from your PEAR repository
|
||||
and `IntermediatePackage` from a Github repository:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/foobar/intermediate.git"
|
||||
},
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear.foobar.repo",
|
||||
"vendor-alias": "foobar"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"foobar/TopLevelPackage1": "*",
|
||||
"foobar/TopLevelPackage2": "*"
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/foobar/intermediate.git"
|
||||
},
|
||||
{
|
||||
"type": "pear",
|
||||
"url": "http://pear.foobar.repo",
|
||||
"vendor-alias": "foobar"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"foobar/TopLevelPackage1": "*",
|
||||
"foobar/TopLevelPackage2": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Package
|
||||
|
||||
|
@ -413,32 +473,34 @@ minimum required fields are `name`, `version`, and either of `dist` or
|
|||
|
||||
Here is an example for the smarty template engine:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "http://smarty-php.googlecode.com/svn/",
|
||||
"type": "svn",
|
||||
"reference": "tags/Smarty_3_1_7/distribution/"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["libs/"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "smarty/smarty",
|
||||
"version": "3.1.7",
|
||||
"dist": {
|
||||
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "http://smarty-php.googlecode.com/svn/",
|
||||
"type": "svn",
|
||||
"reference": "tags/Smarty_3_1_7/distribution/"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["libs/"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"smarty/smarty": "3.1.*"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"smarty/smarty": "3.1.*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Typically you would leave the source part off, as you don't really need it.
|
||||
|
||||
|
@ -463,7 +525,7 @@ there are some use cases for hosting your own repository.
|
|||
might want to keep them separate to packagist. An example of this would be
|
||||
wordpress plugins.
|
||||
|
||||
For hosting your own packages, a native `composer` type of repository is
|
||||
For hosting your own packages, a native `composer` type of repository is
|
||||
recommended, which provides the best performance.
|
||||
|
||||
There are a few tools that can help you create a `composer` repository.
|
||||
|
@ -507,25 +569,30 @@ of the times they are private. To simplify maintenance, one can simply use a
|
|||
repository of type `artifact` with a folder containing ZIP archives of those
|
||||
private packages:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "artifact",
|
||||
"url": "path/to/directory/with/zips/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"private-vendor-one/core": "15.6.2",
|
||||
"private-vendor-two/connectivity": "*",
|
||||
"acme-corp/parser": "10.3.5"
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "artifact",
|
||||
"url": "path/to/directory/with/zips/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"private-vendor-one/core": "15.6.2",
|
||||
"private-vendor-two/connectivity": "*",
|
||||
"acme-corp/parser": "10.3.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each zip artifact is just a ZIP archive with `composer.json` in root folder:
|
||||
|
||||
$ unzip -l acme-corp-parser-10.3.5.zip
|
||||
composer.json
|
||||
...
|
||||
```sh
|
||||
unzip -l acme-corp-parser-10.3.5.zip
|
||||
|
||||
composer.json
|
||||
...
|
||||
```
|
||||
|
||||
If there are two archives with different versions of a package, they are both
|
||||
imported. When an archive with a newer version is added in the artifact folder
|
||||
|
@ -537,13 +604,14 @@ update to the latest version.
|
|||
You can disable the default Packagist repository by adding this to your
|
||||
`composer.json`:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"packagist": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"packagist": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
← [Schema](04-schema.md) | [Community](06-community.md) →
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
## Why aliases?
|
||||
|
||||
When you are using a VCS repository, you will only get comparable versions for
|
||||
branches that look like versions, such as `2.0`. For your `master` branch, you
|
||||
branches that look like versions, such as `2.0` or `2.0.x`. For your `master` branch, you
|
||||
will get a `dev-master` version. For your `bugfix` branch, you will get a
|
||||
`dev-bugfix` version.
|
||||
|
||||
|
@ -28,18 +28,24 @@ someone will want the latest master dev version. Thus, Composer allows you to
|
|||
alias your `dev-master` branch to a `1.0.x-dev` version. It is done by
|
||||
specifying a `branch-alias` field under `extra` in `composer.json`:
|
||||
|
||||
{
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The branch version must begin with `dev-` (non-comparable version), the alias
|
||||
must be a comparable dev version (i.e. start with numbers, and end with
|
||||
`.x-dev`). The `branch-alias` must be present on the branch that it references.
|
||||
For `dev-master`, you need to commit it on the `master` branch.
|
||||
If you alias a non-comparible version (such as dev-develop) `dev-` must prefix the
|
||||
branch name. You may also alias a comparible version (i.e. start with numbers,
|
||||
and end with `.x-dev`), but only as a more specific version.
|
||||
For example, 1.x-dev could be aliased as 1.2.x-dev.
|
||||
|
||||
The alias must be a comparable dev version, and the `branch-alias` must be present on
|
||||
the branch that it references. For `dev-master`, you need to commit it on the
|
||||
`master` branch.
|
||||
|
||||
As a result, anyone can now require `1.0.*` and it will happily install
|
||||
`dev-master`.
|
||||
|
@ -68,18 +74,20 @@ You are using `symfony/monolog-bundle` which requires `monolog/monolog` version
|
|||
|
||||
Just add this to your project's root `composer.json`:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/you/monolog"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"symfony/monolog-bundle": "2.0",
|
||||
"monolog/monolog": "dev-bugfix as 1.0.x-dev"
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/you/monolog"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"symfony/monolog-bundle": "2.0",
|
||||
"monolog/monolog": "dev-bugfix as 1.0.x-dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub
|
||||
and alias it to `1.0.x-dev`.
|
||||
|
|
|
@ -34,13 +34,15 @@ An example use-case would be:
|
|||
|
||||
An example composer.json of such a template package would be:
|
||||
|
||||
{
|
||||
"name": "phpdocumentor/template-responsive",
|
||||
"type": "phpdocumentor-template",
|
||||
"require": {
|
||||
"phpdocumentor/template-installer-plugin": "*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "phpdocumentor/template-responsive",
|
||||
"type": "phpdocumentor-template",
|
||||
"require": {
|
||||
"phpdocumentor/template-installer-plugin": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **IMPORTANT**: to make sure that the template installer is present at the
|
||||
> time the template package is installed, template packages should require
|
||||
|
@ -70,20 +72,22 @@ requirements:
|
|||
|
||||
Example:
|
||||
|
||||
{
|
||||
"name": "phpdocumentor/template-installer-plugin",
|
||||
"type": "composer-plugin",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {"phpDocumentor\\Composer": "src/"}
|
||||
},
|
||||
"extra": {
|
||||
"class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "1.0.0"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "phpdocumentor/template-installer-plugin",
|
||||
"type": "composer-plugin",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {"phpDocumentor\\Composer": "src/"}
|
||||
},
|
||||
"extra": {
|
||||
"class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### The Plugin class
|
||||
|
||||
|
@ -96,20 +100,24 @@ autoloadable and matches the `extra.class` element in the package definition.
|
|||
|
||||
Example:
|
||||
|
||||
namespace phpDocumentor\Composer;
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
namespace phpDocumentor\Composer;
|
||||
|
||||
class TemplateInstallerPlugin implements PluginInterface
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
|
||||
class TemplateInstallerPlugin implements PluginInterface
|
||||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$installer = new TemplateInstaller($io, $composer);
|
||||
$composer->getInstallationManager()->addInstaller($installer);
|
||||
}
|
||||
$installer = new TemplateInstaller($io, $composer);
|
||||
$composer->getInstallationManager()->addInstaller($installer);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### The Custom Installer class
|
||||
|
||||
|
@ -138,39 +146,43 @@ source for the exact signature):
|
|||
|
||||
Example:
|
||||
|
||||
namespace phpDocumentor\Composer;
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Installer\LibraryInstaller;
|
||||
namespace phpDocumentor\Composer;
|
||||
|
||||
class TemplateInstaller extends LibraryInstaller
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Installer\LibraryInstaller;
|
||||
|
||||
class TemplateInstaller extends LibraryInstaller
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getPackageBasePath(PackageInterface $package)
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getPackageBasePath(PackageInterface $package)
|
||||
{
|
||||
$prefix = substr($package->getPrettyName(), 0, 23);
|
||||
if ('phpdocumentor/template-' !== $prefix) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Unable to install template, phpdocumentor templates '
|
||||
.'should always start their package name with '
|
||||
.'"phpdocumentor/template-"'
|
||||
);
|
||||
}
|
||||
|
||||
return 'data/templates/'.substr($package->getPrettyName(), 23);
|
||||
$prefix = substr($package->getPrettyName(), 0, 23);
|
||||
if ('phpdocumentor/template-' !== $prefix) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Unable to install template, phpdocumentor templates '
|
||||
.'should always start their package name with '
|
||||
.'"phpdocumentor/template-"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($packageType)
|
||||
{
|
||||
return 'phpdocumentor-template' === $packageType;
|
||||
}
|
||||
return 'data/templates/'.substr($package->getPrettyName(), 23);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($packageType)
|
||||
{
|
||||
return 'phpdocumentor-template' === $packageType;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The example demonstrates that it is quite simple to extend the
|
||||
[`Composer\Installer\LibraryInstaller`][5] class to strip a prefix
|
||||
(`phpdocumentor/template-`) and use the remaining part to assemble a completely
|
||||
|
|
|
@ -2,14 +2,22 @@
|
|||
tagline: Host your own composer repository
|
||||
-->
|
||||
|
||||
# Handling private packages with Satis
|
||||
# Handling private packages with Satis or Toran Proxy
|
||||
|
||||
Satis is a static `composer` repository generator. It is a bit like an ultra-
|
||||
lightweight, static file-based version of packagist and can be used to host the
|
||||
metadata of your company's private packages, or your own. It basically acts as
|
||||
a micro-packagist. You can get it from
|
||||
[GitHub](http://github.com/composer/satis) or install via CLI:
|
||||
`composer.phar create-project composer/satis --stability=dev`.
|
||||
# Toran Proxy
|
||||
|
||||
[Toran Proxy](https://toranproxy.com/) is a commercial alternative to Satis offering professional support as well as a web UI to manage everything and a better integration with Composer.
|
||||
|
||||
Toran's revenue is also used to pay for Composer and Packagist development and hosting so using it is a good way to support open source financially. You can find more information about how to set it up and use it on the [Toran Proxy](https://toranproxy.com/) website.
|
||||
|
||||
# Satis
|
||||
|
||||
Satis on the other hand is open source but only a static `composer`
|
||||
repository generator. It is a bit like an ultra-lightweight, static file-based
|
||||
version of packagist and can be used to host the metadata of your company's
|
||||
private packages, or your own. You can get it from [GitHub](http://github.com/composer/satis)
|
||||
or install via CLI:
|
||||
`php composer.phar create-project composer/satis --stability=dev --keep-vcs`.
|
||||
|
||||
## Setup
|
||||
|
||||
|
@ -25,36 +33,40 @@ repositories you defined.
|
|||
|
||||
The default file Satis looks for is `satis.json` in the root of the repository.
|
||||
|
||||
{
|
||||
"name": "My Repository",
|
||||
"homepage": "http://packages.example.org",
|
||||
"repositories": [
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
|
||||
{ "type": "vcs", "url": "http://svn.example.org/private/repo" },
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
|
||||
],
|
||||
"require-all": true
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "My Repository",
|
||||
"homepage": "http://packages.example.org",
|
||||
"repositories": [
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
|
||||
{ "type": "vcs", "url": "http://svn.example.org/private/repo" },
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
|
||||
],
|
||||
"require-all": true
|
||||
}
|
||||
```
|
||||
|
||||
If you want to cherry pick which packages you want, you can list all the packages
|
||||
you want to have in your satis repository inside the classic composer `require` key,
|
||||
using a `"*"` constraint to make sure all versions are selected, or another
|
||||
constraint if you want really specific versions.
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
|
||||
{ "type": "vcs", "url": "http://svn.example.org/private/repo" },
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
|
||||
],
|
||||
"require": {
|
||||
"company/package": "*",
|
||||
"company/package2": "*",
|
||||
"company/package3": "2.0.0"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
|
||||
{ "type": "vcs", "url": "http://svn.example.org/private/repo" },
|
||||
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
|
||||
],
|
||||
"require": {
|
||||
"company/package": "*",
|
||||
"company/package2": "*",
|
||||
"company/package3": "2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once you did this, you just run `php bin/satis build <configuration file> <build dir>`.
|
||||
Once you've done this, you just run `php bin/satis build <configuration file> <build dir>`.
|
||||
For example `php bin/satis build config.json web/` would read the `config.json`
|
||||
file and build a static repository inside the `web/` directory.
|
||||
|
||||
|
@ -80,14 +92,16 @@ everything should work smoothly. You don't need to copy all your repositories
|
|||
in every project anymore. Only that one unique repository that will update
|
||||
itself.
|
||||
|
||||
{
|
||||
"repositories": [ { "type": "composer", "url": "http://packages.example.org/" } ],
|
||||
"require": {
|
||||
"company/package": "1.2.0",
|
||||
"company/package2": "1.5.2",
|
||||
"company/package3": "dev-master"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [ { "type": "composer", "url": "http://packages.example.org/" } ],
|
||||
"require": {
|
||||
"company/package": "1.2.0",
|
||||
"company/package2": "1.5.2",
|
||||
"company/package3": "dev-master"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
|
@ -97,39 +111,43 @@ connection options for the server.
|
|||
|
||||
Example using a custom repository using SSH (requires the SSH2 PECL extension):
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "ssh2.sftp://example.org",
|
||||
"options": {
|
||||
"ssh2": {
|
||||
"username": "composer",
|
||||
"pubkey_file": "/home/composer/.ssh/id_rsa.pub",
|
||||
"privkey_file": "/home/composer/.ssh/id_rsa"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "ssh2.sftp://example.org",
|
||||
"options": {
|
||||
"ssh2": {
|
||||
"username": "composer",
|
||||
"pubkey_file": "/home/composer/.ssh/id_rsa.pub",
|
||||
"privkey_file": "/home/composer/.ssh/id_rsa"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Tip:** See [ssh2 context options](http://www.php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-options) for more information.
|
||||
|
||||
Example using HTTP over SSL using a client certificate:
|
||||
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://example.org",
|
||||
"options": {
|
||||
"ssl": {
|
||||
"local_cert": "/home/composer/.ssl/composer.pem"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://example.org",
|
||||
"options": {
|
||||
"ssl": {
|
||||
"local_cert": "/home/composer/.ssl/composer.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Tip:** See [ssl context options](http://www.php.net/manual/en/context.ssl.php) for more information.
|
||||
|
||||
|
@ -145,14 +163,16 @@ Subversion) will not have downloads available and thus installations usually tak
|
|||
To enable your satis installation to create downloads for all (Git, Mercurial and Subversion) your packages, add the
|
||||
following to your `satis.json`:
|
||||
|
||||
{
|
||||
"archive": {
|
||||
"directory": "dist",
|
||||
"format": "tar",
|
||||
"prefix-url": "https://amazing.cdn.example.org",
|
||||
"skip-dev": true
|
||||
}
|
||||
```json
|
||||
{
|
||||
"archive": {
|
||||
"directory": "dist",
|
||||
"format": "tar",
|
||||
"prefix-url": "https://amazing.cdn.example.org",
|
||||
"skip-dev": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Options explained
|
||||
|
||||
|
@ -178,11 +198,14 @@ It is possible to make satis automatically resolve and add all dependencies for
|
|||
with the Downloads functionality to have a complete local mirror of packages. Just add the following
|
||||
to your `satis.json`:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"require-dependencies": true
|
||||
"require-dependencies": true,
|
||||
"require-dev-dependencies": true
|
||||
}
|
||||
```
|
||||
|
||||
When searching for packages, satis will attempt to resolve all the required packages from the listed repositories.
|
||||
Therefore, if you are requiring a package from Packagist, you will need to define it in your `satis.json`.
|
||||
|
||||
Dev dependencies are packaged only if the `require-dev-dependencies` parameter is set to true.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<!--
|
||||
tagline: Access privately hosted packages
|
||||
-->
|
||||
|
||||
# HTTP basic authentication
|
||||
|
||||
Your [Satis or Toran Proxy](handling-private-packages-with-satis.md) server
|
||||
could be secured with http basic authentication. In order to allow your project
|
||||
to have access to these packages you will have to tell composer how to
|
||||
authenticate with your credentials.
|
||||
|
||||
The simplest way to provide your credentials is providing your set
|
||||
of credentials inline with the repository specification such as:
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "http://extremely:secret@repo.example.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This will basically teach composer how to authenticate automatically
|
||||
when reading packages from the provided composer repository.
|
||||
|
||||
This does not work for everybody especially when you don't want to
|
||||
hard code your credentials into your composer.json. There is a second
|
||||
way to provide these details and it is via interaction. If you don't
|
||||
provide the authentication credentials composer will prompt you upon
|
||||
connection to enter the username and password.
|
||||
|
||||
The third way if you want to pre-configure it is via an `auth.json` file
|
||||
located in your `COMPOSER_HOME` or besides your `composer.json`.
|
||||
|
||||
The file should contain a set of hostnames followed each with their own
|
||||
username/password pairs, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"basic-auth": {
|
||||
"repo.example1.org": {
|
||||
"username": "my-username1",
|
||||
"password": "my-secret-password1"
|
||||
},
|
||||
"repo.example2.org": {
|
||||
"username": "my-username2",
|
||||
"password": "my-secret-password2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The main advantage of the auth.json file is that it can be gitignored so
|
||||
that every developer in your team can place their own credentials in there,
|
||||
which makes revokation of credentials much easier than if you all share the
|
||||
same.
|
|
@ -35,13 +35,15 @@ current composer plugin API version is 1.0.0.
|
|||
|
||||
For example
|
||||
|
||||
{
|
||||
"name": "my/plugin-package",
|
||||
"type": "composer-plugin",
|
||||
"require": {
|
||||
"composer-plugin-api": "1.0.0"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "my/plugin-package",
|
||||
"type": "composer-plugin",
|
||||
"require": {
|
||||
"composer-plugin-api": "1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin Class
|
||||
|
||||
|
@ -54,20 +56,24 @@ be read and all internal objects and state can be manipulated as desired.
|
|||
|
||||
Example:
|
||||
|
||||
namespace phpDocumentor\Composer;
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
namespace phpDocumentor\Composer;
|
||||
|
||||
class TemplateInstallerPlugin implements PluginInterface
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
|
||||
class TemplateInstallerPlugin implements PluginInterface
|
||||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$installer = new TemplateInstaller($io, $composer);
|
||||
$composer->getInstallationManager()->addInstaller($installer);
|
||||
}
|
||||
$installer = new TemplateInstaller($io, $composer);
|
||||
$composer->getInstallationManager()->addInstaller($installer);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Handler
|
||||
|
||||
|
@ -88,46 +94,50 @@ The events available for plugins are:
|
|||
|
||||
Example:
|
||||
|
||||
namespace Naderman\Composer\AWS;
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PreFileDownloadEvent;
|
||||
namespace Naderman\Composer\AWS;
|
||||
|
||||
class AwsPlugin implements PluginInterface, EventSubscriberInterface
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PreFileDownloadEvent;
|
||||
|
||||
class AwsPlugin implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
protected $composer;
|
||||
protected $io;
|
||||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
protected $composer;
|
||||
protected $io;
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
}
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
PluginEvents::PRE_FILE_DOWNLOAD => array(
|
||||
array('onPreFileDownload', 0)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
PluginEvents::PRE_FILE_DOWNLOAD => array(
|
||||
array('onPreFileDownload', 0)
|
||||
),
|
||||
);
|
||||
}
|
||||
public function onPreFileDownload(PreFileDownloadEvent $event)
|
||||
{
|
||||
$protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
|
||||
|
||||
public function onPreFileDownload(PreFileDownloadEvent $event)
|
||||
{
|
||||
$protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
|
||||
|
||||
if ($protocol === 's3') {
|
||||
$awsClient = new AwsClient($this->io, $this->composer->getConfig());
|
||||
$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient);
|
||||
$event->setRemoteFilesystem($s3RemoteFilesystem);
|
||||
}
|
||||
if ($protocol === 's3') {
|
||||
$awsClient = new AwsClient($this->io, $this->composer->getConfig());
|
||||
$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient);
|
||||
$event->setRemoteFilesystem($s3RemoteFilesystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Plugins
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ static method) or any command-line executable command. Scripts are useful
|
|||
for executing a package's custom code or package-specific commands during
|
||||
the Composer execution process.
|
||||
|
||||
**NOTE: Only scripts defined in the root package's `composer.json` are
|
||||
executed. If a dependency of the root package specifies its own scripts,
|
||||
Composer does not execute those additional scripts.**
|
||||
> **Note:** Only scripts defined in the root package's `composer.json` are
|
||||
> executed. If a dependency of the root package specifies its own scripts,
|
||||
> Composer does not execute those additional scripts.
|
||||
|
||||
|
||||
## Event names
|
||||
|
@ -26,6 +26,8 @@ Composer fires the following named events during its execution process:
|
|||
- **post-update-cmd**: occurs after the `update` command is executed.
|
||||
- **pre-status-cmd**: occurs before the `status` command is executed.
|
||||
- **post-status-cmd**: occurs after the `status` command is executed.
|
||||
- **pre-dependencies-solving**: occurs before the dependencies are resolved.
|
||||
- **post-dependencies-solving**: occurs after the dependencies are resolved.
|
||||
- **pre-package-install**: occurs before a package is installed.
|
||||
- **post-package-install**: occurs after a package is installed.
|
||||
- **pre-package-update**: occurs before a package is updated.
|
||||
|
@ -40,13 +42,15 @@ Composer fires the following named events during its execution process:
|
|||
installed, during the `create-project` command.
|
||||
- **post-create-project-cmd**: occurs after the `create-project` command is
|
||||
executed.
|
||||
- **pre-archive-cmd**: occurs before the `archive` command is executed.
|
||||
- **post-archive-cmd**: occurs after the `archive` command is executed.
|
||||
|
||||
**NOTE: Composer makes no assumptions about the state of your dependencies
|
||||
prior to `install` or `update`. Therefore, you should not specify scripts that
|
||||
require Composer-managed dependencies in the `pre-update-cmd` or
|
||||
`pre-install-cmd` event hooks. If you need to execute scripts prior to
|
||||
`install` or `update` please make sure they are self-contained within your
|
||||
root package.**
|
||||
> **Note:** Composer makes no assumptions about the state of your dependencies
|
||||
> prior to `install` or `update`. Therefore, you should not specify scripts
|
||||
> that require Composer-managed dependencies in the `pre-update-cmd` or
|
||||
> `pre-install-cmd` event hooks. If you need to execute scripts prior to
|
||||
> `install` or `update` please make sure they are self-contained within your
|
||||
> root package.
|
||||
|
||||
## Defining scripts
|
||||
|
||||
|
@ -59,54 +63,61 @@ For any given event:
|
|||
|
||||
- Scripts execute in the order defined when their corresponding event is fired.
|
||||
- An array of scripts wired to a single event can contain both PHP callbacks
|
||||
and command-line executables commands.
|
||||
and command-line executable commands.
|
||||
- PHP classes containing defined callbacks must be autoloadable via Composer's
|
||||
autoload functionality.
|
||||
|
||||
Script definition example:
|
||||
|
||||
{
|
||||
"scripts": {
|
||||
"post-update-cmd": "MyVendor\\MyClass::postUpdate",
|
||||
"post-package-install": [
|
||||
"MyVendor\\MyClass::postPackageInstall"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"MyVendor\\MyClass::warmCache",
|
||||
"phpunit -c app/"
|
||||
]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"post-update-cmd": "MyVendor\\MyClass::postUpdate",
|
||||
"post-package-install": [
|
||||
"MyVendor\\MyClass::postPackageInstall"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"MyVendor\\MyClass::warmCache",
|
||||
"phpunit -c app/"
|
||||
],
|
||||
"post-create-project-cmd" : [
|
||||
"php -r \"copy('config/local-example.php', 'config/local.php');\""
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using the previous definition example, here's the class `MyVendor\MyClass`
|
||||
that might be used to execute the PHP callbacks:
|
||||
|
||||
<?php
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace MyVendor;
|
||||
namespace MyVendor;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\Event;
|
||||
|
||||
class MyClass
|
||||
class MyClass
|
||||
{
|
||||
public static function postUpdate(Event $event)
|
||||
{
|
||||
public static function postUpdate(Event $event)
|
||||
{
|
||||
$composer = $event->getComposer();
|
||||
// do stuff
|
||||
}
|
||||
|
||||
public static function postPackageInstall(Event $event)
|
||||
{
|
||||
$installedPackage = $event->getOperation()->getPackage();
|
||||
// do stuff
|
||||
}
|
||||
|
||||
public static function warmCache(Event $event)
|
||||
{
|
||||
// make cache toasty
|
||||
}
|
||||
$composer = $event->getComposer();
|
||||
// do stuff
|
||||
}
|
||||
|
||||
public static function postPackageInstall(Event $event)
|
||||
{
|
||||
$installedPackage = $event->getOperation()->getPackage();
|
||||
// do stuff
|
||||
}
|
||||
|
||||
public static function warmCache(Event $event)
|
||||
{
|
||||
// make cache toasty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When an event is fired, Composer's internal event handler receives a
|
||||
`Composer\Script\Event` object, which is passed as the first argument to your
|
||||
PHP callback. This `Event` object has getters for other contextual objects:
|
||||
|
@ -120,6 +131,33 @@ PHP callback. This `Event` object has getters for other contextual objects:
|
|||
|
||||
If you would like to run the scripts for an event manually, the syntax is:
|
||||
|
||||
$ composer run-script [--dev] [--no-dev] script
|
||||
```sh
|
||||
composer run-script [--dev] [--no-dev] script
|
||||
```
|
||||
|
||||
For example `composer run-script post-install-cmd` will run any **post-install-cmd** scripts that have been defined.
|
||||
For example `composer run-script post-install-cmd` will run any
|
||||
**post-install-cmd** scripts that have been defined.
|
||||
|
||||
You can also give additional arguments to the script handler by appending `--`
|
||||
followed by the handler arguments. e.g.
|
||||
`composer run-script post-install-cmd -- --check` will pass`--check` along to
|
||||
the script handler. Those arguments are received as CLI arg by CLI handlers,
|
||||
and can be retrieved as an array via `$event->getArguments()` by PHP handlers.
|
||||
|
||||
## Writing custom commands
|
||||
|
||||
If you add custom scripts that do not fit one of the predefined event name
|
||||
above, you can either run them with run-script or also run them as native
|
||||
Composer commands. For example the handler defined below is executable by
|
||||
simply running `composer test`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** Composer's bin-dir is pushed on top of the PATH so that binaries
|
||||
> of dependencies are easily accessible as CLI commands when writing scripts.
|
||||
|
|
|
@ -21,6 +21,8 @@ This is a list of common pitfalls on using Composer, and how to avoid them.
|
|||
possible interferences with existing vendor installations or `composer.lock`
|
||||
entries.
|
||||
|
||||
5. Try clearing Composer's cache by running `composer clear-cache`.
|
||||
|
||||
## Package not found
|
||||
|
||||
1. Double-check you **don't have typos** in your `composer.json` or repository
|
||||
|
@ -38,6 +40,9 @@ This is a list of common pitfalls on using Composer, and how to avoid them.
|
|||
your repository, especially when maintaining a third party fork and using
|
||||
`replace`.
|
||||
|
||||
5. If you are updating to a recently published version of a package, be aware that
|
||||
Packagist has a delay of up to 1 minute before new packages are visible to Composer.
|
||||
|
||||
## Package not found on travis-ci.org
|
||||
|
||||
1. Check the ["Package not found"](#package-not-found) item above.
|
||||
|
@ -63,12 +68,14 @@ You can fix this by aliasing version 0.11 to 0.1:
|
|||
|
||||
composer.json:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"A": "0.2",
|
||||
"B": "0.11 as 0.1"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"A": "0.2",
|
||||
"B": "0.11 as 0.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [aliases](aliases.md) for more information.
|
||||
|
||||
|
@ -76,7 +83,7 @@ See [aliases](aliases.md) for more information.
|
|||
|
||||
If composer shows memory errors on some commands:
|
||||
|
||||
PHP Fatal error: Allowed memory size of XXXXXX bytes exhausted <...>
|
||||
`PHP Fatal error: Allowed memory size of XXXXXX bytes exhausted <...>`
|
||||
|
||||
The PHP `memory_limit` should be increased.
|
||||
|
||||
|
@ -86,35 +93,67 @@ The PHP `memory_limit` should be increased.
|
|||
|
||||
To get the current `memory_limit` value, run:
|
||||
|
||||
php -r "echo ini_get('memory_limit').PHP_EOL;"
|
||||
```sh
|
||||
php -r "echo ini_get('memory_limit').PHP_EOL;"
|
||||
```
|
||||
|
||||
Try increasing the limit in your `php.ini` file (ex. `/etc/php5/cli/php.ini` for
|
||||
Debian-like systems):
|
||||
|
||||
; Use -1 for unlimited or define an explicit value like 512M
|
||||
memory_limit = -1
|
||||
```ini
|
||||
; Use -1 for unlimited or define an explicit value like 512M
|
||||
memory_limit = -1
|
||||
```
|
||||
|
||||
Or, you can increase the limit with a command-line argument:
|
||||
|
||||
php -d memory_limit=-1 composer.phar <...>
|
||||
```sh
|
||||
php -d memory_limit=-1 composer.phar <...>
|
||||
```
|
||||
|
||||
## "The system cannot find the path specified" (Windows)
|
||||
|
||||
1. Open regedit.
|
||||
2. Search for an ```AutoRun``` key inside ```HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor```
|
||||
or ```HKEY_CURRENT_USER\Software\Microsoft\Command Processor```.
|
||||
2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`,
|
||||
`HKEY_CURRENT_USER\Software\Microsoft\Command Processor`
|
||||
or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`.
|
||||
3. Check if it contains any path to non-existent file, if it's the case, just remove them.
|
||||
|
||||
## API rate limit and two factor authentication
|
||||
## API rate limit and OAuth tokens
|
||||
|
||||
Because of GitHub's rate limits on their API it can happen that Composer prompts
|
||||
for authentication asking your username and password so it can go ahead with its work.
|
||||
Unfortunately this will not work if you enabled two factor authentication on
|
||||
your GitHub account and to solve this issue you need to:
|
||||
|
||||
1. [Create](https://github.com/settings/applications) an oauthtoken on GitHub.
|
||||
If you would prefer not to provide your GitHub credentials to Composer you can
|
||||
manually create a token using the following procedure:
|
||||
|
||||
1. [Create](https://github.com/settings/applications) an OAuth token on GitHub.
|
||||
[Read more](https://github.com/blog/1509-personal-api-tokens) on this.
|
||||
|
||||
2. Add it to the configuration running `composer config -g github-oauth.github.com <oauthtoken>`
|
||||
|
||||
Now Composer should install/update without asking for authentication.
|
||||
|
||||
## proc_open(): fork failed errors
|
||||
If composer shows proc_open() fork failed on some commands:
|
||||
|
||||
`PHP Fatal error: Uncaught exception 'ErrorException' with message 'proc_open(): fork failed - Cannot allocate memory' in phar`
|
||||
|
||||
This could be happening because the VPS runs out of memory and has no Swap space enabled.
|
||||
|
||||
```sh
|
||||
free -m
|
||||
|
||||
total used free shared buffers cached
|
||||
Mem: 2048 357 1690 0 0 237
|
||||
-/+ buffers/cache: 119 1928
|
||||
Swap: 0 0 0
|
||||
```
|
||||
|
||||
To enable the swap you can use for example:
|
||||
|
||||
```sh
|
||||
/bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
|
||||
/sbin/mkswap /var/swap.1
|
||||
/sbin/swapon /var/swap.1
|
||||
```
|
||||
|
|
|
@ -20,10 +20,11 @@ It is defined by adding the `bin` key to a project's `composer.json`.
|
|||
It is specified as an array of files so multiple binaries can be added
|
||||
for any given project.
|
||||
|
||||
{
|
||||
"bin": ["bin/my-script", "bin/my-other-script"]
|
||||
}
|
||||
|
||||
```json
|
||||
{
|
||||
"bin": ["bin/my-script", "bin/my-other-script"]
|
||||
}
|
||||
```
|
||||
|
||||
## What does defining a vendor binary in composer.json do?
|
||||
|
||||
|
@ -46,22 +47,26 @@ symlink is created from each dependency's binaries to `vendor/bin`.
|
|||
|
||||
Say package `my-vendor/project-a` has binaries setup like this:
|
||||
|
||||
{
|
||||
"name": "my-vendor/project-a",
|
||||
"bin": ["bin/project-a-bin"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "my-vendor/project-a",
|
||||
"bin": ["bin/project-a-bin"]
|
||||
}
|
||||
```
|
||||
|
||||
Running `composer install` for this `composer.json` will not do
|
||||
anything with `bin/project-a-bin`.
|
||||
|
||||
Say project `my-vendor/project-b` has requirements setup like this:
|
||||
|
||||
{
|
||||
"name": "my-vendor/project-b",
|
||||
"require": {
|
||||
"my-vendor/project-a": "*"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "my-vendor/project-b",
|
||||
"require": {
|
||||
"my-vendor/project-a": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Running `composer install` for this `composer.json` will look at
|
||||
all of project-b's dependencies and install them to `vendor/bin`.
|
||||
|
@ -95,12 +100,16 @@ Yes, there are two ways an alternate vendor binary location can be specified:
|
|||
|
||||
An example of the former looks like this:
|
||||
|
||||
{
|
||||
"config": {
|
||||
"bin-dir": "scripts"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"bin-dir": "scripts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Running `composer install` for this `composer.json` will result in
|
||||
all of the vendor binaries being installed in `scripts/` instead of
|
||||
`vendor/bin/`.
|
||||
|
||||
You can set `bin-dir` to `./` to put binaries in your project root.
|
||||
|
|
|
@ -11,13 +11,15 @@ This is common if your package is intended for a specific framework such as
|
|||
CakePHP, Drupal or WordPress. Here is an example composer.json file for a
|
||||
WordPress theme:
|
||||
|
||||
{
|
||||
"name": "you/themename",
|
||||
"type": "wordpress-theme",
|
||||
"require": {
|
||||
"composer/installers": "~1.0"
|
||||
}
|
||||
```json
|
||||
{
|
||||
"name": "you/themename",
|
||||
"type": "wordpress-theme",
|
||||
"require": {
|
||||
"composer/installers": "~1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now when your theme is installed with Composer it will be placed into
|
||||
`wp-content/themes/themename/` folder. Check the
|
||||
|
@ -30,13 +32,15 @@ useful example would be for a Drupal multisite setup where the package should be
|
|||
installed into your sites subdirectory. Here we are overriding the install path
|
||||
for a module that uses composer/installers:
|
||||
|
||||
{
|
||||
"extra": {
|
||||
"installer-paths": {
|
||||
"sites/example.com/modules/{$name}": ["vendor/package"]
|
||||
}
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"installer-paths": {
|
||||
"sites/example.com/modules/{$name}": ["vendor/package"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the package would be installed to your folder location, rather than the default
|
||||
composer/installers determined location.
|
||||
|
|
|
@ -24,6 +24,7 @@ If you really feel like you must do this, you have a few options:
|
|||
[config](../04-schema.md#config).
|
||||
3. Remove the `.git` directory of every dependency after the installation, then
|
||||
you can add them to your git repo. You can do that with `rm -rf vendor/**/.git`
|
||||
in ZSH or `find vendor/ -type d -name ".git" -exec rm -rf {} \;` in Bash.
|
||||
but this means you will have to delete those dependencies from disk before
|
||||
running composer update.
|
||||
4. Add a .gitignore rule (`vendor/.git`) to ignore all the vendor `.git` folders.
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Why are unbound version constraints a bad idea?
|
||||
|
||||
A version constraint without an upper bound such as `*`, `>=3.4` or
|
||||
`dev-master` will allow updates to any future version of the dependency.
|
||||
This includes major versions breaking backward compatibility.
|
||||
|
||||
Once a release of your package is tagged, you cannot tweak its dependencies
|
||||
anymore in case a dependency breaks BC - you have to do a new release but the
|
||||
previous one stays broken.
|
||||
|
||||
The only good alternative is to define an upper bound on your constraints,
|
||||
which you can increase in a new release after testing that your package is
|
||||
compatible with the new major version of your dependency.
|
||||
|
||||
For example instead of using `>=3.4` you should use `~3.4` which allows all
|
||||
versions up to `3.999` but does not include `4.0` and above. The `~` operator
|
||||
works very well with libraries follow [semantic versioning](http://semver.org).
|
||||
|
||||
**Note:** As a package maintainer, you can make the life of your users easier
|
||||
by providing an [alias version](../articles/aliases.md) for your development
|
||||
branch to allow it to match bound constraints.
|
|
@ -9,7 +9,7 @@ that the main use of custom VCS & package repositories is to temporarily try
|
|||
some things, or use a fork of a project until your pull request is merged, etc.
|
||||
You should not use them to keep track of private packages. For that you should
|
||||
look into [setting up Satis](../articles/handling-private-packages-with-satis.md)
|
||||
for your company or even for yourself.
|
||||
or getting a [Toran Proxy](https://toranproxy.com) license for your company.
|
||||
|
||||
There are three ways the dependency solver could work with custom repositories:
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
`Composer` type repository fixtures
|
||||
=======================
|
||||
|
||||
This directory contains some examples of what `composer` type repositories can look like. They serve as illustrating examples accompanying the docs, but can also be used as (initial) fixtures for tests.
|
||||
|
||||
* `repo-composer-plain` is a simple, plain `packages.json` file
|
||||
* `repo-composer-with-includes` uses the `includes` mechanism
|
||||
* `repo-composer-with-providers` uses the `providers` mechanism
|
||||
|
||||
Sample Packages used in these fixtures
|
||||
-------
|
||||
|
||||
All these repositories contain the following packages.
|
||||
|
||||
* `foo/bar` versions 1.0.0, 1.0.1 and 1.1.0; dev-default and 1.0.x-dev branches. On dev-default and in 1.1.0, `bar/baz` ~1.0 is required.
|
||||
* `qux/quux` only has a dev-default branch. It `replace`s `gar/nix`.
|
||||
* `gar/nix` has a 1.0.0 version and a dev-default branch. It is being replaced by `qux/quux`.
|
||||
* `bar/baz` has a 1.0.0 version and 1.0.x-dev as well as dev-default branches. Additionally, 1.1.x-dev is a branch alias for dev-default.
|
||||
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
{
|
||||
"packages": {
|
||||
"bar/baz": {
|
||||
"1.0.0": {
|
||||
"name": "bar/baz",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "35810817c14d"
|
||||
},
|
||||
"time": "2014-10-13 12:04:55",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "bar/baz",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "ffff9aae6ed5"
|
||||
},
|
||||
"time": "2014-10-13 12:05:37",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "bar/baz",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "f317e556f2e2"
|
||||
},
|
||||
"time": "2014-10-13 12:06:45",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-default": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"foo/bar": {
|
||||
"1.0.0": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "249dec95a52a"
|
||||
},
|
||||
"time": "2014-10-11 15:42:00",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.1": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "21e3328295d4"
|
||||
},
|
||||
"time": "2014-10-11 15:45:56",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "14dc17c8e860"
|
||||
},
|
||||
"time": "2014-10-11 15:45:59",
|
||||
"type": "library"
|
||||
},
|
||||
"1.1.0": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.1.0",
|
||||
"version_normalized": "1.1.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "d2fa3e69ad5b"
|
||||
},
|
||||
"require": {
|
||||
"bar/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:16",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "foo/bar",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "8e5a5c224336"
|
||||
},
|
||||
"require": {
|
||||
"bar/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:18",
|
||||
"type": "library"
|
||||
}
|
||||
},
|
||||
"gar/nix": {
|
||||
"1.0.0": {
|
||||
"name": "gar/nix",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "44977145d64e"
|
||||
},
|
||||
"time": "2014-10-13 12:03:33",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "gar/nix",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "51cca95a31c2"
|
||||
},
|
||||
"time": "2014-10-13 12:03:35",
|
||||
"type": "library"
|
||||
}
|
||||
},
|
||||
"qux/quux": {
|
||||
"dev-default": {
|
||||
"name": "qux/quux",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "4a10a567baa5"
|
||||
},
|
||||
"replace": {
|
||||
"gar/nix": "1.0.*"
|
||||
},
|
||||
"time": "2014-10-11 15:48:15",
|
||||
"type": "library"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
{
|
||||
"packages": {
|
||||
"bar/baz": {
|
||||
"1.0.0": {
|
||||
"name": "bar/baz",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "35810817c14d"
|
||||
},
|
||||
"time": "2014-10-13 12:04:55",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "bar/baz",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "ffff9aae6ed5"
|
||||
},
|
||||
"time": "2014-10-13 12:05:37",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "bar/baz",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "f317e556f2e2"
|
||||
},
|
||||
"time": "2014-10-13 12:06:45",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-default": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"foo/bar": {
|
||||
"1.0.0": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "249dec95a52a"
|
||||
},
|
||||
"time": "2014-10-11 15:42:00",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.1": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "21e3328295d4"
|
||||
},
|
||||
"time": "2014-10-11 15:45:56",
|
||||
"type": "library"
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "14dc17c8e860"
|
||||
},
|
||||
"time": "2014-10-11 15:45:59",
|
||||
"type": "library"
|
||||
},
|
||||
"1.1.0": {
|
||||
"name": "foo/bar",
|
||||
"version": "1.1.0",
|
||||
"version_normalized": "1.1.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "d2fa3e69ad5b"
|
||||
},
|
||||
"require": {
|
||||
"bar/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:16",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "foo/bar",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "8e5a5c224336"
|
||||
},
|
||||
"require": {
|
||||
"bar/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:18",
|
||||
"type": "library"
|
||||
}
|
||||
},
|
||||
"gar/nix": {
|
||||
"1.0.0": {
|
||||
"name": "gar/nix",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "44977145d64e"
|
||||
},
|
||||
"time": "2014-10-13 12:03:33",
|
||||
"type": "library"
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "gar/nix",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "51cca95a31c2"
|
||||
},
|
||||
"time": "2014-10-13 12:03:35",
|
||||
"type": "library"
|
||||
}
|
||||
},
|
||||
"qux/quux": {
|
||||
"dev-default": {
|
||||
"name": "qux/quux",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http://some.where/over/the/rainbow/",
|
||||
"reference": "4a10a567baa5"
|
||||
},
|
||||
"replace": {
|
||||
"gar/nix": "1.0.*"
|
||||
},
|
||||
"time": "2014-10-11 15:48:15",
|
||||
"type": "library"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"packages": [
|
||||
|
||||
],
|
||||
"includes": {
|
||||
"include/all$5fa86b937f0502d92f776072cd49c002dca742b9.json": {
|
||||
"sha1": "5fa86b937f0502d92f776072cd49c002dca742b9"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"packages": {
|
||||
"bar\/baz": {
|
||||
"1.0.0": {
|
||||
"name": "bar\/baz",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "35810817c14d"
|
||||
},
|
||||
"time": "2014-10-13 12:04:55",
|
||||
"type": "library",
|
||||
"uid": 0
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "bar\/baz",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "ffff9aae6ed5"
|
||||
},
|
||||
"time": "2014-10-13 12:05:37",
|
||||
"type": "library",
|
||||
"uid": 1
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "bar\/baz",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "f317e556f2e2"
|
||||
},
|
||||
"time": "2014-10-13 12:06:45",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-default": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"uid": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"packages": {
|
||||
"foo\/bar": {
|
||||
"1.0.0": {
|
||||
"name": "foo\/bar",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "249dec95a52a"
|
||||
},
|
||||
"time": "2014-10-11 15:42:00",
|
||||
"type": "library",
|
||||
"uid": 3
|
||||
},
|
||||
"1.0.1": {
|
||||
"name": "foo\/bar",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "21e3328295d4"
|
||||
},
|
||||
"time": "2014-10-11 15:45:56",
|
||||
"type": "library",
|
||||
"uid": 4
|
||||
},
|
||||
"1.0.x-dev": {
|
||||
"name": "foo\/bar",
|
||||
"version": "1.0.x-dev",
|
||||
"version_normalized": "1.0.9999999.9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "14dc17c8e860"
|
||||
},
|
||||
"time": "2014-10-11 15:45:59",
|
||||
"type": "library",
|
||||
"uid": 5
|
||||
},
|
||||
"1.1.0": {
|
||||
"name": "foo\/bar",
|
||||
"version": "1.1.0",
|
||||
"version_normalized": "1.1.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "d2fa3e69ad5b"
|
||||
},
|
||||
"require": {
|
||||
"bar\/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:16",
|
||||
"type": "library",
|
||||
"uid": 6
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "foo\/bar",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "8e5a5c224336"
|
||||
},
|
||||
"require": {
|
||||
"bar\/baz": "~1.0"
|
||||
},
|
||||
"time": "2014-10-11 15:43:18",
|
||||
"type": "library",
|
||||
"uid": 7
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"packages": {
|
||||
"qux\/quux": {
|
||||
"dev-default": {
|
||||
"name": "qux\/quux",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "4a10a567baa5"
|
||||
},
|
||||
"replace": {
|
||||
"gar\/nix": "1.0.*"
|
||||
},
|
||||
"time": "2014-10-11 15:48:15",
|
||||
"type": "library",
|
||||
"uid": 10
|
||||
}
|
||||
},
|
||||
"gar\/nix": {
|
||||
"1.0.0": {
|
||||
"name": "gar\/nix",
|
||||
"version": "1.0.0",
|
||||
"version_normalized": "1.0.0.0",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "44977145d64e"
|
||||
},
|
||||
"time": "2014-10-13 12:03:33",
|
||||
"type": "library",
|
||||
"uid": 8
|
||||
},
|
||||
"dev-default": {
|
||||
"name": "gar\/nix",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "51cca95a31c2"
|
||||
},
|
||||
"time": "2014-10-13 12:03:35",
|
||||
"type": "library",
|
||||
"uid": 9
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"providers": {
|
||||
"bar\/baz": {
|
||||
"sha256": "923363b3c22e73abb2e3fd891c8156dd4d0821a97fd3e428bc910833e3e46dbe"
|
||||
},
|
||||
"foo\/bar": {
|
||||
"sha256": "4baabb3303afa3e34a4d3af18fb138e5f3b79029c1f8d9ab5b477ea15776ba0a"
|
||||
},
|
||||
"gar\/nix": {
|
||||
"sha256": "5d210670cb46c8364c8e3fb449967b9bea558b971e5b082f330ae4f1d484c321"
|
||||
},
|
||||
"qux\/quux": {
|
||||
"sha256": "c142d1a07ca354be46b613f59f1d601923a5a00ccc5fcce50a77ecdd461eb72d"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"packages": {
|
||||
"qux\/quux": {
|
||||
"dev-default": {
|
||||
"name": "qux\/quux",
|
||||
"version": "dev-default",
|
||||
"version_normalized": "9999999-dev",
|
||||
"source": {
|
||||
"type": "hg",
|
||||
"url": "http:\/\/some.where\/over\/the\/rainbow\/",
|
||||
"reference": "4a10a567baa5"
|
||||
},
|
||||
"replace": {
|
||||
"gar\/nix": "1.0.*"
|
||||
},
|
||||
"time": "2014-10-11 15:48:15",
|
||||
"type": "library",
|
||||
"uid": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"packages": [],
|
||||
"providers-url": "\/p\/%package%$%hash%.json",
|
||||
"provider-includes": {
|
||||
"p\/provider-active$1893a061e579543822389ecd12d791c612db0c05e22d90e9286e233cacd86ed8.json": {
|
||||
"sha256": "1893a061e579543822389ecd12d791c612db0c05e22d90e9286e233cacd86ed8"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"name": "Package",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [ "name", "description" ],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Package name, including 'vendor-name/' prefix.",
|
||||
"required": true
|
||||
"description": "Package name, including 'vendor-name/' prefix."
|
||||
},
|
||||
"type": {
|
||||
"description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
|
||||
|
@ -18,8 +19,7 @@
|
|||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Short package description.",
|
||||
"required": true
|
||||
"description": "Short package description."
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"description": "Package release date, in 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' format."
|
||||
"description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format."
|
||||
},
|
||||
"license": {
|
||||
"type": ["string", "array"],
|
||||
|
@ -51,11 +51,11 @@
|
|||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [ "name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Full name of the author.",
|
||||
"required": true
|
||||
"description": "Full name of the author."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
@ -136,6 +136,15 @@
|
|||
"description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"http-basic": {
|
||||
"type": "object",
|
||||
"description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"store-auths": {
|
||||
"type": ["string", "boolean"],
|
||||
"description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt."
|
||||
},
|
||||
"vendor-dir": {
|
||||
"type": "string",
|
||||
"description": "The location where all packages are installed, defaults to \"vendor\"."
|
||||
|
@ -182,18 +191,26 @@
|
|||
},
|
||||
"optimize-autoloader": {
|
||||
"type": "boolean",
|
||||
"description": "Always optimize when dumping the autoloader"
|
||||
"description": "Always optimize when dumping the autoloader."
|
||||
},
|
||||
"prepend-autoloader": {
|
||||
"type": "boolean",
|
||||
"description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
|
||||
},
|
||||
"classmap-authoritative": {
|
||||
"type": "boolean",
|
||||
"description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
|
||||
},
|
||||
"github-domains": {
|
||||
"type": "array",
|
||||
"description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"github-expose-hostname": {
|
||||
"type": "boolean",
|
||||
"description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -230,6 +247,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"type": "object",
|
||||
"description": "Description of additional autoload rules for development purpose (eg. a test suite).",
|
||||
"properties": {
|
||||
"psr-0": {
|
||||
"type": "object",
|
||||
"description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"psr-4": {
|
||||
"type": "object",
|
||||
"description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"classmap": {
|
||||
"type": "array",
|
||||
"description": "This is an array of directories that contain classes to be included in the class-map generation process."
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"description": "This is an array of files that are always required on every request."
|
||||
}
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"type": ["object"],
|
||||
"description": "Options for creating package archives for distribution.",
|
||||
|
@ -247,7 +288,8 @@
|
|||
},
|
||||
"minimum-stability": {
|
||||
"type": ["string"],
|
||||
"description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable."
|
||||
"description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.",
|
||||
"pattern": "^dev|alpha|beta|rc|RC|stable$"
|
||||
},
|
||||
"prefer-stable": {
|
||||
"type": ["boolean"],
|
||||
|
|
|
@ -1,42 +1,59 @@
|
|||
[
|
||||
"AFL-1.1", "AFL-1.2", "AFL-2.0", "AFL-2.1", "AFL-3.0", "APL-1.0", "Aladdin",
|
||||
"ANTLR-PD", "Apache-1.0", "Apache-1.1", "Apache-2.0", "APSL-1.0",
|
||||
"APSL-1.1", "APSL-1.2", "APSL-2.0", "Artistic-1.0", "Artistic-2.0", "AAL",
|
||||
"BitTorrent-1.0", "BitTorrent-1.1", "BSL-1.0", "BSD-3-Clause-Clear",
|
||||
"BSD-2-Clause", "BSD-2-Clause-FreeBSD", "BSD-2-Clause-NetBSD",
|
||||
"BSD-3-Clause", "BSD-4-Clause", "BSD-4-Clause-UC", "CECILL-1.0",
|
||||
"CECILL-1.1", "CECILL-2.0", "CECILL-B", "CECILL-C", "ClArtistic",
|
||||
"CNRI-Python", "CNRI-Python-GPL-Compatible", "CDDL-1.0", "CDDL-1.1",
|
||||
"CPAL-1.0", "CPL-1.0", "CATOSL-1.1", "Condor-1.1", "CC-BY-1.0", "CC-BY-2.0",
|
||||
"CC-BY-2.5", "CC-BY-3.0", "CC-BY-ND-1.0", "CC-BY-ND-2.0", "CC-BY-ND-2.5",
|
||||
"CC-BY-ND-3.0", "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5",
|
||||
"CC-BY-NC-3.0", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-2.0", "CC-BY-NC-ND-2.5",
|
||||
"CC-BY-NC-ND-3.0", "CC-BY-NC-SA-1.0", "CC-BY-NC-SA-2.0", "CC-BY-NC-SA-2.5",
|
||||
"CC-BY-NC-SA-3.0", "CC-BY-SA-1.0", "CC-BY-SA-2.0", "CC-BY-SA-2.5",
|
||||
"CC-BY-SA-3.0", "CC0-1.0", "CUA-OPL-1.0", "WTFPL", "EPL-1.0", "eCos-2.0",
|
||||
"ECL-1.0", "ECL-2.0", "EFL-1.0", "EFL-2.0", "Entessa", "ErlPL-1.1",
|
||||
"EUDatagrid", "EUPL-1.0", "EUPL-1.1", "Fair", "Frameworx-1.0", "FTL",
|
||||
"AGPL-3.0", "GFDL-1.1", "GFDL-1.2", "GFDL-1.3", "GPL-1.0", "GPL-1.0+",
|
||||
"GPL-2.0", "GPL-2.0+", "GPL-2.0-with-autoconf-exception",
|
||||
"Glide", "Abstyles", "AFL-1.1", "AFL-1.2", "AFL-2.0", "AFL-2.1", "AFL-3.0",
|
||||
"AMPAS", "APL-1.0", "Adobe-Glyph", "APAFML", "Adobe-2006", "AGPL-1.0",
|
||||
"Afmparse", "Aladdin", "ADSL", "AMDPLPA", "ANTLR-PD", "Apache-1.0",
|
||||
"Apache-1.1", "Apache-2.0", "AML", "APSL-1.0", "APSL-1.1", "APSL-1.2",
|
||||
"APSL-2.0", "Artistic-1.0", "Artistic-1.0-Perl", "Artistic-1.0-cl8",
|
||||
"Artistic-2.0", "AAL", "Bahyph", "Barr", "Beerware", "BitTorrent-1.0",
|
||||
"BitTorrent-1.1", "BSL-1.0", "Borceux", "BSD-2-Clause",
|
||||
"BSD-2-Clause-FreeBSD", "BSD-2-Clause-NetBSD", "BSD-3-Clause",
|
||||
"BSD-3-Clause-Clear", "BSD-4-Clause", "BSD-Protection",
|
||||
"BSD-3-Clause-Attribution", "BSD-4-Clause-UC", "bzip2-1.0.5", "bzip2-1.0.6",
|
||||
"Caldera", "CECILL-1.0", "CECILL-1.1", "CECILL-2.0", "CECILL-B", "CECILL-C",
|
||||
"ClArtistic", "MIT-CMU", "CNRI-Python", "CNRI-Python-GPL-Compatible",
|
||||
"CPOL-1.02", "CDDL-1.0", "CDDL-1.1", "CPAL-1.0", "CPL-1.0", "CATOSL-1.1",
|
||||
"Condor-1.1", "CC-BY-1.0", "CC-BY-2.0", "CC-BY-2.5", "CC-BY-3.0",
|
||||
"CC-BY-4.0", "CC-BY-ND-1.0", "CC-BY-ND-2.0", "CC-BY-ND-2.5", "CC-BY-ND-3.0",
|
||||
"CC-BY-ND-4.0", "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5",
|
||||
"CC-BY-NC-3.0", "CC-BY-NC-4.0", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-2.0",
|
||||
"CC-BY-NC-ND-2.5", "CC-BY-NC-ND-3.0", "CC-BY-NC-ND-4.0", "CC-BY-NC-SA-1.0",
|
||||
"CC-BY-NC-SA-2.0", "CC-BY-NC-SA-2.5", "CC-BY-NC-SA-3.0", "CC-BY-NC-SA-4.0",
|
||||
"CC-BY-SA-1.0", "CC-BY-SA-2.0", "CC-BY-SA-2.5", "CC-BY-SA-3.0",
|
||||
"CC-BY-SA-4.0", "CC0-1.0", "Crossword", "CUA-OPL-1.0", "Cube", "D-FSL-1.0",
|
||||
"diffmark", "WTFPL", "DOC", "Dotseqn", "DSDP", "dvipdfm", "EPL-1.0",
|
||||
"eCos-2.0", "ECL-1.0", "ECL-2.0", "eGenix", "EFL-1.0", "EFL-2.0",
|
||||
"MIT-advertising", "MIT-enna", "Entessa", "ErlPL-1.1", "EUDatagrid",
|
||||
"EUPL-1.0", "EUPL-1.1", "Eurosym", "Fair", "MIT-feh", "Frameworx-1.0",
|
||||
"FTL", "FSFUL", "FSFULLR", "Giftware", "GL2PS", "Glulxe", "AGPL-3.0",
|
||||
"GFDL-1.1", "GFDL-1.2", "GFDL-1.3", "GPL-1.0", "GPL-1.0+", "GPL-2.0",
|
||||
"GPL-2.0+", "GPL-2.0-with-autoconf-exception",
|
||||
"GPL-2.0-with-bison-exception", "GPL-2.0-with-classpath-exception",
|
||||
"GPL-2.0-with-font-exception", "GPL-2.0-with-GCC-exception", "GPL-3.0",
|
||||
"GPL-3.0+", "GPL-3.0-with-autoconf-exception", "GPL-3.0-with-GCC-exception",
|
||||
"LGPL-2.1", "LGPL-2.1+", "LGPL-3.0", "LGPL-3.0+", "LGPL-2.0", "LGPL-2.0+",
|
||||
"gSOAP-1.3b", "HPND", "IPL-1.0", "Imlib2", "IJG", "Intel", "IPA", "ISC",
|
||||
"JSON", "LPPL-1.3a", "LPPL-1.0", "LPPL-1.1", "LPPL-1.2", "LPPL-1.3c",
|
||||
"Libpng", "LPL-1.02", "LPL-1.0", "MS-PL", "MS-RL", "MirOS", "MIT",
|
||||
"Motosoto", "MPL-1.0", "MPL-1.1", "MPL-2.0",
|
||||
"MPL-2.0-no-copyleft-exception", "Multics", "NASA-1.3", "Naumen",
|
||||
"NBPL-1.0", "NGPL", "NOSL", "NPL-1.0", "NPL-1.1", "Nokia", "NPOSL-3.0",
|
||||
"NTP", "OCLC-2.0", "ODbL-1.0", "PDDL-1.0", "OGTSL", "OLDAP-2.2.2",
|
||||
"gnuplot", "gSOAP-1.3b", "HaskellReport", "HPND", "IBM-pibs", "IPL-1.0",
|
||||
"ImageMagick", "iMatix", "Imlib2", "IJG", "Intel-ACPI", "Intel", "IPA",
|
||||
"ISC", "JasPer-2.0", "JSON", "LPPL-1.3a", "LPPL-1.0", "LPPL-1.1",
|
||||
"LPPL-1.2", "LPPL-1.3c", "Latex2e", "BSD-3-Clause-LBNL", "Leptonica",
|
||||
"Libpng", "libtiff", "LPL-1.02", "LPL-1.0", "MakeIndex", "MTLL", "MS-PL",
|
||||
"MS-RL", "MirOS", "MITNFA", "MIT", "Motosoto", "MPL-1.0", "MPL-1.1",
|
||||
"MPL-2.0", "MPL-2.0-no-copyleft-exception", "mpich2", "Multics", "Mup",
|
||||
"NASA-1.3", "Naumen", "NBPL-1.0", "NetCDF", "NGPL", "NOSL", "NPL-1.0",
|
||||
"NPL-1.1", "Newsletr", "NLPL", "Nokia", "NPOSL-3.0", "Noweb", "NRL", "NTP",
|
||||
"Nunit", "OCLC-2.0", "ODbL-1.0", "PDDL-1.0", "OGTSL", "OLDAP-2.2.2",
|
||||
"OLDAP-1.1", "OLDAP-1.2", "OLDAP-1.3", "OLDAP-1.4", "OLDAP-2.0",
|
||||
"OLDAP-2.0.1", "OLDAP-2.1", "OLDAP-2.2", "OLDAP-2.2.1", "OLDAP-2.3",
|
||||
"OLDAP-2.4", "OLDAP-2.5", "OLDAP-2.6", "OLDAP-2.7", "OPL-1.0", "OSL-1.0",
|
||||
"OSL-2.0", "OSL-2.1", "OSL-3.0", "OLDAP-2.8", "OpenSSL", "PHP-3.0",
|
||||
"PHP-3.01", "PostgreSQL", "Python-2.0", "QPL-1.0", "RPSL-1.0", "RPL-1.5",
|
||||
"RHeCos-1.1", "RSCPL", "Ruby", "SAX-PD", "SGI-B-1.0", "SGI-B-1.1",
|
||||
"SGI-B-2.0", "OFL-1.0", "OFL-1.1", "SimPL-2.0", "Sleepycat", "SMLNJ",
|
||||
"SugarCRM-1.1.3", "SISSL", "SPL-1.0", "Watcom-1.0", "NCSA", "VSL-1.0",
|
||||
"W3C", "WXwindows", "Xnet", "X11", "XFree86-1.1", "YPL-1.0", "YPL-1.1",
|
||||
"Zimbra-1.3", "Zlib", "ZPL-1.1", "ZPL-2.0", "ZPL-2.1"
|
||||
]
|
||||
"OLDAP-2.4", "OLDAP-2.5", "OLDAP-2.6", "OLDAP-2.7", "OML", "OPL-1.0",
|
||||
"OSL-1.0", "OSL-1.1", "OSL-2.0", "OSL-2.1", "OSL-3.0", "OLDAP-2.8",
|
||||
"OpenSSL", "PHP-3.0", "PHP-3.01", "Plexus", "PostgreSQL", "psfrag",
|
||||
"psutils", "Python-2.0", "QPL-1.0", "Qhull", "Rdisc", "RPSL-1.0", "RPL-1.1",
|
||||
"RPL-1.5", "RHeCos-1.1", "RSCPL", "Ruby", "SAX-PD", "Saxpath", "SCEA",
|
||||
"SWL", "SGI-B-1.0", "SGI-B-1.1", "SGI-B-2.0", "OFL-1.0", "OFL-1.1",
|
||||
"SimPL-2.0", "Sleepycat", "SNIA", "SMLNJ", "StandardML-NJ",
|
||||
"SugarCRM-1.1.3", "SISSL", "SISSL-1.2", "SPL-1.0", "Watcom-1.0", "TCL",
|
||||
"Unlicense", "TMate", "TORQUE-1.1", "TOSL", "Unicode-TOU", "NCSA", "Vim",
|
||||
"VOSTROM", "VSL-1.0", "W3C", "Wsuipa", "WXwindows", "Xnet", "X11", "Xerox",
|
||||
"XFree86-1.1", "xinetd", "xpp", "XSkat", "YPL-1.0", "YPL-1.1", "Zed",
|
||||
"Zend-2.0", "Zimbra-1.3", "Zlib", "zlib-acknowledgement", "ZPL-1.1",
|
||||
"ZPL-2.0", "ZPL-2.1"
|
||||
]
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Autoload;
|
|||
use Composer\Config;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Installer\InstallationManager;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
|
@ -34,14 +35,29 @@ class AutoloadGenerator
|
|||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcher $eventDispatcher)
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
private $devMode = false;
|
||||
|
||||
public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
public function setDevMode($devMode = true)
|
||||
{
|
||||
$this->devMode = (boolean) $devMode;
|
||||
}
|
||||
|
||||
public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '')
|
||||
{
|
||||
$this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP);
|
||||
$this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array(
|
||||
'optimize' => (bool) $scanPsr0Packages,
|
||||
));
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
|
||||
|
@ -49,6 +65,7 @@ class AutoloadGenerator
|
|||
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
|
||||
$useGlobalIncludePath = (bool) $config->get('use-include-path');
|
||||
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
|
||||
$classMapAuthoritative = $config->get('classmap-authoritative');
|
||||
$targetDir = $vendorPath.'/'.$targetDir;
|
||||
$filesystem->ensureDirectoryExists($targetDir);
|
||||
|
||||
|
@ -172,7 +189,22 @@ EOF;
|
|||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
foreach (ClassMapGenerator::createMap($dir, $blacklist) as $class => $path) {
|
||||
$whitelist = sprintf(
|
||||
'{%s/%s.+(?<!(?<!/)Test\.php)$}',
|
||||
preg_quote($dir),
|
||||
($psrType === 'psr-0' && strpos($namespace, '_') === false) ? preg_quote(strtr($namespace, '\\', '/')) : ''
|
||||
);
|
||||
|
||||
$namespaceFilter = $namespace === '' ? null : $namespace;
|
||||
foreach (ClassMapGenerator::createMap($dir, $whitelist, $this->io, $namespaceFilter) as $class => $path) {
|
||||
if (!isset($classMap[$class])) {
|
||||
$path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
|
||||
$classMap[$class] = $path.",\n";
|
||||
}
|
||||
}
|
||||
/*
|
||||
* RKERNER
|
||||
* foreach (ClassMapGenerator::createMap($dir, $blacklist) as $class => $path) {
|
||||
if ('' === $namespace || 0 === strpos($class, $namespace)) {
|
||||
if (!isset($classMap[$class])) {
|
||||
$path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
|
||||
|
@ -180,14 +212,15 @@ EOF;
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
|
||||
foreach ($autoloads['classmap'] as $dir) {
|
||||
foreach (ClassMapGenerator::createMap($dir, $blacklist) as $class => $path) {
|
||||
foreach (ClassMapGenerator::createMap($dir, null, $this->io) as $class => $path) {
|
||||
//REKERNER foreach (ClassMapGenerator::createMap($dir, $blacklist) as $class => $path) {
|
||||
$path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
|
||||
$classMap[$class] = $path.",\n";
|
||||
}
|
||||
|
@ -213,7 +246,7 @@ EOF;
|
|||
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
|
||||
}
|
||||
file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
|
||||
file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader));
|
||||
file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative));
|
||||
|
||||
// use stream_copy_to_stream instead of copy
|
||||
// to work around https://bugs.php.net/bug.php?id=64634
|
||||
|
@ -224,7 +257,9 @@ EOF;
|
|||
fclose($targetLoader);
|
||||
unset($sourceLoader, $targetLoader);
|
||||
|
||||
$this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP);
|
||||
$this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array(
|
||||
'optimize' => (bool) $scanPsr0Packages,
|
||||
));
|
||||
}
|
||||
|
||||
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
|
||||
|
@ -240,7 +275,7 @@ EOF;
|
|||
|
||||
$packageMap[] = array(
|
||||
$package,
|
||||
$installationManager->getInstallPath($package)
|
||||
$installationManager->getInstallPath($package),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -370,7 +405,6 @@ EOF;
|
|||
protected function getIncludeFilesFile(array $files, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
|
||||
{
|
||||
$filesCode = '';
|
||||
$files = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($files));
|
||||
foreach ($files as $functionFile) {
|
||||
$filesCode .= ' '.$this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile).",\n";
|
||||
}
|
||||
|
@ -437,7 +471,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
|
|||
AUTOLOAD;
|
||||
}
|
||||
|
||||
protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)
|
||||
protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)
|
||||
{
|
||||
// TODO the class ComposerAutoloaderInit should be revert to a closure
|
||||
// when APC has been fixed:
|
||||
|
@ -472,9 +506,6 @@ class ComposerAutoloaderInit$suffix
|
|||
self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));
|
||||
|
||||
\$vendorDir = $vendorPathCode;
|
||||
\$baseDir = $appBaseDirCode;
|
||||
|
||||
|
||||
HEADER;
|
||||
|
||||
|
@ -517,6 +548,13 @@ PSR4;
|
|||
CLASSMAP;
|
||||
}
|
||||
|
||||
if ($classMapAuthoritative) {
|
||||
$file .= <<<'CLASSMAPAUTHORITATIVE'
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
|
||||
CLASSMAPAUTHORITATIVE;
|
||||
}
|
||||
|
||||
if ($useGlobalIncludePath) {
|
||||
$file .= <<<'INCLUDEPATH'
|
||||
$loader->setUseIncludePath(true);
|
||||
|
@ -530,7 +568,6 @@ INCLUDEPATH;
|
|||
|
||||
|
||||
REGISTER_AUTOLOAD;
|
||||
|
||||
}
|
||||
|
||||
$file .= <<<REGISTER_LOADER
|
||||
|
@ -540,15 +577,14 @@ REGISTER_AUTOLOAD;
|
|||
REGISTER_LOADER;
|
||||
|
||||
if ($useIncludeFiles) {
|
||||
$file .= <<<'INCLUDE_FILES'
|
||||
$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
foreach ($includeFiles as $file) {
|
||||
require $file;
|
||||
$file .= <<<INCLUDE_FILES
|
||||
\$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
foreach (\$includeFiles as \$file) {
|
||||
composerRequire$suffix(\$file);
|
||||
}
|
||||
|
||||
|
||||
INCLUDE_FILES;
|
||||
|
||||
}
|
||||
|
||||
$file .= <<<METHOD_FOOTER
|
||||
|
@ -562,8 +598,12 @@ METHOD_FOOTER;
|
|||
return $file . <<<FOOTER
|
||||
}
|
||||
|
||||
FOOTER;
|
||||
function composerRequire$suffix(\$file)
|
||||
{
|
||||
require \$file;
|
||||
}
|
||||
|
||||
FOOTER;
|
||||
}
|
||||
|
||||
protected function parseAutoloadsType(array $packageMap, $type, PackageInterface $mainPackage)
|
||||
|
@ -574,6 +614,9 @@ FOOTER;
|
|||
list($package, $installPath) = $item;
|
||||
|
||||
$autoload = $package->getAutoload();
|
||||
if ($this->devMode && $package === $mainPackage) {
|
||||
$autoload = array_merge_recursive($autoload, $package->getDevAutoload());
|
||||
}
|
||||
|
||||
// skip misconfigured packages
|
||||
if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
|
||||
|
@ -585,24 +628,19 @@ FOOTER;
|
|||
|
||||
foreach ($autoload[$type] as $namespace => $paths) {
|
||||
foreach ((array) $paths as $path) {
|
||||
// remove target-dir from file paths of the root package
|
||||
if ($type === 'files' && $package === $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
|
||||
$targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $package->getTargetDir())));
|
||||
$path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
|
||||
if (($type === 'files' || $type === 'classmap') && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
|
||||
// remove target-dir from file paths of the root package
|
||||
if ($package === $mainPackage) {
|
||||
$targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $package->getTargetDir())));
|
||||
$path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
|
||||
} else {
|
||||
// add target-dir from file paths that don't have it
|
||||
$path = $package->getTargetDir() . '/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
// add target-dir from file paths that don't have it
|
||||
if ($type === 'files' && $package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
|
||||
$path = $package->getTargetDir() . '/' . $path;
|
||||
}
|
||||
|
||||
// remove target-dir from classmap entries of the root package
|
||||
if ($type === 'classmap' && $package === $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
|
||||
$targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $package->getTargetDir())));
|
||||
$path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
|
||||
}
|
||||
|
||||
// add target-dir to classmap entries that don't have it
|
||||
|
||||
/* RKERNER
|
||||
* // add target-dir to classmap entries that don't have it
|
||||
if ($type === 'classmap' && $package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
|
||||
$path = $package->getTargetDir() . '/' . $path;
|
||||
}
|
||||
|
@ -629,6 +667,16 @@ FOOTER;
|
|||
} else {
|
||||
$autoloads[$namespace][] = $installPath.'/'.$path;
|
||||
}
|
||||
*/
|
||||
|
||||
$relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path;
|
||||
|
||||
if ($type === 'files' || $type === 'classmap') {
|
||||
$autoloads[] = $relativePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoloads[$namespace][] = $relativePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -636,47 +684,93 @@ FOOTER;
|
|||
return $autoloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts packages by dependency weight
|
||||
*
|
||||
* Packages of equal weight retain the original order
|
||||
*
|
||||
* @param array $packageMap
|
||||
* @return array
|
||||
*/
|
||||
protected function sortPackageMap(array $packageMap)
|
||||
{
|
||||
$positions = array();
|
||||
$names = array();
|
||||
$indexes = array();
|
||||
|
||||
foreach ($packageMap as $position => $item) {
|
||||
$mainName = $item[0]->getName();
|
||||
$names = array_merge(array_fill_keys($item[0]->getNames(), $mainName), $names);
|
||||
$names[$mainName] = $mainName;
|
||||
$indexes[$mainName] = $positions[$mainName] = $position;
|
||||
}
|
||||
$packages = array();
|
||||
$paths = array();
|
||||
$usageList = array();
|
||||
|
||||
foreach ($packageMap as $item) {
|
||||
$position = $positions[$item[0]->getName()];
|
||||
foreach (array_merge($item[0]->getRequires(), $item[0]->getDevRequires()) as $link) {
|
||||
list($package, $path) = $item;
|
||||
$name = $package->getName();
|
||||
$packages[$name] = $package;
|
||||
$paths[$name] = $path;
|
||||
|
||||
foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) {
|
||||
$target = $link->getTarget();
|
||||
if (!isset($names[$target])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$target = $names[$target];
|
||||
if ($positions[$target] <= $position) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($positions as $key => $value) {
|
||||
if ($value >= $position) {
|
||||
break;
|
||||
}
|
||||
$positions[$key]--;
|
||||
}
|
||||
|
||||
$positions[$target] = $position - 1;
|
||||
$usageList[$target][] = $name;
|
||||
}
|
||||
asort($positions);
|
||||
}
|
||||
|
||||
$computing = array();
|
||||
$computed = array();
|
||||
$computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) {
|
||||
// reusing computed importance
|
||||
if (isset($computed[$name])) {
|
||||
return $computed[$name];
|
||||
}
|
||||
|
||||
// canceling circular dependency
|
||||
if (isset($computing[$name])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$computing[$name] = true;
|
||||
$weight = 0;
|
||||
|
||||
if (isset($usageList[$name])) {
|
||||
foreach ($usageList[$name] as $user) {
|
||||
$weight -= 1 - $computeImportance($user);
|
||||
}
|
||||
}
|
||||
|
||||
unset($computing[$name]);
|
||||
$computed[$name] = $weight;
|
||||
|
||||
return $weight;
|
||||
};
|
||||
|
||||
$weightList = array();
|
||||
|
||||
foreach ($packages as $name => $package) {
|
||||
$weight = $computeImportance($name);
|
||||
$weightList[$name] = $weight;
|
||||
}
|
||||
|
||||
$stable_sort = function (&$array) {
|
||||
static $transform, $restore;
|
||||
|
||||
$i = 0;
|
||||
|
||||
if (!$transform) {
|
||||
$transform = function (&$v, $k) use (&$i) {
|
||||
$v = array($v, ++$i, $k, $v);
|
||||
};
|
||||
|
||||
$restore = function (&$v, $k) {
|
||||
$v = $v[3];
|
||||
};
|
||||
}
|
||||
|
||||
array_walk($array, $transform);
|
||||
asort($array);
|
||||
array_walk($array, $restore);
|
||||
};
|
||||
|
||||
$stable_sort($weightList);
|
||||
|
||||
$sortedPackageMap = array();
|
||||
foreach (array_keys($positions) as $packageName) {
|
||||
$sortedPackageMap[] = $packageMap[$indexes[$packageName]];
|
||||
|
||||
foreach (array_keys($weightList) as $name) {
|
||||
$sortedPackageMap[] = array($packages[$name], $paths[$name]);
|
||||
}
|
||||
|
||||
return $sortedPackageMap;
|
||||
|
|
|
@ -54,9 +54,15 @@ class ClassLoader
|
|||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
|
@ -143,6 +149,8 @@ class ClassLoader
|
|||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
|
@ -202,10 +210,13 @@ class ClassLoader
|
|||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths) {
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
|
@ -239,6 +250,27 @@ class ClassLoader
|
|||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
|
@ -266,7 +298,7 @@ class ClassLoader
|
|||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
include $file;
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -290,9 +322,29 @@ class ClassLoader
|
|||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if ($file === null && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if ($file === null) {
|
||||
// Remember that this class does not exist.
|
||||
return $this->classMap[$class] = false;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
|
@ -321,7 +373,7 @@ class ClassLoader
|
|||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php';
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
|
@ -347,8 +399,15 @@ class ClassLoader
|
|||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
// Remember that this class does not exist.
|
||||
return $this->classMap[$class] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
|
|
|
@ -12,20 +12,23 @@
|
|||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
* ClassMapGenerator
|
||||
*
|
||||
* @author Gyula Sallai <salla016@gmail.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ClassMapGenerator
|
||||
{
|
||||
/**
|
||||
* Generate a class map file
|
||||
*
|
||||
* @param Traversable $dirs Directories or a single path to search in
|
||||
* @param string $file The name of the class map file
|
||||
* @param \Traversable $dirs Directories or a single path to search in
|
||||
* @param string $file The name of the class map file
|
||||
*/
|
||||
public static function dump($dirs, $file)
|
||||
{
|
||||
|
@ -41,20 +44,23 @@ class ClassMapGenerator
|
|||
/**
|
||||
* Iterate over all files in the given directory searching for classes
|
||||
*
|
||||
* @param Iterator|string $path The path to search in or an iterator
|
||||
* @param string $blacklist Regex that matches against the file path that exclude from the classmap.
|
||||
* @param \Iterator|string $path The path to search in or an iterator
|
||||
* @param string $whitelist Regex that matches against the file path
|
||||
* @param IOInterface $io IO object
|
||||
* @param string $namespace Optional namespace prefix to filter by
|
||||
*
|
||||
* @return array A class map array
|
||||
*
|
||||
* @throws \RuntimeException When the path is neither an existing file nor directory
|
||||
*/
|
||||
public static function createMap($path, $blacklist = '')
|
||||
public static function createMap($path, $whitelist = null, IOInterface $io = null, $namespace = null)
|
||||
// RKERNER: public static function createMap($path, $blacklist = '')
|
||||
{
|
||||
if (is_string($path)) {
|
||||
if (is_file($path)) {
|
||||
$path = array(new \SplFileInfo($path));
|
||||
} elseif (is_dir($path)) {
|
||||
$path = Finder::create()->files()->followLinks()->name('/\.(php|inc)$/')->in($path);
|
||||
$path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path);
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
'Could not scan for classes inside "'.$path.
|
||||
|
@ -68,18 +74,31 @@ class ClassMapGenerator
|
|||
foreach ($path as $file) {
|
||||
$filePath = $file->getRealPath();
|
||||
|
||||
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc'))) {
|
||||
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc', 'hh'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
|
||||
if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
|
||||
// RKERNER: if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes = self::findClasses($filePath);
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$map[$class] = $filePath;
|
||||
// skip classes not within the given namespace prefix
|
||||
if (null !== $namespace && 0 !== strpos($class, $namespace)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($map[$class])) {
|
||||
$map[$class] = $filePath;
|
||||
} elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example)s?/}i', strtr($map[$class].' '.$filePath, '\\', '/'))) {
|
||||
$io->write(
|
||||
'<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
|
||||
' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.</warning>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +117,15 @@ class ClassMapGenerator
|
|||
$traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait';
|
||||
|
||||
try {
|
||||
$contents = php_strip_whitespace($path);
|
||||
$contents = @php_strip_whitespace($path);
|
||||
if (!$contents) {
|
||||
if (!file_exists($path)) {
|
||||
throw new \Exception('File does not exist');
|
||||
}
|
||||
if (!is_readable($path)) {
|
||||
throw new \Exception('File is not readable');
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
@ -109,12 +136,15 @@ class ClassMapGenerator
|
|||
}
|
||||
|
||||
// strip heredocs/nowdocs
|
||||
$contents = preg_replace('{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s', 'null', $contents);
|
||||
$contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
|
||||
// strip strings
|
||||
$contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s', 'null', $contents);
|
||||
// strip leading non-php code if needed
|
||||
if (substr($contents, 0, 2) !== '<?') {
|
||||
$contents = preg_replace('{^.+?<\?}s', '<?', $contents);
|
||||
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
|
||||
if ($replacements === 0) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
// strip non-php blocks in the file
|
||||
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
|
||||
|
@ -126,7 +156,7 @@ class ClassMapGenerator
|
|||
|
||||
preg_match_all('{
|
||||
(?:
|
||||
\b(?<![\$:>])(?P<type>class|interface'.$traits.') \s+ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
|
||||
\b(?<![\$:>])(?P<type>class|interface'.$traits.') \s+ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*)
|
||||
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;]
|
||||
)
|
||||
}ix', $contents, $matches);
|
||||
|
@ -138,7 +168,12 @@ class ClassMapGenerator
|
|||
if (!empty($matches['ns'][$i])) {
|
||||
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
|
||||
} else {
|
||||
$classes[] = ltrim($namespace . $matches['name'][$i], '\\');
|
||||
$name = $matches['name'][$i];
|
||||
if ($name[0] === ':') {
|
||||
// This is an XHP class, https://github.com/facebook/xhp
|
||||
$name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
|
||||
}
|
||||
$classes[] = ltrim($namespace . $name, '\\');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,28 @@ class Cache
|
|||
$this->io->write('Writing '.$this->root . $file.' into cache');
|
||||
}
|
||||
|
||||
return file_put_contents($this->root . $file, $contents);
|
||||
try {
|
||||
return file_put_contents($this->root . $file, $contents);
|
||||
} catch (\ErrorException $e) {
|
||||
if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
|
||||
// Remove partial file.
|
||||
unlink($this->root . $file);
|
||||
|
||||
$message = sprintf(
|
||||
'<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available</warning>',
|
||||
$this->root . $file,
|
||||
$m[1],
|
||||
$m[2],
|
||||
@disk_free_space($this->root . dirname($file))
|
||||
);
|
||||
|
||||
$this->io->write($message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -129,14 +150,14 @@ class Cache
|
|||
|
||||
public function gcIsNecessary()
|
||||
{
|
||||
return (!self::$cacheCollected && !mt_rand(0, 50));
|
||||
return (!self::$cacheCollected && !mt_rand(0, 50));
|
||||
}
|
||||
|
||||
public function remove($file)
|
||||
{
|
||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||
if ($this->enabled && file_exists($this->root . $file)) {
|
||||
return unlink($this->root . $file);
|
||||
return $this->filesystem->unlink($this->root . $file);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -144,28 +165,32 @@ class Cache
|
|||
|
||||
public function gc($ttl, $maxSize)
|
||||
{
|
||||
$expire = new \DateTime();
|
||||
$expire->modify('-'.$ttl.' seconds');
|
||||
if ($this->enabled) {
|
||||
$expire = new \DateTime();
|
||||
$expire->modify('-'.$ttl.' seconds');
|
||||
|
||||
$finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
|
||||
foreach ($finder as $file) {
|
||||
unlink($file->getRealPath());
|
||||
}
|
||||
|
||||
$totalSize = $this->filesystem->size($this->root);
|
||||
if ($totalSize > $maxSize) {
|
||||
$iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
|
||||
while ($totalSize > $maxSize && $iterator->valid()) {
|
||||
$filepath = $iterator->current()->getRealPath();
|
||||
$totalSize -= $this->filesystem->size($filepath);
|
||||
unlink($filepath);
|
||||
$iterator->next();
|
||||
$finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
|
||||
foreach ($finder as $file) {
|
||||
$this->filesystem->unlink($file->getPathname());
|
||||
}
|
||||
|
||||
$totalSize = $this->filesystem->size($this->root);
|
||||
if ($totalSize > $maxSize) {
|
||||
$iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
|
||||
while ($totalSize > $maxSize && $iterator->valid()) {
|
||||
$filepath = $iterator->current()->getPathname();
|
||||
$totalSize -= $this->filesystem->size($filepath);
|
||||
$this->filesystem->unlink($filepath);
|
||||
$iterator->next();
|
||||
}
|
||||
}
|
||||
|
||||
self::$cacheCollected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
self::$cacheCollected = true;
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function sha1($file)
|
||||
|
|
|
@ -40,6 +40,5 @@ EOT
|
|||
See http://getcomposer.org/ for more information.</comment>
|
||||
EOT
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@ namespace Composer\Command;
|
|||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
@ -37,7 +40,7 @@ class ArchiveCommand extends Command
|
|||
->setDescription('Create an archive of this composer package')
|
||||
->setDefinition(array(
|
||||
new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'),
|
||||
new InputArgument('version', InputArgument::OPTIONAL, 'The package version to archive'),
|
||||
new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'),
|
||||
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar or zip', 'tar'),
|
||||
new InputOption('dir', false, InputOption::VALUE_REQUIRED, 'Write the archive to this directory', '.'),
|
||||
))
|
||||
|
@ -55,13 +58,26 @@ EOT
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
return $this->archive(
|
||||
$composer = $this->getComposer(false);
|
||||
if ($composer) {
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
|
||||
}
|
||||
|
||||
$returnCode = $this->archive(
|
||||
$this->getIO(),
|
||||
$input->getArgument('package'),
|
||||
$input->getArgument('version'),
|
||||
$input->getOption('format'),
|
||||
$input->getOption('dir')
|
||||
);
|
||||
|
||||
if (0 === $returnCode && $composer) {
|
||||
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
|
||||
}
|
||||
|
||||
return $returnCode;
|
||||
}
|
||||
|
||||
protected function archive(IOInterface $io, $packageName = null, $version = null, $format = 'tar', $dest = '.')
|
||||
|
@ -103,16 +119,17 @@ EOT
|
|||
$pool = new Pool();
|
||||
$pool->addRepository($repos);
|
||||
|
||||
$constraint = ($version) ? new VersionConstraint('>=', $version) : null;
|
||||
$packages = $pool->whatProvides($packageName, $constraint);
|
||||
$parser = new VersionParser();
|
||||
$constraint = ($version) ? $parser->parseConstraints($version) : null;
|
||||
$packages = $pool->whatProvides($packageName, $constraint, true);
|
||||
|
||||
if (count($packages) > 1) {
|
||||
$package = $packages[0];
|
||||
$package = reset($packages);
|
||||
$io->write('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
|
||||
$io->write('Alternatives were '.implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)).'.');
|
||||
$io->write('<comment>Please use a more specific constraint to pick a different package.</comment>');
|
||||
} elseif ($packages) {
|
||||
$package = $packages[0];
|
||||
$package = reset($packages);
|
||||
$io->write('<info>Found an exact match '.$package->getPrettyString().'.</info>');
|
||||
} else {
|
||||
$io->write('<error>Could not find a package matching '.$packageName.'.</error>');
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<?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\Command;
|
||||
|
||||
use Composer\Cache;
|
||||
use Composer\Factory;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author David Neilsen <petah.p@gmail.com>
|
||||
*/
|
||||
class ClearCacheCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('clear-cache')
|
||||
->setAliases(array('clearcache'))
|
||||
->setDescription('Clears composer\'s internal package cache.')
|
||||
->setHelp(<<<EOT
|
||||
The <info>clear-cache</info> deletes all cached packages from composer's
|
||||
cache directory.
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$config = Factory::createConfig();
|
||||
$io = $this->getIO();
|
||||
|
||||
$cachePaths = array(
|
||||
'cache-dir' => $config->get('cache-dir'),
|
||||
'cache-files-dir' => $config->get('cache-files-dir'),
|
||||
'cache-repo-dir' => $config->get('cache-repo-dir'),
|
||||
'cache-vcs-dir' => $config->get('cache-vcs-dir'),
|
||||
);
|
||||
|
||||
foreach ($cachePaths as $key => $cachePath) {
|
||||
$cachePath = realpath($cachePath);
|
||||
if (!$cachePath) {
|
||||
$io->write("<info>Cache directory does not exist ($key): $cachePath</info>");
|
||||
|
||||
return;
|
||||
}
|
||||
$cache = new Cache($io, $cachePath);
|
||||
if (!$cache->isEnabled()) {
|
||||
$io->write("<info>Cache is not enabled ($key): $cachePath</info>");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$io->write("<info>Clearing cache ($key): $cachePath</info>");
|
||||
$cache->gc(0, 0);
|
||||
}
|
||||
|
||||
$io->write('<info>All caches cleared.</info>');
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ use Composer\Composer;
|
|||
use Composer\Console\Application;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\IO\NullIO;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Command\Command as BaseCommand;
|
||||
|
||||
/**
|
||||
|
@ -68,6 +70,15 @@ abstract class Command extends BaseCommand
|
|||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cached composer instance
|
||||
*/
|
||||
public function resetComposer()
|
||||
{
|
||||
$this->composer = null;
|
||||
$this->getApplication()->resetComposer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IOInterface
|
||||
*/
|
||||
|
@ -93,4 +104,16 @@ abstract class Command extends BaseCommand
|
|||
{
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) {
|
||||
$input->setOption('no-progress', true);
|
||||
}
|
||||
|
||||
parent::initialize($input, $output);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,9 +53,11 @@ class ConfigCommand extends Command
|
|||
->setDefinition(array(
|
||||
new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
|
||||
new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'),
|
||||
new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'),
|
||||
new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
|
||||
new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
|
||||
new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'),
|
||||
new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
|
||||
new InputArgument('setting-key', null, 'Setting key'),
|
||||
new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
|
||||
))
|
||||
|
@ -98,11 +100,13 @@ EOT
|
|||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
parent::initialize($input, $output);
|
||||
|
||||
if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
|
||||
throw new \RuntimeException('--file and --global can not be combined');
|
||||
}
|
||||
|
||||
$this->config = Factory::createConfig();
|
||||
$this->config = Factory::createConfig($this->getIO());
|
||||
|
||||
// Get the local composer.json, global config.json, or if the user
|
||||
// passed in a file to use
|
||||
|
@ -113,15 +117,27 @@ EOT
|
|||
$this->configFile = new JsonFile($configFile);
|
||||
$this->configSource = new JsonConfigSource($this->configFile);
|
||||
|
||||
$authConfigFile = $input->getOption('global')
|
||||
? ($this->config->get('home') . '/auth.json')
|
||||
: dirname(realpath($input->getOption('file'))) . '/auth.json';
|
||||
|
||||
$this->authConfigFile = new JsonFile($authConfigFile);
|
||||
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
||||
|
||||
// initialize the global file if it's not there
|
||||
if ($input->getOption('global') && !$this->configFile->exists()) {
|
||||
touch($this->configFile->getPath());
|
||||
$this->configFile->write(array('config' => new \ArrayObject));
|
||||
@chmod($this->configFile->getPath(), 0600);
|
||||
}
|
||||
if ($input->getOption('global') && !$this->authConfigFile->exists()) {
|
||||
touch($this->authConfigFile->getPath());
|
||||
$this->authConfigFile->write(array('http-basic' => new \ArrayObject, 'github-oauth' => new \ArrayObject));
|
||||
@chmod($this->authConfigFile->getPath(), 0600);
|
||||
}
|
||||
|
||||
if (!$this->configFile->exists()) {
|
||||
throw new \RuntimeException('No composer.json found in the current directory');
|
||||
throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,13 +162,15 @@ EOT
|
|||
}
|
||||
}
|
||||
|
||||
system($editor . ' ' . $this->configFile->getPath() . (defined('PHP_WINDOWS_VERSION_BUILD') ? '': ' > `tty`'));
|
||||
$file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
|
||||
system($editor . ' ' . $file . (defined('PHP_WINDOWS_VERSION_BUILD') ? '' : ' > `tty`'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$input->getOption('global')) {
|
||||
$this->config->merge($this->configFile->read());
|
||||
$this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array()));
|
||||
}
|
||||
|
||||
// List the configuration of the file settings
|
||||
|
@ -203,7 +221,7 @@ EOT
|
|||
|
||||
$value = $data;
|
||||
} elseif (isset($data['config'][$settingKey])) {
|
||||
$value = $data['config'][$settingKey];
|
||||
$value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
|
||||
} else {
|
||||
throw new \RuntimeException($settingKey.' is not defined');
|
||||
}
|
||||
|
@ -236,16 +254,29 @@ EOT
|
|||
}
|
||||
|
||||
// handle github-oauth
|
||||
if (preg_match('/^github-oauth\.(.+)/', $settingKey, $matches)) {
|
||||
if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) {
|
||||
if ($input->getOption('unset')) {
|
||||
return $this->configSource->removeConfigSetting('github-oauth.'.$matches[1]);
|
||||
$this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
|
||||
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (1 !== count($values)) {
|
||||
throw new \RuntimeException('Too many arguments, expected only one token');
|
||||
if ($matches[1] === 'github-oauth') {
|
||||
if (1 !== count($values)) {
|
||||
throw new \RuntimeException('Too many arguments, expected only one token');
|
||||
}
|
||||
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
|
||||
$this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
|
||||
} elseif ($matches[1] === 'http-basic') {
|
||||
if (2 !== count($values)) {
|
||||
throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
|
||||
}
|
||||
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
|
||||
$this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
|
||||
}
|
||||
|
||||
return $this->configSource->addConfigSetting('github-oauth.'.$matches[1], $values[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
$booleanValidator = function ($val) { return in_array($val, array('true', 'false', '1', '0'), true); };
|
||||
|
@ -259,6 +290,16 @@ EOT
|
|||
function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); },
|
||||
function ($val) { return $val; }
|
||||
),
|
||||
'store-auths' => array(
|
||||
function ($val) { return in_array($val, array('true', 'false', 'prompt'), true); },
|
||||
function ($val) {
|
||||
if ('prompt' === $val) {
|
||||
return 'prompt';
|
||||
}
|
||||
|
||||
return $val !== 'false' && (bool) $val;
|
||||
}
|
||||
),
|
||||
'notify-on-install' => array($booleanValidator, $booleanNormalizer),
|
||||
'vendor-dir' => array('is_string', function ($val) { return $val; }),
|
||||
'bin-dir' => array('is_string', function ($val) { return $val; }),
|
||||
|
@ -284,7 +325,9 @@ EOT
|
|||
),
|
||||
'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
|
||||
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||
'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
|
||||
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
|
||||
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
||||
);
|
||||
$multiConfigValues = array(
|
||||
'github-protocols' => array(
|
||||
|
@ -294,8 +337,8 @@ EOT
|
|||
}
|
||||
|
||||
foreach ($vals as $val) {
|
||||
if (!in_array($val, array('git', 'https'))) {
|
||||
return 'valid protocols include: git, https';
|
||||
if (!in_array($val, array('git', 'https', 'ssh'))) {
|
||||
return 'valid protocols include: git, https, ssh';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,7 +363,7 @@ EOT
|
|||
);
|
||||
|
||||
foreach ($uniqueConfigValues as $name => $callbacks) {
|
||||
if ($settingKey === $name) {
|
||||
if ($settingKey === $name) {
|
||||
if ($input->getOption('unset')) {
|
||||
return $this->configSource->removeConfigSetting($settingKey);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ use Composer\Installer\ProjectInstaller;
|
|||
use Composer\Installer\InstallationManager;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Repository\ComposerRepository;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\FilesystemRepository;
|
||||
|
@ -56,7 +56,7 @@ class CreateProjectCommand extends Command
|
|||
->setDefinition(array(
|
||||
new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'),
|
||||
new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
|
||||
new InputArgument('version', InputArgument::OPTIONAL, 'Version, will defaults to latest'),
|
||||
new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'),
|
||||
new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'),
|
||||
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.'),
|
||||
|
@ -69,6 +69,7 @@ class CreateProjectCommand extends Command
|
|||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||
new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'),
|
||||
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
|
||||
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>create-project</info> command creates a new project from a given
|
||||
|
@ -102,22 +103,7 @@ EOT
|
|||
|
||||
$preferSource = false;
|
||||
$preferDist = false;
|
||||
switch ($config->get('preferred-install')) {
|
||||
case 'source':
|
||||
$preferSource = true;
|
||||
break;
|
||||
case 'dist':
|
||||
$preferDist = true;
|
||||
break;
|
||||
case 'auto':
|
||||
default:
|
||||
// noop
|
||||
break;
|
||||
}
|
||||
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist')) {
|
||||
$preferSource = $input->getOption('prefer-source');
|
||||
$preferDist = $input->getOption('prefer-dist');
|
||||
}
|
||||
$this->updatePreferredOptions($config, $input, $preferSource, $preferDist);
|
||||
|
||||
if ($input->getOption('no-custom-installers')) {
|
||||
$output->writeln('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
|
||||
|
@ -139,14 +125,19 @@ EOT
|
|||
$input->getOption('no-scripts'),
|
||||
$input->getOption('keep-vcs'),
|
||||
$input->getOption('no-progress'),
|
||||
$input->getOption('no-install')
|
||||
$input->getOption('no-install'),
|
||||
$input->getOption('ignore-platform-reqs'),
|
||||
$input
|
||||
);
|
||||
}
|
||||
|
||||
public function installProject(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false)
|
||||
public function installProject(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, InputInterface $input)
|
||||
{
|
||||
$oldCwd = getcwd();
|
||||
|
||||
// we need to manually load the configuration to pass the auth credentials to the io interface!
|
||||
$io->loadConfiguration($config);
|
||||
|
||||
if ($packageName !== null) {
|
||||
$installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disablePlugins, $noScripts, $keepVcs, $noProgress);
|
||||
} else {
|
||||
|
@ -161,13 +152,17 @@ EOT
|
|||
$composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages);
|
||||
}
|
||||
|
||||
$rootPackageConfig = $composer->getConfig();
|
||||
$this->updatePreferredOptions($rootPackageConfig, $input, $preferSource, $preferDist);
|
||||
|
||||
// install dependencies of the created project
|
||||
if ($noInstall === false) {
|
||||
$installer = Installer::create($io, $composer);
|
||||
$installer->setPreferSource($preferSource)
|
||||
->setPreferDist($preferDist)
|
||||
->setDevMode($installDevPackages)
|
||||
->setRunScripts( ! $noScripts);
|
||||
->setRunScripts(!$noScripts)
|
||||
->setIgnorePlatformRequirements($ignorePlatformReqs);
|
||||
|
||||
if ($disablePlugins) {
|
||||
$installer->disablePlugins();
|
||||
|
@ -238,12 +233,18 @@ EOT
|
|||
return 0;
|
||||
}
|
||||
|
||||
protected function installRootPackage(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false)
|
||||
protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false)
|
||||
{
|
||||
if (null === $repositoryUrl) {
|
||||
$sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
|
||||
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) {
|
||||
$sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io)));
|
||||
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION) && file_exists($repositoryUrl)) {
|
||||
$json = new JsonFile($repositoryUrl, new RemoteFilesystem($io, $config));
|
||||
$data = $json->read();
|
||||
if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
|
||||
$sourceRepo = new ComposerRepository(array('url' => 'file://' . strtr(realpath($repositoryUrl), '\\', '/')), $io, $config);
|
||||
} else {
|
||||
$sourceRepo = new FilesystemRepository($json);
|
||||
}
|
||||
} elseif (0 === strpos($repositoryUrl, 'http')) {
|
||||
$sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
|
||||
} else {
|
||||
|
@ -251,7 +252,6 @@ EOT
|
|||
}
|
||||
|
||||
$parser = new VersionParser();
|
||||
$candidates = array();
|
||||
$requirements = $parser->parseNameVersionPairs(array($packageName));
|
||||
$name = strtolower($requirements[0]['name']);
|
||||
if (!$packageVersion && isset($requirements[0]['version'])) {
|
||||
|
@ -275,15 +275,11 @@ EOT
|
|||
$pool = new Pool($stability);
|
||||
$pool->addRepository($sourceRepo);
|
||||
|
||||
$constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null;
|
||||
$candidates = $pool->whatProvides($name, $constraint);
|
||||
foreach ($candidates as $key => $candidate) {
|
||||
if ($candidate->getName() !== $name) {
|
||||
unset($candidates[$key]);
|
||||
}
|
||||
}
|
||||
// find the latest version if there are multiple
|
||||
$versionSelector = new VersionSelector($pool);
|
||||
$package = $versionSelector->findBestCandidate($name, $packageVersion);
|
||||
|
||||
if (!$candidates) {
|
||||
if (!$package) {
|
||||
throw new \InvalidArgumentException("Could not find package $name" . ($packageVersion ? " with version $packageVersion." : " with stability $stability."));
|
||||
}
|
||||
|
||||
|
@ -292,15 +288,6 @@ EOT
|
|||
$directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);
|
||||
}
|
||||
|
||||
// select highest version if we have many
|
||||
$package = reset($candidates);
|
||||
foreach ($candidates as $candidate) {
|
||||
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
|
||||
$package = $candidate;
|
||||
}
|
||||
}
|
||||
unset($candidates);
|
||||
|
||||
$io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>');
|
||||
|
||||
if ($disablePlugins) {
|
||||
|
@ -343,4 +330,34 @@ EOT
|
|||
{
|
||||
return new InstallationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated preferSource or preferDist based on the preferredInstall config option
|
||||
* @param Config $config
|
||||
* @param InputInterface $input
|
||||
* @param boolean $preferSource
|
||||
* @param boolean $preferDist
|
||||
*/
|
||||
protected function updatePreferredOptions(Config $config, InputInterface $input, &$preferSource, &$preferDist)
|
||||
{
|
||||
switch ($config->get('preferred-install')) {
|
||||
case 'source':
|
||||
$preferSource = true;
|
||||
$preferDist = false;
|
||||
break;
|
||||
case 'dist':
|
||||
$preferSource = false;
|
||||
$preferDist = true;
|
||||
break;
|
||||
case 'auto':
|
||||
default:
|
||||
// noop
|
||||
break;
|
||||
}
|
||||
|
||||
if ($input->getOption('prefer-source') || $input->getOption('prefer-dist')) {
|
||||
$preferSource = $input->getOption('prefer-source');
|
||||
$preferDist = $input->getOption('prefer-dist');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,13 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class DiagnoseCommand extends Command
|
||||
{
|
||||
/** @var RemoteFileSystem */
|
||||
protected $rfs;
|
||||
|
||||
/** @var ProcessExecutor */
|
||||
protected $process;
|
||||
|
||||
/** @var int */
|
||||
protected $failures = 0;
|
||||
|
||||
protected function configure()
|
||||
|
@ -48,7 +53,23 @@ EOT
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->rfs = new RemoteFilesystem($this->getIO());
|
||||
$composer = $this->getComposer(false);
|
||||
|
||||
if ($composer) {
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
$output->write('Checking composer.json: ');
|
||||
$this->outputResult($output, $this->checkComposerSchema());
|
||||
}
|
||||
|
||||
if ($composer) {
|
||||
$config = $composer->getConfig();
|
||||
} else {
|
||||
$config = Factory::createConfig();
|
||||
}
|
||||
|
||||
$this->rfs = new RemoteFilesystem($this->getIO(), $config);
|
||||
$this->process = new ProcessExecutor($this->getIO());
|
||||
|
||||
$output->write('Checking platform settings: ');
|
||||
|
@ -70,26 +91,29 @@ EOT
|
|||
$this->outputResult($output, $this->checkHttpsProxyFullUriRequestParam());
|
||||
}
|
||||
|
||||
$composer = $this->getComposer(false);
|
||||
if ($composer) {
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
$output->write('Checking composer.json: ');
|
||||
$this->outputResult($output, $this->checkComposerSchema());
|
||||
}
|
||||
|
||||
if ($composer) {
|
||||
$config = $composer->getConfig();
|
||||
} else {
|
||||
$config = Factory::createConfig();
|
||||
}
|
||||
|
||||
if ($oauth = $config->get('github-oauth')) {
|
||||
foreach ($oauth as $domain => $token) {
|
||||
$output->write('Checking '.$domain.' oauth access: ');
|
||||
$this->outputResult($output, $this->checkGithubOauth($domain, $token));
|
||||
}
|
||||
} else {
|
||||
$output->write('Checking github.com rate limit: ');
|
||||
$rate = $this->getGithubRateLimit('github.com');
|
||||
|
||||
if (10 > $rate['remaining']) {
|
||||
$output->writeln('<warning>WARNING</warning>');
|
||||
$output->writeln(sprintf(
|
||||
'<comment>Github has a rate limit on their API. '
|
||||
. 'You currently have <options=bold>%u</options=bold> '
|
||||
. 'out of <options=bold>%u</options=bold> requests left.' . PHP_EOL
|
||||
. 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL
|
||||
. ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens</comment>',
|
||||
$rate['remaining'],
|
||||
$rate['limit']
|
||||
));
|
||||
} else {
|
||||
$output->writeln('<info>OK</info>');
|
||||
}
|
||||
}
|
||||
|
||||
$output->write('Checking disk free space: ');
|
||||
|
@ -129,7 +153,7 @@ EOT
|
|||
{
|
||||
$this->process->execute('git config color.ui', $output);
|
||||
if (strtolower(trim($output)) === 'always') {
|
||||
return '<warning>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</warning>';
|
||||
return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -139,7 +163,7 @@ EOT
|
|||
{
|
||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||
try {
|
||||
$json = $this->rfs->getContents('packagist.org', $protocol . '://packagist.org/packages.json', false);
|
||||
$this->rfs->getContents('packagist.org', $protocol . '://packagist.org/packages.json', false);
|
||||
} catch (\Exception $e) {
|
||||
return $e;
|
||||
}
|
||||
|
@ -183,7 +207,7 @@ EOT
|
|||
try {
|
||||
$this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assert the situation, maybe packagist.org is down ('.$e->getMessage().')';
|
||||
return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
||||
return 'It seems there is a problem with your proxy server, try setting the "HTTP_PROXY_REQUEST_FULLURI" and "HTTPS_PROXY_REQUEST_FULLURI" environment variables to "false"';
|
||||
|
@ -207,12 +231,12 @@ EOT
|
|||
|
||||
$url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
|
||||
try {
|
||||
$rfcResult = $this->rfs->getContents('api.github.com', $url, false);
|
||||
$this->rfs->getContents('github.com', $url, false);
|
||||
} catch (TransportException $e) {
|
||||
try {
|
||||
$this->rfs->getContents('api.github.com', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
$this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false)));
|
||||
} catch (TransportException $e) {
|
||||
return 'Unable to assert the situation, maybe github is down ('.$e->getMessage().')';
|
||||
return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
|
||||
}
|
||||
|
||||
return 'It seems there is a problem with your proxy server, try setting the "HTTPS_PROXY_REQUEST_FULLURI" environment variable to "false"';
|
||||
|
@ -227,10 +251,33 @@ EOT
|
|||
try {
|
||||
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/user/repos' : 'https://'.$domain.'/api/v3/user/repos';
|
||||
|
||||
return $this->rfs->getContents($domain, $url, false) ? true : 'Unexpected error';
|
||||
return $this->rfs->getContents($domain, $url, false, array(
|
||||
'retry-auth-failure' => false
|
||||
)) ? true : 'Unexpected error';
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof TransportException && $e->getCode() === 401) {
|
||||
return '<warning>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</warning>';
|
||||
return '<comment>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</comment>';
|
||||
}
|
||||
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function getGithubRateLimit($domain, $token = null)
|
||||
{
|
||||
if ($token) {
|
||||
$this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
|
||||
}
|
||||
|
||||
try {
|
||||
$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);
|
||||
|
||||
return $data['resources']['core'];
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof TransportException && $e->getCode() === 401) {
|
||||
return '<comment>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</comment>';
|
||||
}
|
||||
|
||||
return $e;
|
||||
|
@ -255,7 +302,7 @@ EOT
|
|||
$latest = trim($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false));
|
||||
|
||||
if (Composer::VERSION !== $latest && Composer::VERSION !== '@package_version@') {
|
||||
return '<warning>Your are not running the latest version</warning>';
|
||||
return '<comment>You are not running the latest version</comment>';
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -271,7 +318,7 @@ EOT
|
|||
if ($result instanceof \Exception) {
|
||||
$output->writeln('['.get_class($result).'] '.$result->getMessage());
|
||||
} elseif ($result) {
|
||||
$output->writeln($result);
|
||||
$output->writeln(trim($result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +327,7 @@ EOT
|
|||
{
|
||||
$output = '';
|
||||
$out = function ($msg, $style) use (&$output) {
|
||||
$output .= '<'.$style.'>'.$msg.'</'.$style.'>';
|
||||
$output .= '<'.$style.'>'.$msg.'</'.$style.'>'.PHP_EOL;
|
||||
};
|
||||
|
||||
// code below taken from getcomposer.org/installer, any changes should be made there and replicated here
|
||||
|
@ -312,7 +359,7 @@ EOT
|
|||
$warnings['openssl'] = true;
|
||||
}
|
||||
|
||||
if (ini_get('apc.enable_cli')) {
|
||||
if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) {
|
||||
$warnings['apc_cli'] = true;
|
||||
}
|
||||
|
||||
|
@ -341,13 +388,13 @@ EOT
|
|||
foreach ($errors as $error => $current) {
|
||||
switch ($error) {
|
||||
case 'php':
|
||||
$text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
|
||||
$text = "Your PHP ({$current}) is too old, you must upgrade to PHP 5.3.2 or higher.";
|
||||
break;
|
||||
|
||||
case 'allow_url_fopen':
|
||||
$text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL;
|
||||
$text = "The allow_url_fopen setting is incorrect.".PHP_EOL;
|
||||
$text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
|
||||
$text .= " allow_url_fopen = On";
|
||||
$text .= " allow_url_fopen = On";
|
||||
$displayIniMessage = true;
|
||||
break;
|
||||
}
|
||||
|
@ -361,51 +408,51 @@ EOT
|
|||
foreach ($warnings as $warning => $current) {
|
||||
switch ($warning) {
|
||||
case 'apc_cli':
|
||||
$text = PHP_EOL."The apc.enable_cli setting is incorrect.".PHP_EOL;
|
||||
$text = "The apc.enable_cli setting is incorrect.".PHP_EOL;
|
||||
$text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
|
||||
$text .= " apc.enable_cli = Off";
|
||||
$text .= " apc.enable_cli = Off";
|
||||
$displayIniMessage = true;
|
||||
break;
|
||||
|
||||
case 'sigchild':
|
||||
$text = PHP_EOL."PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
|
||||
$text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
|
||||
$text .= "Recompile it without this flag if possible, see also:".PHP_EOL;
|
||||
$text .= " https://bugs.php.net/bug.php?id=22999";
|
||||
$text .= " https://bugs.php.net/bug.php?id=22999";
|
||||
break;
|
||||
|
||||
case 'curlwrappers':
|
||||
$text = PHP_EOL."PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
|
||||
$text .= "Recompile it without this flag if possible";
|
||||
$text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
|
||||
$text .= " Recompile it without this flag if possible";
|
||||
break;
|
||||
|
||||
case 'openssl':
|
||||
$text = PHP_EOL."The openssl extension is missing, which will reduce the security and stability of Composer.".PHP_EOL;
|
||||
$text .= "If possible you should enable it or recompile php with --with-openssl";
|
||||
$text = "The openssl extension is missing, which will reduce the security and stability of Composer.".PHP_EOL;
|
||||
$text .= " If possible you should enable it or recompile php with --with-openssl";
|
||||
break;
|
||||
|
||||
case 'php':
|
||||
$text = PHP_EOL."Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL;
|
||||
$text .= "Composer works with 5.3.2+ for most people, but there might be edge case issues.";
|
||||
$text = "Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL;
|
||||
$text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues.";
|
||||
break;
|
||||
|
||||
case 'xdebug_loaded':
|
||||
$text = PHP_EOL."The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
|
||||
$text .= "Disabling it when using Composer is recommended, but should not cause issues beyond slowness.";
|
||||
$text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
|
||||
$text .= " Disabling it when using Composer is recommended.";
|
||||
break;
|
||||
|
||||
case 'xdebug_profile':
|
||||
$text = PHP_EOL."The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
|
||||
$text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
|
||||
$text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL;
|
||||
$text .= " xdebug.profiler_enabled = 0";
|
||||
$text .= " xdebug.profiler_enabled = 0";
|
||||
$displayIniMessage = true;
|
||||
break;
|
||||
}
|
||||
$out($text, 'warning');
|
||||
$out($text, 'comment');
|
||||
}
|
||||
}
|
||||
|
||||
if ($displayIniMessage) {
|
||||
$out($iniMessage, 'warning');
|
||||
$out($iniMessage, 'comment');
|
||||
}
|
||||
|
||||
return !$warnings && !$errors ? true : $output;
|
||||
|
|
|
@ -30,7 +30,8 @@ class DumpAutoloadCommand extends Command
|
|||
->setAliases(array('dumpautoload'))
|
||||
->setDescription('Dumps the autoloader')
|
||||
->setDefinition(array(
|
||||
new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 packages to be loaded with classmaps too, good for production.'),
|
||||
new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
|
||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
<info>php composer.phar dump-autoload</info>
|
||||
|
@ -51,7 +52,7 @@ EOT
|
|||
$package = $composer->getPackage();
|
||||
$config = $composer->getConfig();
|
||||
|
||||
$optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
|
||||
$optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
|
||||
|
||||
if ($optimize) {
|
||||
$output->writeln('<info>Generating optimized autoload files</info>');
|
||||
|
@ -59,6 +60,8 @@ EOT
|
|||
$output->writeln('<info>Generating autoload files</info>');
|
||||
}
|
||||
|
||||
$composer->getAutoloadGenerator()->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
|
||||
$generator = $composer->getAutoloadGenerator();
|
||||
$generator->setDevMode(!$input->getOption('no-dev'));
|
||||
$generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
<?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\Command;
|
||||
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Factory;
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
*/
|
||||
class HomeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('browse')
|
||||
->setAliases(array('home'))
|
||||
->setDescription('Opens the package\'s repository URL or homepage in your browser.')
|
||||
->setDefinition(array(
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'),
|
||||
new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
|
||||
new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The home command opens or shows a package's repository URL or
|
||||
homepage in your default browser.
|
||||
|
||||
To open the homepage by default, use -H or --homepage.
|
||||
To show instead of open the repository or homepage URL, use -s or --show.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$repo = $this->initializeRepo();
|
||||
$return = 0;
|
||||
|
||||
foreach ($input->getArgument('packages') as $packageName) {
|
||||
$package = $this->getPackage($repo, $packageName);
|
||||
|
||||
if (!$package instanceof CompletePackageInterface) {
|
||||
$return = 1;
|
||||
$output->writeln('<warning>Package '.$packageName.' not found</warning>');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$support = $package->getSupport();
|
||||
$url = isset($support['source']) ? $support['source'] : $package->getSourceUrl();
|
||||
if (!$url || $input->getOption('homepage')) {
|
||||
$url = $package->getHomepage();
|
||||
}
|
||||
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$return = 1;
|
||||
$output->writeln('<warning>'.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.'</warning>');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($input->getOption('show')) {
|
||||
$output->writeln(sprintf('<info>%s</info>', $url));
|
||||
} else {
|
||||
$this->openBrowser($url);
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* finds a package by name
|
||||
*
|
||||
* @param RepositoryInterface $repos
|
||||
* @param string $name
|
||||
* @return CompletePackageInterface
|
||||
*/
|
||||
protected function getPackage(RepositoryInterface $repos, $name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
$pool = new Pool('dev');
|
||||
$pool->addRepository($repos);
|
||||
$matches = $pool->whatProvides($name);
|
||||
|
||||
foreach ($matches as $index => $package) {
|
||||
// skip providers/replacers
|
||||
if ($package->getName() !== $name) {
|
||||
unset($matches[$index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* opens a url in your system default browser
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
private function openBrowser($url)
|
||||
{
|
||||
$url = ProcessExecutor::escape($url);
|
||||
|
||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
||||
return passthru('start "web" explorer "' . $url . '"');
|
||||
}
|
||||
|
||||
passthru('which xdg-open', $linux);
|
||||
passthru('which open', $osx);
|
||||
|
||||
if (0 === $linux) {
|
||||
passthru('xdg-open ' . $url);
|
||||
} elseif (0 === $osx) {
|
||||
passthru('open ' . $url);
|
||||
} else {
|
||||
$this->getIO()->write('no suitable browser opening command found, open yourself: ' . $url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the repo
|
||||
*
|
||||
* @return CompositeRepository
|
||||
*/
|
||||
private function initializeRepo()
|
||||
{
|
||||
$composer = $this->getComposer(false);
|
||||
|
||||
if ($composer) {
|
||||
$repo = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
|
||||
} else {
|
||||
$defaultRepos = Factory::createDefaultRepositories($this->getIO());
|
||||
$repo = new CompositeRepository($defaultRepos);
|
||||
}
|
||||
|
||||
return $repo;
|
||||
}
|
||||
}
|
|
@ -12,12 +12,15 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Factory;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
@ -30,8 +33,10 @@ use Symfony\Component\Process\ExecutableFinder;
|
|||
*/
|
||||
class InitCommand extends Command
|
||||
{
|
||||
protected $repos;
|
||||
|
||||
private $gitConfig;
|
||||
private $repos;
|
||||
private $pool;
|
||||
|
||||
public function parseAuthorString($author)
|
||||
{
|
||||
|
@ -101,7 +106,7 @@ EOT
|
|||
}
|
||||
|
||||
if (isset($options['require-dev'])) {
|
||||
$options['require-dev'] = $this->formatRequirements($options['require-dev']) ;
|
||||
$options['require-dev'] = $this->formatRequirements($options['require-dev']);
|
||||
if (array() === $options['require-dev']) {
|
||||
$options['require-dev'] = new \stdClass;
|
||||
}
|
||||
|
@ -224,10 +229,7 @@ EOT
|
|||
$output,
|
||||
$dialog->getQuestion('Author', $author),
|
||||
function ($value) use ($self, $author) {
|
||||
if (null === $value) {
|
||||
return $author;
|
||||
}
|
||||
|
||||
$value = $value ?: $author;
|
||||
$author = $self->parseAuthorString($value);
|
||||
|
||||
return sprintf('%s <%s>', $author['name'], $author['email']);
|
||||
|
@ -284,9 +286,11 @@ EOT
|
|||
|
||||
protected function findPackages($name)
|
||||
{
|
||||
$packages = array();
|
||||
return $this->getRepos()->search($name);
|
||||
}
|
||||
|
||||
// init repos
|
||||
protected function getRepos()
|
||||
{
|
||||
if (!$this->repos) {
|
||||
$this->repos = new CompositeRepository(array_merge(
|
||||
array(new PlatformRepository),
|
||||
|
@ -294,7 +298,7 @@ EOT
|
|||
));
|
||||
}
|
||||
|
||||
return $this->repos->search($name);
|
||||
return $this->repos;
|
||||
}
|
||||
|
||||
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
|
||||
|
@ -306,15 +310,17 @@ EOT
|
|||
$requires = $this->normalizeRequirements($requires);
|
||||
$result = array();
|
||||
|
||||
foreach ($requires as $key => $requirement) {
|
||||
if (!isset($requirement['version']) && $input->isInteractive()) {
|
||||
$question = $dialog->getQuestion('Please provide a version constraint for the '.$requirement['name'].' requirement');
|
||||
if ($constraint = $dialog->ask($output, $question)) {
|
||||
$requirement['version'] = $constraint;
|
||||
}
|
||||
}
|
||||
foreach ($requires as $requirement) {
|
||||
if (!isset($requirement['version'])) {
|
||||
throw new \InvalidArgumentException('The requirement '.$requirement['name'].' must contain a version constraint');
|
||||
// determine the best version automatically
|
||||
$version = $this->findBestVersionForPackage($input, $requirement['name']);
|
||||
$requirement['version'] = $version;
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'Using version <info>%s</info> for <info>%s</info>',
|
||||
$requirement['version'],
|
||||
$requirement['name']
|
||||
));
|
||||
}
|
||||
|
||||
$result[] = $requirement['name'] . ' ' . $requirement['version'];
|
||||
|
@ -327,17 +333,11 @@ EOT
|
|||
$matches = $this->findPackages($package);
|
||||
|
||||
if (count($matches)) {
|
||||
$output->writeln(array(
|
||||
'',
|
||||
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
|
||||
''
|
||||
));
|
||||
|
||||
$exactMatch = null;
|
||||
$choices = array();
|
||||
foreach ($matches as $position => $package) {
|
||||
$choices[] = sprintf(' <info>%5s</info> %s', "[$position]", $package['name']);
|
||||
if ($package['name'] === $package) {
|
||||
foreach ($matches as $position => $foundPackage) {
|
||||
$choices[] = sprintf(' <info>%5s</info> %s', "[$position]", $foundPackage['name']);
|
||||
if ($foundPackage['name'] === $package) {
|
||||
$exactMatch = true;
|
||||
break;
|
||||
}
|
||||
|
@ -345,6 +345,12 @@ EOT
|
|||
|
||||
// no match, prompt which to pick
|
||||
if (!$exactMatch) {
|
||||
$output->writeln(array(
|
||||
'',
|
||||
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
|
||||
''
|
||||
));
|
||||
|
||||
$output->writeln($choices);
|
||||
$output->writeln('');
|
||||
|
||||
|
@ -369,7 +375,7 @@ EOT
|
|||
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or the complete package name if it is not listed', false, ':'), $validator, 3);
|
||||
}
|
||||
|
||||
// no constraint yet, prompt user
|
||||
// no constraint yet, determine the best version automatically
|
||||
if (false !== $package && false === strpos($package, ' ')) {
|
||||
$validator = function ($input) {
|
||||
$input = trim($input);
|
||||
|
@ -377,9 +383,20 @@ EOT
|
|||
return $input ?: false;
|
||||
};
|
||||
|
||||
$constraint = $dialog->askAndValidate($output, $dialog->getQuestion('Enter the version constraint to require', false, ':'), $validator, 3);
|
||||
$constraint = $dialog->askAndValidate(
|
||||
$output,
|
||||
$dialog->getQuestion('Enter the version constraint to require (or leave blank to use the latest version)', false, ':'),
|
||||
$validator,
|
||||
3)
|
||||
;
|
||||
if (false === $constraint) {
|
||||
continue;
|
||||
$constraint = $this->findBestVersionForPackage($input, $package);
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'Using version <info>%s</info> for <info>%s</info>',
|
||||
$constraint,
|
||||
$package
|
||||
));
|
||||
}
|
||||
|
||||
$package .= ' '.$constraint;
|
||||
|
@ -419,7 +436,7 @@ EOT
|
|||
$finder = new ExecutableFinder();
|
||||
$gitBin = $finder->find('git');
|
||||
|
||||
$cmd = new Process(sprintf('%s config -l', escapeshellarg($gitBin)));
|
||||
$cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin)));
|
||||
$cmd->run();
|
||||
|
||||
if ($cmd->isSuccessful()) {
|
||||
|
@ -504,4 +521,57 @@ EOT
|
|||
|
||||
return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
private function getPool(InputInterface $input)
|
||||
{
|
||||
if (!$this->pool) {
|
||||
$this->pool = new Pool($this->getMinimumStability($input));
|
||||
$this->pool->addRepository($this->getRepos());
|
||||
}
|
||||
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
private function getMinimumStability(InputInterface $input)
|
||||
{
|
||||
if ($input->hasOption('stability')) {
|
||||
return $input->getOption('stability') ?: 'stable';
|
||||
}
|
||||
|
||||
$file = Factory::getComposerFile();
|
||||
if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
|
||||
if (!empty($composer['minimum-stability'])) {
|
||||
return $composer['minimum-stability'];
|
||||
}
|
||||
}
|
||||
|
||||
return 'stable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a package name, this determines the best version to use in the require key.
|
||||
*
|
||||
* This returns a version with the ~ operator prefixed when possible.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param string $name
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function findBestVersionForPackage(InputInterface $input, $name)
|
||||
{
|
||||
// find the latest version allowed in this pool
|
||||
$versionSelector = new VersionSelector($this->getPool($input));
|
||||
$package = $versionSelector->findBestCandidate($name);
|
||||
|
||||
if (!$package) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Could not find package %s at any version for your minimum-stability (%s). Check the package spelling or your minimum-stability',
|
||||
$name,
|
||||
$this->getMinimumStability($input)
|
||||
));
|
||||
}
|
||||
|
||||
return $versionSelector->findRecommendedRequireVersion($package);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use Composer\Plugin\CommandEvent;
|
|||
use Composer\Plugin\PluginEvents;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
|
@ -40,10 +41,13 @@ class InstallCommand extends Command
|
|||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
|
||||
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
|
||||
new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
|
||||
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('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump')
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
|
||||
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>install</info> command reads the composer.lock file from
|
||||
|
@ -60,11 +64,21 @@ EOT
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($args = $input->getArgument('packages')) {
|
||||
$output->writeln('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($input->getOption('no-custom-installers')) {
|
||||
$output->writeln('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
|
||||
$input->setOption('no-plugins', true);
|
||||
}
|
||||
|
||||
if ($input->getOption('dev')) {
|
||||
$output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
|
||||
}
|
||||
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
$io = $this->getIO();
|
||||
|
@ -96,7 +110,7 @@ EOT
|
|||
$preferDist = $input->getOption('prefer-dist');
|
||||
}
|
||||
|
||||
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
|
||||
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
|
||||
|
||||
$install
|
||||
->setDryRun($input->getOption('dry-run'))
|
||||
|
@ -104,8 +118,10 @@ EOT
|
|||
->setPreferSource($preferSource)
|
||||
->setPreferDist($preferDist)
|
||||
->setDevMode(!$input->getOption('no-dev'))
|
||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setOptimizeAutoloader($optimize)
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
;
|
||||
|
||||
if ($input->getOption('no-plugins')) {
|
||||
|
|
|
@ -16,6 +16,8 @@ use Composer\Json\JsonFile;
|
|||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Symfony\Component\Console\Helper\TableHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -33,6 +35,7 @@ class LicensesCommand extends Command
|
|||
->setDescription('Show information about licenses of dependencies')
|
||||
->setDefinition(array(
|
||||
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
|
||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The license command displays detailed information about the licenses of
|
||||
|
@ -55,9 +58,10 @@ EOT
|
|||
|
||||
$versionParser = new VersionParser;
|
||||
|
||||
$packages = array();
|
||||
foreach ($repo->getPackages() as $package) {
|
||||
$packages[$package->getName()] = $package;
|
||||
if ($input->getOption('no-dev')) {
|
||||
$packages = $this->filterRequiredPackages($repo, $root);
|
||||
} else {
|
||||
$packages = $this->appendPackages($repo->getPackages(), array());
|
||||
}
|
||||
|
||||
ksort($packages);
|
||||
|
@ -102,4 +106,47 @@ EOT
|
|||
throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find package requires and child requires
|
||||
*
|
||||
* @param RepositoryInterface $repo
|
||||
* @param PackageInterface $package
|
||||
*/
|
||||
private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $package, $bucket = array())
|
||||
{
|
||||
$requires = array_keys($package->getRequires());
|
||||
|
||||
$packageListNames = array_keys($bucket);
|
||||
$packages = array_filter(
|
||||
$repo->getPackages(),
|
||||
function ($package) use ($requires, $packageListNames) {
|
||||
return in_array($package->getName(), $requires) && !in_array($package->getName(), $packageListNames);
|
||||
}
|
||||
);
|
||||
|
||||
$bucket = $this->appendPackages($packages, $bucket);
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$bucket = $this->filterRequiredPackages($repo, $package, $bucket);
|
||||
}
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds packages to the package list
|
||||
*
|
||||
* @param array $packages the list of packages to add
|
||||
* @param array $bucket the list to add packages to
|
||||
* @return array
|
||||
*/
|
||||
public function appendPackages(array $packages, array $bucket)
|
||||
{
|
||||
foreach ($packages as $package) {
|
||||
$bucket[$package->getName()] = $package;
|
||||
}
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<?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\Command;
|
||||
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\Installer;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Factory;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Pierre du Plessis <pdples@gmail.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class RemoveCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('remove')
|
||||
->setDescription('Removes a package from the require or require-dev')
|
||||
->setDefinition(array(
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.'),
|
||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
|
||||
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('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
||||
new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'),
|
||||
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>remove</info> command removes a package from the current
|
||||
list of installed packages
|
||||
|
||||
<info>php composer.phar remove</info>
|
||||
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$packages = $input->getArgument('packages');
|
||||
|
||||
$file = Factory::getComposerFile();
|
||||
|
||||
$jsonFile = new JsonFile($file);
|
||||
$composer = $jsonFile->read();
|
||||
$composerBackup = file_get_contents($jsonFile->getPath());
|
||||
|
||||
$json = new JsonConfigSource($jsonFile);
|
||||
|
||||
$type = $input->getOption('dev') ? 'require-dev' : 'require';
|
||||
$altType = !$input->getOption('dev') ? 'require-dev' : 'require';
|
||||
|
||||
foreach ($packages as $package) {
|
||||
if (isset($composer[$type][$package])) {
|
||||
$json->removeLink($type, $package);
|
||||
} elseif (isset($composer[$altType][$package])) {
|
||||
$output->writeln('<warning>'.$package.' could not be found in '.$type.' but it is present in '.$altType.'</warning>');
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
if ($this->getIO()->isInteractive()) {
|
||||
if ($dialog->askConfirmation($output, $dialog->getQuestion('Do you want to remove it from '.$altType, 'yes', '?'), true)) {
|
||||
$json->removeLink($altType, $package);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$output->writeln('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->getOption('no-update')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update packages
|
||||
$composer = $this->getComposer();
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
$io = $this->getIO();
|
||||
|
||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
|
||||
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
||||
|
||||
$install = Installer::create($io, $composer);
|
||||
|
||||
$updateDevMode = !$input->getOption('update-no-dev');
|
||||
$install
|
||||
->setVerbose($input->getOption('verbose'))
|
||||
->setDevMode($updateDevMode)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($packages)
|
||||
->setWhitelistDependencies($input->getOption('update-with-dependencies'))
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
;
|
||||
|
||||
$status = $install->run();
|
||||
if ($status !== 0) {
|
||||
$output->writeln("\n".'<error>Removal failed, reverting '.$file.' to its original content.</error>');
|
||||
file_put_contents($jsonFile->getPath(), $composerBackup);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ use Composer\Json\JsonManipulator;
|
|||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
|
@ -36,15 +38,21 @@ class RequireCommand extends InitCommand
|
|||
->setName('require')
|
||||
->setDescription('Adds required packages to your composer.json and installs them')
|
||||
->setDefinition(array(
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package name optionally including 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('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('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('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
||||
new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'),
|
||||
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
||||
new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The require command adds required packages to your composer.json and installs them
|
||||
The require command adds required packages to your composer.json and installs them.
|
||||
|
||||
If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.
|
||||
|
||||
If you do not want to install the new dependencies immediately you can call it with --no-update
|
||||
|
||||
|
@ -57,6 +65,7 @@ EOT
|
|||
{
|
||||
$file = Factory::getComposerFile();
|
||||
|
||||
$newlyCreated = !file_exists($file);
|
||||
if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) {
|
||||
$output->writeln('<error>'.$file.' could not be created.</error>');
|
||||
|
||||
|
@ -74,13 +83,22 @@ EOT
|
|||
}
|
||||
|
||||
$json = new JsonFile($file);
|
||||
$composer = $json->read();
|
||||
$composerDefinition = $json->read();
|
||||
$composerBackup = file_get_contents($json->getPath());
|
||||
|
||||
$composer = $this->getComposer();
|
||||
$repos = $composer->getRepositoryManager()->getRepositories();
|
||||
|
||||
$this->repos = new CompositeRepository(array_merge(
|
||||
array(new PlatformRepository),
|
||||
$repos
|
||||
));
|
||||
|
||||
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
|
||||
|
||||
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
|
||||
$baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
|
||||
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
|
||||
$baseRequirements = array_key_exists($requireKey, $composerDefinition) ? $composerDefinition[$requireKey] : array();
|
||||
$requirements = $this->formatRequirements($requirements);
|
||||
|
||||
// validate requirements format
|
||||
|
@ -89,22 +107,30 @@ EOT
|
|||
$versionParser->parseConstraints($constraint);
|
||||
}
|
||||
|
||||
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
|
||||
$sortPackages = $input->getOption('sort-packages');
|
||||
|
||||
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey, $removeKey, $sortPackages)) {
|
||||
foreach ($requirements as $package => $version) {
|
||||
$baseRequirements[$package] = $version;
|
||||
|
||||
if (isset($composerDefinition[$removeKey][$package])) {
|
||||
unset($composerDefinition[$removeKey][$package]);
|
||||
}
|
||||
}
|
||||
|
||||
$composer[$requireKey] = $baseRequirements;
|
||||
$json->write($composer);
|
||||
$composerDefinition[$requireKey] = $baseRequirements;
|
||||
$json->write($composerDefinition);
|
||||
}
|
||||
|
||||
$output->writeln('<info>'.$file.' has been updated</info>');
|
||||
$output->writeln('<info>'.$file.' has been '.($newlyCreated ? 'created' : 'updated').'</info>');
|
||||
|
||||
if ($input->getOption('no-update')) {
|
||||
return 0;
|
||||
}
|
||||
$updateDevMode = !$input->getOption('update-no-dev');
|
||||
|
||||
// Update packages
|
||||
$this->resetComposer();
|
||||
$composer = $this->getComposer();
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
$io = $this->getIO();
|
||||
|
@ -118,28 +144,38 @@ EOT
|
|||
->setVerbose($input->getOption('verbose'))
|
||||
->setPreferSource($input->getOption('prefer-source'))
|
||||
->setPreferDist($input->getOption('prefer-dist'))
|
||||
->setDevMode(true)
|
||||
->setDevMode($updateDevMode)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist(array_keys($requirements));
|
||||
->setUpdateWhitelist(array_keys($requirements))
|
||||
->setWhitelistDependencies($input->getOption('update-with-dependencies'))
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
;
|
||||
|
||||
$status = $install->run();
|
||||
if ($status !== 0) {
|
||||
$output->writeln("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
|
||||
file_put_contents($json->getPath(), $composerBackup);
|
||||
if ($newlyCreated) {
|
||||
$output->writeln("\n".'<error>Installation failed, deleting '.$file.'.</error>');
|
||||
unlink($json->getPath());
|
||||
} else {
|
||||
$output->writeln("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
|
||||
file_put_contents($json->getPath(), $composerBackup);
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
private function updateFileCleanly($json, array $base, array $new, $requireKey)
|
||||
private function updateFileCleanly($json, array $base, array $new, $requireKey, $removeKey, $sortPackages)
|
||||
{
|
||||
$contents = file_get_contents($json->getPath());
|
||||
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
|
||||
foreach ($new as $package => $constraint) {
|
||||
if (!$manipulator->addLink($requireKey, $package, $constraint)) {
|
||||
if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
|
||||
return false;
|
||||
}
|
||||
if (!$manipulator->removeSubNode($removeKey, $package)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\Script\CommandEvent;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -23,6 +24,30 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class RunScriptCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var array Array with command events
|
||||
*/
|
||||
protected $commandEvents = array(
|
||||
ScriptEvents::PRE_INSTALL_CMD,
|
||||
ScriptEvents::POST_INSTALL_CMD,
|
||||
ScriptEvents::PRE_UPDATE_CMD,
|
||||
ScriptEvents::POST_UPDATE_CMD,
|
||||
ScriptEvents::PRE_STATUS_CMD,
|
||||
ScriptEvents::POST_STATUS_CMD,
|
||||
ScriptEvents::POST_ROOT_PACKAGE_INSTALL,
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array Array with script events
|
||||
*/
|
||||
protected $scriptEvents = array(
|
||||
ScriptEvents::PRE_ARCHIVE_CMD,
|
||||
ScriptEvents::POST_ARCHIVE_CMD,
|
||||
ScriptEvents::PRE_AUTOLOAD_DUMP,
|
||||
ScriptEvents::POST_AUTOLOAD_DUMP
|
||||
);
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
|
@ -30,6 +55,7 @@ class RunScriptCommand extends Command
|
|||
->setDescription('Run the scripts defined in composer.json.')
|
||||
->setDefinition(array(
|
||||
new InputArgument('script', InputArgument::REQUIRED, 'Script name to run.'),
|
||||
new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
|
||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
|
||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
|
||||
))
|
||||
|
@ -45,21 +71,30 @@ EOT
|
|||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$script = $input->getArgument('script');
|
||||
if (!in_array($script, array(
|
||||
ScriptEvents::PRE_INSTALL_CMD,
|
||||
ScriptEvents::POST_INSTALL_CMD,
|
||||
ScriptEvents::PRE_UPDATE_CMD,
|
||||
ScriptEvents::POST_UPDATE_CMD,
|
||||
ScriptEvents::PRE_STATUS_CMD,
|
||||
ScriptEvents::POST_STATUS_CMD,
|
||||
))) {
|
||||
if (!in_array($script, $this->commandEvents) && !in_array($script, $this->scriptEvents)) {
|
||||
if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
|
||||
throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script));
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Script "%s" does not exist', $script));
|
||||
}
|
||||
|
||||
$this->getComposer()->getEventDispatcher()->dispatchCommandEvent($script, $input->getOption('dev') || !$input->getOption('no-dev'));
|
||||
$composer = $this->getComposer();
|
||||
$hasListeners = $composer->getEventDispatcher()->hasEventListeners(new CommandEvent($script, $composer, $this->getIO()));
|
||||
if (!$hasListeners) {
|
||||
throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script));
|
||||
}
|
||||
|
||||
// add the bin dir to the PATH to make local binaries of deps usable in scripts
|
||||
$binDir = $composer->getConfig()->get('bin-dir');
|
||||
if (is_dir($binDir)) {
|
||||
putenv('PATH='.realpath($binDir).PATH_SEPARATOR.getenv('PATH'));
|
||||
}
|
||||
|
||||
$args = $input->getArgument('args');
|
||||
|
||||
if (in_array($script, $this->commandEvents)) {
|
||||
return $composer->getEventDispatcher()->dispatchCommandEvent($script, $input->getOption('dev') || !$input->getOption('no-dev'), $args);
|
||||
}
|
||||
|
||||
return $composer->getEventDispatcher()->dispatchScript($script, $input->getOption('dev') || !$input->getOption('no-dev'), $args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?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\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ScriptAliasCommand extends Command
|
||||
{
|
||||
private $script;
|
||||
|
||||
public function __construct($script)
|
||||
{
|
||||
$this->script = $script;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName($this->script)
|
||||
->setDescription('Run the '.$this->script.' script as defined in composer.json.')
|
||||
->setDefinition(array(
|
||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
|
||||
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
|
||||
new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>run-script</info> command runs scripts defined in composer.json:
|
||||
|
||||
<info>php composer.phar run-script post-update-cmd</info>
|
||||
EOT
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$composer = $this->getComposer();
|
||||
|
||||
// add the bin dir to the PATH to make local binaries of deps usable in scripts
|
||||
$binDir = $composer->getConfig()->get('bin-dir');
|
||||
if (is_dir($binDir)) {
|
||||
putenv('PATH='.realpath($binDir).PATH_SEPARATOR.getenv('PATH'));
|
||||
}
|
||||
|
||||
$args = $input->getArguments();
|
||||
|
||||
return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args']);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* @author Igor Wiedler <igor@wiedler.ch>
|
||||
|
@ -42,6 +43,7 @@ class SelfUpdateCommand extends Command
|
|||
new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
|
||||
new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
|
||||
new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
|
||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>self-update</info> command checks getcomposer.org for newer
|
||||
|
@ -57,8 +59,8 @@ EOT
|
|||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE;
|
||||
$remoteFilesystem = new RemoteFilesystem($this->getIO());
|
||||
$config = Factory::createConfig();
|
||||
$remoteFilesystem = new RemoteFilesystem($this->getIO(), $config);
|
||||
$cacheDir = $config->get('cache-dir');
|
||||
$rollbackDir = $config->get('home');
|
||||
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
|
||||
|
@ -104,7 +106,7 @@ EOT
|
|||
|
||||
$output->writeln(sprintf("Updating to version <info>%s</info>.", $updateVersion));
|
||||
$remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar");
|
||||
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename);
|
||||
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
|
||||
if (!file_exists($tempFilename)) {
|
||||
$output->writeln('<error>The download of the new composer version failed for an unexpected reason</error>');
|
||||
|
||||
|
@ -113,15 +115,13 @@ EOT
|
|||
|
||||
// remove saved installations of composer
|
||||
if ($input->getOption('clean-backups')) {
|
||||
$files = $this->getOldInstallationFiles($rollbackDir);
|
||||
$finder = $this->getOldInstallationFinder($rollbackDir);
|
||||
|
||||
if (!empty($files)) {
|
||||
$fs = new Filesystem;
|
||||
|
||||
foreach ($files as $file) {
|
||||
$output->writeln('<info>Removing: '.$file.'</info>');
|
||||
$fs->remove($file);
|
||||
}
|
||||
$fs = new Filesystem;
|
||||
foreach ($finder as $file) {
|
||||
$file = (string) $file;
|
||||
$output->writeln('<info>Removing: '.$file.'</info>');
|
||||
$fs->remove($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,18 +173,19 @@ EOT
|
|||
protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
|
||||
{
|
||||
try {
|
||||
@chmod($newFilename, 0777 & ~umask());
|
||||
// test the phar validity
|
||||
$phar = new \Phar($newFilename);
|
||||
// free the variable to unlock the file
|
||||
unset($phar);
|
||||
@chmod($newFilename, fileperms($localFilename));
|
||||
if (!ini_get('phar.readonly')) {
|
||||
// test the phar validity
|
||||
$phar = new \Phar($newFilename);
|
||||
// free the variable to unlock the file
|
||||
unset($phar);
|
||||
}
|
||||
|
||||
// copy current file into installations dir
|
||||
if ($backupTarget && file_exists($localFilename)) {
|
||||
@copy($localFilename, $backupTarget);
|
||||
}
|
||||
|
||||
unset($phar);
|
||||
rename($newFilename, $localFilename);
|
||||
} catch (\Exception $e) {
|
||||
if ($backupTarget) {
|
||||
|
@ -200,18 +201,25 @@ EOT
|
|||
|
||||
protected function getLastBackupVersion($rollbackDir)
|
||||
{
|
||||
$files = $this->getOldInstallationFiles($rollbackDir);
|
||||
if (empty($files)) {
|
||||
return false;
|
||||
$finder = $this->getOldInstallationFinder($rollbackDir);
|
||||
$finder->sortByName();
|
||||
$files = iterator_to_array($finder);
|
||||
|
||||
if (count($files)) {
|
||||
return basename(end($files), self::OLD_INSTALL_EXT);
|
||||
}
|
||||
|
||||
sort($files);
|
||||
|
||||
return basename(end($files), self::OLD_INSTALL_EXT);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getOldInstallationFiles($rollbackDir)
|
||||
protected function getOldInstallationFinder($rollbackDir)
|
||||
{
|
||||
return glob($rollbackDir . '/*' . self::OLD_INSTALL_EXT) ?: array();
|
||||
$finder = Finder::create()
|
||||
->depth(0)
|
||||
->files()
|
||||
->name('*' . self::OLD_INSTALL_EXT)
|
||||
->in($rollbackDir);
|
||||
|
||||
return $finder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class ShowCommand extends Command
|
|||
{
|
||||
$this
|
||||
->setName('show')
|
||||
->setAliases(array('info'))
|
||||
->setDescription('Show information about packages')
|
||||
->setDefinition(array(
|
||||
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'),
|
||||
|
@ -50,6 +51,7 @@ class ShowCommand extends Command
|
|||
new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
|
||||
new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
|
||||
new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
|
||||
new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The show command displays detailed information about a package, or
|
||||
|
@ -193,8 +195,9 @@ EOT
|
|||
$width--;
|
||||
}
|
||||
|
||||
$writeVersion = !$input->getOption('name-only') && $showVersion && ($nameLength + $versionLength + 3 <= $width);
|
||||
$writeDescription = !$input->getOption('name-only') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width);
|
||||
$writePath = !$input->getOption('name-only') && $input->getOption('path');
|
||||
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion && ($nameLength + $versionLength + 3 <= $width);
|
||||
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width);
|
||||
foreach ($packages[$type] as $package) {
|
||||
if (is_object($package)) {
|
||||
$output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
|
||||
|
@ -211,6 +214,11 @@ EOT
|
|||
}
|
||||
$output->write(' ' . $description);
|
||||
}
|
||||
|
||||
if ($writePath) {
|
||||
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
|
||||
$output->write(' ' . $path);
|
||||
}
|
||||
} else {
|
||||
$output->write($indent . $package);
|
||||
}
|
||||
|
@ -287,6 +295,16 @@ EOT
|
|||
$output->writeln('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
|
||||
$output->writeln('<info>names</info> : ' . implode(', ', $package->getNames()));
|
||||
|
||||
if ($package->isAbandoned()) {
|
||||
$replacement = ($package->getReplacementPackage() !== null)
|
||||
? ' The author suggests using the ' . $package->getReplacementPackage(). ' package instead.'
|
||||
: null;
|
||||
|
||||
$output->writeln(
|
||||
sprintf('<error>Attention: This package is abandoned and no longer maintained.%s</error>', $replacement)
|
||||
);
|
||||
}
|
||||
|
||||
if ($package->getSupport()) {
|
||||
$output->writeln("\n<info>support</info>");
|
||||
foreach ($package->getSupport() as $type => $value) {
|
||||
|
|
|
@ -84,7 +84,7 @@ EOT
|
|||
foreach ($errors as $path => $changes) {
|
||||
if ($input->getOption('verbose')) {
|
||||
$indentedChanges = implode("\n", array_map(function ($line) {
|
||||
return ' ' . $line;
|
||||
return ' ' . ltrim($line);
|
||||
}, explode("\n", $changes)));
|
||||
$output->writeln('<info>'.$path.'</info>:');
|
||||
$output->writeln($indentedChanges);
|
||||
|
|
|
@ -41,11 +41,15 @@ class UpdateCommand extends Command
|
|||
new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
|
||||
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
|
||||
new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
|
||||
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('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
|
||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.')
|
||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
||||
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.'),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
The <info>update</info> command reads the composer.json file from the
|
||||
|
@ -58,6 +62,11 @@ To limit the update operation to a few packages, you can list the package(s)
|
|||
you want to update as such:
|
||||
|
||||
<info>php composer.phar update vendor/package1 foo/mypackage [...]</info>
|
||||
|
||||
You may also use an asterisk (*) pattern to limit the update operation to package(s)
|
||||
from a specific vendor:
|
||||
|
||||
<info>php composer.phar update vendor/package1 foo/* [...]</info>
|
||||
EOT
|
||||
)
|
||||
;
|
||||
|
@ -70,6 +79,10 @@ EOT
|
|||
$input->setOption('no-plugins', true);
|
||||
}
|
||||
|
||||
if ($input->getOption('dev')) {
|
||||
$output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
|
||||
}
|
||||
|
||||
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||
$io = $this->getIO();
|
||||
|
@ -101,7 +114,7 @@ EOT
|
|||
$preferDist = $input->getOption('prefer-dist');
|
||||
}
|
||||
|
||||
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
|
||||
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
|
||||
|
||||
$install
|
||||
->setDryRun($input->getOption('dry-run'))
|
||||
|
@ -109,11 +122,15 @@ EOT
|
|||
->setPreferSource($preferSource)
|
||||
->setPreferDist($preferDist)
|
||||
->setDevMode(!$input->getOption('no-dev'))
|
||||
->setDumpAutoloader(!$input->getOption('no-autoloader'))
|
||||
->setRunScripts(!$input->getOption('no-scripts'))
|
||||
->setOptimizeAutoloader($optimize)
|
||||
->setUpdate(true)
|
||||
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
|
||||
->setWhitelistDependencies($input->getOption('with-dependencies'))
|
||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||
->setPreferStable($input->getOption('prefer-stable'))
|
||||
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||
;
|
||||
|
||||
if ($input->getOption('no-plugins')) {
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
|
||||
namespace Composer\Command;
|
||||
|
||||
use Composer\Package\Loader\ValidatingArrayLoader;
|
||||
use Composer\Util\ConfigValidator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
|
@ -34,6 +36,7 @@ class ValidateCommand extends Command
|
|||
->setName('validate')
|
||||
->setDescription('Validates a composer.json')
|
||||
->setDefinition(array(
|
||||
new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'),
|
||||
new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json')
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
|
@ -65,7 +68,8 @@ EOT
|
|||
}
|
||||
|
||||
$validator = new ConfigValidator($this->getIO());
|
||||
list($errors, $publishErrors, $warnings) = $validator->validate($file);
|
||||
$checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL;
|
||||
list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);
|
||||
|
||||
// output errors/warnings
|
||||
if (!$errors && !$publishErrors && !$warnings) {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Json\JsonFile;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
|
@ -24,6 +25,7 @@ use Symfony\Component\Process\Process;
|
|||
class Compiler
|
||||
{
|
||||
private $version;
|
||||
private $branchAliasVersion = '';
|
||||
private $versionDate;
|
||||
|
||||
/**
|
||||
|
@ -48,13 +50,22 @@ class Compiler
|
|||
if ($process->run() != 0) {
|
||||
throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
|
||||
}
|
||||
|
||||
$date = new \DateTime(trim($process->getOutput()));
|
||||
$date->setTimezone(new \DateTimeZone('UTC'));
|
||||
$this->versionDate = $date->format('Y-m-d H:i:s');
|
||||
|
||||
$process = new Process('git describe --tags HEAD');
|
||||
$process = new Process('git describe --tags --exact-match HEAD');
|
||||
if ($process->run() == 0) {
|
||||
$this->version = trim($process->getOutput());
|
||||
} else {
|
||||
// get branch-alias defined in composer.json for dev-master (if any)
|
||||
$localConfig = __DIR__.'/../../composer.json';
|
||||
$file = new JsonFile($localConfig);
|
||||
$localConfig = $file->read();
|
||||
if (isset($localConfig['extra']['branch-alias']['dev-master'])) {
|
||||
$this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-master'];
|
||||
}
|
||||
}
|
||||
|
||||
$phar = new \Phar($pharFile, 0, 'composer.phar');
|
||||
|
@ -138,6 +149,7 @@ class Compiler
|
|||
|
||||
if ($path === 'src/Composer/Composer.php') {
|
||||
$content = str_replace('@package_version@', $this->version, $content);
|
||||
$content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
|
||||
$content = str_replace('@release_date@', $this->versionDate, $content);
|
||||
}
|
||||
|
||||
|
@ -200,6 +212,16 @@ class Compiler
|
|||
* the license that is located at the bottom of this file.
|
||||
*/
|
||||
|
||||
// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
|
||||
if (extension_loaded('apc') && ini_get('apc.enable_cli') && ini_get('apc.cache_by_default')) {
|
||||
if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
|
||||
ini_set('apc.cache_by_default', 0);
|
||||
} else {
|
||||
fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
|
||||
fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
Phar::mapPhar('composer.phar');
|
||||
|
||||
EOF;
|
||||
|
|
|
@ -29,6 +29,7 @@ use Composer\Autoload\AutoloadGenerator;
|
|||
class Composer
|
||||
{
|
||||
const VERSION = '@package_version@';
|
||||
const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
|
||||
const RELEASE_DATE = '@release_date@';
|
||||
|
||||
/**
|
||||
|
@ -67,7 +68,7 @@ class Composer
|
|||
private $config;
|
||||
|
||||
/**
|
||||
* @var EventDispatcher\EventDispatcher
|
||||
* @var EventDispatcher
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
|
@ -190,7 +191,7 @@ class Composer
|
|||
}
|
||||
|
||||
/**
|
||||
* @param EventDispatcher\EventDispatcher $eventDispatcher
|
||||
* @param EventDispatcher $eventDispatcher
|
||||
*/
|
||||
public function setEventDispatcher(EventDispatcher $eventDispatcher)
|
||||
{
|
||||
|
@ -198,7 +199,7 @@ class Composer
|
|||
}
|
||||
|
||||
/**
|
||||
* @return EventDispatcher\EventDispatcher
|
||||
* @return EventDispatcher
|
||||
*/
|
||||
public function getEventDispatcher()
|
||||
{
|
||||
|
|
|
@ -19,12 +19,14 @@ use Composer\Config\ConfigSourceInterface;
|
|||
*/
|
||||
class Config
|
||||
{
|
||||
const RELATIVE_PATHS = 1;
|
||||
|
||||
public static $defaultConfig = array(
|
||||
'process-timeout' => 300,
|
||||
'use-include-path' => false,
|
||||
'preferred-install' => 'auto',
|
||||
'notify-on-install' => true,
|
||||
'github-protocols' => array('git', 'https'),
|
||||
'github-protocols' => array('git', 'https', 'ssh'),
|
||||
'vendor-dir' => 'vendor',
|
||||
'bin-dir' => '{$vendor-dir}/bin',
|
||||
'cache-dir' => '{$home}/cache',
|
||||
|
@ -37,8 +39,14 @@ class Config
|
|||
'discard-changes' => false,
|
||||
'autoloader-suffix' => null,
|
||||
'optimize-autoloader' => false,
|
||||
'classmap-authoritative' => false,
|
||||
'prepend-autoloader' => true,
|
||||
'github-domains' => array('github.com'),
|
||||
'github-expose-hostname' => true,
|
||||
'store-auths' => 'prompt',
|
||||
// valid keys without defaults (auth config stuff):
|
||||
// github-oauth
|
||||
// http-basic
|
||||
);
|
||||
|
||||
public static $defaultRepositories = array(
|
||||
|
@ -50,14 +58,22 @@ class Config
|
|||
);
|
||||
|
||||
private $config;
|
||||
private $baseDir;
|
||||
private $repositories;
|
||||
private $configSource;
|
||||
private $authConfigSource;
|
||||
private $useEnvironment;
|
||||
|
||||
public function __construct()
|
||||
/**
|
||||
* @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings
|
||||
*/
|
||||
public function __construct($useEnvironment = true, $baseDir = null)
|
||||
{
|
||||
// load defaults
|
||||
$this->config = static::$defaultConfig;
|
||||
$this->repositories = static::$defaultRepositories;
|
||||
$this->useEnvironment = (bool) $useEnvironment;
|
||||
$this->baseDir = $baseDir;
|
||||
}
|
||||
|
||||
public function setConfigSource(ConfigSourceInterface $source)
|
||||
|
@ -70,17 +86,27 @@ class Config
|
|||
return $this->configSource;
|
||||
}
|
||||
|
||||
public function setAuthConfigSource(ConfigSourceInterface $source)
|
||||
{
|
||||
$this->authConfigSource = $source;
|
||||
}
|
||||
|
||||
public function getAuthConfigSource()
|
||||
{
|
||||
return $this->authConfigSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges new config values with the existing ones (overriding)
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
public function merge(array $config)
|
||||
public function merge($config)
|
||||
{
|
||||
// override defaults with given config
|
||||
if (!empty($config['config']) && is_array($config['config'])) {
|
||||
foreach ($config['config'] as $key => $val) {
|
||||
if (in_array($key, array('github-oauth')) && isset($this->config[$key])) {
|
||||
if (in_array($key, array('github-oauth', 'http-basic')) && isset($this->config[$key])) {
|
||||
$this->config[$key] = array_merge($this->config[$key], $val);
|
||||
} else {
|
||||
$this->config[$key] = $val;
|
||||
|
@ -99,7 +125,7 @@ class Config
|
|||
}
|
||||
|
||||
// disable a repository with an anonymous {"name": false} repo
|
||||
if (1 === count($repository) && false === current($repository)) {
|
||||
if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
|
||||
unset($this->repositories[key($repository)]);
|
||||
continue;
|
||||
}
|
||||
|
@ -127,10 +153,11 @@ class Config
|
|||
* Returns a setting
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $flags Options (see class constants)
|
||||
* @throws \RuntimeException
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key)
|
||||
public function get($key, $flags = 0)
|
||||
{
|
||||
switch ($key) {
|
||||
case 'vendor-dir':
|
||||
|
@ -143,7 +170,14 @@ class Config
|
|||
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
|
||||
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
|
||||
|
||||
return rtrim($this->process(getenv($env) ?: $this->config[$key]), '/\\');
|
||||
$val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key], $flags), '/\\');
|
||||
$val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val);
|
||||
|
||||
if (substr($key, -4) !== '-dir') {
|
||||
return $val;
|
||||
}
|
||||
|
||||
return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val);
|
||||
|
||||
case 'cache-ttl':
|
||||
return (int) $this->config[$key];
|
||||
|
@ -179,10 +213,10 @@ class Config
|
|||
return (int) $this->config['cache-ttl'];
|
||||
|
||||
case 'home':
|
||||
return rtrim($this->process($this->config[$key]), '/\\');
|
||||
return rtrim($this->process($this->config[$key], $flags), '/\\');
|
||||
|
||||
case 'discard-changes':
|
||||
if ($env = getenv('COMPOSER_DISCARD_CHANGES')) {
|
||||
if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
|
||||
if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) {
|
||||
throw new \RuntimeException(
|
||||
"Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"
|
||||
|
@ -206,7 +240,7 @@ class Config
|
|||
|
||||
case 'github-protocols':
|
||||
if (reset($this->config['github-protocols']) === 'http') {
|
||||
throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https" or "git"');
|
||||
throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"');
|
||||
}
|
||||
|
||||
return $this->config[$key];
|
||||
|
@ -216,17 +250,17 @@ class Config
|
|||
return null;
|
||||
}
|
||||
|
||||
return $this->process($this->config[$key]);
|
||||
return $this->process($this->config[$key], $flags);
|
||||
}
|
||||
}
|
||||
|
||||
public function all()
|
||||
public function all($flags = 0)
|
||||
{
|
||||
$all = array(
|
||||
'repositories' => $this->getRepositories(),
|
||||
);
|
||||
foreach (array_keys($this->config) as $key) {
|
||||
$all['config'][$key] = $this->get($key);
|
||||
$all['config'][$key] = $this->get($key, $flags);
|
||||
}
|
||||
|
||||
return $all;
|
||||
|
@ -254,10 +288,11 @@ class Config
|
|||
/**
|
||||
* Replaces {$refs} inside a config string
|
||||
*
|
||||
* @param string a config string that can contain {$refs-to-other-config}
|
||||
* @param string $value a config string that can contain {$refs-to-other-config}
|
||||
* @param int $flags Options (see class constants)
|
||||
* @return string
|
||||
*/
|
||||
private function process($value)
|
||||
private function process($value, $flags)
|
||||
{
|
||||
$config = $this;
|
||||
|
||||
|
@ -265,8 +300,43 @@ class Config
|
|||
return $value;
|
||||
}
|
||||
|
||||
return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) {
|
||||
return $config->get($match[1]);
|
||||
return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) {
|
||||
return $config->get($match[1], $flags);
|
||||
}, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns relative paths in absolute paths without realpath()
|
||||
*
|
||||
* Since the dirs might not exist yet we can not call realpath or it will fail.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private function realpath($path)
|
||||
{
|
||||
if (substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return $this->baseDir . '/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a Composer environment variable
|
||||
*
|
||||
* This should be used to read COMPOSER_ environment variables
|
||||
* that overload config values.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string|boolean
|
||||
*/
|
||||
private function getComposerEnv($var)
|
||||
{
|
||||
if ($this->useEnvironment) {
|
||||
return getenv($var);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,4 +66,11 @@ interface ConfigSourceInterface
|
|||
* @param string $name Name
|
||||
*/
|
||||
public function removeLink($type, $name);
|
||||
|
||||
/**
|
||||
* Gives a user-friendly name to this source (file path or so)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
||||
|
|
|
@ -23,17 +23,34 @@ use Composer\Json\JsonManipulator;
|
|||
*/
|
||||
class JsonConfigSource implements ConfigSourceInterface
|
||||
{
|
||||
/**
|
||||
* @var \Composer\Json\JsonFile
|
||||
*/
|
||||
private $file;
|
||||
private $manipulator;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $authConfig;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param JsonFile $file
|
||||
* @param bool $authConfig
|
||||
*/
|
||||
public function __construct(JsonFile $file)
|
||||
public function __construct(JsonFile $file, $authConfig = false)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->authConfig = $authConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->file->getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +79,16 @@ class JsonConfigSource implements ConfigSourceInterface
|
|||
public function addConfigSetting($name, $value)
|
||||
{
|
||||
$this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) {
|
||||
$config['config'][$key] = $val;
|
||||
if ($key === 'github-oauth' || $key === 'http-basic') {
|
||||
list($key, $host) = explode('.', $key, 2);
|
||||
if ($this->authConfig) {
|
||||
$config[$key][$host] = $val;
|
||||
} else {
|
||||
$config['config'][$key][$host] = $val;
|
||||
}
|
||||
} else {
|
||||
$config['config'][$key] = $val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -72,7 +98,16 @@ class JsonConfigSource implements ConfigSourceInterface
|
|||
public function removeConfigSetting($name)
|
||||
{
|
||||
$this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) {
|
||||
unset($config['config'][$key]);
|
||||
if ($key === 'github-oauth' || $key === 'http-basic') {
|
||||
list($key, $host) = explode('.', $key, 2);
|
||||
if ($this->authConfig) {
|
||||
unset($config[$key][$host]);
|
||||
} else {
|
||||
unset($config['config'][$key][$host]);
|
||||
}
|
||||
} else {
|
||||
unset($config['config'][$key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,20 +140,34 @@ class JsonConfigSource implements ConfigSourceInterface
|
|||
|
||||
if ($this->file->exists()) {
|
||||
$contents = file_get_contents($this->file->getPath());
|
||||
} elseif ($this->authConfig) {
|
||||
$contents = "{\n}\n";
|
||||
} else {
|
||||
$contents = "{\n \"config\": {\n }\n}\n";
|
||||
}
|
||||
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
|
||||
$newFile = !$this->file->exists();
|
||||
|
||||
// override manipulator method for auth config files
|
||||
if ($this->authConfig && $method === 'addConfigSetting') {
|
||||
$method = 'addSubNode';
|
||||
list($mainNode, $name) = explode('.', $args[0], 2);
|
||||
$args = array($mainNode, $name, $args[1]);
|
||||
} elseif ($this->authConfig && $method === 'removeConfigSetting') {
|
||||
$method = 'removeSubNode';
|
||||
list($mainNode, $name) = explode('.', $args[0], 2);
|
||||
$args = array($mainNode, $name);
|
||||
}
|
||||
|
||||
// try to update cleanly
|
||||
if (call_user_func_array(array($manipulator, $method), $args)) {
|
||||
file_put_contents($this->file->getPath(), $manipulator->getContents());
|
||||
} else {
|
||||
// on failed clean update, call the fallback and rewrite the whole file
|
||||
$config = $this->file->read();
|
||||
array_unshift($args, $config);
|
||||
$this->arrayUnshiftRef($args, $config);
|
||||
call_user_func_array($fallback, $args);
|
||||
$this->file->write($config);
|
||||
}
|
||||
|
@ -127,4 +176,19 @@ class JsonConfigSource implements ConfigSourceInterface
|
|||
@chmod($this->file->getPath(), 0600);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a reference to an element to the beginning of an array.
|
||||
*
|
||||
* @param array $array
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function arrayUnshiftRef(&$array, &$value)
|
||||
{
|
||||
$return = array_unshift($array, '');
|
||||
$array[0] = &$value;
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,11 +56,11 @@ class Application extends BaseApplication
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
if (function_exists('ini_set')) {
|
||||
if (function_exists('ini_set') && extension_loaded('xdebug')) {
|
||||
ini_set('xdebug.show_exception_trace', false);
|
||||
ini_set('xdebug.scream', false);
|
||||
|
||||
}
|
||||
|
||||
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
}
|
||||
|
@ -94,9 +94,18 @@ class Application extends BaseApplication
|
|||
$output->writeln('<warning>Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.</warning>');
|
||||
}
|
||||
|
||||
if (defined('COMPOSER_DEV_WARNING_TIME') && $this->getCommandName($input) !== 'self-update' && $this->getCommandName($input) !== 'selfupdate') {
|
||||
if (time() > COMPOSER_DEV_WARNING_TIME) {
|
||||
$output->writeln(sprintf('<warning>Warning: This development build of composer is over 30 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
|
||||
if (defined('COMPOSER_DEV_WARNING_TIME')) {
|
||||
$commandName = '';
|
||||
if ($name = $this->getCommandName($input)) {
|
||||
try {
|
||||
$commandName = $this->find($name)->getName();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
}
|
||||
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
|
||||
if (time() > COMPOSER_DEV_WARNING_TIME) {
|
||||
$output->writeln(sprintf('<warning>Warning: This development build of composer is over 30 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,14 +113,34 @@ class Application extends BaseApplication
|
|||
$input->setInteractive(false);
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--profile')) {
|
||||
$startTime = microtime(true);
|
||||
$this->io->enableDebugging($startTime);
|
||||
}
|
||||
|
||||
// switch working dir
|
||||
if ($newWorkDir = $this->getNewWorkingDir($input)) {
|
||||
$oldWorkingDir = getcwd();
|
||||
chdir($newWorkDir);
|
||||
if ($output->getVerbosity() >= 4) {
|
||||
$output->writeln('Changed CWD to ' . getcwd());
|
||||
}
|
||||
}
|
||||
|
||||
// add non-standard scripts as own commands
|
||||
$file = Factory::getComposerFile();
|
||||
if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
|
||||
if (isset($composer['scripts']) && is_array($composer['scripts'])) {
|
||||
foreach ($composer['scripts'] as $script => $dummy) {
|
||||
if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
|
||||
if ($this->has($script)) {
|
||||
$output->writeln('<warning>A script named '.$script.' would override a native Composer function and has been skipped</warning>');
|
||||
} else {
|
||||
$this->add(new Command\ScriptAliasCommand($script));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--profile')) {
|
||||
$startTime = microtime(true);
|
||||
$this->io->enableDebugging($startTime);
|
||||
}
|
||||
|
||||
$result = parent::doRun($input, $output);
|
||||
|
@ -129,6 +158,7 @@ class Application extends BaseApplication
|
|||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function getNewWorkingDir(InputInterface $input)
|
||||
|
@ -147,18 +177,30 @@ class Application extends BaseApplication
|
|||
public function renderException($exception, $output)
|
||||
{
|
||||
try {
|
||||
$composer = $this->getComposer(false);
|
||||
$composer = $this->getComposer(false, true);
|
||||
if ($composer) {
|
||||
$config = $composer->getConfig();
|
||||
|
||||
$minSpaceFree = 1024*1024;
|
||||
if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
|
||||
|| (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
|
||||
|| (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
|
||||
) {
|
||||
$output->writeln('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>');
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
|
||||
$output->writeln('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>');
|
||||
$output->writeln('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>');
|
||||
}
|
||||
|
||||
if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
|
||||
$output->writeln('<error>The following exception is caused by a lack of memory and not having swap configured</error>');
|
||||
$output->writeln('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>');
|
||||
}
|
||||
|
||||
return parent::renderException($exception, $output);
|
||||
}
|
||||
|
@ -184,12 +226,19 @@ class Application extends BaseApplication
|
|||
$message = $e->getMessage() . ':' . PHP_EOL . $errors;
|
||||
throw new JsonValidationException($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cached composer instance
|
||||
*/
|
||||
public function resetComposer()
|
||||
{
|
||||
$this->composer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IOInterface
|
||||
*/
|
||||
|
@ -227,6 +276,9 @@ class Application extends BaseApplication
|
|||
$commands[] = new Command\RunScriptCommand();
|
||||
$commands[] = new Command\LicensesCommand();
|
||||
$commands[] = new Command\GlobalCommand();
|
||||
$commands[] = new Command\ClearCacheCommand();
|
||||
$commands[] = new Command\RemoveCommand();
|
||||
$commands[] = new Command\HomeCommand();
|
||||
|
||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||
$commands[] = new Command\SelfUpdateCommand();
|
||||
|
@ -240,6 +292,16 @@ class Application extends BaseApplication
|
|||
*/
|
||||
public function getLongVersion()
|
||||
{
|
||||
if (Composer::BRANCH_ALIAS_VERSION) {
|
||||
return sprintf(
|
||||
'<info>%s</info> version <comment>%s (%s)</comment> %s',
|
||||
$this->getName(),
|
||||
Composer::BRANCH_ALIAS_VERSION,
|
||||
$this->getVersion(),
|
||||
Composer::RELEASE_DATE
|
||||
);
|
||||
}
|
||||
|
||||
return parent::getLongVersion() . ' ' . Composer::RELEASE_DATE;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
|
|||
class DefaultPolicy implements PolicyInterface
|
||||
{
|
||||
private $preferStable;
|
||||
private $preferLowest;
|
||||
|
||||
public function __construct($preferStable = false)
|
||||
public function __construct($preferStable = false, $preferLowest = false)
|
||||
{
|
||||
$this->preferStable = $preferStable;
|
||||
$this->preferLowest = $preferLowest;
|
||||
}
|
||||
|
||||
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
|
||||
|
@ -42,11 +44,11 @@ class DefaultPolicy implements PolicyInterface
|
|||
return $constraint->matchSpecific($version, true);
|
||||
}
|
||||
|
||||
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package)
|
||||
public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
|
||||
{
|
||||
$packages = array();
|
||||
|
||||
foreach ($pool->whatProvides($package->getName()) as $candidate) {
|
||||
foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
|
||||
if ($candidate !== $package) {
|
||||
$packages[] = $candidate;
|
||||
}
|
||||
|
@ -151,18 +153,18 @@ class DefaultPolicy implements PolicyInterface
|
|||
}
|
||||
|
||||
// priority equal, sort by package id to make reproducible
|
||||
if ($a->getId() === $b->getId()) {
|
||||
if ($a->id === $b->id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a->getId() < $b->getId()) ? -1 : 1;
|
||||
return ($a->id < $b->id) ? -1 : 1;
|
||||
}
|
||||
|
||||
if (isset($installedMap[$a->getId()])) {
|
||||
if (isset($installedMap[$a->id])) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (isset($installedMap[$b->getId()])) {
|
||||
if (isset($installedMap[$b->id])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
|
|||
|
||||
protected function pruneToBestVersion(Pool $pool, $literals)
|
||||
{
|
||||
$operator = $this->preferLowest ? '<' : '>';
|
||||
$bestLiterals = array($literals[0]);
|
||||
$bestPackage = $pool->literalToPackage($literals[0]);
|
||||
foreach ($literals as $i => $literal) {
|
||||
|
@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
|
|||
|
||||
$package = $pool->literalToPackage($literal);
|
||||
|
||||
if ($this->versionCompare($package, $bestPackage, '>')) {
|
||||
if ($this->versionCompare($package, $bestPackage, $operator)) {
|
||||
$bestPackage = $package;
|
||||
$bestLiterals = array($literal);
|
||||
} elseif ($this->versionCompare($package, $bestPackage, '==')) {
|
||||
|
@ -215,26 +218,6 @@ class DefaultPolicy implements PolicyInterface
|
|||
return $bestLiterals;
|
||||
}
|
||||
|
||||
protected function selectNewestPackages(array $installedMap, array $literals)
|
||||
{
|
||||
$maxLiterals = array($literals[0]);
|
||||
$maxPackage = $literals[0]->getPackage();
|
||||
foreach ($literals as $i => $literal) {
|
||||
if (0 === $i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->versionCompare($literal->getPackage(), $maxPackage, '>')) {
|
||||
$maxPackage = $literal->getPackage();
|
||||
$maxLiterals = array($literal);
|
||||
} elseif ($this->versionCompare($literal->getPackage(), $maxPackage, '==')) {
|
||||
$maxLiterals[] = $literal;
|
||||
}
|
||||
}
|
||||
|
||||
return $maxLiterals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes that installed packages come first and then all highest priority packages
|
||||
*/
|
||||
|
@ -247,7 +230,7 @@ class DefaultPolicy implements PolicyInterface
|
|||
foreach ($literals as $literal) {
|
||||
$package = $pool->literalToPackage($literal);
|
||||
|
||||
if (isset($installedMap[$package->getId()])) {
|
||||
if (isset($installedMap[$package->id])) {
|
||||
$selected[] = $literal;
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ namespace Composer\DependencyResolver;
|
|||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\LinkConstraint\LinkConstraintInterface;
|
||||
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||
use Composer\Package\LinkConstraint\EmptyConstraint;
|
||||
|
@ -23,8 +22,8 @@ use Composer\Repository\RepositoryInterface;
|
|||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\ComposerRepository;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\Repository\StreamableRepositoryInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* A package pool contains repositories that provide packages.
|
||||
|
@ -45,11 +44,13 @@ class Pool
|
|||
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;
|
||||
|
||||
public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
|
||||
|
@ -66,6 +67,12 @@ class Pool
|
|||
$this->filterRequires = $filterRequires;
|
||||
}
|
||||
|
||||
public function setWhitelist($whitelist)
|
||||
{
|
||||
$this->whitelist = $whitelist;
|
||||
$this->providerCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a repository and its packages to this package pool
|
||||
*
|
||||
|
@ -89,76 +96,6 @@ class Pool
|
|||
$this->providerRepos[] = $repo;
|
||||
$repo->setRootAliases($rootAliases);
|
||||
$repo->resetPackageIds();
|
||||
} elseif ($repo instanceof StreamableRepositoryInterface) {
|
||||
foreach ($repo->getMinimalPackages() as $package) {
|
||||
$name = $package['name'];
|
||||
$version = $package['version'];
|
||||
$stability = VersionParser::parseStability($version);
|
||||
|
||||
// collect names
|
||||
$names = array(
|
||||
$name => true,
|
||||
);
|
||||
if (isset($package['provide'])) {
|
||||
foreach ($package['provide'] as $target => $constraint) {
|
||||
$names[$target] = true;
|
||||
}
|
||||
}
|
||||
if (isset($package['replace'])) {
|
||||
foreach ($package['replace'] as $target => $constraint) {
|
||||
$names[$target] = true;
|
||||
}
|
||||
}
|
||||
$names = array_keys($names);
|
||||
|
||||
if ($exempt || $this->isPackageAcceptable($names, $stability)) {
|
||||
$package['id'] = $this->id++;
|
||||
$package['stability'] = $stability;
|
||||
$this->packages[] = $package;
|
||||
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][$package['id']] = $this->packages[$this->id - 2];
|
||||
}
|
||||
|
||||
// handle root package aliases
|
||||
unset($rootAliasData);
|
||||
if (isset($rootAliases[$name][$version])) {
|
||||
$rootAliasData = $rootAliases[$name][$version];
|
||||
} elseif (isset($package['alias_normalized']) && isset($rootAliases[$name][$package['alias_normalized']])) {
|
||||
$rootAliasData = $rootAliases[$name][$package['alias_normalized']];
|
||||
}
|
||||
|
||||
if (isset($rootAliasData)) {
|
||||
$alias = $package;
|
||||
unset($alias['raw']);
|
||||
$alias['version'] = $rootAliasData['alias_normalized'];
|
||||
$alias['alias'] = $rootAliasData['alias'];
|
||||
$alias['alias_of'] = $package['id'];
|
||||
$alias['id'] = $this->id++;
|
||||
$alias['root_alias'] = true;
|
||||
$this->packages[] = $alias;
|
||||
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][$alias['id']] = $this->packages[$this->id - 2];
|
||||
}
|
||||
}
|
||||
|
||||
// handle normal package aliases
|
||||
if (isset($package['alias'])) {
|
||||
$alias = $package;
|
||||
unset($alias['raw']);
|
||||
$alias['version'] = $package['alias_normalized'];
|
||||
$alias['alias'] = $package['alias'];
|
||||
$alias['alias_of'] = $package['id'];
|
||||
$alias['id'] = $this->id++;
|
||||
$this->packages[] = $alias;
|
||||
|
||||
foreach ($names as $provided) {
|
||||
$this->packageByName[$provided][$alias['id']] = $this->packages[$this->id - 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($repo->getPackages() as $package) {
|
||||
$names = $package->getNames();
|
||||
|
@ -166,6 +103,7 @@ class Pool
|
|||
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;
|
||||
|
@ -184,6 +122,7 @@ class Pool
|
|||
|
||||
$package->getRepository()->addPackage($aliasPackage);
|
||||
$this->packages[] = $aliasPackage;
|
||||
$this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
|
||||
|
||||
foreach ($aliasPackage->getNames() as $name) {
|
||||
$this->packageByName[$name][] = $aliasPackage;
|
||||
|
@ -214,44 +153,54 @@ class Pool
|
|||
*/
|
||||
public function packageById($id)
|
||||
{
|
||||
return $this->ensurePackageIsLoaded($this->packages[$id - 1]);
|
||||
return $this->packages[$id - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all packages providing the given package name and match the constraint
|
||||
*
|
||||
* @param string $name The package name to be searched for
|
||||
* @param LinkConstraintInterface $constraint A constraint that all returned
|
||||
* packages must match or null to return all
|
||||
* @return array A set of packages
|
||||
* @param string $name The package name to be searched for
|
||||
* @param LinkConstraintInterface $constraint A constraint that all returned
|
||||
* packages must match or null to return all
|
||||
* @param bool $mustMatchName Whether the name of returned packages
|
||||
* must match the given name
|
||||
* @return PackageInterface[] A set of packages
|
||||
*/
|
||||
public function whatProvides($name, LinkConstraintInterface $constraint = null)
|
||||
public function whatProvides($name, LinkConstraintInterface $constraint = null, $mustMatchName = false)
|
||||
{
|
||||
if (isset($this->providerCache[$name][(string) $constraint])) {
|
||||
return $this->providerCache[$name][(string) $constraint];
|
||||
$key = ((int) $mustMatchName).$constraint;
|
||||
if (isset($this->providerCache[$name][$key])) {
|
||||
return $this->providerCache[$name][$key];
|
||||
}
|
||||
|
||||
return $this->providerCache[$name][(string) $constraint] = $this->computeWhatProvides($name, $constraint);
|
||||
return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see whatProvides
|
||||
*/
|
||||
private function computeWhatProvides($name, $constraint)
|
||||
private function computeWhatProvides($name, $constraint, $mustMatchName = false)
|
||||
{
|
||||
$candidates = array();
|
||||
|
||||
foreach ($this->providerRepos as $repo) {
|
||||
foreach ($repo->whatProvides($this, $name) as $candidate) {
|
||||
$candidates[] = $candidate;
|
||||
if ($candidate->getId() < 1) {
|
||||
if ($candidate->id < 1) {
|
||||
$candidate->setId($this->id++);
|
||||
$this->packages[$this->id - 2] = $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->packageByName[$name])) {
|
||||
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]);
|
||||
}
|
||||
} elseif (isset($this->packageByName[$name])) {
|
||||
$candidates = array_merge($candidates, $this->packageByName[$name]);
|
||||
}
|
||||
|
||||
|
@ -259,6 +208,20 @@ class Pool
|
|||
$nameMatch = false;
|
||||
|
||||
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 && (
|
||||
(!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
|
||||
($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
switch ($this->match($candidate, $name, $constraint)) {
|
||||
case self::MATCH_NONE:
|
||||
break;
|
||||
|
@ -269,15 +232,15 @@ class Pool
|
|||
|
||||
case self::MATCH:
|
||||
$nameMatch = true;
|
||||
$matches[] = $this->ensurePackageIsLoaded($candidate);
|
||||
$matches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_PROVIDE:
|
||||
$provideMatches[] = $this->ensurePackageIsLoaded($candidate);
|
||||
$provideMatches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_REPLACE:
|
||||
$matches[] = $this->ensurePackageIsLoaded($candidate);
|
||||
$matches[] = $candidate;
|
||||
break;
|
||||
|
||||
case self::MATCH_FILTERED:
|
||||
|
@ -312,7 +275,7 @@ class Pool
|
|||
{
|
||||
$package = $this->literalToPackage($literal);
|
||||
|
||||
if (isset($installedMap[$package->getId()])) {
|
||||
if (isset($installedMap[$package->id])) {
|
||||
$prefix = ($literal > 0 ? 'keep' : 'remove');
|
||||
} else {
|
||||
$prefix = ($literal > 0 ? 'install' : 'don\'t install');
|
||||
|
@ -338,28 +301,6 @@ class Pool
|
|||
return false;
|
||||
}
|
||||
|
||||
private function ensurePackageIsLoaded($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
if (isset($data['alias_of'])) {
|
||||
$aliasOf = $this->packageById($data['alias_of']);
|
||||
$package = $this->packages[$data['id'] - 1] = $data['repo']->loadAliasPackage($data, $aliasOf);
|
||||
$package->setRootPackageAlias(!empty($data['root_alias']));
|
||||
} else {
|
||||
$package = $this->packages[$data['id'] - 1] = $data['repo']->loadPackage($data);
|
||||
}
|
||||
|
||||
foreach ($package->getNames() as $name) {
|
||||
$this->packageByName[$name][$data['id']] = $package;
|
||||
}
|
||||
$package->setId($data['id']);
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the package matches the given constraint directly or through
|
||||
* provided or replaced packages
|
||||
|
@ -371,19 +312,10 @@ class Pool
|
|||
*/
|
||||
private function match($candidate, $name, LinkConstraintInterface $constraint = null)
|
||||
{
|
||||
// handle array packages
|
||||
if (is_array($candidate)) {
|
||||
$candidateName = $candidate['name'];
|
||||
$candidateVersion = $candidate['version'];
|
||||
$isDev = $candidate['stability'] === 'dev';
|
||||
$isAlias = isset($candidate['alias_of']);
|
||||
} else {
|
||||
// handle object packages
|
||||
$candidateName = $candidate->getName();
|
||||
$candidateVersion = $candidate->getVersion();
|
||||
$isDev = $candidate->getStability() === 'dev';
|
||||
$isAlias = $candidate instanceof AliasPackage;
|
||||
}
|
||||
$candidateName = $candidate->getName();
|
||||
$candidateVersion = $candidate->getVersion();
|
||||
$isDev = $candidate->getStability() === 'dev';
|
||||
$isAlias = $candidate instanceof AliasPackage;
|
||||
|
||||
if (!$isDev && !$isAlias && isset($this->filterRequires[$name])) {
|
||||
$requireFilter = $this->filterRequires[$name];
|
||||
|
@ -401,17 +333,8 @@ class Pool
|
|||
return self::MATCH_NAME;
|
||||
}
|
||||
|
||||
if (is_array($candidate)) {
|
||||
$provides = isset($candidate['provide'])
|
||||
? $this->versionParser->parseLinks($candidateName, $candidateVersion, 'provides', $candidate['provide'])
|
||||
: array();
|
||||
$replaces = isset($candidate['replace'])
|
||||
? $this->versionParser->parseLinks($candidateName, $candidateVersion, 'replaces', $candidate['replace'])
|
||||
: array();
|
||||
} else {
|
||||
$provides = $candidate->getProvides();
|
||||
$replaces = $candidate->getReplaces();
|
||||
}
|
||||
$provides = $candidate->getProvides();
|
||||
$replaces = $candidate->getReplaces();
|
||||
|
||||
// aliases create multiple replaces/provides for one target so they can not use the shortcut below
|
||||
if (isset($replaces[0]) || isset($provides[0])) {
|
||||
|
|
|
@ -80,7 +80,13 @@ class Problem
|
|||
$rule = $reason['rule'];
|
||||
$job = $reason['job'];
|
||||
|
||||
if ($job && $job['cmd'] === 'install' && empty($job['packages'])) {
|
||||
if (isset($job['constraint'])) {
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
||||
} else {
|
||||
$packages = array();
|
||||
}
|
||||
|
||||
if ($job && $job['cmd'] === 'install' && empty($packages)) {
|
||||
// handle php extensions
|
||||
if (0 === stripos($job['packageName'], 'ext-')) {
|
||||
$ext = substr($job['packageName'], 4);
|
||||
|
@ -124,7 +130,7 @@ class Problem
|
|||
$messages[] = $this->jobToText($job);
|
||||
} elseif ($rule) {
|
||||
if ($rule instanceof Rule) {
|
||||
$messages[] = $rule->getPrettyString($installedMap);
|
||||
$messages[] = $rule->getPrettyString($this->pool, $installedMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,18 +167,25 @@ class Problem
|
|||
{
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
if (!$job['packages']) {
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
||||
if (!$packages) {
|
||||
return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']);
|
||||
}
|
||||
|
||||
return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($job['packages']).'.';
|
||||
return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.';
|
||||
case 'update':
|
||||
return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.';
|
||||
case 'remove':
|
||||
return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).'';
|
||||
}
|
||||
|
||||
return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($job['packages']).'])';
|
||||
if (isset($job['constraint'])) {
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
||||
} else {
|
||||
$packages = array();
|
||||
}
|
||||
|
||||
return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])';
|
||||
}
|
||||
|
||||
protected function getPackageList($packages)
|
||||
|
|
|
@ -43,22 +43,31 @@ class Request
|
|||
$this->addJob($packageName, 'remove', $constraint);
|
||||
}
|
||||
|
||||
protected function addJob($packageName, $cmd, LinkConstraintInterface $constraint = null)
|
||||
/**
|
||||
* Mark an existing package as being installed and having to remain installed
|
||||
*
|
||||
* These jobs will not be tempered with by the solver
|
||||
*/
|
||||
public function fix($packageName, LinkConstraintInterface $constraint = null)
|
||||
{
|
||||
$this->addJob($packageName, 'install', $constraint, true);
|
||||
}
|
||||
|
||||
protected function addJob($packageName, $cmd, LinkConstraintInterface $constraint = null, $fixed = false)
|
||||
{
|
||||
$packageName = strtolower($packageName);
|
||||
$packages = $this->pool->whatProvides($packageName, $constraint);
|
||||
|
||||
$this->jobs[] = array(
|
||||
'packages' => $packages,
|
||||
'cmd' => $cmd,
|
||||
'packageName' => $packageName,
|
||||
'constraint' => $constraint,
|
||||
'fixed' => $fixed
|
||||
);
|
||||
}
|
||||
|
||||
public function updateAll()
|
||||
{
|
||||
$this->jobs[] = array('cmd' => 'update-all', 'packages' => array());
|
||||
$this->jobs[] = array('cmd' => 'update-all');
|
||||
}
|
||||
|
||||
public function getJobs()
|
||||
|
|
|
@ -29,10 +29,13 @@ class Rule
|
|||
const RULE_LEARNED = 12;
|
||||
const RULE_PACKAGE_ALIAS = 13;
|
||||
|
||||
protected $pool;
|
||||
/**
|
||||
* READ-ONLY: The literals this rule consists of.
|
||||
* @var array
|
||||
*/
|
||||
public $literals;
|
||||
|
||||
protected $disabled;
|
||||
protected $literals;
|
||||
protected $type;
|
||||
protected $id;
|
||||
protected $reason;
|
||||
|
@ -42,10 +45,8 @@ class Rule
|
|||
|
||||
protected $ruleHash;
|
||||
|
||||
public function __construct(Pool $pool, array $literals, $reason, $reasonData, $job = null)
|
||||
public function __construct(array $literals, $reason, $reasonData, $job = null)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
|
||||
// sort all packages ascending by id
|
||||
sort($literals);
|
||||
|
||||
|
@ -160,6 +161,9 @@ class Rule
|
|||
return !$this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use public literals member
|
||||
*/
|
||||
public function getLiterals()
|
||||
{
|
||||
return $this->literals;
|
||||
|
@ -170,14 +174,14 @@ class Rule
|
|||
return 1 === count($this->literals);
|
||||
}
|
||||
|
||||
public function getPrettyString(array $installedMap = array())
|
||||
public function getPrettyString(Pool $pool, array $installedMap = array())
|
||||
{
|
||||
$ruleText = '';
|
||||
foreach ($this->literals as $i => $literal) {
|
||||
if ($i != 0) {
|
||||
$ruleText .= '|';
|
||||
}
|
||||
$ruleText .= $this->pool->literalToPrettyString($literal, $installedMap);
|
||||
$ruleText .= $pool->literalToPrettyString($literal, $installedMap);
|
||||
}
|
||||
|
||||
switch ($this->reason) {
|
||||
|
@ -191,24 +195,24 @@ class Rule
|
|||
return "Remove command rule ($ruleText)";
|
||||
|
||||
case self::RULE_PACKAGE_CONFLICT:
|
||||
$package1 = $this->pool->literalToPackage($this->literals[0]);
|
||||
$package2 = $this->pool->literalToPackage($this->literals[1]);
|
||||
$package1 = $pool->literalToPackage($this->literals[0]);
|
||||
$package2 = $pool->literalToPackage($this->literals[1]);
|
||||
|
||||
return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique(array($package2)).'.';
|
||||
return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.';
|
||||
|
||||
case self::RULE_PACKAGE_REQUIRES:
|
||||
$literals = $this->literals;
|
||||
$sourceLiteral = array_shift($literals);
|
||||
$sourcePackage = $this->pool->literalToPackage($sourceLiteral);
|
||||
$sourcePackage = $pool->literalToPackage($sourceLiteral);
|
||||
|
||||
$requires = array();
|
||||
foreach ($literals as $literal) {
|
||||
$requires[] = $this->pool->literalToPackage($literal);
|
||||
$requires[] = $pool->literalToPackage($literal);
|
||||
}
|
||||
|
||||
$text = $this->reasonData->getPrettyString($sourcePackage);
|
||||
if ($requires) {
|
||||
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($requires) . '.';
|
||||
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.';
|
||||
} else {
|
||||
$targetName = $this->reasonData->getTarget();
|
||||
|
||||
|
@ -235,22 +239,24 @@ class Rule
|
|||
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
|
||||
return $ruleText;
|
||||
case self::RULE_PACKAGE_SAME_NAME:
|
||||
return 'Can only install one of: ' . $this->formatPackagesUnique($this->literals) . '.';
|
||||
return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $this->literals) . '.';
|
||||
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
|
||||
return $ruleText;
|
||||
case self::RULE_LEARNED:
|
||||
return 'Conclusion: '.$ruleText;
|
||||
case self::RULE_PACKAGE_ALIAS:
|
||||
return $ruleText;
|
||||
default:
|
||||
return '('.$ruleText.')';
|
||||
}
|
||||
}
|
||||
|
||||
protected function formatPackagesUnique(array $packages)
|
||||
protected function formatPackagesUnique($pool, array $packages)
|
||||
{
|
||||
$prepared = array();
|
||||
foreach ($packages as $package) {
|
||||
if (!is_object($package)) {
|
||||
$package = $this->pool->literalToPackage($package);
|
||||
$package = $pool->literalToPackage($package);
|
||||
}
|
||||
$prepared[$package->getName()]['name'] = $package->getPrettyName();
|
||||
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
|
||||
|
@ -275,7 +281,7 @@ class Rule
|
|||
if ($i != 0) {
|
||||
$result .= '|';
|
||||
}
|
||||
$result .= $this->pool->literalToString($literal);
|
||||
$result .= $literal;
|
||||
}
|
||||
|
||||
$result .= ')';
|
||||
|
|
|
@ -22,6 +22,13 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
const TYPE_JOB = 1;
|
||||
const TYPE_LEARNED = 4;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Lookup table for rule id to rule object
|
||||
*
|
||||
* @var Rule[]
|
||||
*/
|
||||
public $ruleById;
|
||||
|
||||
protected static $types = array(
|
||||
-1 => 'UNKNOWN',
|
||||
self::TYPE_PACKAGE => 'PACKAGE',
|
||||
|
@ -30,7 +37,6 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
);
|
||||
|
||||
protected $rules;
|
||||
protected $ruleById;
|
||||
protected $nextRuleId;
|
||||
|
||||
protected $rulesByHash;
|
||||
|
@ -144,17 +150,22 @@ class RuleSet implements \IteratorAggregate, \Countable
|
|||
return false;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function getPrettyString(Pool $pool = null)
|
||||
{
|
||||
$string = "\n";
|
||||
foreach ($this->rules as $type => $rules) {
|
||||
$string .= str_pad(self::$types[$type], 8, ' ') . ": ";
|
||||
foreach ($rules as $rule) {
|
||||
$string .= $rule."\n";
|
||||
$string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
|
||||
}
|
||||
$string .= "\n\n";
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getPrettyString(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
|
|||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -25,6 +26,8 @@ class RuleSetGenerator
|
|||
protected $rules;
|
||||
protected $jobs;
|
||||
protected $installedMap;
|
||||
protected $whitelistedMap;
|
||||
protected $addedMap;
|
||||
|
||||
public function __construct(PolicyInterface $policy, Pool $pool)
|
||||
{
|
||||
|
@ -38,27 +41,27 @@ class RuleSetGenerator
|
|||
* This rule is of the form (-A|B|C), where B and C are the providers of
|
||||
* one requirement of the package A.
|
||||
*
|
||||
* @param PackageInterface $package The package with a requirement
|
||||
* @param array $providers The providers of the requirement
|
||||
* @param int $reason A RULE_* constant describing the
|
||||
* reason for generating this rule
|
||||
* @param mixed $reasonData Any data, e.g. the requirement name,
|
||||
* that goes with the reason
|
||||
* @return Rule The generated rule or null if tautological
|
||||
* @param PackageInterface $package The package with a requirement
|
||||
* @param array $providers The providers of the requirement
|
||||
* @param int $reason A RULE_* constant describing the
|
||||
* reason for generating this rule
|
||||
* @param mixed $reasonData Any data, e.g. the requirement name,
|
||||
* that goes with the reason
|
||||
* @return Rule The generated rule or null if tautological
|
||||
*/
|
||||
protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
|
||||
{
|
||||
$literals = array(-$package->getId());
|
||||
$literals = array(-$package->id);
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
// self fulfilling rule?
|
||||
if ($provider === $package) {
|
||||
return null;
|
||||
}
|
||||
$literals[] = $provider->getId();
|
||||
$literals[] = $provider->id;
|
||||
}
|
||||
|
||||
return new Rule($this->pool, $literals, $reason, $reasonData);
|
||||
return new Rule($literals, $reason, $reasonData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,20 +70,20 @@ class RuleSetGenerator
|
|||
* The rule is (A|B|C) with A, B and C different packages. If the given
|
||||
* set of packages is empty an impossible rule is generated.
|
||||
*
|
||||
* @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 $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
|
||||
* @return Rule The generated rule
|
||||
*/
|
||||
protected function createInstallOneOfRule(array $packages, $reason, $job)
|
||||
{
|
||||
$literals = array();
|
||||
foreach ($packages as $package) {
|
||||
$literals[] = $package->getId();
|
||||
$literals[] = $package->id;
|
||||
}
|
||||
|
||||
return new Rule($this->pool, $literals, $reason, $job['packageName'], $job);
|
||||
return new Rule($literals, $reason, $job['packageName'], $job);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,15 +91,15 @@ class RuleSetGenerator
|
|||
*
|
||||
* 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
|
||||
* @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 Rule($this->pool, array(-$package->getId()), $reason, $job['packageName'], $job);
|
||||
return new Rule(array(-$package->id), $reason, $job['packageName'], $job);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,13 +108,13 @@ class RuleSetGenerator
|
|||
* The rule for conflicting packages A and B is (-A|-B). A is called the issuer
|
||||
* and B the provider.
|
||||
*
|
||||
* @param PackageInterface $issuer The package declaring the conflict
|
||||
* @param PackageInterface $provider The package causing the conflict
|
||||
* @param int $reason A RULE_* constant describing the
|
||||
* reason for generating this rule
|
||||
* @param mixed $reasonData Any data, e.g. the package name, that
|
||||
* goes with the reason
|
||||
* @return Rule The generated rule
|
||||
* @param PackageInterface $issuer The package declaring the conflict
|
||||
* @param PackageInterface $provider The package causing the conflict
|
||||
* @param int $reason A RULE_* constant describing the
|
||||
* reason for generating this rule
|
||||
* @param mixed $reasonData Any data, e.g. the package name, that
|
||||
* goes with the reason
|
||||
* @return Rule The generated rule
|
||||
*/
|
||||
protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
|
||||
{
|
||||
|
@ -120,7 +123,7 @@ class RuleSetGenerator
|
|||
return null;
|
||||
}
|
||||
|
||||
return new Rule($this->pool, array(-$issuer->getId(), -$provider->getId()), $reason, $reasonData);
|
||||
return new Rule(array(-$issuer->id, -$provider->id), $reason, $reasonData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,20 +144,59 @@ class RuleSetGenerator
|
|||
$this->rules->add($newRule, $type);
|
||||
}
|
||||
|
||||
protected function addRulesForPackage(PackageInterface $package)
|
||||
protected function whitelistFromPackage(PackageInterface $package)
|
||||
{
|
||||
$workQueue = new \SplQueue;
|
||||
$workQueue->enqueue($package);
|
||||
|
||||
while (!$workQueue->isEmpty()) {
|
||||
$package = $workQueue->dequeue();
|
||||
if (isset($this->addedMap[$package->getId()])) {
|
||||
if (isset($this->whitelistedMap[$package->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addedMap[$package->getId()] = true;
|
||||
$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;
|
||||
$workQueue->enqueue($package);
|
||||
|
||||
while (!$workQueue->isEmpty()) {
|
||||
$package = $workQueue->dequeue();
|
||||
if (isset($this->addedMap[$package->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addedMap[$package->id] = true;
|
||||
|
||||
foreach ($package->getRequires() as $link) {
|
||||
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
||||
|
||||
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));
|
||||
|
@ -173,7 +215,7 @@ class RuleSetGenerator
|
|||
}
|
||||
|
||||
// check obsoletes and implicit obsoletes of a package
|
||||
$isInstalled = (isset($this->installedMap[$package->getId()]));
|
||||
$isInstalled = (isset($this->installedMap[$package->id]));
|
||||
|
||||
foreach ($package->getReplaces() as $link) {
|
||||
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
||||
|
@ -221,41 +263,46 @@ class RuleSetGenerator
|
|||
return $impossible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all rules for all update packages of a given package
|
||||
*
|
||||
* @param PackageInterface $package Rules for this package's updates are to
|
||||
* be added
|
||||
*/
|
||||
private function addRulesForUpdatePackages(PackageInterface $package)
|
||||
{
|
||||
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
|
||||
|
||||
foreach ($updates as $update) {
|
||||
$this->addRulesForPackage($update);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addRulesForJobs()
|
||||
protected function whitelistFromJobs()
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
if ($job['packages']) {
|
||||
foreach ($job['packages'] as $package) {
|
||||
if (!isset($this->installedMap[$package->getId()])) {
|
||||
$this->addRulesForPackage($package);
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
|
||||
foreach ($packages as $package) {
|
||||
$this->whitelistFromPackage($package);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addRulesForJobs($ignorePlatformReqs)
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'install':
|
||||
if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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($job['packages'], Rule::RULE_JOB_INSTALL, $job);
|
||||
$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
|
||||
foreach ($job['packages'] as $package) {
|
||||
$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);
|
||||
}
|
||||
|
@ -264,18 +311,26 @@ class RuleSetGenerator
|
|||
}
|
||||
}
|
||||
|
||||
public function getRulesFor($jobs, $installedMap)
|
||||
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->addRulesForPackage($package);
|
||||
$this->addRulesForUpdatePackages($package);
|
||||
$this->whitelistFromPackage($package);
|
||||
}
|
||||
$this->whitelistFromJobs();
|
||||
|
||||
$this->pool->setWhitelist($this->whitelistedMap);
|
||||
|
||||
$this->addedMap = array();
|
||||
foreach ($this->installedMap as $package) {
|
||||
$this->addRulesForPackage($package, $ignorePlatformReqs);
|
||||
}
|
||||
|
||||
$this->addRulesForJobs();
|
||||
$this->addRulesForJobs($ignorePlatformReqs);
|
||||
|
||||
return $this->rules;
|
||||
}
|
||||
|
|
|
@ -69,11 +69,11 @@ class RuleWatchGraph
|
|||
* above example the rule was (-A|+B), then A turning true means that
|
||||
* B must now be decided true as well.
|
||||
*
|
||||
* @param int $decidedLiteral The literal which was decided (A in our example)
|
||||
* @param int $level The level at which the decision took place and at which
|
||||
* all resulting decisions should be made.
|
||||
* @param Decisions $decisions Used to check previous decisions and to
|
||||
* register decisions resulting from propagation
|
||||
* @param int $decidedLiteral The literal which was decided (A in our example)
|
||||
* @param int $level The level at which the decision took place and at which
|
||||
* all resulting decisions should be made.
|
||||
* @param Decisions $decisions Used to check previous decisions and to
|
||||
* register decisions resulting from propagation
|
||||
* @return Rule|null If a conflict is found the conflicting rule is returned
|
||||
*/
|
||||
public function propagateLiteral($decidedLiteral, $level, $decisions)
|
||||
|
@ -95,7 +95,7 @@ class RuleWatchGraph
|
|||
$otherWatch = $node->getOtherWatch($literal);
|
||||
|
||||
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
|
||||
$ruleLiterals = $node->getRule()->getLiterals();
|
||||
$ruleLiterals = $node->getRule()->literals;
|
||||
|
||||
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
|
||||
return $literal !== $ruleLiteral &&
|
||||
|
|
|
@ -35,7 +35,7 @@ class RuleWatchNode
|
|||
{
|
||||
$this->rule = $rule;
|
||||
|
||||
$literals = $rule->getLiterals();
|
||||
$literals = $rule->literals;
|
||||
|
||||
$this->watch1 = count($literals) > 0 ? $literals[0] : 0;
|
||||
$this->watch2 = count($literals) > 1 ? $literals[1] : 0;
|
||||
|
@ -51,10 +51,10 @@ class RuleWatchNode
|
|||
*/
|
||||
public function watch2OnHighest(Decisions $decisions)
|
||||
{
|
||||
$literals = $this->rule->getLiterals();
|
||||
$literals = $this->rule->literals;
|
||||
|
||||
// if there are only 2 elements, both are being watched anyway
|
||||
if ($literals < 3) {
|
||||
if (count($literals) < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -55,13 +56,13 @@ class Solver
|
|||
|
||||
$rulesCount = count($this->rules);
|
||||
for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
|
||||
$rule = $this->rules->ruleById($ruleIndex);
|
||||
$rule = $this->rules->ruleById[$ruleIndex];
|
||||
|
||||
if (!$rule->isAssertion() || $rule->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$literals = $rule->getLiterals();
|
||||
$literals = $rule->literals;
|
||||
$literal = $literals[0];
|
||||
|
||||
if (!$this->decisions->decided(abs($literal))) {
|
||||
|
@ -82,7 +83,6 @@ class Solver
|
|||
$conflict = $this->decisions->decisionRule($literal);
|
||||
|
||||
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
|
||||
|
||||
$problem = new Problem($this->pool);
|
||||
|
||||
$problem->addRule($rule);
|
||||
|
@ -104,7 +104,7 @@ class Solver
|
|||
continue;
|
||||
}
|
||||
|
||||
$assertRuleLiterals = $assertRule->getLiterals();
|
||||
$assertRuleLiterals = $assertRule->literals;
|
||||
$assertRuleLiteral = $assertRuleLiterals[0];
|
||||
|
||||
if (abs($literal) !== abs($assertRuleLiteral)) {
|
||||
|
@ -125,29 +125,37 @@ class Solver
|
|||
{
|
||||
$this->installedMap = array();
|
||||
foreach ($this->installed->getPackages() as $package) {
|
||||
$this->installedMap[$package->getId()] = $package;
|
||||
$this->installedMap[$package->id] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkForRootRequireProblems($ignorePlatformReqs)
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
switch ($job['cmd']) {
|
||||
case 'update':
|
||||
foreach ($job['packages'] as $package) {
|
||||
if (isset($this->installedMap[$package->getId()])) {
|
||||
$this->updateMap[$package->getId()] = true;
|
||||
$packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
|
||||
foreach ($packages as $package) {
|
||||
if (isset($this->installedMap[$package->id])) {
|
||||
$this->updateMap[$package->id] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'update-all':
|
||||
foreach ($this->installedMap as $package) {
|
||||
$this->updateMap[$package->getId()] = true;
|
||||
$this->updateMap[$package->id] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'install':
|
||||
if (!$job['packages']) {
|
||||
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 Rule($this->pool, array(), null, null, $job));
|
||||
$problem->addRule(new Rule(array(), null, null, $job));
|
||||
$this->problems[] = $problem;
|
||||
}
|
||||
break;
|
||||
|
@ -155,15 +163,14 @@ class Solver
|
|||
}
|
||||
}
|
||||
|
||||
public function solve(Request $request)
|
||||
public function solve(Request $request, $ignorePlatformReqs = false)
|
||||
{
|
||||
$this->jobs = $request->getJobs();
|
||||
|
||||
$this->setupInstalledMap();
|
||||
|
||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
|
||||
$this->checkForRootRequireProblems($ignorePlatformReqs);
|
||||
$this->decisions = new Decisions($this->pool);
|
||||
|
||||
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap);
|
||||
$this->watchGraph = new RuleWatchGraph;
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
|
@ -349,7 +356,7 @@ class Solver
|
|||
while (true) {
|
||||
$this->learnedPool[count($this->learnedPool) - 1][] = $rule;
|
||||
|
||||
foreach ($rule->getLiterals() as $literal) {
|
||||
foreach ($rule->literals as $literal) {
|
||||
// skip the one true literal
|
||||
if ($this->decisions->satisfy($literal)) {
|
||||
continue;
|
||||
|
@ -434,7 +441,7 @@ class Solver
|
|||
);
|
||||
}
|
||||
|
||||
$newRule = new Rule($this->pool, $learnedLiterals, Rule::RULE_LEARNED, $why);
|
||||
$newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
|
||||
|
||||
return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
|
||||
}
|
||||
|
@ -473,7 +480,7 @@ class Solver
|
|||
$this->problems[] = $problem;
|
||||
|
||||
$seen = array();
|
||||
$literals = $conflictRule->getLiterals();
|
||||
$literals = $conflictRule->literals;
|
||||
|
||||
foreach ($literals as $literal) {
|
||||
// skip the one true literal
|
||||
|
@ -496,7 +503,7 @@ class Solver
|
|||
$problem->addRule($why);
|
||||
$this->analyzeUnsolvableRule($problem, $why);
|
||||
|
||||
$literals = $why->getLiterals();
|
||||
$literals = $why->literals;
|
||||
|
||||
foreach ($literals as $literal) {
|
||||
// skip the one true literal
|
||||
|
@ -601,7 +608,6 @@ class Solver
|
|||
$installedPos = 0;
|
||||
|
||||
while (true) {
|
||||
|
||||
if (1 === $level) {
|
||||
$conflictRule = $this->propagate($level);
|
||||
if (null !== $conflictRule) {
|
||||
|
@ -621,7 +627,7 @@ class Solver
|
|||
$decisionQueue = array();
|
||||
$noneSatisfied = true;
|
||||
|
||||
foreach ($rule->getLiterals() as $literal) {
|
||||
foreach ($rule->literals as $literal) {
|
||||
if ($this->decisions->satisfy($literal)) {
|
||||
$noneSatisfied = false;
|
||||
break;
|
||||
|
@ -650,7 +656,6 @@ class Solver
|
|||
}
|
||||
|
||||
if ($noneSatisfied && count($decisionQueue)) {
|
||||
|
||||
$oLevel = $level;
|
||||
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
|
||||
|
||||
|
@ -682,8 +687,8 @@ class Solver
|
|||
$i = 0;
|
||||
}
|
||||
|
||||
$rule = $this->rules->ruleById($i);
|
||||
$literals = $rule->getLiterals();
|
||||
$rule = $this->rules->ruleById[$i];
|
||||
$literals = $rule->literals;
|
||||
|
||||
if ($rule->isDisabled()) {
|
||||
continue;
|
||||
|
@ -734,7 +739,6 @@ class Solver
|
|||
|
||||
// minimization step
|
||||
if (count($this->branches)) {
|
||||
|
||||
$lastLiteral = null;
|
||||
$lastLevel = null;
|
||||
$lastBranchIndex = 0;
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\DependencyResolver\Operation;
|
||||
|
||||
/**
|
||||
* @author Nils Adermann <naderman@naderman.de>
|
||||
|
@ -50,16 +49,15 @@ class Transaction
|
|||
$package = $this->pool->literalToPackage($literal);
|
||||
|
||||
// wanted & installed || !wanted & !installed
|
||||
if (($literal > 0) == (isset($this->installedMap[$package->getId()]))) {
|
||||
if (($literal > 0) == (isset($this->installedMap[$package->id]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($literal > 0) {
|
||||
if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
|
||||
|
||||
$source = $installMeansUpdateMap[abs($literal)];
|
||||
|
||||
$updateMap[$package->getId()] = array(
|
||||
$updateMap[$package->id] = array(
|
||||
'package' => $package,
|
||||
'source' => $source,
|
||||
'reason' => $reason,
|
||||
|
@ -67,9 +65,9 @@ class Transaction
|
|||
|
||||
// avoid updates to one package from multiple origins
|
||||
unset($installMeansUpdateMap[abs($literal)]);
|
||||
$ignoreRemove[$source->getId()] = true;
|
||||
$ignoreRemove[$source->id] = true;
|
||||
} else {
|
||||
$installMap[$package->getId()] = array(
|
||||
$installMap[$package->id] = array(
|
||||
'package' => $package,
|
||||
'reason' => $reason,
|
||||
);
|
||||
|
@ -79,16 +77,16 @@ class Transaction
|
|||
|
||||
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->getId()]) &&
|
||||
!isset($ignoreRemove[$package->getId()])) {
|
||||
$uninstallMap[$package->getId()] = array(
|
||||
isset($this->installedMap[$package->id]) &&
|
||||
!isset($ignoreRemove[$package->id])) {
|
||||
$uninstallMap[$package->id] = array(
|
||||
'package' => $package,
|
||||
'reason' => $reason,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +107,7 @@ class Transaction
|
|||
|
||||
while (!empty($queue)) {
|
||||
$package = array_pop($queue);
|
||||
$packageId = $package->getId();
|
||||
$packageId = $package->id;
|
||||
|
||||
if (!isset($visited[$packageId])) {
|
||||
array_push($queue, $package);
|
||||
|
@ -126,7 +124,7 @@ class Transaction
|
|||
}
|
||||
}
|
||||
|
||||
$visited[$package->getId()] = true;
|
||||
$visited[$package->id] = true;
|
||||
} else {
|
||||
if (isset($installMap[$packageId])) {
|
||||
$this->install(
|
||||
|
@ -167,7 +165,7 @@ class Transaction
|
|||
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
|
||||
|
||||
foreach ($possibleRequires as $require) {
|
||||
unset($roots[$require->getId()]);
|
||||
unset($roots[$require->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,13 +186,13 @@ class Transaction
|
|||
}
|
||||
|
||||
// !wanted & installed
|
||||
if ($literal <= 0 && isset($this->installedMap[$package->getId()])) {
|
||||
if ($literal <= 0 && isset($this->installedMap[$package->id])) {
|
||||
$updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
|
||||
|
||||
$literals = array($package->getId());
|
||||
$literals = array($package->id);
|
||||
|
||||
foreach ($updates as $update) {
|
||||
$literals[] = $update->getId();
|
||||
$literals[] = $update->id;
|
||||
}
|
||||
|
||||
foreach ($literals as $updateLiteral) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Base downloader for archives
|
||||
|
@ -47,22 +48,28 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
throw $e;
|
||||
}
|
||||
|
||||
unlink($fileName);
|
||||
$this->filesystem->unlink($fileName);
|
||||
|
||||
// get file list
|
||||
$contentDir = $this->listFiles($temporaryDir);
|
||||
$contentDir = $this->getFolderContent($temporaryDir);
|
||||
|
||||
// only one dir in the archive, extract its contents out of it
|
||||
if (1 === count($contentDir) && !is_file($contentDir[0])) {
|
||||
$contentDir = $this->listFiles($contentDir[0]);
|
||||
if (1 === count($contentDir) && is_dir(reset($contentDir))) {
|
||||
$contentDir = $this->getFolderContent((string) reset($contentDir));
|
||||
}
|
||||
|
||||
// move files back out of the temp dir
|
||||
foreach ($contentDir as $file) {
|
||||
$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);
|
||||
|
@ -128,14 +135,19 @@ abstract class ArchiveDownloader extends FileDownloader
|
|||
abstract protected function extract($file, $path);
|
||||
|
||||
/**
|
||||
* Returns the list of files in a directory including dotfiles
|
||||
* Returns the folder content, excluding dotfiles
|
||||
*
|
||||
* @param string $dir Directory
|
||||
* @return \SplFileInfo[]
|
||||
*/
|
||||
private function listFiles($dir)
|
||||
private function getFolderContent($dir)
|
||||
{
|
||||
$files = array_merge(glob($dir . '/.*') ?: array(), glob($dir . '/*') ?: array());
|
||||
$finder = Finder::create()
|
||||
->ignoreVCS(false)
|
||||
->ignoreDotFiles(false)
|
||||
->depth(0)
|
||||
->in($dir);
|
||||
|
||||
return array_values(array_filter($files, function ($el) {
|
||||
return basename($el) !== '.' && basename($el) !== '..';
|
||||
}));
|
||||
return iterator_to_array($finder);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Downloader\DownloaderInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@ use Composer\Util\Filesystem;
|
|||
*/
|
||||
class DownloadManager
|
||||
{
|
||||
private $io;
|
||||
private $preferDist = false;
|
||||
private $preferSource = false;
|
||||
private $filesystem;
|
||||
|
@ -31,11 +32,13 @@ 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
|
||||
*/
|
||||
public function __construct($preferSource = false, Filesystem $filesystem = null)
|
||||
public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
|
||||
{
|
||||
$this->io = $io;
|
||||
$this->preferSource = $preferSource;
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
}
|
||||
|
@ -100,7 +103,7 @@ class DownloadManager
|
|||
/**
|
||||
* Returns downloader for a specific installation type.
|
||||
*
|
||||
* @param string $type installation type
|
||||
* @param string $type installation type
|
||||
* @return DownloaderInterface
|
||||
*
|
||||
* @throws \InvalidArgumentException if downloader for provided type is not registered
|
||||
|
@ -118,12 +121,12 @@ class DownloadManager
|
|||
/**
|
||||
* Returns downloader for already installed package.
|
||||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param PackageInterface $package package instance
|
||||
* @return DownloaderInterface|null
|
||||
*
|
||||
* @throws \InvalidArgumentException if package has no installation source specified
|
||||
* @throws \LogicException if specific downloader used to load package with
|
||||
* wrong type
|
||||
* wrong type
|
||||
*/
|
||||
public function getDownloaderForInstalledPackage(PackageInterface $package)
|
||||
{
|
||||
|
@ -161,6 +164,7 @@ class DownloadManager
|
|||
* @param bool $preferSource prefer installation from source
|
||||
*
|
||||
* @throws \InvalidArgumentException if package have no urls to download from
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function download(PackageInterface $package, $targetDir, $preferSource = null)
|
||||
{
|
||||
|
@ -168,18 +172,48 @@ class DownloadManager
|
|||
$sourceType = $package->getSourceType();
|
||||
$distType = $package->getDistType();
|
||||
|
||||
if ((!$package->isDev() || $this->preferDist || !$sourceType) && !($preferSource && $sourceType) && $distType) {
|
||||
$package->setInstallationSource('dist');
|
||||
} elseif ($sourceType) {
|
||||
$package->setInstallationSource('source');
|
||||
} else {
|
||||
$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 ((!$package->isDev() || $this->preferDist) && !$preferSource) {
|
||||
$sources = array_reverse($sources);
|
||||
}
|
||||
|
||||
$this->filesystem->ensureDirectoryExists($targetDir);
|
||||
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
$downloader->download($package, $targetDir);
|
||||
foreach ($sources as $i => $source) {
|
||||
if (isset($e)) {
|
||||
$this->io->write(' <warning>Now trying to download from ' . $source . '</warning>');
|
||||
}
|
||||
$package->setInstallationSource($source);
|
||||
try {
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
if ($downloader) {
|
||||
$downloader->download($package, $targetDir);
|
||||
}
|
||||
break;
|
||||
} catch (\RuntimeException $e) {
|
||||
if ($i === count($sources) - 1) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->io->write(
|
||||
' <warning>Failed to download '.
|
||||
$package->getPrettyName().
|
||||
' from ' . $source . ': '.
|
||||
$e->getMessage().'</warning>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,6 +228,10 @@ class DownloadManager
|
|||
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
|
||||
{
|
||||
$downloader = $this->getDownloaderForInstalledPackage($initial);
|
||||
if (!$downloader) {
|
||||
return;
|
||||
}
|
||||
|
||||
$installationSource = $initial->getInstallationSource();
|
||||
|
||||
if ('dist' === $installationSource) {
|
||||
|
@ -230,6 +268,8 @@ class DownloadManager
|
|||
public function remove(PackageInterface $package, $targetDir)
|
||||
{
|
||||
$downloader = $this->getDownloaderForInstalledPackage($package);
|
||||
$downloader->remove($package, $targetDir);
|
||||
if ($downloader) {
|
||||
$downloader->remove($package, $targetDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ use Composer\Plugin\PluginEvents;
|
|||
use Composer\Plugin\PreFileDownloadEvent;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\GitHub;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
|
@ -56,11 +55,10 @@ class FileDownloader implements DownloaderInterface
|
|||
$this->io = $io;
|
||||
$this->config = $config;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->rfs = $rfs ?: new RemoteFilesystem($io);
|
||||
$this->rfs = $rfs ?: new RemoteFilesystem($io, $config);
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
$this->cache = $cache;
|
||||
|
||||
|
||||
if ($this->cache && $this->cache->gcIsNecessary()) {
|
||||
$this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
|
||||
}
|
||||
|
@ -79,18 +77,40 @@ class FileDownloader implements DownloaderInterface
|
|||
*/
|
||||
public function download(PackageInterface $package, $path)
|
||||
{
|
||||
$url = $package->getDistUrl();
|
||||
if (!$url) {
|
||||
if (!$package->getDistUrl()) {
|
||||
throw new \InvalidArgumentException('The given package is missing url information');
|
||||
}
|
||||
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->filesystem->ensureDirectoryExists($path);
|
||||
$this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
|
||||
|
||||
$urls = $package->getDistUrls();
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
return $this->doDownload($package, $path, $url);
|
||||
} catch (\Exception $e) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->write('');
|
||||
$this->io->write('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
|
||||
} elseif (count($urls)) {
|
||||
$this->io->write('');
|
||||
$this->io->write(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
|
||||
}
|
||||
|
||||
if (!count($urls)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->write('');
|
||||
}
|
||||
|
||||
protected function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$this->filesystem->emptyDirectory($path);
|
||||
|
||||
$fileName = $this->getFileName($package, $path);
|
||||
|
||||
$this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
|
||||
|
||||
$processedUrl = $this->processUrl($package, $url);
|
||||
$hostname = parse_url($processedUrl, PHP_URL_HOST);
|
||||
|
||||
|
@ -100,61 +120,39 @@ class FileDownloader implements DownloaderInterface
|
|||
}
|
||||
$rfs = $preFileDownloadEvent->getRemoteFilesystem();
|
||||
|
||||
if (strpos($hostname, '.github.com') === (strlen($hostname) - 11)) {
|
||||
$hostname = 'github.com';
|
||||
}
|
||||
|
||||
try {
|
||||
$checksum = $package->getDistSha1Checksum();
|
||||
$cacheKey = $this->getCacheKey($package);
|
||||
|
||||
try {
|
||||
// download if we don't have it in cache or the cache is invalidated
|
||||
if (!$this->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) {
|
||||
if (!$this->outputProgress) {
|
||||
$this->io->write(' Downloading');
|
||||
}
|
||||
// download if we don't have it in cache or the cache is invalidated
|
||||
if (!$this->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) {
|
||||
if (!$this->outputProgress) {
|
||||
$this->io->write(' Downloading');
|
||||
}
|
||||
|
||||
// try to download 3 times then fail hard
|
||||
$retries = 3;
|
||||
while ($retries--) {
|
||||
try {
|
||||
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress);
|
||||
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;
|
||||
}
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->write(' Download failed, retrying...');
|
||||
}
|
||||
usleep(500000);
|
||||
// try to download 3 times then fail hard
|
||||
$retries = 3;
|
||||
while ($retries--) {
|
||||
try {
|
||||
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
|
||||
break;
|
||||
} catch (TransportException $e) {
|
||||
// if we got an http response with a proper code, then requesting again will probably not help, abort
|
||||
if ((0 !== $e->getCode() && !in_array($e->getCode(),array(500, 502, 503, 504))) || !$retries) {
|
||||
throw $e;
|
||||
}
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->write(' Download failed, retrying...');
|
||||
}
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->cache) {
|
||||
$this->cache->copyFrom($cacheKey, $fileName);
|
||||
}
|
||||
} else {
|
||||
$this->io->write(' Loading from cache');
|
||||
}
|
||||
} catch (TransportException $e) {
|
||||
if (!in_array($e->getCode(), array(404, 403, 412))) {
|
||||
throw $e;
|
||||
}
|
||||
if ('github.com' === $hostname && !$this->io->hasAuthentication($hostname)) {
|
||||
$message = "\n".'Could not fetch '.$processedUrl.', enter your GitHub credentials '.($e->getCode() === 404 ? 'to access private repos' : 'to go over the API rate limit');
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, null, $rfs);
|
||||
if (!$gitHubUtil->authorizeOAuth($hostname)
|
||||
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($hostname, $message))
|
||||
) {
|
||||
throw $e;
|
||||
}
|
||||
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress);
|
||||
} else {
|
||||
throw $e;
|
||||
if ($this->cache) {
|
||||
$this->cache->copyFrom($cacheKey, $fileName);
|
||||
}
|
||||
} else {
|
||||
$this->io->write(' Loading from cache');
|
||||
}
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
|
@ -209,10 +207,7 @@ class FileDownloader implements DownloaderInterface
|
|||
{
|
||||
$this->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
|
||||
if (!$this->filesystem->removeDirectory($path)) {
|
||||
// retry after a bit on windows since it tends to be touchy with mass removals
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250000) && !$this->filesystem->removeDirectory($path))) {
|
||||
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
||||
}
|
||||
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ namespace Composer\Downloader;
|
|||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\GitHub;
|
||||
use Composer\Util\Git as GitUtil;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Config;
|
||||
|
||||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
|
@ -22,26 +26,37 @@ use Composer\Util\Git as GitUtil;
|
|||
class GitDownloader extends VcsDownloader
|
||||
{
|
||||
private $hasStashedChanges = false;
|
||||
private $gitUtil;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
|
||||
{
|
||||
parent::__construct($io, $config, $process, $fs);
|
||||
$this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path)
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$this->cleanEnv();
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
$ref = $package->getSourceReference();
|
||||
$flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : '';
|
||||
$command = 'git clone %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer';
|
||||
$command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer';
|
||||
$this->io->write(" Cloning ".$ref);
|
||||
|
||||
$commandCallable = function($url) use ($ref, $path, $command) {
|
||||
return sprintf($command, escapeshellarg($url), escapeshellarg($path), escapeshellarg($ref));
|
||||
$commandCallable = function ($url) use ($ref, $path, $command) {
|
||||
return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($ref));
|
||||
};
|
||||
|
||||
$this->runCommand($commandCallable, $package->getSourceUrl(), $path, true);
|
||||
$this->setPushUrl($package, $path);
|
||||
$this->gitUtil->runCommand($commandCallable, $url, $path, true);
|
||||
if ($url !== $package->getSourceUrl()) {
|
||||
$url = $package->getSourceUrl();
|
||||
$this->process->execute(sprintf('git remote set-url origin %s', ProcessExecutor::escape($url)), $output, $path);
|
||||
}
|
||||
$this->setPushUrl($path, $url);
|
||||
|
||||
if ($newRef = $this->updateToCommit($path, $ref, $package->getPrettyVersion(), $package->getReleaseDate())) {
|
||||
if ($package->getDistReference() === $package->getSourceReference()) {
|
||||
|
@ -54,9 +69,9 @@ class GitDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
$this->cleanEnv();
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
if (!is_dir($path.'/.git')) {
|
||||
throw new \RuntimeException('The .git directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information');
|
||||
|
@ -66,17 +81,11 @@ class GitDownloader extends VcsDownloader
|
|||
$this->io->write(" Checking out ".$ref);
|
||||
$command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer';
|
||||
|
||||
// capture username/password from URL if there is one
|
||||
$this->process->execute('git remote -v', $output, $path);
|
||||
if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) {
|
||||
$this->io->setAuthentication($match[3], urldecode($match[1]), urldecode($match[2]));
|
||||
}
|
||||
|
||||
$commandCallable = function($url) use ($command) {
|
||||
return sprintf($command, escapeshellarg($url));
|
||||
$commandCallable = function ($url) use ($command) {
|
||||
return sprintf($command, ProcessExecutor::escape ($url));
|
||||
};
|
||||
|
||||
$this->runCommand($commandCallable, $target->getSourceUrl(), $path);
|
||||
$this->gitUtil->runCommand($commandCallable, $url, $path);
|
||||
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
|
||||
if ($target->getDistReference() === $target->getSourceReference()) {
|
||||
$target->setDistReference($newRef);
|
||||
|
@ -90,7 +99,7 @@ class GitDownloader extends VcsDownloader
|
|||
*/
|
||||
public function getLocalChanges(PackageInterface $package, $path)
|
||||
{
|
||||
$this->cleanEnv();
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
if (!is_dir($path.'/.git')) {
|
||||
return;
|
||||
|
@ -109,7 +118,7 @@ class GitDownloader extends VcsDownloader
|
|||
*/
|
||||
protected function cleanChanges(PackageInterface $package, $path, $update)
|
||||
{
|
||||
$this->cleanEnv();
|
||||
GitUtil::cleanEnv();
|
||||
$path = $this->normalizePath($path);
|
||||
if (!$changes = $this->getLocalChanges($package, $path)) {
|
||||
return;
|
||||
|
@ -186,7 +195,7 @@ class GitDownloader extends VcsDownloader
|
|||
$path = $this->normalizePath($path);
|
||||
if ($this->hasStashedChanges) {
|
||||
$this->hasStashedChanges = false;
|
||||
$this->io->write(' <info>Re-applying stashed changes');
|
||||
$this->io->write(' <info>Re-applying stashed changes</info>');
|
||||
if (0 !== $this->process->execute('git stash pop', $output, $path)) {
|
||||
throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
|
||||
}
|
||||
|
@ -194,13 +203,15 @@ class GitDownloader extends VcsDownloader
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the given apth to the given commit ref
|
||||
* Updates the given path to the given commit ref
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $reference
|
||||
* @param string $branch
|
||||
* @param DateTime $date
|
||||
* @param string $path
|
||||
* @param string $reference
|
||||
* @param string $branch
|
||||
* @param \DateTime $date
|
||||
* @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function updateToCommit($path, $reference, $branch, $date)
|
||||
{
|
||||
|
@ -218,7 +229,7 @@ class GitDownloader extends VcsDownloader
|
|||
&& $branches
|
||||
&& preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
|
||||
) {
|
||||
$command = sprintf('git checkout -B %s %s && git reset --hard %2$s', escapeshellarg($branch), escapeshellarg('composer/'.$reference));
|
||||
$command = sprintf('git checkout -B %s %s && git reset --hard %2$s', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
return;
|
||||
}
|
||||
|
@ -231,192 +242,41 @@ class GitDownloader extends VcsDownloader
|
|||
$branch = 'v' . $branch;
|
||||
}
|
||||
|
||||
$command = sprintf('git checkout %s', escapeshellarg($branch));
|
||||
$fallbackCommand = sprintf('git checkout -B %s %s', escapeshellarg($branch), escapeshellarg('composer/'.$branch));
|
||||
$command = sprintf('git checkout %s', ProcessExecutor::escape($branch));
|
||||
$fallbackCommand = sprintf('git checkout -B %s %s', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
|
||||
if (0 === $this->process->execute($command, $output, $path)
|
||||
|| 0 === $this->process->execute($fallbackCommand, $output, $path)
|
||||
) {
|
||||
$command = sprintf('git reset --hard %s', escapeshellarg($reference));
|
||||
$command = sprintf('git reset --hard %s', ProcessExecutor::escape($reference));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$command = sprintf($template, escapeshellarg($gitRef));
|
||||
$command = sprintf($template, ProcessExecutor::escape($gitRef));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reference was not found (prints "fatal: reference is not a tree: $ref")
|
||||
if ($date && false !== strpos($this->process->getErrorOutput(), $reference)) {
|
||||
$date = $date->format('U');
|
||||
|
||||
// guess which remote branch to look at first
|
||||
$command = 'git branch -r';
|
||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
||||
}
|
||||
|
||||
$guessTemplate = 'git log --until=%s --date=raw -n1 --pretty=%%H %s';
|
||||
foreach ($this->process->splitLines($output) as $line) {
|
||||
if (preg_match('{^composer/'.preg_quote($branch).'(?:\.x)?$}i', trim($line))) {
|
||||
// find the previous commit by date in the given branch
|
||||
if (0 === $this->process->execute(sprintf($guessTemplate, $date, escapeshellarg(trim($line))), $output, $path)) {
|
||||
$newReference = trim($output);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($newReference)) {
|
||||
// no matching branch found, find the previous commit by date in all commits
|
||||
if (0 !== $this->process->execute(sprintf($guessTemplate, $date, '--all'), $output, $path)) {
|
||||
throw new \RuntimeException('Failed to execute ' . $this->sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput());
|
||||
}
|
||||
$newReference = trim($output);
|
||||
}
|
||||
|
||||
// checkout the new recovered ref
|
||||
$command = sprintf($template, escapeshellarg($newReference));
|
||||
if (0 === $this->process->execute($command, $output, $path)) {
|
||||
$this->io->write(' '.$reference.' is gone (history was rewritten?), recovered by checking out '.$newReference);
|
||||
|
||||
return $newReference;
|
||||
}
|
||||
if (false !== strpos($this->process->getErrorOutput(), $reference)) {
|
||||
$this->io->write(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Failed to execute ' . $this->sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput());
|
||||
throw new \RuntimeException('Failed to execute ' . GitUtil::sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a command doing attempts for each protocol supported by github.
|
||||
*
|
||||
* @param callable $commandCallable A callable building the command for the given url
|
||||
* @param string $url
|
||||
* @param string $cwd
|
||||
* @param bool $initialClone If true, the directory if cleared between every attempt
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function runCommand($commandCallable, $url, $cwd, $initialClone = false)
|
||||
{
|
||||
if ($initialClone) {
|
||||
$origCwd = $cwd;
|
||||
$cwd = null;
|
||||
}
|
||||
|
||||
if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
|
||||
throw new \InvalidArgumentException('The source URL '.$url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
|
||||
}
|
||||
|
||||
// public github, autoswitch protocols
|
||||
if (preg_match('{^(?:https?|git)(://'.$this->getGitHubDomainsRegex().'/.*)}', $url, $match)) {
|
||||
$protocols = $this->config->get('github-protocols');
|
||||
if (!is_array($protocols)) {
|
||||
throw new \RuntimeException('Config value "github-protocols" must be an array, got '.gettype($protocols));
|
||||
}
|
||||
$messages = array();
|
||||
foreach ($protocols as $protocol) {
|
||||
$url = $protocol . $match[1];
|
||||
if (0 === $this->process->execute(call_user_func($commandCallable, $url), $ignoredOutput, $cwd)) {
|
||||
return;
|
||||
}
|
||||
$messages[] = '- ' . $url . "\n" . preg_replace('#^#m', ' ', $this->process->getErrorOutput());
|
||||
if ($initialClone) {
|
||||
$this->filesystem->removeDirectory($origCwd);
|
||||
}
|
||||
}
|
||||
|
||||
// failed to checkout, first check git accessibility
|
||||
$this->throwException('Failed to clone ' . $this->sanitizeUrl($url) .' via '.implode(', ', $protocols).' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
|
||||
}
|
||||
|
||||
$command = call_user_func($commandCallable, $url);
|
||||
if (0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
|
||||
// private github repository without git access, try https with auth
|
||||
if (preg_match('{^git@'.$this->getGitHubDomainsRegex().':(.+?)\.git$}i', $url, $match)) {
|
||||
if (!$this->io->hasAuthentication($match[1])) {
|
||||
$gitHubUtil = new GitHub($this->io, $this->config, $this->process);
|
||||
$message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
|
||||
|
||||
if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
|
||||
$gitHubUtil->authorizeOAuthInteractively($match[1], $message);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->io->hasAuthentication($match[1])) {
|
||||
$auth = $this->io->getAuthentication($match[1]);
|
||||
$url = 'https://'.urlencode($auth['username']) . ':' . urlencode($auth['password']) . '@'.$match[1].'/'.$match[2].'.git';
|
||||
|
||||
$command = call_user_func($commandCallable, $url);
|
||||
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} elseif ( // private non-github repo that failed to authenticate
|
||||
$this->io->isInteractive() &&
|
||||
preg_match('{(https?://)([^/]+)(.*)$}i', $url, $match) &&
|
||||
strpos($this->process->getErrorOutput(), 'fatal: Authentication failed') !== false
|
||||
) {
|
||||
// TODO this should use an auth manager class that prompts and stores in the config
|
||||
if ($this->io->hasAuthentication($match[2])) {
|
||||
$auth = $this->io->getAuthentication($match[2]);
|
||||
} else {
|
||||
$this->io->write($url.' requires Authentication');
|
||||
$auth = array(
|
||||
'username' => $this->io->ask('Username: '),
|
||||
'password' => $this->io->askAndHideAnswer('Password: '),
|
||||
);
|
||||
}
|
||||
|
||||
$url = $match[1].urlencode($auth['username']).':'.urlencode($auth['password']).'@'.$match[2].$match[3];
|
||||
|
||||
$command = call_user_func($commandCallable, $url);
|
||||
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
|
||||
$this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($initialClone) {
|
||||
$this->filesystem->removeDirectory($origCwd);
|
||||
}
|
||||
$this->throwException('Failed to execute ' . $this->sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput(), $url);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGitHubDomainsRegex()
|
||||
{
|
||||
return '('.implode('|', array_map('preg_quote', $this->config->get('github-domains'))).')';
|
||||
}
|
||||
|
||||
protected function throwException($message, $url)
|
||||
{
|
||||
if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
|
||||
throw new \RuntimeException('Failed to clone '.$this->sanitizeUrl($url).', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput());
|
||||
}
|
||||
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
protected function sanitizeUrl($message)
|
||||
{
|
||||
return preg_replace('{://([^@]+?):.+?@}', '://$1:***@', $message);
|
||||
}
|
||||
|
||||
protected function setPushUrl(PackageInterface $package, $path)
|
||||
protected function setPushUrl($path, $url)
|
||||
{
|
||||
// set push url for github projects
|
||||
if (preg_match('{^(?:https?|git)://'.$this->getGitHubDomainsRegex().'/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) {
|
||||
if (preg_match('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
|
||||
$protocols = $this->config->get('github-protocols');
|
||||
$pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
|
||||
if ($protocols[0] !== 'git') {
|
||||
$pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
|
||||
}
|
||||
$cmd = sprintf('git remote set-url --push origin %s', escapeshellarg($pushUrl));
|
||||
$cmd = sprintf('git remote set-url --push origin %s', ProcessExecutor::escape($pushUrl));
|
||||
$this->process->execute($cmd, $ignoredOutput, $path);
|
||||
}
|
||||
}
|
||||
|
@ -462,12 +322,6 @@ class GitDownloader extends VcsDownloader
|
|||
$this->hasStashedChanges = true;
|
||||
}
|
||||
|
||||
protected function cleanEnv()
|
||||
{
|
||||
$util = new GitUtil;
|
||||
$util->cleanEnv();
|
||||
}
|
||||
|
||||
protected function normalizePath($path)
|
||||
{
|
||||
if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<?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\Downloader;
|
||||
|
||||
use Composer\Config;
|
||||
use Composer\Cache;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
* GZip archive downloader.
|
||||
*
|
||||
* @author Pavel Puchkin <i@neoascetic.me>
|
||||
*/
|
||||
class GzipDownloader extends ArchiveDownloader
|
||||
{
|
||||
protected $process;
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null)
|
||||
{
|
||||
$this->process = $process ?: new ProcessExecutor($io);
|
||||
parent::__construct($io, $config, $eventDispatcher, $cache);
|
||||
}
|
||||
|
||||
protected function extract($file, $path)
|
||||
{
|
||||
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));
|
||||
|
||||
// Try to use gunzip on *nix
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);
|
||||
|
||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
||||
throw new \RuntimeException($processError);
|
||||
}
|
||||
|
||||
// Windows version of PHP has built-in support of gzip functions
|
||||
$archiveFile = gzopen($file, 'rb');
|
||||
$targetFile = fopen($targetFilepath, 'wb');
|
||||
while ($string = gzread($archiveFile, 4096)) {
|
||||
fwrite($targetFile, $string, strlen($string));
|
||||
}
|
||||
gzclose($archiveFile);
|
||||
fclose($targetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFileName(PackageInterface $package, $path)
|
||||
{
|
||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
namespace Composer\Downloader;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
/**
|
||||
* @author Per Bernhardt <plb@webfactory.de>
|
||||
|
@ -22,12 +23,12 @@ class HgDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path)
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$url = escapeshellarg($package->getSourceUrl());
|
||||
$ref = escapeshellarg($package->getSourceReference());
|
||||
$url = ProcessExecutor::escape($url);
|
||||
$ref = ProcessExecutor::escape($package->getSourceReference());
|
||||
$this->io->write(" Cloning ".$package->getSourceReference());
|
||||
$command = sprintf('hg clone %s %s', $url, escapeshellarg($path));
|
||||
$command = sprintf('hg clone %s %s', $url, ProcessExecutor::escape($path));
|
||||
if (0 !== $this->process->execute($command, $ignoredOutput)) {
|
||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
||||
}
|
||||
|
@ -40,10 +41,10 @@ class HgDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
$url = escapeshellarg($target->getSourceUrl());
|
||||
$ref = escapeshellarg($target->getSourceReference());
|
||||
$url = ProcessExecutor::escape($url);
|
||||
$ref = ProcessExecutor::escape($target->getSourceReference());
|
||||
$this->io->write(" Updating to ".$target->getSourceReference());
|
||||
|
||||
if (!is_dir($path.'/.hg')) {
|
||||
|
|
|
@ -127,19 +127,20 @@ class PearPackageExtractor
|
|||
/**
|
||||
* Builds list of copy and list of remove actions that would transform extracted PEAR tarball into installed package.
|
||||
*
|
||||
* @param string $source string path to extracted files
|
||||
* @param array $roles array [role => roleRoot] relative root for files having that role
|
||||
* @param array $vars list of values can be used for replacement tasks
|
||||
* @return array array of 'source' => 'target', where source is location of file in the tarball (relative to source
|
||||
* path, and target is destination of file (also relative to $source path)
|
||||
* @param string $source string path to extracted files
|
||||
* @param array $roles array [role => roleRoot] relative root for files having that role
|
||||
* @param array $vars list of values can be used for replacement tasks
|
||||
* @return array array of 'source' => 'target', where source is location of file in the tarball (relative to source
|
||||
* path, and target is destination of file (also relative to $source path)
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function buildCopyActions($source, array $roles, $vars)
|
||||
{
|
||||
/** @var $package \SimpleXmlElement */
|
||||
$package = simplexml_load_file($this->combine($source, 'package.xml'));
|
||||
if(false === $package)
|
||||
if (false === $package) {
|
||||
throw new \RuntimeException('Package definition file is not valid.');
|
||||
}
|
||||
|
||||
$packageSchemaVersion = $package['version'];
|
||||
if ('1.0' == $packageSchemaVersion) {
|
||||
|
@ -203,16 +204,16 @@ class PearPackageExtractor
|
|||
/** @var $child \SimpleXMLElement */
|
||||
if ($child->getName() == 'dir') {
|
||||
$dirSource = $this->combine($source, (string) $child['name']);
|
||||
$dirTarget = $child['baseinstalldir'] ? : $target;
|
||||
$dirRole = $child['role'] ? : $role;
|
||||
$dirTarget = $child['baseinstalldir'] ?: $target;
|
||||
$dirRole = $child['role'] ?: $role;
|
||||
$dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
|
||||
$result = array_merge($result, $dirFiles);
|
||||
} elseif ($child->getName() == 'file') {
|
||||
$fileRole = (string) $child['role'] ? : $role;
|
||||
$fileRole = (string) $child['role'] ?: $role;
|
||||
if (isset($targetRoles[$fileRole])) {
|
||||
$fileName = (string) ($child['name'] ? : $child[0]); // $child[0] means text content
|
||||
$fileName = (string) ($child['name'] ?: $child[0]); // $child[0] means text content
|
||||
$fileSource = $this->combine($source, $fileName);
|
||||
$fileTarget = $this->combine((string) $child['baseinstalldir'] ? : $target, $fileName);
|
||||
$fileTarget = $this->combine((string) $child['baseinstalldir'] ?: $target, $fileName);
|
||||
if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
|
||||
$fileTarget = $packageName . '/' . $fileTarget;
|
||||
}
|
||||
|
@ -233,15 +234,15 @@ class PearPackageExtractor
|
|||
/** @var $child \SimpleXMLElement */
|
||||
if ('dir' == $child->getName()) {
|
||||
$dirSource = $this->combine($source, $child['name']);
|
||||
$dirTarget = $child['baseinstalldir'] ? : $target;
|
||||
$dirRole = $child['role'] ? : $role;
|
||||
$dirTarget = $child['baseinstalldir'] ?: $target;
|
||||
$dirRole = $child['role'] ?: $role;
|
||||
$dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
|
||||
$result = array_merge($result, $dirFiles);
|
||||
} elseif ('file' == $child->getName()) {
|
||||
$fileRole = (string) $child['role'] ? : $role;
|
||||
$fileRole = (string) $child['role'] ?: $role;
|
||||
if (isset($targetRoles[$fileRole])) {
|
||||
$fileSource = $this->combine($source, (string) $child['name']);
|
||||
$fileTarget = $this->combine((string) ($child['baseinstalldir'] ? : $target), (string) $child['name']);
|
||||
$fileTarget = $this->combine((string) ($child['baseinstalldir'] ?: $target), (string) $child['name']);
|
||||
$fileTasks = array();
|
||||
foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) {
|
||||
if ('replace' == $taskNode->getName()) {
|
||||
|
|
|
@ -22,18 +22,17 @@ use Composer\Util\Perforce;
|
|||
class PerforceDownloader extends VcsDownloader
|
||||
{
|
||||
protected $perforce;
|
||||
protected $perforceInjected = false;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path)
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$ref = $package->getSourceReference();
|
||||
$label = $package->getPrettyVersion();
|
||||
$label = $this->getLabelFromSourceReference($ref);
|
||||
|
||||
$this->io->write(' Cloning ' . $ref);
|
||||
$this->initPerforce($package, $path);
|
||||
$this->initPerforce($package, $path, $url);
|
||||
$this->perforce->setStream($ref);
|
||||
$this->perforce->p4Login($this->io);
|
||||
$this->perforce->writeP4ClientSpec();
|
||||
|
@ -42,10 +41,21 @@ class PerforceDownloader extends VcsDownloader
|
|||
$this->perforce->cleanupClientSpec();
|
||||
}
|
||||
|
||||
public function initPerforce($package, $path)
|
||||
private function getLabelFromSourceReference($ref)
|
||||
{
|
||||
if ($this->perforce) {
|
||||
$pos = strpos($ref,'@');
|
||||
if (false !== $pos) {
|
||||
return substr($ref, $pos + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function initPerforce($package, $path, $url)
|
||||
{
|
||||
if (!empty($this->perforce)) {
|
||||
$this->perforce->initializePath($path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,7 +64,7 @@ class PerforceDownloader extends VcsDownloader
|
|||
if ($repository instanceof VcsRepository) {
|
||||
$repoConfig = $this->getRepoConfig($repository);
|
||||
}
|
||||
$this->perforce = Perforce::create($repoConfig, $package->getSourceUrl(), $path);
|
||||
$this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io);
|
||||
}
|
||||
|
||||
private function getRepoConfig(VcsRepository $repository)
|
||||
|
@ -65,9 +75,9 @@ class PerforceDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
$this->doDownload($target, $path);
|
||||
$this->doDownload($target, $path, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,7 +42,7 @@ class RarDownloader extends ArchiveDownloader
|
|||
|
||||
// Try to use unrar on *nix
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$command = 'unrar x ' . escapeshellarg($file) . ' ' . escapeshellarg($path) . ' && chmod -R u+w ' . escapeshellarg($path);
|
||||
$command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
||||
|
||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||
return;
|
||||
|
|
|
@ -24,10 +24,10 @@ class SvnDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doDownload(PackageInterface $package, $path)
|
||||
public function doDownload(PackageInterface $package, $path, $url)
|
||||
{
|
||||
$url = $package->getSourceUrl();
|
||||
$ref = $package->getSourceReference();
|
||||
SvnUtil::cleanEnv();
|
||||
$ref = $package->getSourceReference();
|
||||
|
||||
$this->io->write(" Checking out ".$package->getSourceReference());
|
||||
$this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
|
||||
|
@ -36,17 +36,24 @@ class SvnDownloader extends VcsDownloader
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
|
||||
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
|
||||
{
|
||||
$url = $target->getSourceUrl();
|
||||
SvnUtil::cleanEnv();
|
||||
$ref = $target->getSourceReference();
|
||||
|
||||
if (!is_dir($path.'/.svn')) {
|
||||
throw new \RuntimeException('The .svn directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information');
|
||||
}
|
||||
|
||||
$flags = "";
|
||||
if (0 === $this->process->execute('svn --version', $output)) {
|
||||
if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match) && version_compare($match[1], '1.7.0', '>=')) {
|
||||
$flags .= ' --ignore-ancestry';
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->write(" Checking out " . $ref);
|
||||
$this->execute($url, "svn switch", sprintf("%s/%s", $url, $ref), $path);
|
||||
$this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +84,7 @@ class SvnDownloader extends VcsDownloader
|
|||
*/
|
||||
protected function execute($baseUrl, $command, $url, $cwd = null, $path = null)
|
||||
{
|
||||
$util = new SvnUtil($baseUrl, $this->io);
|
||||
$util = new SvnUtil($baseUrl, $this->io, $this->config);
|
||||
try {
|
||||
return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
|
||||
} catch (\RuntimeException $e) {
|
||||
|
@ -144,14 +151,20 @@ class SvnDownloader extends VcsDownloader
|
|||
*/
|
||||
protected function getCommitLogs($fromReference, $toReference, $path)
|
||||
{
|
||||
// strip paths from references and only keep the actual revision
|
||||
$fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference);
|
||||
$toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference);
|
||||
if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference) ) {
|
||||
// strip paths from references and only keep the actual revision
|
||||
$fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference);
|
||||
$toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference);
|
||||
|
||||
$command = sprintf('svn log -r%s:%s --incremental', $fromRevision, $toRevision);
|
||||
$command = sprintf('svn log -r%s:%s --incremental', $fromRevision, $toRevision);
|
||||
|
||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||
throw new \RuntimeException(
|
||||
'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$output = "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
|
|
@ -15,9 +15,10 @@ namespace Composer\Downloader;
|
|||
/**
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class TransportException extends \Exception
|
||||
class TransportException extends \RuntimeException
|
||||
{
|
||||
protected $headers;
|
||||
protected $response;
|
||||
|
||||
public function setHeaders($headers)
|
||||
{
|
||||
|
@ -28,4 +29,14 @@ class TransportException extends \Exception
|
|||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function setResponse($response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,8 +55,28 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
}
|
||||
|
||||
$this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
|
||||
$this->filesystem->removeDirectory($path);
|
||||
$this->doDownload($package, $path);
|
||||
$this->filesystem->emptyDirectory($path);
|
||||
|
||||
$urls = $package->getSourceUrls();
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
if (Filesystem::isLocalPath($url)) {
|
||||
$url = realpath($url);
|
||||
}
|
||||
$this->doDownload($package, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage());
|
||||
} elseif (count($urls)) {
|
||||
$this->io->write(' Failed, trying the next URL');
|
||||
}
|
||||
if (!count($urls)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->write('');
|
||||
}
|
||||
|
||||
|
@ -87,17 +107,31 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
$this->io->write(" - Updating <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
|
||||
|
||||
$this->cleanChanges($initial, $path, true);
|
||||
try {
|
||||
$this->doUpdate($initial, $target, $path);
|
||||
} catch (\Exception $e) {
|
||||
// in case of failed update, try to reapply the changes before aborting
|
||||
$this->reapplyChanges($path);
|
||||
$urls = $target->getSourceUrls();
|
||||
while ($url = array_shift($urls)) {
|
||||
try {
|
||||
if (Filesystem::isLocalPath($url)) {
|
||||
$url = realpath($url);
|
||||
}
|
||||
$this->doUpdate($initial, $target, $path, $url);
|
||||
break;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage());
|
||||
} elseif (count($urls)) {
|
||||
$this->io->write(' Failed, trying the next URL');
|
||||
} else {
|
||||
// in case of failed update, try to reapply the changes before aborting
|
||||
$this->reapplyChanges($path);
|
||||
|
||||
throw $e;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->reapplyChanges($path);
|
||||
|
||||
//print the commit logs if in verbose mode
|
||||
// print the commit logs if in verbose mode
|
||||
if ($this->io->isVerbose()) {
|
||||
$message = 'Pulling in changes:';
|
||||
$logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);
|
||||
|
@ -128,10 +162,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
$this->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
|
||||
$this->cleanChanges($package, $path, false);
|
||||
if (!$this->filesystem->removeDirectory($path)) {
|
||||
// retry after a bit on windows since it tends to be touchy with mass removals
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250) && !$this->filesystem->removeDirectory($path))) {
|
||||
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
||||
}
|
||||
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,10 +178,10 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
/**
|
||||
* Prompt the user to check if changes should be stashed/removed or the operation aborted
|
||||
*
|
||||
* @param PackageInterface $package
|
||||
* @param string $path
|
||||
* @param bool $update if true (update) the changes can be stashed and reapplied after an update,
|
||||
* if false (remove) the changes should be assumed to be lost if the operation is not aborted
|
||||
* @param PackageInterface $package
|
||||
* @param string $path
|
||||
* @param bool $update if true (update) the changes can be stashed and reapplied after an update,
|
||||
* if false (remove) the changes should be assumed to be lost if the operation is not aborted
|
||||
* @throws \RuntimeException in case the operation must be aborted
|
||||
*/
|
||||
protected function cleanChanges(PackageInterface $package, $path, $update)
|
||||
|
@ -176,8 +207,9 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
*
|
||||
* @param PackageInterface $package package instance
|
||||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
*/
|
||||
abstract protected function doDownload(PackageInterface $package, $path);
|
||||
abstract protected function doDownload(PackageInterface $package, $path, $url);
|
||||
|
||||
/**
|
||||
* Updates specific package in specific folder from initial to target version.
|
||||
|
@ -185,8 +217,9 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
|
|||
* @param PackageInterface $initial initial package
|
||||
* @param PackageInterface $target updated package
|
||||
* @param string $path download path
|
||||
* @param string $url package url
|
||||
*/
|
||||
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path);
|
||||
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
|
||||
|
||||
/**
|
||||
* Fetches the commit logs between two commits
|
||||
|
|
|
@ -38,12 +38,16 @@ class ZipDownloader extends ArchiveDownloader
|
|||
|
||||
// try to use unzip on *nix
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$command = 'unzip '.escapeshellarg($file).' -d '.escapeshellarg($path) . ' && chmod -R u+w ' . escapeshellarg($path);
|
||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||
return;
|
||||
}
|
||||
$command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
||||
try {
|
||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
||||
} catch (\Exception $e) {
|
||||
$processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('ZipArchive')) {
|
||||
|
|
|
@ -24,6 +24,16 @@ class Event
|
|||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array Arguments passed by the user, these will be forwarded to CLI script handlers
|
||||
*/
|
||||
protected $args;
|
||||
|
||||
/**
|
||||
* @var array Flags usable in PHP script handlers
|
||||
*/
|
||||
protected $flags;
|
||||
|
||||
/**
|
||||
* @var boolean Whether the event should not be passed to more listeners
|
||||
*/
|
||||
|
@ -32,11 +42,15 @@ class Event
|
|||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name The event name
|
||||
* @param string $name The event name
|
||||
* @param array $args Arguments passed by the user
|
||||
* @param array $flags Optional flags to pass data not as argument
|
||||
*/
|
||||
public function __construct($name)
|
||||
public function __construct($name, array $args = array(), array $flags = array())
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->args = $args;
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,6 +63,26 @@ class Event
|
|||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event's arguments.
|
||||
*
|
||||
* @return array The event arguments
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event's flags.
|
||||
*
|
||||
* @return array The event flags
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if stopPropagation has been called
|
||||
*
|
||||
|
|
|
@ -12,9 +12,14 @@
|
|||
|
||||
namespace Composer\EventDispatcher;
|
||||
|
||||
use Composer\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Installer\InstallerEvent;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Script;
|
||||
use Composer\Script\CommandEvent;
|
||||
use Composer\Script\PackageEvent;
|
||||
|
@ -39,6 +44,7 @@ class EventDispatcher
|
|||
protected $io;
|
||||
protected $loader;
|
||||
protected $process;
|
||||
protected $listeners;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -57,8 +63,10 @@ class EventDispatcher
|
|||
/**
|
||||
* Dispatch an event
|
||||
*
|
||||
* @param string $eventName An event name
|
||||
* @param Event $event
|
||||
* @param string $eventName An event name
|
||||
* @param Event $event
|
||||
* @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 dispatch($eventName, Event $event = null)
|
||||
{
|
||||
|
@ -66,51 +74,78 @@ class EventDispatcher
|
|||
$event = new Event($eventName);
|
||||
}
|
||||
|
||||
$this->doDispatch($event);
|
||||
return $this->doDispatch($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a script event.
|
||||
*
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param Script\Event $event
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param bool $devMode
|
||||
* @param array $additionalArgs Arguments passed by the user
|
||||
* @param array $flags Optional flags to pass data not as argument
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
*/
|
||||
public function dispatchScript($eventName, Script\Event $event = null)
|
||||
public function dispatchScript($eventName, $devMode = false, $additionalArgs = array(), $flags = array())
|
||||
{
|
||||
if (null == $event) {
|
||||
$event = new Script\Event($eventName, $this->composer, $this->io);
|
||||
}
|
||||
|
||||
$this->doDispatch($event);
|
||||
return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a package event.
|
||||
*
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param boolean $devMode Whether or not we are in dev mode
|
||||
* @param OperationInterface $operation The package being installed/updated/removed
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param boolean $devMode Whether or not we are in dev mode
|
||||
* @param OperationInterface $operation The package being installed/updated/removed
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
*/
|
||||
public function dispatchPackageEvent($eventName, $devMode, OperationInterface $operation)
|
||||
{
|
||||
$this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $operation));
|
||||
return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $operation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a command event.
|
||||
*
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param boolean $devMode Whether or not we are in dev mode
|
||||
* @param string $eventName The constant in ScriptEvents
|
||||
* @param boolean $devMode Whether or not we are in dev mode
|
||||
* @param array $additionalArgs Arguments passed by the user
|
||||
* @param array $flags Optional flags to pass data not as argument
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
*/
|
||||
public function dispatchCommandEvent($eventName, $devMode)
|
||||
public function dispatchCommandEvent($eventName, $devMode, $additionalArgs = array(), $flags = array())
|
||||
{
|
||||
$this->doDispatch(new CommandEvent($eventName, $this->composer, $this->io, $devMode));
|
||||
return $this->doDispatch(new CommandEvent($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a installer event.
|
||||
*
|
||||
* @param string $eventName The constant in InstallerEvents
|
||||
* @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
|
||||
*
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
*/
|
||||
public function dispatchInstallerEvent($eventName, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
|
||||
{
|
||||
return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $policy, $pool, $installedRepo, $request, $operations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the listeners of an event.
|
||||
*
|
||||
* @param Event $event The event object to pass to the event handlers/listeners.
|
||||
* @param Event $event The event object to pass to the event handlers/listeners.
|
||||
* @param string $additionalArgs
|
||||
* @return int return code of the executed script if any, for php scripts a false return
|
||||
* value is changed to 1, anything else to 0
|
||||
* @throws \RuntimeException
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
@ -118,9 +153,11 @@ class EventDispatcher
|
|||
{
|
||||
$listeners = $this->getListeners($event);
|
||||
|
||||
$return = 0;
|
||||
foreach ($listeners as $callable) {
|
||||
if (!is_string($callable) && is_callable($callable)) {
|
||||
call_user_func($callable, $event);
|
||||
$event = $this->checkListenerExpectedEvent($callable, $event);
|
||||
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
||||
} elseif ($this->isPhpScript($callable)) {
|
||||
$className = substr($callable, 0, strpos($callable, '::'));
|
||||
$methodName = substr($callable, strpos($callable, '::') + 2);
|
||||
|
@ -135,15 +172,16 @@ class EventDispatcher
|
|||
}
|
||||
|
||||
try {
|
||||
$this->executeEventPhpScript($className, $methodName, $event);
|
||||
$return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
|
||||
} catch (\Exception $e) {
|
||||
$message = "Script %s handling the %s event terminated with an exception";
|
||||
$this->io->write('<error>'.sprintf($message, $callable, $event->getName()).'</error>');
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
if (0 !== ($exitCode = $this->process->execute($callable))) {
|
||||
$event->getIO()->write(sprintf('<error>Script %s handling the %s event returned with an error</error>', $callable, $event->getName()));
|
||||
$args = implode(' ', array_map(array('Composer\Util\ProcessExecutor','escape'), $event->getArguments()));
|
||||
if (0 !== ($exitCode = $this->process->execute($callable . ($args === '' ? '' : ' '.$args)))) {
|
||||
$this->io->write(sprintf('<error>Script %s handling the %s event returned with an error</error>', $callable, $event->getName()));
|
||||
|
||||
throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
|
||||
}
|
||||
|
@ -153,6 +191,8 @@ class EventDispatcher
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +202,41 @@ class EventDispatcher
|
|||
*/
|
||||
protected function executeEventPhpScript($className, $methodName, Event $event)
|
||||
{
|
||||
$className::$methodName($event);
|
||||
$event = $this->checkListenerExpectedEvent(array($className, $methodName), $event);
|
||||
|
||||
return $className::$methodName($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $target
|
||||
* @param Event $event
|
||||
* @return Event|CommandEvent
|
||||
*/
|
||||
protected function checkListenerExpectedEvent($target, Event $event)
|
||||
{
|
||||
if (!$event instanceof Script\Event) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflected = new \ReflectionParameter($target, 0);
|
||||
} catch (\Exception $e) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$typehint = $reflected->getClass();
|
||||
|
||||
if (!$typehint instanceof \ReflectionClass) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
$expected = $typehint->getName();
|
||||
|
||||
if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
|
||||
$event = new CommandEvent($event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments());
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -220,6 +294,19 @@ class EventDispatcher
|
|||
return call_user_func_array('array_merge', $listeners[$event->getName()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an event has listeners registered
|
||||
*
|
||||
* @param Event $event
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasEventListeners(Event $event)
|
||||
{
|
||||
$listeners = $this->getListeners($event);
|
||||
|
||||
return count($listeners) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all listeners defined as scripts in the package
|
||||
*
|
||||
|
|
|
@ -17,7 +17,7 @@ use Composer\Json\JsonFile;
|
|||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\Archiver;
|
||||
use Composer\Repository\RepositoryManager;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\WritableRepositoryInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
@ -36,14 +36,12 @@ use Composer\Package\Version\VersionParser;
|
|||
class Factory
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
* @return Config
|
||||
*/
|
||||
public static function createConfig()
|
||||
protected static function getHomeDir()
|
||||
{
|
||||
// determine home and cache dirs
|
||||
$home = getenv('COMPOSER_HOME');
|
||||
$cacheDir = getenv('COMPOSER_CACHE_DIR');
|
||||
if (!$home) {
|
||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
||||
if (!getenv('APPDATA')) {
|
||||
|
@ -57,6 +55,18 @@ class Factory
|
|||
$home = rtrim(getenv('HOME'), '/') . '/.composer';
|
||||
}
|
||||
}
|
||||
|
||||
return $home;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $home
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getCacheDir($home)
|
||||
{
|
||||
$cacheDir = getenv('COMPOSER_CACHE_DIR');
|
||||
if (!$cacheDir) {
|
||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
||||
if ($cacheDir = getenv('LOCALAPPDATA')) {
|
||||
|
@ -70,6 +80,21 @@ class Factory
|
|||
}
|
||||
}
|
||||
|
||||
return $cacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOInterface|null $io
|
||||
* @return Config
|
||||
*/
|
||||
public static function createConfig(IOInterface $io = null, $cwd = null)
|
||||
{
|
||||
$cwd = $cwd ?: getcwd();
|
||||
|
||||
// determine home and cache dirs
|
||||
$home = self::getHomeDir();
|
||||
$cacheDir = self::getCacheDir($home);
|
||||
|
||||
// Protect directory against web access. Since HOME could be
|
||||
// the www-data's user home and be web-accessible it is a
|
||||
// potential security risk
|
||||
|
@ -82,48 +107,30 @@ class Factory
|
|||
}
|
||||
}
|
||||
|
||||
$config = new Config();
|
||||
$config = new Config(true, $cwd);
|
||||
|
||||
// add dirs to the config
|
||||
$config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir)));
|
||||
|
||||
// load global config
|
||||
$file = new JsonFile($home.'/config.json');
|
||||
if ($file->exists()) {
|
||||
if ($io && $io->isDebug()) {
|
||||
$io->write('Loading config file ' . $file->getPath());
|
||||
}
|
||||
$config->merge($file->read());
|
||||
}
|
||||
$config->setConfigSource(new JsonConfigSource($file));
|
||||
|
||||
// move old cache dirs to the new locations
|
||||
$legacyPaths = array(
|
||||
'cache-repo-dir' => array('/cache' => '/http*', '/cache.svn' => '/*', '/cache.github' => '/*'),
|
||||
'cache-vcs-dir' => array('/cache.git' => '/*', '/cache.hg' => '/*'),
|
||||
'cache-files-dir' => array('/cache.files' => '/*'),
|
||||
);
|
||||
foreach ($legacyPaths as $key => $oldPaths) {
|
||||
foreach ($oldPaths as $oldPath => $match) {
|
||||
$dir = $config->get($key);
|
||||
if ('/cache.github' === $oldPath) {
|
||||
$dir .= '/github.com';
|
||||
}
|
||||
$oldPath = $config->get('home').$oldPath;
|
||||
$oldPathMatch = $oldPath . $match;
|
||||
if (is_dir($oldPath) && $dir !== $oldPath) {
|
||||
if (!is_dir($dir)) {
|
||||
if (!@mkdir($dir, 0777, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (is_array($children = glob($oldPathMatch))) {
|
||||
foreach ($children as $child) {
|
||||
@rename($child, $dir.'/'.basename($child));
|
||||
}
|
||||
}
|
||||
if ($config->get('cache-dir') != $oldPath) {
|
||||
@rmdir($oldPath);
|
||||
}
|
||||
}
|
||||
// load global auth file
|
||||
$file = new JsonFile($config->get('home').'/auth.json');
|
||||
if ($file->exists()) {
|
||||
if ($io && $io->isDebug()) {
|
||||
$io->write('Loading config file ' . $file->getPath());
|
||||
}
|
||||
$config->merge(array('config' => $file->read()));
|
||||
}
|
||||
$config->setAuthConfigSource(new JsonConfigSource($file, true));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
@ -146,7 +153,7 @@ class Factory
|
|||
$repos = array();
|
||||
|
||||
if (!$config) {
|
||||
$config = static::createConfig();
|
||||
$config = static::createConfig($io);
|
||||
}
|
||||
if (!$rm) {
|
||||
if (!$io) {
|
||||
|
@ -157,11 +164,14 @@ class Factory
|
|||
}
|
||||
|
||||
foreach ($config->getRepositories() as $index => $repo) {
|
||||
if (is_string($repo)) {
|
||||
throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
|
||||
}
|
||||
if (!is_array($repo)) {
|
||||
throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
|
||||
throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
|
||||
}
|
||||
if (!isset($repo['type'])) {
|
||||
throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') must have a type defined');
|
||||
throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
|
||||
}
|
||||
$name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
|
||||
while (isset($repos[$name])) {
|
||||
|
@ -176,16 +186,19 @@ class Factory
|
|||
/**
|
||||
* Creates a Composer instance
|
||||
*
|
||||
* @param IOInterface $io IO instance
|
||||
* @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
|
||||
* read from the default filename
|
||||
* @param IOInterface $io IO instance
|
||||
* @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
|
||||
* read from the default filename
|
||||
* @param bool $disablePlugins Whether plugins should not be loaded
|
||||
* @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer)
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \UnexpectedValueException
|
||||
* @return Composer
|
||||
*/
|
||||
public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false)
|
||||
public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true)
|
||||
{
|
||||
$cwd = $cwd ?: getcwd();
|
||||
|
||||
// load Composer configuration
|
||||
if (null === $localConfig) {
|
||||
$localConfig = static::getComposerFile();
|
||||
|
@ -197,7 +210,7 @@ class Factory
|
|||
|
||||
if (!$file->exists()) {
|
||||
if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
|
||||
$message = 'Composer could not find a composer.json file in '.getcwd();
|
||||
$message = 'Composer could not find a composer.json file in '.$cwd;
|
||||
} else {
|
||||
$message = 'Composer could not find the config file: '.$localConfig;
|
||||
}
|
||||
|
@ -209,26 +222,45 @@ class Factory
|
|||
$localConfig = $file->read();
|
||||
}
|
||||
|
||||
// Configuration defaults
|
||||
$config = static::createConfig();
|
||||
// Load config and override with local config/auth config
|
||||
$config = static::createConfig($io, $cwd);
|
||||
$config->merge($localConfig);
|
||||
$io->loadConfiguration($config);
|
||||
if (isset($composerFile)) {
|
||||
if ($io && $io->isDebug()) {
|
||||
$io->write('Loading config file ' . $composerFile);
|
||||
}
|
||||
$localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json');
|
||||
if ($localAuthFile->exists()) {
|
||||
if ($io && $io->isDebug()) {
|
||||
$io->write('Loading config file ' . $localAuthFile->getPath());
|
||||
}
|
||||
$config->merge(array('config' => $localAuthFile->read()));
|
||||
$config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
|
||||
}
|
||||
}
|
||||
|
||||
$vendorDir = $config->get('vendor-dir');
|
||||
$binDir = $config->get('bin-dir');
|
||||
|
||||
// setup process timeout
|
||||
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
|
||||
|
||||
// initialize composer
|
||||
$composer = new Composer();
|
||||
$composer->setConfig($config);
|
||||
|
||||
if ($fullLoad) {
|
||||
// load auth configs into the IO instance
|
||||
$io->loadConfiguration($config);
|
||||
|
||||
// setup process timeout
|
||||
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
|
||||
}
|
||||
|
||||
// initialize event dispatcher
|
||||
$dispatcher = new EventDispatcher($composer, $io);
|
||||
$composer->setEventDispatcher($dispatcher);
|
||||
|
||||
// initialize repository manager
|
||||
$rm = $this->createRepositoryManager($io, $config, $dispatcher);
|
||||
$composer->setRepositoryManager($rm);
|
||||
|
||||
// load local repository
|
||||
$this->addLocalRepository($rm, $vendorDir);
|
||||
|
@ -237,45 +269,47 @@ class Factory
|
|||
$parser = new VersionParser;
|
||||
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
|
||||
$package = $loader->load($localConfig);
|
||||
$composer->setPackage($package);
|
||||
|
||||
// initialize installation manager
|
||||
$im = $this->createInstallationManager();
|
||||
|
||||
// Composer composition
|
||||
$composer->setPackage($package);
|
||||
$composer->setRepositoryManager($rm);
|
||||
$composer->setInstallationManager($im);
|
||||
|
||||
// initialize download manager
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher);
|
||||
if ($fullLoad) {
|
||||
// initialize download manager
|
||||
$dm = $this->createDownloadManager($io, $config, $dispatcher);
|
||||
$composer->setDownloadManager($dm);
|
||||
|
||||
$composer->setDownloadManager($dm);
|
||||
$composer->setEventDispatcher($dispatcher);
|
||||
|
||||
// initialize autoload generator
|
||||
$generator = new AutoloadGenerator($dispatcher);
|
||||
$composer->setAutoloadGenerator($generator);
|
||||
|
||||
// add installers to the manager
|
||||
$this->createDefaultInstallers($im, $composer, $io);
|
||||
|
||||
$globalRepository = $this->createGlobalRepository($config, $vendorDir);
|
||||
$pm = $this->createPluginManager($composer, $io, $globalRepository);
|
||||
$composer->setPluginManager($pm);
|
||||
|
||||
if (!$disablePlugins) {
|
||||
$pm->loadInstalledPlugins();
|
||||
// initialize autoload generator
|
||||
$generator = new AutoloadGenerator($dispatcher, $io);
|
||||
$composer->setAutoloadGenerator($generator);
|
||||
}
|
||||
|
||||
// purge packages if they have been deleted on the filesystem
|
||||
$this->purgePackages($rm, $im);
|
||||
// add installers to the manager (must happen after download manager is created since they read it out of $composer)
|
||||
$this->createDefaultInstallers($im, $composer, $io);
|
||||
|
||||
if ($fullLoad) {
|
||||
$globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
|
||||
$pm = $this->createPluginManager($io, $composer, $globalComposer);
|
||||
$composer->setPluginManager($pm);
|
||||
|
||||
if (!$disablePlugins) {
|
||||
$pm->loadInstalledPlugins();
|
||||
}
|
||||
|
||||
// once we have plugins and custom installers we can
|
||||
// purge packages from local repos if they have been deleted on the filesystem
|
||||
if ($rm->getLocalRepository()) {
|
||||
$this->purgePackages($rm->getLocalRepository(), $im);
|
||||
}
|
||||
}
|
||||
|
||||
// init locker if possible
|
||||
if (isset($composerFile)) {
|
||||
if ($fullLoad && isset($composerFile)) {
|
||||
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
|
||||
? substr($composerFile, 0, -4).'lock'
|
||||
: $composerFile . '.lock';
|
||||
$locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io)), $rm, $im, md5_file($composerFile));
|
||||
$locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
|
||||
$composer->setLocker($locker);
|
||||
}
|
||||
|
||||
|
@ -313,22 +347,26 @@ class Factory
|
|||
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param string $vendorDir
|
||||
/**
|
||||
* @param Config $config
|
||||
* @return Composer|null
|
||||
*/
|
||||
protected function createGlobalRepository(Config $config, $vendorDir)
|
||||
protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins)
|
||||
{
|
||||
if ($config->get('home') == $vendorDir) {
|
||||
return null;
|
||||
if (realpath($config->get('home')) === getcwd()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $config->get('home').'/vendor/composer/installed.json';
|
||||
if (!file_exists($path)) {
|
||||
return null;
|
||||
$composer = null;
|
||||
try {
|
||||
$composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false);
|
||||
} catch (\Exception $e) {
|
||||
if ($io->isDebug()) {
|
||||
$io->write('Failed to initialize global composer: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return new Repository\InstalledFilesystemRepository(new JsonFile($path));
|
||||
return $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -344,7 +382,7 @@ class Factory
|
|||
$cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
|
||||
}
|
||||
|
||||
$dm = new Downloader\DownloadManager();
|
||||
$dm = new Downloader\DownloadManager($io);
|
||||
switch ($config->get('preferred-install')) {
|
||||
case 'dist':
|
||||
$dm->setPreferDist(true);
|
||||
|
@ -365,6 +403,7 @@ class Factory
|
|||
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache));
|
||||
$dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache));
|
||||
|
||||
|
@ -392,11 +431,14 @@ class Factory
|
|||
}
|
||||
|
||||
/**
|
||||
* @param IOInterface $io
|
||||
* @param Composer $composer
|
||||
* @param Composer $globalComposer
|
||||
* @return Plugin\PluginManager
|
||||
*/
|
||||
protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
|
||||
protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null)
|
||||
{
|
||||
return new Plugin\PluginManager($composer, $io, $globalRepository);
|
||||
return new Plugin\PluginManager($io, $composer, $globalComposer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,12 +463,11 @@ class Factory
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Repository\RepositoryManager $rm
|
||||
* @param Installer\InstallationManager $im
|
||||
* @param WritableRepositoryInterface $repo repository to purge packages from
|
||||
* @param Installer\InstallationManager $im manager to check whether packages are still installed
|
||||
*/
|
||||
protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im)
|
||||
protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
|
||||
{
|
||||
$repo = $rm->getLocalRepository();
|
||||
foreach ($repo->getPackages() as $package) {
|
||||
if (!$im->isPackageInstalled($repo, $package)) {
|
||||
$repo->removePackage($package);
|
||||
|
@ -435,10 +476,10 @@ class Factory
|
|||
}
|
||||
|
||||
/**
|
||||
* @param IOInterface $io IO instance
|
||||
* @param mixed $config either a configuration array or a filename to read from, if null it will read from
|
||||
* the default filename
|
||||
* @param bool $disablePlugins Whether plugins should not be loaded
|
||||
* @param IOInterface $io IO instance
|
||||
* @param mixed $config either a configuration array or a filename to read from, if null it will read from
|
||||
* the default filename
|
||||
* @param bool $disablePlugins Whether plugins should not be loaded
|
||||
* @return Composer
|
||||
*/
|
||||
public static function create(IOInterface $io, $config = null, $disablePlugins = false)
|
||||
|
|
|
@ -68,5 +68,12 @@ abstract class BaseIO implements IOInterface
|
|||
$this->setAuthentication($domain, $token, 'x-oauth-basic');
|
||||
}
|
||||
}
|
||||
|
||||
// reload http basic credentials from config if available
|
||||
if ($creds = $config->get('http-basic')) {
|
||||
foreach ($creds as $domain => $cred) {
|
||||
$this->setAuthentication($domain, $cred['username'], $cred['password']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\IO;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
|
||||
/**
|
||||
* The Input/Output helper.
|
||||
|
@ -95,13 +96,11 @@ class ConsoleIO extends BaseIO
|
|||
public function write($messages, $newline = true)
|
||||
{
|
||||
if (null !== $this->startTime) {
|
||||
$messages = (array) $messages;
|
||||
$messages[0] = sprintf(
|
||||
'[%.1fMB/%.2fs] %s',
|
||||
memory_get_usage() / 1024 / 1024,
|
||||
microtime(true) - $this->startTime,
|
||||
$messages[0]
|
||||
);
|
||||
$memoryUsage = memory_get_usage() / 1024 / 1024;
|
||||
$timeSpent = microtime(true) - $this->startTime;
|
||||
$messages = array_map(function ($message) use ($memoryUsage, $timeSpent) {
|
||||
return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
|
||||
}, (array) $messages);
|
||||
}
|
||||
$this->output->write($messages, $newline);
|
||||
$this->lastMessage = join($newline ? "\n" : '', (array) $messages);
|
||||
|
@ -112,6 +111,14 @@ class ConsoleIO extends BaseIO
|
|||
*/
|
||||
public function overwrite($messages, $newline = true, $size = null)
|
||||
{
|
||||
if (!$this->output->isDecorated()) {
|
||||
if (!$messages) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->write($messages, count($messages) === 1 || $newline);
|
||||
}
|
||||
|
||||
// messages can be an array, let's convert it to string anyway
|
||||
$messages = join($newline ? "\n" : '', (array) $messages);
|
||||
|
||||
|
@ -171,6 +178,18 @@ class ConsoleIO extends BaseIO
|
|||
{
|
||||
// handle windows
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$finder = new ExecutableFinder();
|
||||
|
||||
// use bash if it's present
|
||||
if ($finder->find('bash') && $finder->find('stty')) {
|
||||
$this->write($question, false);
|
||||
$value = rtrim(shell_exec('bash -c "stty -echo; read -n0 discard; read -r mypassword; stty echo; echo $mypassword"'));
|
||||
$this->write('');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
// fallback to hiddeninput executable
|
||||
$exe = __DIR__.'\\hiddeninput.exe';
|
||||
|
||||
// handle code running from a phar
|
||||
|
|
|
@ -26,11 +26,12 @@ use Composer\DependencyResolver\SolverProblemsException;
|
|||
use Composer\Downloader\DownloadManager;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\Installer\InstallationManager;
|
||||
use Composer\Config;
|
||||
use Composer\Installer\InstallerEvents;
|
||||
use Composer\Installer\NoopInstaller;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\CompletePackage;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\LinkConstraint\VersionConstraint;
|
||||
use Composer\Package\Locker;
|
||||
|
@ -104,7 +105,16 @@ class Installer
|
|||
protected $dryRun = false;
|
||||
protected $verbose = false;
|
||||
protected $update = false;
|
||||
protected $dumpAutoloader = true;
|
||||
protected $runScripts = true;
|
||||
protected $ignorePlatformReqs = false;
|
||||
protected $preferStable = false;
|
||||
protected $preferLowest = false;
|
||||
/**
|
||||
* Array of package names/globs flagged for update
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $updateWhitelist = null;
|
||||
protected $whitelistDependencies = false;
|
||||
|
||||
|
@ -148,9 +158,14 @@ class Installer
|
|||
* Run installation (or update)
|
||||
*
|
||||
* @return int 0 on success or a positive error code on failure
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
gc_collect_cycles();
|
||||
gc_disable();
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->verbose = true;
|
||||
$this->runScripts = false;
|
||||
|
@ -218,16 +233,37 @@ class Installer
|
|||
}
|
||||
$this->installationManager->notifyInstalls();
|
||||
|
||||
// output suggestions
|
||||
foreach ($this->suggestedPackages as $suggestion) {
|
||||
$target = $suggestion['target'];
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
if (in_array($target, $package->getNames())) {
|
||||
continue 2;
|
||||
// output suggestions if we're in dev mode
|
||||
if ($this->devMode) {
|
||||
foreach ($this->suggestedPackages as $suggestion) {
|
||||
$target = $suggestion['target'];
|
||||
foreach ($installedRepo->getPackages() as $package) {
|
||||
if (in_array($target, $package->getNames())) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
|
||||
}
|
||||
}
|
||||
|
||||
# Find abandoned packages and warn user
|
||||
foreach ($localRepo->getPackages() as $package) {
|
||||
if (!$package instanceof CompletePackage || !$package->isAbandoned()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
|
||||
$replacement = (is_string($package->getReplacementPackage()))
|
||||
? 'Use ' . $package->getReplacementPackage() . ' instead'
|
||||
: 'No replacement was suggested';
|
||||
|
||||
$this->io->write(
|
||||
sprintf(
|
||||
"<error>Package %s is abandoned, you should avoid using it. %s.</error>",
|
||||
$package->getPrettyName(),
|
||||
$replacement
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->dryRun) {
|
||||
|
@ -252,8 +288,10 @@ class Installer
|
|||
$request->install($link->getTarget(), $link->getConstraint());
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request);
|
||||
$solver = new Solver($policy, $pool, $installedRepo);
|
||||
$ops = $solver->solve($request);
|
||||
$ops = $solver->solve($request, $this->ignorePlatformReqs);
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request, $ops);
|
||||
foreach ($ops as $op) {
|
||||
if ($op->getJobType() === 'uninstall') {
|
||||
$devPackages[] = $op->getPackage();
|
||||
|
@ -271,27 +309,37 @@ class Installer
|
|||
$platformDevReqs,
|
||||
$aliases,
|
||||
$this->package->getMinimumStability(),
|
||||
$this->package->getStabilityFlags()
|
||||
$this->package->getStabilityFlags(),
|
||||
$this->preferStable || $this->package->getPreferStable(),
|
||||
$this->preferLowest
|
||||
);
|
||||
if ($updatedLock) {
|
||||
$this->io->write('<info>Writing lock file</info>');
|
||||
}
|
||||
}
|
||||
|
||||
// write autoloader
|
||||
if ($this->optimizeAutoloader) {
|
||||
$this->io->write('<info>Generating optimized autoload files</info>');
|
||||
} else {
|
||||
$this->io->write('<info>Generating autoload files</info>');
|
||||
}
|
||||
if ($this->dumpAutoloader) {
|
||||
// write autoloader
|
||||
if ($this->optimizeAutoloader) {
|
||||
$this->io->write('<info>Generating optimized autoload files</info>');
|
||||
} else {
|
||||
$this->io->write('<info>Generating autoload files</info>');
|
||||
}
|
||||
|
||||
$this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
|
||||
$this->autoloadGenerator->setDevMode($this->devMode);
|
||||
$this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
|
||||
}
|
||||
|
||||
if ($this->runScripts) {
|
||||
// dispatch post event
|
||||
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
|
||||
$this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode);
|
||||
}
|
||||
|
||||
$vendorDir = $this->config->get('vendor-dir');
|
||||
if (is_dir($vendorDir)) {
|
||||
touch($vendorDir);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -361,7 +409,7 @@ class Installer
|
|||
}
|
||||
|
||||
if ($this->update) {
|
||||
$this->io->write('<info>Updating dependencies'.($withDevReqs?' (including require-dev)':'').'</info>');
|
||||
$this->io->write('<info>Updating dependencies'.($withDevReqs ? ' (including require-dev)' : '').'</info>');
|
||||
|
||||
$request->updateAll();
|
||||
|
||||
|
@ -412,7 +460,7 @@ class Installer
|
|||
}
|
||||
}
|
||||
} elseif ($installFromLock) {
|
||||
$this->io->write('<info>Installing dependencies'.($withDevReqs?' (including require-dev)':'').' from lock file</info>');
|
||||
$this->io->write('<info>Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').' from lock file</info>');
|
||||
|
||||
if (!$this->locker->isFresh()) {
|
||||
$this->io->write('<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.</warning>');
|
||||
|
@ -432,7 +480,7 @@ class Installer
|
|||
$request->install($link->getTarget(), $link->getConstraint());
|
||||
}
|
||||
} else {
|
||||
$this->io->write('<info>Installing dependencies'.($withDevReqs?' (including require-dev)':'').'</info>');
|
||||
$this->io->write('<info>Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').'</info>');
|
||||
|
||||
if ($withDevReqs) {
|
||||
$links = array_merge($this->package->getRequires(), $this->package->getDevRequires());
|
||||
|
@ -449,9 +497,11 @@ class Installer
|
|||
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
|
||||
|
||||
// solve dependencies
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request);
|
||||
$solver = new Solver($policy, $pool, $installedRepo);
|
||||
try {
|
||||
$operations = $solver->solve($request);
|
||||
$operations = $solver->solve($request, $this->ignorePlatformReqs);
|
||||
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request, $operations);
|
||||
} catch (SolverProblemsException $e) {
|
||||
$this->io->write('<error>Your requirements could not be resolved to an installable set of packages.</error>');
|
||||
$this->io->write($e->getMessage());
|
||||
|
@ -468,6 +518,7 @@ class Installer
|
|||
}
|
||||
|
||||
$operations = $this->movePluginsToFront($operations);
|
||||
$operations = $this->moveUninstallsToFront($operations);
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
// collect suggestions
|
||||
|
@ -481,11 +532,6 @@ class Installer
|
|||
}
|
||||
}
|
||||
|
||||
$event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());
|
||||
if (defined($event) && $this->runScripts) {
|
||||
$this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
|
||||
}
|
||||
|
||||
// not installing from lock, force dev packages' references if they're in root package refs
|
||||
if (!$installFromLock) {
|
||||
$package = null;
|
||||
|
@ -501,6 +547,23 @@ class Installer
|
|||
$package->setDistReference($references[$package->getName()]);
|
||||
}
|
||||
}
|
||||
if ('update' === $operation->getJobType()
|
||||
&& $operation->getTargetPackage()->isDev()
|
||||
&& $operation->getTargetPackage()->getVersion() === $operation->getInitialPackage()->getVersion()
|
||||
&& $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference()
|
||||
) {
|
||||
if ($this->io->isDebug()) {
|
||||
$this->io->write(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version');
|
||||
$this->io->write('');
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());
|
||||
if (defined($event) && $this->runScripts) {
|
||||
$this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
|
||||
}
|
||||
|
||||
// output non-alias ops in dry run, output alias ops in debug verbosity
|
||||
|
@ -520,11 +583,11 @@ class Installer
|
|||
if ($reason instanceof Rule) {
|
||||
switch ($reason->getReason()) {
|
||||
case Rule::RULE_JOB_INSTALL:
|
||||
$this->io->write(' REASON: Required by root: '.$reason->getRequiredPackage());
|
||||
$this->io->write(' REASON: Required by root: '.$reason->getPrettyString($pool));
|
||||
$this->io->write('');
|
||||
break;
|
||||
case Rule::RULE_PACKAGE_REQUIRES:
|
||||
$this->io->write(' REASON: '.$reason->getPrettyString());
|
||||
$this->io->write(' REASON: '.$reason->getPrettyString($pool));
|
||||
$this->io->write('');
|
||||
break;
|
||||
}
|
||||
|
@ -569,15 +632,45 @@ class Installer
|
|||
continue;
|
||||
}
|
||||
|
||||
if ($package->getRequires() === array() && ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer')) {
|
||||
$installerOps[] = $op;
|
||||
unset($operations[$idx]);
|
||||
if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') {
|
||||
// ignore requirements to platform or composer-plugin-api
|
||||
$requires = array_keys($package->getRequires());
|
||||
foreach ($requires as $index => $req) {
|
||||
if ($req === 'composer-plugin-api' || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) {
|
||||
unset($requires[$index]);
|
||||
}
|
||||
}
|
||||
// if there are no other requirements, move the plugin to the top of the op list
|
||||
if (!count($requires)) {
|
||||
$installerOps[] = $op;
|
||||
unset($operations[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($installerOps, $operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removals of packages should be executed before installations in
|
||||
* case two packages resolve to the same path (due to custom installers)
|
||||
*
|
||||
* @param OperationInterface[] $operations
|
||||
* @return OperationInterface[] reordered operation list
|
||||
*/
|
||||
private function moveUninstallsToFront(array $operations)
|
||||
{
|
||||
$uninstOps = array();
|
||||
foreach ($operations as $idx => $op) {
|
||||
if ($op instanceof UninstallOperation) {
|
||||
$uninstOps[] = $op;
|
||||
unset($operations[$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($uninstOps, $operations);
|
||||
}
|
||||
|
||||
private function createPool($withDevReqs)
|
||||
{
|
||||
$minimumStability = $this->package->getMinimumStability();
|
||||
|
@ -594,6 +687,10 @@ class Installer
|
|||
}
|
||||
$rootConstraints = array();
|
||||
foreach ($requires as $req => $constraint) {
|
||||
// skip platform requirements from the root package to avoid filtering out existing platform packages
|
||||
if ($this->ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) {
|
||||
continue;
|
||||
}
|
||||
$rootConstraints[$req] = $constraint->getConstraint();
|
||||
}
|
||||
|
||||
|
@ -602,7 +699,22 @@ class Installer
|
|||
|
||||
private function createPolicy()
|
||||
{
|
||||
return new DefaultPolicy($this->package->getPreferStable());
|
||||
$preferStable = null;
|
||||
$preferLowest = null;
|
||||
if (!$this->update && $this->locker->isLocked()) {
|
||||
$preferStable = $this->locker->getPreferStable();
|
||||
$preferLowest = $this->locker->getPreferLowest();
|
||||
}
|
||||
// old lock file without prefer stable/lowest will return null
|
||||
// so in this case we use the composer.json info
|
||||
if (null === $preferStable) {
|
||||
$preferStable = $this->preferStable || $this->package->getPreferStable();
|
||||
}
|
||||
if (null === $preferLowest) {
|
||||
$preferLowest = $this->preferLowest;
|
||||
}
|
||||
|
||||
return new DefaultPolicy($preferStable, $preferLowest);
|
||||
}
|
||||
|
||||
private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
|
||||
|
@ -631,7 +743,7 @@ class Installer
|
|||
|| !isset($provided[$package->getName()])
|
||||
|| !$provided[$package->getName()]->getConstraint()->matches($constraint)
|
||||
) {
|
||||
$request->install($package->getName(), $constraint);
|
||||
$request->fix($package->getName(), $constraint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -785,9 +897,8 @@ class Installer
|
|||
}
|
||||
|
||||
foreach ($this->updateWhitelist as $whiteListedPattern => $void) {
|
||||
$cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern));
|
||||
|
||||
if (preg_match("{^".$cleanedWhiteListedPattern."$}i", $package->getName())) {
|
||||
$patternRegexp = $this->packageNameToRegexp($whiteListedPattern);
|
||||
if (preg_match($patternRegexp, $package->getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -795,6 +906,19 @@ class Installer
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a regexp from a package name, expanding * globs as required
|
||||
*
|
||||
* @param string $whiteListedPattern
|
||||
* @return string
|
||||
*/
|
||||
private function packageNameToRegexp($whiteListedPattern)
|
||||
{
|
||||
$cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern));
|
||||
|
||||
return "{^" . $cleanedWhiteListedPattern . "$}i";
|
||||
}
|
||||
|
||||
private function extractPlatformRequirements($links)
|
||||
{
|
||||
$platformReqs = array();
|
||||
|
@ -844,11 +968,27 @@ class Installer
|
|||
|
||||
$seen = array();
|
||||
|
||||
$rootRequiredPackageNames = array_keys($rootRequires);
|
||||
|
||||
foreach ($this->updateWhitelist as $packageName => $void) {
|
||||
$packageQueue = new \SplQueue;
|
||||
|
||||
$depPackages = $pool->whatProvides($packageName);
|
||||
if (count($depPackages) == 0 && !in_array($packageName, $requiredPackageNames) && !in_array($packageName, array('nothing', 'lock'))) {
|
||||
|
||||
$nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true);
|
||||
|
||||
// check if the name is a glob pattern that did not match directly
|
||||
if (!$nameMatchesRequiredPackage) {
|
||||
$whitelistPatternRegexp = $this->packageNameToRegexp($packageName);
|
||||
foreach ($rootRequiredPackageNames as $rootRequiredPackageName) {
|
||||
if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) {
|
||||
$nameMatchesRequiredPackage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock'))) {
|
||||
$this->io->write('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
|
||||
}
|
||||
|
||||
|
@ -951,6 +1091,16 @@ class Installer
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if this is a dry run (simulation mode).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDryRun()
|
||||
{
|
||||
return $this->dryRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* prefer source installation
|
||||
*
|
||||
|
@ -1016,6 +1166,21 @@ class Installer
|
|||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set whether to run autoloader or not
|
||||
*
|
||||
* @param boolean $dumpAutoloader
|
||||
* @return Installer
|
||||
*/
|
||||
public function setDumpAutoloader($dumpAutoloader = true)
|
||||
{
|
||||
$this->dumpAutoloader = (boolean) $dumpAutoloader;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set whether to run scripts or not
|
||||
*
|
||||
|
@ -1055,6 +1220,29 @@ class Installer
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if running in verbose mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isVerbose()
|
||||
{
|
||||
return $this->verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* set ignore Platform Package requirements
|
||||
*
|
||||
* @param boolean $ignorePlatformReqs
|
||||
* @return Installer
|
||||
*/
|
||||
public function setIgnorePlatformRequirements($ignorePlatformReqs = false)
|
||||
{
|
||||
$this->ignorePlatformReqs = (boolean) $ignorePlatformReqs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* restrict the update operation to a few packages, all other packages
|
||||
* that are already installed will be kept at their current version
|
||||
|
@ -1072,7 +1260,7 @@ class Installer
|
|||
/**
|
||||
* Should dependencies of whitelisted packages be updated recursively?
|
||||
*
|
||||
* @param boolean $updateDependencies
|
||||
* @param boolean $updateDependencies
|
||||
* @return Installer
|
||||
*/
|
||||
public function setWhitelistDependencies($updateDependencies = true)
|
||||
|
@ -1082,6 +1270,32 @@ class Installer
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should packages be prefered in a stable version when updating?
|
||||
*
|
||||
* @param boolean $preferStable
|
||||
* @return Installer
|
||||
*/
|
||||
public function setPreferStable($preferStable = true)
|
||||
{
|
||||
$this->preferStable = (boolean) $preferStable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should packages be prefered in a lowest version when updating?
|
||||
*
|
||||
* @param boolean $preferLowest
|
||||
* @return Installer
|
||||
*/
|
||||
public function setPreferLowest($preferLowest = true)
|
||||
{
|
||||
$this->preferLowest = (boolean) $preferLowest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables plugins.
|
||||
*
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Composer\Installer;
|
|||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Plugin\PluginInstaller;
|
||||
use Composer\Repository\RepositoryInterface;
|
||||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?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\Installer;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\EventDispatcher\Event;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
|
||||
/**
|
||||
* An event for all installer.
|
||||
*
|
||||
* @author François Pluchino <francois.pluchino@gmail.com>
|
||||
*/
|
||||
class InstallerEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* @var PolicyInterface
|
||||
*/
|
||||
private $policy;
|
||||
|
||||
/**
|
||||
* @var Pool
|
||||
*/
|
||||
private $pool;
|
||||
|
||||
/**
|
||||
* @var CompositeRepository
|
||||
*/
|
||||
private $installedRepo;
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var OperationInterface[]
|
||||
*/
|
||||
private $operations;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param Composer $composer
|
||||
* @param IOInterface $io
|
||||
* @param PolicyInterface $policy
|
||||
* @param Pool $pool
|
||||
* @param CompositeRepository $installedRepo
|
||||
* @param Request $request
|
||||
* @param OperationInterface[] $operations
|
||||
*/
|
||||
public function __construct($eventName, Composer $composer, IOInterface $io, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
|
||||
{
|
||||
parent::__construct($eventName);
|
||||
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->policy = $policy;
|
||||
$this->pool = $pool;
|
||||
$this->installedRepo = $installedRepo;
|
||||
$this->request = $request;
|
||||
$this->operations = $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Composer
|
||||
*/
|
||||
public function getComposer()
|
||||
{
|
||||
return $this->composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IOInterface
|
||||
*/
|
||||
public function getIO()
|
||||
{
|
||||
return $this->io;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PolicyInterface
|
||||
*/
|
||||
public function getPolicy()
|
||||
{
|
||||
return $this->policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pool
|
||||
*/
|
||||
public function getPool()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?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\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 event listener method receives a
|
||||
* 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';
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue