1
0
Fork 0

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.php
pull/1607/head
msiebeneicher 2015-02-11 17:54:35 +01:00
commit f28785a49d
268 changed files with 11600 additions and 3566 deletions

View File

@ -5,19 +5,19 @@ php:
- 5.3 - 5.3
- 5.4 - 5.4
- 5.5 - 5.5
- 5.6
- hhvm - hhvm
matrix:
allow_failures:
- php: hhvm
before_script: before_script:
- sudo apt-get install parallel - sudo apt-get install parallel
- rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
- composer install --dev --prefer-source - composer install --prefer-source
- bin/composer install --dev --prefer-source - bin/composer install --prefer-source
- git config --global user.name travis-ci - git config --global user.name travis-ci
- git config --global user.email travis@example.com - git config --global user.email travis@example.com
script: script:
- 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

View File

@ -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) ### 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 * Break: The `install` command now has --dev enabled by default. --no-dev can be used to install without dev requirements

29
CONTRIBUTING.md Normal file
View File

@ -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).

View File

@ -1,11 +1,11 @@
Composer - Dependency Management for PHP 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. 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 Installation / Usage
-------------------- --------------------
@ -32,18 +32,6 @@ themselves. To create libraries/packages please read the
3. Run Composer: `php composer.phar install` 3. Run Composer: `php composer.phar install`
4. Browse for more packages on [Packagist](https://packagist.org). 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) Global installation of Composer (manual)
---------------------------------------- ----------------------------------------
@ -55,20 +43,6 @@ Updating Composer
Running `php composer.phar self-update` or equivalent will update a phar Running `php composer.phar self-update` or equivalent will update a phar
install with the latest version. 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 Community
--------- ---------

View File

@ -1,6 +1,6 @@
{ {
"name": "composer/composer", "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"], "keywords": ["package", "dependency", "autoload"],
"homepage": "http://getcomposer.org/", "homepage": "http://getcomposer.org/",
"type": "library", "type": "library",
@ -23,14 +23,14 @@
}, },
"require": { "require": {
"php": ">=5.3.2", "php": ">=5.3.2",
"justinrainbow/json-schema": "1.1.*", "justinrainbow/json-schema": "~1.3",
"seld/jsonlint": "1.*", "seld/jsonlint": "~1.0",
"symfony/console": "~2.3", "symfony/console": "~2.3",
"symfony/finder": "~2.2", "symfony/finder": "~2.2",
"symfony/process": "~2.1" "symfony/process": "~2.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~3.7.10" "phpunit/phpunit": "~4.0"
}, },
"suggest": { "suggest": {
"ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic", "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic",
@ -39,10 +39,16 @@
"autoload": { "autoload": {
"psr-0": { "Composer": "src/" } "psr-0": { "Composer": "src/" }
}, },
"autoload-dev": {
"psr-0": { "Composer\\Test": "tests/" }
},
"bin": ["bin/composer"], "bin": ["bin/composer"],
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0-dev" "dev-master": "1.0-dev"
} }
},
"scripts": {
"test": "phpunit"
} }
} }

698
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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 add it to your project, all you need to do is create a `composer.json` file
which describes the project's dependencies. which describes the project's dependencies.
{ ```json
"require": { {
"monolog/monolog": "1.2.*" "require": {
} "monolog/monolog": "1.2.*"
} }
}
```
We are simply stating that our project requires some `monolog/monolog` package, We are simply stating that our project requires some `monolog/monolog` package,
any version beginning with `1.2`. any version beginning with `1.2`.
@ -45,7 +47,7 @@ any version beginning with `1.2`.
## System Requirements ## System Requirements
Composer requires PHP 5.3.2+ to run. A few sensitive php settings and compile 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. incompatibilities.
To install packages from sources instead of simple zip archives, you will need 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, Composer is multi-platform and we strive to make it run equally well on Windows,
Linux and OSX. Linux and OSX.
## Installation - *nix ## Installation - Linux / Unix / OSX
### Downloading the Composer Executable ### 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 #### Locally
To actually get Composer, we need to do two things. The first one is installing Installing Composer locally is a matter of just running the installer in your
Composer (again, this means downloading it into your project): 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 > **Note:** If the above fails for some reason, you can download the installer
your working directory. This file is the Composer binary. It is a PHAR (PHP > 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 archive), which is an archive format for PHP which can be run on the command
line, amongst other things. line, amongst other things.
You can install Composer to a specific directory by using the `--install-dir` You can install Composer to a specific directory by using the `--install-dir`
option and providing a target directory (it can be an absolute or relative path): 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 #### 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: You can run these commands to easily access `composer` from anywhere on your system:
$ curl -sS https://getcomposer.org/installer | php ```sh
$ mv composer.phar /usr/local/bin/composer 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 > **Note:** If the above fails due to permissions, run the `mv` line
> again with sudo. > 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`. 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 ## Installation - Windows
### Using the Installer ### 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 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. 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 ### Manual Installation
Change to a directory on your `PATH` and run the install snippet to download Change to a directory on your `PATH` and run the install snippet to download
composer.phar: composer.phar:
C:\Users\username>cd C:\bin ```sh
C:\bin>php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));" 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`: 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: Close your current terminal. Test usage with a new terminal:
C:\Users\username>composer -V ```sh
Composer version 27d8904 C:\Users\username>composer -V
Composer version 27d8904
C:\Users\username> ```
## Using Composer ## 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: 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 If you did a global install and do not have the phar in that directory
run this instead: run this instead:
$ composer install ```sh
composer install
```
Following the [example above](#declaring-dependencies), this will download Following the [example above](#declaring-dependencies), this will download
monolog into the `vendor/monolog/monolog` directory. 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 downloads. To use it, just add the following line to your code's bootstrap
process: process:
require 'vendor/autoload.php'; ```php
require 'vendor/autoload.php';
```
Woah! Now start using monolog! To keep learning more about Composer, keep Woah! Now start using monolog! To keep learning more about Composer, keep
reading the "Basic Usage" chapter. reading the "Basic Usage" chapter.

View File

@ -1,23 +1,8 @@
# Basic usage # Basic usage
## Installation ## Installing
To install Composer, you just need to download the `composer.phar` executable. If you have not yet installed Composer, refer to the [Intro](00-intro.md) chapter.
$ 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
## `composer.json`: Project Setup ## `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 `require` key. You're simply telling Composer which packages your project
depends on. depends on.
{ ```json
"require": { {
"monolog/monolog": "1.0.*" "require": {
} "monolog/monolog": "1.0.*"
} }
}
```
As you can see, `require` takes an object that maps **package names** (e.g. `monolog/monolog`) As you can see, `require` takes an object that maps **package names** (e.g. `monolog/monolog`)
to **package versions** (e.g. `1.0.*`). 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. Version constraints can be specified in a few different ways.
Name | Example | Description Name | Example | Description
-------------- | --------------------- | ----------- -------------- | ------------------------------------------------------------------------ | -----------
Exact version | `1.0.2` | You can specify the exact version of a package. 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. Range | `>=1.0` `>=1.0 <2.0` <code>&gt;=1.0 &lt;1.1 &#124;&#124; &gt;=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>&#124;&#124;</code>) will be treated as a **logical OR**. 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`. 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`.
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. 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 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 it is mostly useful for projects respecting [semantic
versioning](http://semver.org/). A common usage would be to mark the minimum 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 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 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. `~` 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 ### Stability
By default only stable releases are taken into consideration. If you would like 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 To fetch the defined dependencies into your local project, just run the
`install` command of `composer.phar`. `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 This will find the latest version of `monolog/monolog` that matches the
supplied version constraint and download it into the `vendor` directory. 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. many new versions since then.
If no `composer.lock` file exists, Composer will read the dependencies and 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 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 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 latest matching versions (according to your `composer.json` file) and also update
the lock file with the new version. 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: 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, > **Note:** For libraries it is not necessarily recommended to commit the lock file,
> see also: [Libraries - Lock file](02-libraries.md#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 `vendor/autoload.php` file. You can simply include this file and you
will get autoloading for free. 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 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 project depends on monolog, you can just start using classes from it, and they
will be autoloaded. will be autoloaded.
$log = new Monolog\Logger('name'); ```php
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING)); $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 You can even add your own code to the autoloader by adding an `autoload` field
to `composer.json`. to `composer.json`.
{ ```json
"autoload": { {
"psr-4": {"Acme\\": "src/"} "autoload": {
} "psr-4": {"Acme\\": "src/"}
} }
}
```
Composer will register a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader Composer will register a [PSR-4](http://www.php-fig.org/psr/psr-4/) autoloader
for the `Acme` namespace. 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 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. 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. the `vendor/autoload.php` file.
Including that file will also return the autoloader instance, so you can store 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. 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. This can be useful for autoloading classes in a test suite, for example.
$loader = require 'vendor/autoload.php'; ```php
$loader->add('Acme\\Test\\', __DIR__); $loader = require 'vendor/autoload.php';
$loader->add('Acme\\Test\\', __DIR__);
```
In addition to PSR-4 autoloading, classmap is also supported. This allows 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 classes to be autoloaded even if they do not conform to PSR-4. See the

View File

@ -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 In order to make that package installable you need to give it a name. You do
this by adding a `name` to `composer.json`: this by adding a `name` to `composer.json`:
{ ```json
"name": "acme/hello-world", {
"require": { "name": "acme/hello-world",
"monolog/monolog": "1.0.*" "require": {
} "monolog/monolog": "1.0.*"
} }
}
```
In this case the project name is `acme/hello-world`, where `acme` is the In this case the project name is `acme/hello-world`, where `acme` is the
vendor name. Supplying a vendor name is mandatory. 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. includes PHP itself, PHP extensions and some system libraries.
* `php` represents the PHP version of the user, allowing you to apply * `php` represents the PHP version of the user, allowing you to apply
constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can constraints, e.g. `>=5.4.0`. To require a 64bit version of php, you can
require the `php-64bit` package. require the `php-64bit` package.
* `hhvm` represents the version of the HHVM runtime (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 * `ext-<name>` allows you to require PHP extensions (includes core
extensions). Versioning can be quite inconsistent here, so it's often extensions). Versioning can be quite inconsistent here, so it's often
@ -42,8 +47,8 @@ includes PHP itself, PHP extensions and some system libraries.
package name is `ext-gd`. package name is `ext-gd`.
* `lib-<name>` allows constraints to be made on versions of libraries used by * `lib-<name>` allows constraints to be made on versions of libraries used by
PHP. The following are available: `curl`, `iconv`, `libxml`, `openssl`, PHP. The following are available: `curl`, `iconv`, `icu`, `libxml`,
`pcre`, `uuid`, `xsl`. `openssl`, `pcre`, `uuid`, `xsl`.
You can use `composer show --platform` to get a list of your locally available You can use `composer show --platform` to get a list of your locally available
platform packages. 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, If you are creating packages by hand and really have to specify it explicitly,
you can just add a `version` field: you can just add a `version` field:
{ ```json
"version": "1.0.0" {
} "version": "1.0.0"
}
```
> **Note:** You should avoid specifying the version field explicitly, because > **Note:** You should avoid specifying the version field explicitly, because
> for tags the value must match the tag name. > 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 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 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 of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes
a number. can also be followed by a number.
Here are a few examples of valid tag names: Here are a few examples of valid tag names:
1.0.0 - 1.0.0
v1.0.0 - v1.0.0
1.10.5-RC1 - 1.10.5-RC1
v4.4.4beta2 - v4.4.4-beta2
v2.0.0-alpha - v2.0.0-alpha
v2.0.4-p1 - v2.0.4-p1
> **Note:** Even if your tag is prefixed with `v`, a [version constraint](01-basic-usage.md#package-versions) > **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 > 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: Here are some examples of version branch names:
1.x - 1.x
1.0 (equals 1.0.x) - 1.0 (equals 1.0.x)
1.1.x - 1.1.x
> **Note:** When you install a development version, it will be automatically > **Note:** When you install a development version, it will be automatically
> pulled from its `source`. See the [`install`](03-cli.md#install) command > 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 accomplish this by creating a new `blog` directory somewhere, containing a
`composer.json`: `composer.json`:
{ ```json
"name": "acme/blog", {
"require": { "name": "acme/blog",
"acme/hello-world": "dev-master" "require": {
} "acme/hello-world": "dev-master"
} }
}
```
The name is not needed in this case, since we don't want to publish the blog 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 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 We do this by adding a package repository specification to the blog's
`composer.json`: `composer.json`:
{ ```json
"name": "acme/blog", {
"repositories": [ "name": "acme/blog",
{ "repositories": [
"type": "vcs", {
"url": "https://github.com/username/hello-world" "type": "vcs",
} "url": "https://github.com/username/hello-world"
],
"require": {
"acme/hello-world": "dev-master"
} }
],
"require": {
"acme/hello-world": "dev-master"
} }
}
```
For more details on how package repositories work and what other types are For more details on how package repositories work and what other types are
available, see [Repositories](05-repositories.md). available, see [Repositories](05-repositories.md).

View File

@ -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 You've already learned how to use the command-line interface to do some
things. This chapter documents all the available commands. 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, When you run the command it will interactively ask you to fill in the fields,
while using some smart defaults. while using some smart defaults.
$ php composer.phar init ```sh
php composer.phar init
```
### Options ### Options
@ -54,7 +56,9 @@ while using some smart defaults.
The `install` command reads the `composer.json` file from the current The `install` command reads the `composer.json` file from the current
directory, resolves the dependencies, and installs them into `vendor`. directory, resolves the dependencies, and installs them into `vendor`.
$ php composer.phar install ```sh
php composer.phar install
```
If there is a `composer.lock` file in the current directory, it will use the 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 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 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 vendors. It is also a way to circumvent problems with git if you do not
have a proper setup. 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 * **--dry-run:** If you want to run through an installation without actually
installing a package, you can use `--dry-run`. This will simulate the installing a package, you can use `--dry-run`. This will simulate the
installation and show you what would happen. installation and show you what would happen.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior). * **--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-scripts:** Skips execution of scripts defined in `composer.json`.
* **--no-plugins:** Disables plugins. * **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some * **--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 In order to get the latest versions of the dependencies and to update the
`composer.lock` file, you should use the `update` command. `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 This will resolve all dependencies of the project and write the exact versions
into `composer.lock`. into `composer.lock`.
If you just want to update a few packages and not all, you can list them as such: If you 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: 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 ### Options
* **--prefer-source:** Install packages from `source` when available. * **--prefer-source:** Install packages from `source` when available.
* **--prefer-dist:** Install packages from `dist` when available. * **--prefer-dist:** Install packages from `dist` when available.
* **--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. * **--dry-run:** Simulate the command without actually doing anything.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior). * **--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-scripts:** Skips execution of scripts defined in `composer.json`.
* **--no-plugins:** Disables plugins. * **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some * **--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:** Only updates the lock file hash to suppress warning about the
lock file being out of date. lock file being out of date.
* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist. * **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
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 ## require
The `require` command adds new packages to the `composer.json` file from 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 After adding/changing the requirements, the modified requirements will be
installed or updated. installed or updated.
@ -139,16 +161,47 @@ installed or updated.
If you do not want to choose requirements interactively, you can just pass them If you do not want to choose requirements interactively, you can just pass them
to the command. 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 ### Options
* **--prefer-source:** Install packages from `source` when available. * **--prefer-source:** Install packages from `source` when available.
* **--prefer-dist:** Install packages from `dist` when available. * **--prefer-dist:** Install packages from `dist` when available.
* **--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`. * **--dev:** Add packages to `require-dev`.
* **--no-update:** Disables the automatic update of the dependencies. * **--no-update:** Disables the automatic update of the dependencies.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters. terminals or scripts which don't handle backspace characters.
* **--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 ## 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 `$COMPOSER_HOME/vendor/bin` to your `$PATH` environment variable. Here is an
example: 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 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 your PATH). If you wish to update the binary later on you can just run a
global update: global update:
$ php composer.phar global update ```sh
php composer.phar global update
```
## search ## 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 repositories. Usually this will be just packagist. You simply pass it the
terms you want to search for. 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. 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. 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 If you want to see the details of a certain package, you can pass the package
name. name.
$ php composer.phar show monolog/monolog ```sh
php composer.phar show monolog/monolog
name : monolog/monolog name : monolog/monolog
versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1 versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1
type : library type : library
names : monolog/monolog names : monolog/monolog
source : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da source : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da
dist : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da dist : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da
license : MIT license : MIT
autoload autoload
psr-0 psr-0
Monolog : src/ Monolog : src/
requires requires
php >=5.3.0 php >=5.3.0
```
You can even pass the package version, which will tell you the details of that You can even pass the package version, which will tell you the details of that
specific version. specific version.
$ php composer.phar show monolog/monolog 1.0.2 ```sh
php composer.phar show monolog/monolog 1.0.2
```
### Options ### Options
@ -219,19 +284,30 @@ specific version.
* **--platform (-p):** List only platform packages (php & extensions). * **--platform (-p):** List only platform packages (php & extensions).
* **--self (-s):** List the root package info. * **--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 ## depends
The `depends` command tells you which other packages depend on a certain The `depends` command tells you which other packages depend on a certain
package. You can specify which link types (`require`, `require-dev`) package. You can specify which link types (`require`, `require-dev`)
should be included in the listing. By default both are used. 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 nrk/monolog-fluent
poc/poc poc/poc
propel/propel propel/propel
symfony/monolog-bridge symfony/monolog-bridge
symfony/symfony symfony/symfony
```
### Options ### 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` file, and before you tag a release. It will check if your
`composer.json` is valid. `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 ## 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 installed from source, the `status` command allows you to check if you have
local changes in any of them. 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 With the `--verbose` option you get some more information about what was
changed: changed:
$ php composer.phar status -v ```sh
You have changes in the following dependencies: php composer.phar status -v
vendor/seld/jsonlint:
M README.mdown You have changes in the following dependencies:
vendor/seld/jsonlint:
M README.mdown
```
## self-update ## self-update
To update composer itself to the latest version, just run the `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. 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: 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)), 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 you may have to run the command with `root` privileges
$ sudo composer self-update ```sh
sudo composer self-update
```
### Options ### 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 `config` command allows you to edit some basic composer settings in either
the local composer.json file or the global config.json file. the local composer.json file or the global config.json file.
$ php composer.phar config --list ```sh
php composer.phar config --list
```
### Usage ### Usage
@ -304,23 +399,27 @@ options.
### Options ### Options
* **--global (-g):** Operate on the global config file located at * **--global (-g):** Operate on the global config file located at
`$COMPOSER_HOME/config.json` by default. Without this option, this command `$COMPOSER_HOME/config.json` by default. Without this option, this command
affects the local composer.json file or a file specified by `--file`. 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 * **--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 defined by the `EDITOR` env variable. With the `--global` option, this opens
the global config file. the global config file.
* **--unset:** Remove the configuration element named by `setting-key`. * **--unset:** Remove the configuration element named by `setting-key`.
* **--list (-l):** Show the list of current config variables. With the `--global` * **--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 * **--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 ### Modifying Repositories
In addition to modifying the config section, the `config` command also supports making In addition to modifying the config section, the `config` command also supports making
changes to the repositories section by using it the following way: 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 ## 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. 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 It is also possible to run the command without params in a directory with an
existing `composer.json` file to bootstrap a project. 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 * **--keep-vcs:** Skip the deletion of the VCS metadata for the created
project. This is mostly useful if you run the command in non-interactive project. This is mostly useful if you run the command in non-interactive
mode. mode.
* **--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 ## dump-autoload
@ -385,12 +489,22 @@ performance.
* **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster * **--optimize (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
autoloader. This is recommended especially for production, but can take autoloader. This is recommended especially for production, but can take
a bit of time to run so it is currently not done by default. a bit of time to run so it is currently not done by default.
* **--no-dev:** Disables autoload-dev rules.
## clear-cache
Deletes all content from Composer's cache directories.
## licenses ## licenses
Lists the name, version and license of every package installed. Use Lists the name, version and license of every package installed. Use
`--format=json` to get machine readable output. `--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 ## run-script
To run [scripts](articles/scripts.md) manually you can use this command, 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 want to run the `diagnose` command to perform automated checks for many common
problems. problems.
$ php composer.phar diagnose ```sh
php composer.phar diagnose
```
## archive ## 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 given version. It can also be used to archive your entire project without
excluded/ignored files. 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 ### Options
@ -422,7 +540,9 @@ excluded/ignored files.
To get more information about a certain command, just use `help`. To get more information about a certain command, just use `help`.
$ php composer.phar help install ```sh
php composer.phar help install
```
## Environment variables ## Environment variables
@ -438,7 +558,9 @@ By setting the `COMPOSER` env variable it is possible to set the filename of
For example: For example:
$ COMPOSER=composer-other.json php composer.phar install ```sh
COMPOSER=composer-other.json php composer.phar install
```
### COMPOSER_ROOT_VERSION ### COMPOSER_ROOT_VERSION

View File

@ -1,4 +1,4 @@
# composer.json # The composer.json Schema
This chapter will explain all of the fields available in `composer.json`. 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). be omitted (see below).
This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix 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 of `-dev`, `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`.
RC suffixes can also be followed by a number. The patch, alpha, beta and RC suffixes can also be followed by a number.
Examples: Examples:
1.0.0 - 1.0.0
1.0.2 - 1.0.2
1.1.0 - 1.1.0
0.2.5 - 0.2.5
1.0.0-dev - 1.0.0-dev
1.0.0-alpha3 - 1.0.0-alpha3
1.0.0-beta2 - 1.0.0-beta2
1.0.0-RC5 - 1.0.0-RC5
- v2.0.4-p1
Optional if the package repository can infer the version from somewhere, such 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 as the VCS tag name in the VCS repository. In that case it is also recommended
@ -113,11 +114,11 @@ searching and filtering.
Examples: Examples:
logging - logging
events - events
database - database
redis - redis
templating - templating
Optional. 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): The recommended notation for the most common licenses is (alphabetical):
Apache-2.0 - Apache-2.0
BSD-2-Clause - BSD-2-Clause
BSD-3-Clause - BSD-3-Clause
BSD-4-Clause - BSD-4-Clause
GPL-2.0 - GPL-2.0
GPL-2.0+ - GPL-2.0+
GPL-3.0 - GPL-3.0
GPL-3.0+ - GPL-3.0+
LGPL-2.1 - LGPL-2.1
LGPL-2.1+ - LGPL-2.1+
LGPL-3.0 - LGPL-3.0
LGPL-3.0+ - LGPL-3.0+
MIT - MIT
Optional, but it is highly recommended to supply this. More identifiers are Optional, but it is highly recommended to supply this. More identifiers are
listed at the [SPDX Open Source License Registry](http://www.spdx.org/licenses/). 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: An Example:
{ ```json
"license": "MIT" {
} "license": "MIT"
}
```
For a package, when there is a choice between licenses ("disjunctive license"), For a package, when there is a choice between licenses ("disjunctive license"),
multiple can be specified as array. multiple can be specified as array.
An Example for disjunctive licenses: An Example for disjunctive licenses:
{ ```json
"license": [ {
"LGPL-2.1", "license": [
"GPL-3.0+" "LGPL-2.1",
] "GPL-3.0+"
} ]
}
```
Alternatively they can be separated with "or" and enclosed in parenthesis; Alternatively they can be separated with "or" and enclosed in parenthesis;
{ ```json
"license": "(LGPL-2.1 or GPL-3.0+)" {
} "license": "(LGPL-2.1 or GPL-3.0+)"
}
```
Similarly when multiple licenses need to be applied ("conjunctive license"), Similarly when multiple licenses need to be applied ("conjunctive license"),
they should be separated with "and" and enclosed in parenthesis. they should be separated with "and" and enclosed in parenthesis.
@ -201,22 +207,24 @@ Each author object can have following properties:
An example: An example:
{ ```json
"authors": [ {
{ "authors": [
"name": "Nils Adermann", {
"email": "naderman@naderman.de", "name": "Nils Adermann",
"homepage": "http://www.naderman.de", "email": "naderman@naderman.de",
"role": "Developer" "homepage": "http://www.naderman.de",
}, "role": "Developer"
{ },
"name": "Jordi Boggiano", {
"email": "j.boggiano@seld.be", "name": "Jordi Boggiano",
"homepage": "http://seld.be", "email": "j.boggiano@seld.be",
"role": "Developer" "homepage": "http://seld.be",
} "role": "Developer"
] }
} ]
}
```
Optional, but highly recommended. Optional, but highly recommended.
@ -235,12 +243,14 @@ Support information includes the following:
An example: An example:
{ ```json
"support": { {
"email": "support@example.org", "support": {
"irc": "irc://irc.freenode.org/composer" "email": "support@example.org",
} "irc": "irc://irc.freenode.org/composer"
} }
}
```
Optional. Optional.
@ -251,11 +261,13 @@ All of the following take an object which maps package names to
Example: Example:
{ ```json
"require": { {
"monolog/monolog": "1.0.*" "require": {
} "monolog/monolog": "1.0.*"
} }
}
```
All links are optional fields. All links are optional fields.
@ -267,24 +279,28 @@ allow unstable packages of a dependency for example.
Example: Example:
{ ```json
"require": { {
"monolog/monolog": "1.0.*@beta", "require": {
"acme/foo": "@dev" "monolog/monolog": "1.0.*@beta",
} "acme/foo": "@dev"
} }
}
```
If one of your dependencies has a dependency on an unstable package you need to 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. explicitly require it as well, along with its sufficient stability flag.
Example: Example:
{ ```json
"require": { {
"doctrine/doctrine-fixtures-bundle": "dev-master", "require": {
"doctrine/data-fixtures": "@dev" "doctrine/doctrine-fixtures-bundle": "dev-master",
} "doctrine/data-fixtures": "@dev"
} }
}
```
`require` and `require-dev` additionally support explicit references (i.e. `require` and `require-dev` additionally support explicit references (i.e.
commit) for dev versions to make sure they are locked to a given state, even commit) for dev versions to make sure they are locked to a given state, even
@ -293,12 +309,14 @@ and append the reference with `#<ref>`.
Example: Example:
{ ```json
"require": { {
"monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7", "require": {
"acme/foo": "1.0.x-dev#abc123" "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 > **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 > 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 Lists packages that conflict with this version of this package. They
will not be allowed to be installed together with your package. 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 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 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 #### 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 `logger` package, any library that implements this logger interface would
simply list it in `provide`. simply list it in `provide`.
### suggest #### suggest
Suggested packages that can enhance or work well with this package. These are Suggested packages that can enhance or work well with this package. These are
just informational and are displayed after the package is installed, to give just informational and are displayed after the package is installed, to give
@ -370,11 +388,13 @@ and not version constraints.
Example: Example:
{ ```json
"suggest": { {
"monolog/monolog": "Allows more advanced logging of the application flow" "suggest": {
} "monolog/monolog": "Allows more advanced logging of the application flow"
} }
}
```
### autoload ### autoload
@ -403,32 +423,38 @@ key => value array which may be found in the generated file
Example: Example:
{ ```json
"autoload": { {
"psr-4": { "autoload": {
"Monolog\\": "src/", "psr-4": {
"Vendor\\Namespace\\": "", "Monolog\\": "src/",
} "Vendor\\Namespace\\": ""
} }
} }
}
```
If you need to search for a same prefix in multiple directories, If you need to search for a same prefix in multiple directories,
you can specify them as an array as such: you can specify them as an array as such:
{ ```json
"autoload": { {
"psr-4": { "Monolog\\": ["src/", "lib/"] } "autoload": {
} "psr-4": { "Monolog\\": ["src/", "lib/"] }
} }
}
```
If you want to have a fallback directory where any namespace will be looked for, If you want to have a fallback directory where any namespace will be looked for,
you can use an empty prefix like: you can use an empty prefix like:
{ ```json
"autoload": { {
"psr-4": { "": "src/" } "autoload": {
} "psr-4": { "": "src/" }
} }
}
```
#### PSR-0 #### PSR-0
@ -444,44 +470,52 @@ array which may be found in the generated file `vendor/composer/autoload_namespa
Example: Example:
{ ```json
"autoload": { {
"psr-0": { "autoload": {
"Monolog\\": "src/", "psr-0": {
"Vendor\\Namespace\\": "src/", "Monolog\\": "src/",
"Vendor_Namespace_": "src/" "Vendor\\Namespace\\": "src/",
} "Vendor_Namespace_": "src/"
} }
} }
}
```
If you need to search for a same prefix in multiple directories, If you need to search for a same prefix in multiple directories,
you can specify them as an array as such: you can specify them as an array as such:
{ ```json
"autoload": { {
"psr-0": { "Monolog\\": ["src/", "lib/"] } "autoload": {
} "psr-0": { "Monolog\\": ["src/", "lib/"] }
} }
}
```
The PSR-0 style is not limited to namespace declarations only but may be 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 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 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: in the root of the package, for example, it may be declared like this:
{ ```json
"autoload": { {
"psr-0": { "UniqueGlobalClass": "" } "autoload": {
} "psr-0": { "UniqueGlobalClass": "" }
} }
}
```
If you want to have a fallback directory where any namespace can be, you can If you want to have a fallback directory where any namespace can be, you can
use an empty prefix like: use an empty prefix like:
{ ```json
"autoload": { {
"psr-0": { "": "src/" } "autoload": {
} "psr-0": { "": "src/" }
} }
}
```
#### Classmap #### Classmap
@ -496,11 +530,13 @@ to search for classes.
Example: Example:
{ ```json
"autoload": { {
"classmap": ["src/", "lib/", "Something.php"] "autoload": {
} "classmap": ["src/", "lib/", "Something.php"]
} }
}
```
#### Files #### Files
@ -510,11 +546,37 @@ that cannot be autoloaded by PHP.
Example: Example:
{ ```json
"autoload": { {
"files": ["src/MyLibrary/functions.php"] "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 ### include-path
@ -526,9 +588,11 @@ A list of paths which should get appended to PHP's `include_path`.
Example: Example:
{ ```json
"include-path": ["lib/"] {
} "include-path": ["lib/"]
}
```
Optional. Optional.
@ -552,12 +616,14 @@ it from `vendor/symfony/yaml`.
To do that, `autoload` and `target-dir` are defined as follows: To do that, `autoload` and `target-dir` are defined as follows:
{ ```json
"autoload": { {
"psr-0": { "Symfony\\Component\\Yaml\\": "" } "autoload": {
}, "psr-0": { "Symfony\\Component\\Yaml\\": "" }
"target-dir": "Symfony/Component/Yaml" },
} "target-dir": "Symfony/Component/Yaml"
}
```
Optional. Optional.
@ -615,47 +681,49 @@ For more information on any of these, see [Repositories](05-repositories.md).
Example: Example:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "composer", {
"url": "http://packages.example.com" "type": "composer",
}, "url": "http://packages.example.com"
{ },
"type": "composer", {
"url": "https://packages.example.com", "type": "composer",
"options": { "url": "https://packages.example.com",
"ssl": { "options": {
"verify_peer": "true" "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/"
}
} }
} }
] },
} {
"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 > **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. 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 * **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 `auto`. This option allows you to set the install method Composer will prefer to
use. 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 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 for example prioritize the https protocol if you are behind a proxy or have somehow
performances with the git protocol. bad performances with the git protocol.
* **github-oauth:** A list of domain names and oauth keys. For example using * **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` `{"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 to access private repositories on github and to circumvent the low IP-based
rate limiting of their API. rate limiting of their API.
[Read more](articles/troubleshooting.md#api-rate-limit-and-two-factor-authentication) [Read more](articles/troubleshooting.md#api-rate-limit-and-oauth-tokens)
on how to get an oauth token for GitHub. 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 * **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 * **bin-dir:** Defaults to `vendor/bin`. If a project includes binaries, they
will be symlinked into this directory. 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 `C:\Users\<user>\AppData\Local\Composer` on Windows. Stores all the caches
used by composer. See also [COMPOSER_HOME](03-cli.md#composer-home). used by composer. See also [COMPOSER_HOME](03-cli.md#composer-home).
* **cache-files-dir:** Defaults to `$cache-dir/files`. Stores the zip archives * **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. the generated Composer autoloader. When null a random one will be generated.
* **optimize-autoloader** Defaults to `false`. Always optimize when dumping * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
the autoloader. 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-domains:** Defaults to `["github.com"]`. A list of domains to use in
github mode. This is used for GitHub Enterprise setups. 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 * **notify-on-install:** Defaults to `true`. Composer allows repositories to
define a notification URL, so that they get notified whenever a package from 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. that repository is installed. This option allows you to disable that behaviour.
@ -727,11 +809,18 @@ The following options are supported:
Example: Example:
{ ```json
"config": { {
"bin-dir": "bin" "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> ### 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 This can be virtually anything. To access it from within a script event
handler, you can do: handler, you can do:
$extra = $event->getComposer()->getPackage()->getExtra(); ```php
$extra = $event->getComposer()->getPackage()->getExtra();
```
Optional. Optional.
@ -774,11 +865,13 @@ The following options are supported:
Example: Example:
{ ```json
"archive": { {
"exclude": ["/foo/bar", "baz", "/*.test", "!/foo/bar/baz"] "archive": {
} "exclude": ["/foo/bar", "baz", "/*.test", "!/foo/bar/baz"]
} }
}
```
The example will include `/dir/foo/bar/file`, `/foo/bar/baz`, `/file.php`, 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`. `/foo/my.test` but it will exclude `/foo/bar/any`, `/foo/baz`, and `/my.test`.

View File

@ -66,16 +66,18 @@ repository URL would be `example.org`.
The only required field is `packages`. The JSON structure is as follows: The only required field is `packages`. The JSON structure is as follows:
{ ```json
"packages": { {
"vendor/package-name": { "packages": {
"dev-master": { @composer.json }, "vendor/package-name": {
"1.0.x-dev": { @composer.json }, "dev-master": { @composer.json },
"0.0.1": { @composer.json }, "1.0.x-dev": { @composer.json },
"1.0.0": { @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 The `@composer.json` marker would be the contents of the `composer.json` from
that package version including as a minimum: that package version including as a minimum:
@ -86,14 +88,16 @@ that package version including as a minimum:
Here is a minimal package definition: Here is a minimal package definition:
{ ```json
"name": "smarty/smarty", {
"version": "3.1.7", "name": "smarty/smarty",
"dist": { "version": "3.1.7",
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip", "dist": {
"type": "zip" "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). 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: An example value:
{ ```json
"notify-batch": "/downloads/" {
} "notify-batch": "/downloads/"
}
```
For `example.org/packages.json` containing a `monolog/monolog` package, this For `example.org/packages.json` containing a `monolog/monolog` package, this
would send a `POST` request to `example.org/downloads/` with following would send a `POST` request to `example.org/downloads/` with following
JSON request body: JSON request body:
{ ```json
"downloads": [ {
{"name": "monolog/monolog", "version": "1.2.1.0"}, "downloads": [
] {"name": "monolog/monolog", "version": "1.2.1.0"}
} ]
}
```
The version field will contain the normalized representation of the version The version field will contain the normalized representation of the version
number. number.
@ -132,19 +140,21 @@ files.
An example: An example:
{ ```json
"includes": { {
"packages-2011.json": { "includes": {
"sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17" "packages-2011.json": {
}, "sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17"
"packages-2012-01.json": { },
"sha1": "897cde726f8a3918faf27c803b336da223d400dd" "packages-2012-01.json": {
}, "sha1": "897cde726f8a3918faf27c803b336da223d400dd"
"packages-2012-02.json": { },
"sha1": "26f911ad717da26bbcac3f8f435280d13917efa5" "packages-2012-02.json": {
} "sha1": "26f911ad717da26bbcac3f8f435280d13917efa5"
} }
} }
}
```
The SHA-1 sum of the file allows it to be cached and only re-requested if the The SHA-1 sum of the file allows it to be cached and only re-requested if the
hash changed. hash changed.
@ -164,35 +174,39 @@ is an absolute path from the repository root.
An example: An example:
{ ```json
"provider-includes": { {
"providers-a.json": { "provider-includes": {
"sha256": "f5b4bc0b354108ef08614e569c1ed01a2782e67641744864a74e788982886f4c" "providers-a.json": {
}, "sha256": "f5b4bc0b354108ef08614e569c1ed01a2782e67641744864a74e788982886f4c"
"providers-b.json": {
"sha256": "b38372163fac0573053536f5b8ef11b86f804ea8b016d239e706191203f6efac"
}
}, },
"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 Those files contain lists of package names and hashes to verify the file
integrity, for example: integrity, for example:
{ ```json
"providers": { {
"acme/foo": { "providers": {
"sha256": "38968de1305c2e17f4de33aea164515bc787c42c7e2d6e25948539a14268bb82" "acme/foo": {
}, "sha256": "38968de1305c2e17f4de33aea164515bc787c42c7e2d6e25948539a14268bb82"
"acme/bar": { },
"sha256": "4dd24c930bd6e1103251306d6336ac813b563a220d9ca14f4743c032fb047233" "acme/bar": {
} "sha256": "4dd24c930bd6e1103251306d6336ac813b563a220d9ca14f4743c032fb047233"
} }
} }
}
```
The file above declares that acme/foo and acme/bar can be found in this The file above declares that acme/foo and acme/bar can be found in this
repository, by loading the file referenced by `providers-url`, replacing repository, by loading the file referenced by `providers-url`, replacing
`%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). themselves just contain package definitions as described [above](#packages).
This field is optional. You probably don't need it for your own custom 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: Example assuming you patched monolog to fix a bug in the `bugfix` branch:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "vcs", {
"url": "https://github.com/igorw/monolog" "type": "vcs",
} "url": "https://github.com/igorw/monolog"
],
"require": {
"monolog/monolog": "dev-bugfix"
} }
],
"require": {
"monolog/monolog": "dev-bugfix"
} }
}
```
When you run `php composer.phar update`, you should get your modified version When you run `php composer.phar update`, you should get your modified version
of `monolog/monolog` instead of the one from packagist. 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 Exactly the same solution allows you to work with your private repositories at
GitHub and BitBucket: GitHub and BitBucket:
{ ```json
"require": { {
"vendor/my-private-repo": "dev-master" "require": {
}, "vendor/my-private-repo": "dev-master"
"repositories": [ },
{ "repositories": [
"type": "vcs", {
"url": "git@bitbucket.org:vendor/my-private-repo.git" "type": "vcs",
} "url": "git@bitbucket.org:vendor/my-private-repo.git"
] }
} ]
}
```
The only requirement is the installation of SSH keys for a git client. 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 should you need to specify one for whatever reason, you can use `git`, `svn` or
`hg` as the repository type instead of `vcs`. `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 #### Subversion Options
Since Subversion has no native concept of branches and tags, Composer assumes 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 values. For example if you used capitalized names you could configure the
repository like this: repository like this:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "vcs", {
"url": "http://svn.example.org/projectA/", "type": "vcs",
"trunk-path": "Trunk", "url": "http://svn.example.org/projectA/",
"branches-path": "Branches", "trunk-path": "Trunk",
"tags-path": "Tags" "branches-path": "Branches",
} "tags-path": "Tags"
] }
} ]
}
```
If you have no branches or tags directory you can disable them entirely by If you have no branches or tags directory you can disable them entirely by
setting the `branches-path` or `tags-path` to `false`. 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 setting the `"package-path"` option to the sub-directory, in this example it
would be `"package-path": "foo/bar/"`. 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 ### PEAR
It is possible to install packages from any PEAR channel by using the `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`: Example using `pear2.php.net`:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "pear", {
"url": "http://pear2.php.net" "type": "pear",
} "url": "http://pear2.php.net"
],
"require": {
"pear-pear2.php.net/PEAR2_Text_Markdown": "*",
"pear-pear2/PEAR2_HTTP_Request": "*"
} }
],
"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 In this case the short name of the channel is `pear2`, so the
`PEAR2_HTTP_Request` package name becomes `pear-pear2/PEAR2_HTTP_Request`. `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 `TopLevelPackage1`, and `TopLevelPackage2` packages from your PEAR repository
and `IntermediatePackage` from a Github repository: and `IntermediatePackage` from a Github repository:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "git", {
"url": "https://github.com/foobar/intermediate.git" "type": "git",
}, "url": "https://github.com/foobar/intermediate.git"
{ },
"type": "pear", {
"url": "http://pear.foobar.repo", "type": "pear",
"vendor-alias": "foobar" "url": "http://pear.foobar.repo",
} "vendor-alias": "foobar"
],
"require": {
"foobar/TopLevelPackage1": "*",
"foobar/TopLevelPackage2": "*"
} }
],
"require": {
"foobar/TopLevelPackage1": "*",
"foobar/TopLevelPackage2": "*"
} }
}
```
### Package ### 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: Here is an example for the smarty template engine:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "package", {
"package": { "type": "package",
"name": "smarty/smarty", "package": {
"version": "3.1.7", "name": "smarty/smarty",
"dist": { "version": "3.1.7",
"url": "http://www.smarty.net/files/Smarty-3.1.7.zip", "dist": {
"type": "zip" "url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
}, "type": "zip"
"source": { },
"url": "http://smarty-php.googlecode.com/svn/", "source": {
"type": "svn", "url": "http://smarty-php.googlecode.com/svn/",
"reference": "tags/Smarty_3_1_7/distribution/" "type": "svn",
}, "reference": "tags/Smarty_3_1_7/distribution/"
"autoload": { },
"classmap": ["libs/"] "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. 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 might want to keep them separate to packagist. An example of this would be
wordpress plugins. 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. recommended, which provides the best performance.
There are a few tools that can help you create a `composer` repository. 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 repository of type `artifact` with a folder containing ZIP archives of those
private packages: private packages:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "artifact", {
"url": "path/to/directory/with/zips/" "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"
} }
],
"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: Each zip artifact is just a ZIP archive with `composer.json` in root folder:
$ unzip -l acme-corp-parser-10.3.5.zip ```sh
composer.json 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 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 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 You can disable the default Packagist repository by adding this to your
`composer.json`: `composer.json`:
{ ```json
"repositories": [ {
{ "repositories": [
"packagist": false {
} "packagist": false
] }
} ]
}
```
&larr; [Schema](04-schema.md) | [Community](06-community.md) &rarr; &larr; [Schema](04-schema.md) | [Community](06-community.md) &rarr;

View File

@ -7,7 +7,7 @@
## Why aliases? ## Why aliases?
When you are using a VCS repository, you will only get comparable versions for 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 will get a `dev-master` version. For your `bugfix` branch, you will get a
`dev-bugfix` version. `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 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`: specifying a `branch-alias` field under `extra` in `composer.json`:
{ ```json
"extra": { {
"branch-alias": { "extra": {
"dev-master": "1.0.x-dev" "branch-alias": {
} "dev-master": "1.0.x-dev"
} }
} }
}
```
The branch version must begin with `dev-` (non-comparable version), the alias If you alias a non-comparible version (such as dev-develop) `dev-` must prefix the
must be a comparable dev version (i.e. start with numbers, and end with branch name. You may also alias a comparible version (i.e. start with numbers,
`.x-dev`). The `branch-alias` must be present on the branch that it references. and end with `.x-dev`), but only as a more specific version.
For `dev-master`, you need to commit it on the `master` branch. 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 As a result, anyone can now require `1.0.*` and it will happily install
`dev-master`. `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`: Just add this to your project's root `composer.json`:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "vcs", {
"url": "https://github.com/you/monolog" "type": "vcs",
} "url": "https://github.com/you/monolog"
],
"require": {
"symfony/monolog-bundle": "2.0",
"monolog/monolog": "dev-bugfix as 1.0.x-dev"
} }
],
"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 That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub
and alias it to `1.0.x-dev`. and alias it to `1.0.x-dev`.

View File

@ -34,13 +34,15 @@ An example use-case would be:
An example composer.json of such a template package would be: An example composer.json of such a template package would be:
{ ```json
"name": "phpdocumentor/template-responsive", {
"type": "phpdocumentor-template", "name": "phpdocumentor/template-responsive",
"require": { "type": "phpdocumentor-template",
"phpdocumentor/template-installer-plugin": "*" "require": {
} "phpdocumentor/template-installer-plugin": "*"
} }
}
```
> **IMPORTANT**: to make sure that the template installer is present at the > **IMPORTANT**: to make sure that the template installer is present at the
> time the template package is installed, template packages should require > time the template package is installed, template packages should require
@ -70,20 +72,22 @@ requirements:
Example: Example:
{ ```json
"name": "phpdocumentor/template-installer-plugin", {
"type": "composer-plugin", "name": "phpdocumentor/template-installer-plugin",
"license": "MIT", "type": "composer-plugin",
"autoload": { "license": "MIT",
"psr-0": {"phpDocumentor\\Composer": "src/"} "autoload": {
}, "psr-0": {"phpDocumentor\\Composer": "src/"}
"extra": { },
"class": "phpDocumentor\\Composer\\TemplateInstallerPlugin" "extra": {
}, "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
"require": { },
"composer-plugin-api": "1.0.0" "require": {
} "composer-plugin-api": "1.0.0"
} }
}
```
### The Plugin class ### The Plugin class
@ -96,20 +100,24 @@ autoloadable and matches the `extra.class` element in the package definition.
Example: Example:
namespace phpDocumentor\Composer; ```php
<?php
use Composer\Composer; namespace phpDocumentor\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
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 ### The Custom Installer class
@ -138,39 +146,43 @@ source for the exact signature):
Example: Example:
namespace phpDocumentor\Composer; ```php
<?php
use Composer\Package\PackageInterface; namespace phpDocumentor\Composer;
use Composer\Installer\LibraryInstaller;
class TemplateInstaller extends LibraryInstaller use Composer\Package\PackageInterface;
use Composer\Installer\LibraryInstaller;
class TemplateInstaller extends LibraryInstaller
{
/**
* {@inheritDoc}
*/
public function getPackageBasePath(PackageInterface $package)
{ {
/** $prefix = substr($package->getPrettyName(), 0, 23);
* {@inheritDoc} if ('phpdocumentor/template-' !== $prefix) {
*/ throw new \InvalidArgumentException(
public function getPackageBasePath(PackageInterface $package) 'Unable to install template, phpdocumentor templates '
{ .'should always start their package name with '
$prefix = substr($package->getPrettyName(), 0, 23); .'"phpdocumentor/template-"'
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);
} }
/** return 'data/templates/'.substr($package->getPrettyName(), 23);
* {@inheritDoc}
*/
public function supports($packageType)
{
return 'phpdocumentor-template' === $packageType;
}
} }
/**
* {@inheritDoc}
*/
public function supports($packageType)
{
return 'phpdocumentor-template' === $packageType;
}
}
```
The example demonstrates that it is quite simple to extend the The example demonstrates that it is quite simple to extend the
[`Composer\Installer\LibraryInstaller`][5] class to strip a prefix [`Composer\Installer\LibraryInstaller`][5] class to strip a prefix
(`phpdocumentor/template-`) and use the remaining part to assemble a completely (`phpdocumentor/template-`) and use the remaining part to assemble a completely

View File

@ -2,14 +2,22 @@
tagline: Host your own composer repository 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- # Toran Proxy
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 [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.
a micro-packagist. You can get it from
[GitHub](http://github.com/composer/satis) or install via CLI: 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.
`composer.phar create-project composer/satis --stability=dev`.
# 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 ## Setup
@ -25,36 +33,40 @@ repositories you defined.
The default file Satis looks for is `satis.json` in the root of the repository. The default file Satis looks for is `satis.json` in the root of the repository.
{ ```json
"name": "My Repository", {
"homepage": "http://packages.example.org", "name": "My Repository",
"repositories": [ "homepage": "http://packages.example.org",
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" }, "repositories": [
{ "type": "vcs", "url": "http://svn.example.org/private/repo" }, { "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" } { "type": "vcs", "url": "http://svn.example.org/private/repo" },
], { "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
"require-all": true ],
} "require-all": true
}
```
If you want to cherry pick which packages you want, you can list all the packages 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, 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 using a `"*"` constraint to make sure all versions are selected, or another
constraint if you want really specific versions. constraint if you want really specific versions.
{ ```json
"repositories": [ {
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo" }, "repositories": [
{ "type": "vcs", "url": "http://svn.example.org/private/repo" }, { "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
{ "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" } { "type": "vcs", "url": "http://svn.example.org/private/repo" },
], { "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
"require": { ],
"company/package": "*", "require": {
"company/package2": "*", "company/package": "*",
"company/package3": "2.0.0" "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` For example `php bin/satis build config.json web/` would read the `config.json`
file and build a static repository inside the `web/` directory. 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 in every project anymore. Only that one unique repository that will update
itself. itself.
{ ```json
"repositories": [ { "type": "composer", "url": "http://packages.example.org/" } ], {
"require": { "repositories": [ { "type": "composer", "url": "http://packages.example.org/" } ],
"company/package": "1.2.0", "require": {
"company/package2": "1.5.2", "company/package": "1.2.0",
"company/package3": "dev-master" "company/package2": "1.5.2",
} "company/package3": "dev-master"
} }
}
```
### Security ### Security
@ -97,39 +111,43 @@ connection options for the server.
Example using a custom repository using SSH (requires the SSH2 PECL extension): Example using a custom repository using SSH (requires the SSH2 PECL extension):
{ ```json
"repositories": [ {
{ "repositories": [
"type": "composer", {
"url": "ssh2.sftp://example.org", "type": "composer",
"options": { "url": "ssh2.sftp://example.org",
"ssh2": { "options": {
"username": "composer", "ssh2": {
"pubkey_file": "/home/composer/.ssh/id_rsa.pub", "username": "composer",
"privkey_file": "/home/composer/.ssh/id_rsa" "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. > **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: Example using HTTP over SSL using a client certificate:
{ ```json
"repositories": [ {
{ "repositories": [
"type": "composer", {
"url": "https://example.org", "type": "composer",
"options": { "url": "https://example.org",
"ssl": { "options": {
"local_cert": "/home/composer/.ssl/composer.pem" "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. > **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 To enable your satis installation to create downloads for all (Git, Mercurial and Subversion) your packages, add the
following to your `satis.json`: following to your `satis.json`:
{ ```json
"archive": { {
"directory": "dist", "archive": {
"format": "tar", "directory": "dist",
"prefix-url": "https://amazing.cdn.example.org", "format": "tar",
"skip-dev": true "prefix-url": "https://amazing.cdn.example.org",
} "skip-dev": true
} }
}
```
#### Options explained #### 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 with the Downloads functionality to have a complete local mirror of packages. Just add the following
to your `satis.json`: 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. 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`. 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.

View File

@ -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.

View File

@ -35,13 +35,15 @@ current composer plugin API version is 1.0.0.
For example For example
{ ```json
"name": "my/plugin-package", {
"type": "composer-plugin", "name": "my/plugin-package",
"require": { "type": "composer-plugin",
"composer-plugin-api": "1.0.0" "require": {
} "composer-plugin-api": "1.0.0"
} }
}
```
### Plugin Class ### Plugin Class
@ -54,20 +56,24 @@ be read and all internal objects and state can be manipulated as desired.
Example: Example:
namespace phpDocumentor\Composer; ```php
<?php
use Composer\Composer; namespace phpDocumentor\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
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 ## Event Handler
@ -88,46 +94,50 @@ The events available for plugins are:
Example: Example:
namespace Naderman\Composer\AWS; ```php
<?php
use Composer\Composer; namespace Naderman\Composer\AWS;
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 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; $this->composer = $composer;
protected $io; $this->io = $io;
}
public function activate(Composer $composer, IOInterface $io) public static function getSubscribedEvents()
{ {
$this->composer = $composer; return array(
$this->io = $io; PluginEvents::PRE_FILE_DOWNLOAD => array(
} array('onPreFileDownload', 0)
),
);
}
public static function getSubscribedEvents() public function onPreFileDownload(PreFileDownloadEvent $event)
{ {
return array( $protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
PluginEvents::PRE_FILE_DOWNLOAD => array(
array('onPreFileDownload', 0)
),
);
}
public function onPreFileDownload(PreFileDownloadEvent $event) if ($protocol === 's3') {
{ $awsClient = new AwsClient($this->io, $this->composer->getConfig());
$protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME); $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 ## Using Plugins

View File

@ -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 for executing a package's custom code or package-specific commands during
the Composer execution process. the Composer execution process.
**NOTE: Only scripts defined in the root package's `composer.json` are > **Note:** Only scripts defined in the root package's `composer.json` are
executed. If a dependency of the root package specifies its own scripts, > executed. If a dependency of the root package specifies its own scripts,
Composer does not execute those additional scripts.** > Composer does not execute those additional scripts.
## Event names ## Event names
@ -26,6 +26,8 @@ Composer fires the following named events during its execution process:
- **post-update-cmd**: occurs after the `update` command is executed. - **post-update-cmd**: occurs after the `update` command is executed.
- **pre-status-cmd**: occurs before the `status` command is executed. - **pre-status-cmd**: occurs before the `status` command is executed.
- **post-status-cmd**: occurs after the `status` command is executed. - **post-status-cmd**: occurs after the `status` command 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. - **pre-package-install**: occurs before a package is installed.
- **post-package-install**: occurs after a package is installed. - **post-package-install**: occurs after a package is installed.
- **pre-package-update**: occurs before a package is updated. - **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. installed, during the `create-project` command.
- **post-create-project-cmd**: occurs after the `create-project` command is - **post-create-project-cmd**: occurs after the `create-project` command is
executed. 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 > **Note:** Composer makes no assumptions about the state of your dependencies
prior to `install` or `update`. Therefore, you should not specify scripts that > prior to `install` or `update`. Therefore, you should not specify scripts
require Composer-managed dependencies in the `pre-update-cmd` or > that require Composer-managed dependencies in the `pre-update-cmd` or
`pre-install-cmd` event hooks. If you need to execute scripts prior to > `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 > `install` or `update` please make sure they are self-contained within your
root package.** > root package.
## Defining scripts ## Defining scripts
@ -59,54 +63,61 @@ For any given event:
- Scripts execute in the order defined when their corresponding event is fired. - 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 - 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 - PHP classes containing defined callbacks must be autoloadable via Composer's
autoload functionality. autoload functionality.
Script definition example: Script definition example:
{ ```json
"scripts": { {
"post-update-cmd": "MyVendor\\MyClass::postUpdate", "scripts": {
"post-package-install": [ "post-update-cmd": "MyVendor\\MyClass::postUpdate",
"MyVendor\\MyClass::postPackageInstall" "post-package-install": [
], "MyVendor\\MyClass::postPackageInstall"
"post-install-cmd": [ ],
"MyVendor\\MyClass::warmCache", "post-install-cmd": [
"phpunit -c app/" "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` Using the previous definition example, here's the class `MyVendor\MyClass`
that might be used to execute the PHP callbacks: 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
$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
}
} }
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 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 `Composer\Script\Event` object, which is passed as the first argument to your
PHP callback. This `Event` object has getters for other contextual objects: 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: 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.

View File

@ -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` possible interferences with existing vendor installations or `composer.lock`
entries. entries.
5. Try clearing Composer's cache by running `composer clear-cache`.
## Package not found ## Package not found
1. Double-check you **don't have typos** in your `composer.json` or repository 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 your repository, especially when maintaining a third party fork and using
`replace`. `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 ## Package not found on travis-ci.org
1. Check the ["Package not found"](#package-not-found) item above. 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: composer.json:
{ ```json
"require": { {
"A": "0.2", "require": {
"B": "0.11 as 0.1" "A": "0.2",
} "B": "0.11 as 0.1"
} }
}
```
See [aliases](aliases.md) for more information. 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: 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. 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: 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 Try increasing the limit in your `php.ini` file (ex. `/etc/php5/cli/php.ini` for
Debian-like systems): Debian-like systems):
; Use -1 for unlimited or define an explicit value like 512M ```ini
memory_limit = -1 ; 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: 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) ## "The system cannot find the path specified" (Windows)
1. Open regedit. 1. Open regedit.
2. Search for an ```AutoRun``` key inside ```HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor``` 2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`,
or ```HKEY_CURRENT_USER\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. 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 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. 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. [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>` 2. Add it to the configuration running `composer config -g github-oauth.github.com <oauthtoken>`
Now Composer should install/update without asking for authentication. 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
```

View File

@ -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 It is specified as an array of files so multiple binaries can be added
for any given project. for any given project.
{ ```json
"bin": ["bin/my-script", "bin/my-other-script"] {
} "bin": ["bin/my-script", "bin/my-other-script"]
}
```
## What does defining a vendor binary in composer.json do? ## 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: Say package `my-vendor/project-a` has binaries setup like this:
{ ```json
"name": "my-vendor/project-a", {
"bin": ["bin/project-a-bin"] "name": "my-vendor/project-a",
} "bin": ["bin/project-a-bin"]
}
```
Running `composer install` for this `composer.json` will not do Running `composer install` for this `composer.json` will not do
anything with `bin/project-a-bin`. anything with `bin/project-a-bin`.
Say project `my-vendor/project-b` has requirements setup like this: Say project `my-vendor/project-b` has requirements setup like this:
{ ```json
"name": "my-vendor/project-b", {
"require": { "name": "my-vendor/project-b",
"my-vendor/project-a": "*" "require": {
} "my-vendor/project-a": "*"
} }
}
```
Running `composer install` for this `composer.json` will look at Running `composer install` for this `composer.json` will look at
all of project-b's dependencies and install them to `vendor/bin`. all of project-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: An example of the former looks like this:
{ ```json
"config": { {
"bin-dir": "scripts" "config": {
} "bin-dir": "scripts"
} }
}
```
Running `composer install` for this `composer.json` will result in Running `composer install` for this `composer.json` will result in
all of the vendor binaries being installed in `scripts/` instead of all of the vendor binaries being installed in `scripts/` instead of
`vendor/bin/`. `vendor/bin/`.
You can set `bin-dir` to `./` to put binaries in your project root.

View File

@ -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 CakePHP, Drupal or WordPress. Here is an example composer.json file for a
WordPress theme: WordPress theme:
{ ```json
"name": "you/themename", {
"type": "wordpress-theme", "name": "you/themename",
"require": { "type": "wordpress-theme",
"composer/installers": "~1.0" "require": {
} "composer/installers": "~1.0"
} }
}
```
Now when your theme is installed with Composer it will be placed into Now when your theme is installed with Composer it will be placed into
`wp-content/themes/themename/` folder. Check the `wp-content/themes/themename/` folder. Check the
@ -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 installed into your sites subdirectory. Here we are overriding the install path
for a module that uses composer/installers: for a module that uses composer/installers:
{ ```json
"extra": { {
"installer-paths": { "extra": {
"sites/example.com/modules/{$name}": ["vendor/package"] "installer-paths": {
} "sites/example.com/modules/{$name}": ["vendor/package"]
} }
} }
}
```
Now the package would be installed to your folder location, rather than the default Now the package would be installed to your folder location, rather than the default
composer/installers determined location. composer/installers determined location.

View File

@ -24,6 +24,7 @@ If you really feel like you must do this, you have a few options:
[config](../04-schema.md#config). [config](../04-schema.md#config).
3. Remove the `.git` directory of every dependency after the installation, then 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` 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 but this means you will have to delete those dependencies from disk before
running composer update. running composer update.
4. Add a .gitignore rule (`vendor/.git`) to ignore all the vendor `.git` folders. 4. Add a .gitignore rule (`vendor/.git`) to ignore all the vendor `.git` folders.

View File

@ -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.

View File

@ -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. 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 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) 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: There are three ways the dependency solver could work with custom repositories:

20
doc/fixtures/fixtures.md Normal file
View File

@ -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.

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -0,0 +1,10 @@
{
"packages": [
],
"includes": {
"include/all$5fa86b937f0502d92f776072cd49c002dca742b9.json": {
"sha1": "5fa86b937f0502d92f776072cd49c002dca742b9"
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"providers": {
"bar\/baz": {
"sha256": "923363b3c22e73abb2e3fd891c8156dd4d0821a97fd3e428bc910833e3e46dbe"
},
"foo\/bar": {
"sha256": "4baabb3303afa3e34a4d3af18fb138e5f3b79029c1f8d9ab5b477ea15776ba0a"
},
"gar\/nix": {
"sha256": "5d210670cb46c8364c8e3fb449967b9bea558b971e5b082f330ae4f1d484c321"
},
"qux\/quux": {
"sha256": "c142d1a07ca354be46b613f59f1d601923a5a00ccc5fcce50a77ecdd461eb72d"
}
}
}

View File

@ -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
}
}
}
}

View File

@ -0,0 +1,9 @@
{
"packages": [],
"providers-url": "\/p\/%package%$%hash%.json",
"provider-includes": {
"p\/provider-active$1893a061e579543822389ecd12d791c612db0c05e22d90e9286e233cacd86ed8.json": {
"sha256": "1893a061e579543822389ecd12d791c612db0c05e22d90e9286e233cacd86ed8"
}
}
}

View File

@ -1,12 +1,13 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#",
"name": "Package", "name": "Package",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "name", "description" ],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Package name, including 'vendor-name/' prefix.", "description": "Package name, including 'vendor-name/' prefix."
"required": true
}, },
"type": { "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.", "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": { "description": {
"type": "string", "type": "string",
"description": "Short package description.", "description": "Short package description."
"required": true
}, },
"keywords": { "keywords": {
"type": "array", "type": "array",
@ -39,7 +39,7 @@
}, },
"time": { "time": {
"type": "string", "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": { "license": {
"type": ["string", "array"], "type": ["string", "array"],
@ -51,11 +51,11 @@
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "name"],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Full name of the author.", "description": "Full name of the author."
"required": true
}, },
"email": { "email": {
"type": "string", "type": "string",
@ -136,6 +136,15 @@
"description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.", "description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
"additionalProperties": true "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": { "vendor-dir": {
"type": "string", "type": "string",
"description": "The location where all packages are installed, defaults to \"vendor\"." "description": "The location where all packages are installed, defaults to \"vendor\"."
@ -182,18 +191,26 @@
}, },
"optimize-autoloader": { "optimize-autoloader": {
"type": "boolean", "type": "boolean",
"description": "Always optimize when dumping the autoloader" "description": "Always optimize when dumping the autoloader."
}, },
"prepend-autoloader": { "prepend-autoloader": {
"type": "boolean", "type": "boolean",
"description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." "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": { "github-domains": {
"type": "array", "type": "array",
"description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
"items": { "items": {
"type": "string" "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": { "archive": {
"type": ["object"], "type": ["object"],
"description": "Options for creating package archives for distribution.", "description": "Options for creating package archives for distribution.",
@ -247,7 +288,8 @@
}, },
"minimum-stability": { "minimum-stability": {
"type": ["string"], "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": { "prefer-stable": {
"type": ["boolean"], "type": ["boolean"],

View File

@ -1,42 +1,59 @@
[ [
"AFL-1.1", "AFL-1.2", "AFL-2.0", "AFL-2.1", "AFL-3.0", "APL-1.0", "Aladdin", "Glide", "Abstyles", "AFL-1.1", "AFL-1.2", "AFL-2.0", "AFL-2.1", "AFL-3.0",
"ANTLR-PD", "Apache-1.0", "Apache-1.1", "Apache-2.0", "APSL-1.0", "AMPAS", "APL-1.0", "Adobe-Glyph", "APAFML", "Adobe-2006", "AGPL-1.0",
"APSL-1.1", "APSL-1.2", "APSL-2.0", "Artistic-1.0", "Artistic-2.0", "AAL", "Afmparse", "Aladdin", "ADSL", "AMDPLPA", "ANTLR-PD", "Apache-1.0",
"BitTorrent-1.0", "BitTorrent-1.1", "BSL-1.0", "BSD-3-Clause-Clear", "Apache-1.1", "Apache-2.0", "AML", "APSL-1.0", "APSL-1.1", "APSL-1.2",
"BSD-2-Clause", "BSD-2-Clause-FreeBSD", "BSD-2-Clause-NetBSD", "APSL-2.0", "Artistic-1.0", "Artistic-1.0-Perl", "Artistic-1.0-cl8",
"BSD-3-Clause", "BSD-4-Clause", "BSD-4-Clause-UC", "CECILL-1.0", "Artistic-2.0", "AAL", "Bahyph", "Barr", "Beerware", "BitTorrent-1.0",
"CECILL-1.1", "CECILL-2.0", "CECILL-B", "CECILL-C", "ClArtistic", "BitTorrent-1.1", "BSL-1.0", "Borceux", "BSD-2-Clause",
"CNRI-Python", "CNRI-Python-GPL-Compatible", "CDDL-1.0", "CDDL-1.1", "BSD-2-Clause-FreeBSD", "BSD-2-Clause-NetBSD", "BSD-3-Clause",
"CPAL-1.0", "CPL-1.0", "CATOSL-1.1", "Condor-1.1", "CC-BY-1.0", "CC-BY-2.0", "BSD-3-Clause-Clear", "BSD-4-Clause", "BSD-Protection",
"CC-BY-2.5", "CC-BY-3.0", "CC-BY-ND-1.0", "CC-BY-ND-2.0", "CC-BY-ND-2.5", "BSD-3-Clause-Attribution", "BSD-4-Clause-UC", "bzip2-1.0.5", "bzip2-1.0.6",
"CC-BY-ND-3.0", "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5", "Caldera", "CECILL-1.0", "CECILL-1.1", "CECILL-2.0", "CECILL-B", "CECILL-C",
"CC-BY-NC-3.0", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-2.0", "CC-BY-NC-ND-2.5", "ClArtistic", "MIT-CMU", "CNRI-Python", "CNRI-Python-GPL-Compatible",
"CC-BY-NC-ND-3.0", "CC-BY-NC-SA-1.0", "CC-BY-NC-SA-2.0", "CC-BY-NC-SA-2.5", "CPOL-1.02", "CDDL-1.0", "CDDL-1.1", "CPAL-1.0", "CPL-1.0", "CATOSL-1.1",
"CC-BY-NC-SA-3.0", "CC-BY-SA-1.0", "CC-BY-SA-2.0", "CC-BY-SA-2.5", "Condor-1.1", "CC-BY-1.0", "CC-BY-2.0", "CC-BY-2.5", "CC-BY-3.0",
"CC-BY-SA-3.0", "CC0-1.0", "CUA-OPL-1.0", "WTFPL", "EPL-1.0", "eCos-2.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",
"ECL-1.0", "ECL-2.0", "EFL-1.0", "EFL-2.0", "Entessa", "ErlPL-1.1", "CC-BY-ND-4.0", "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5",
"EUDatagrid", "EUPL-1.0", "EUPL-1.1", "Fair", "Frameworx-1.0", "FTL", "CC-BY-NC-3.0", "CC-BY-NC-4.0", "CC-BY-NC-ND-1.0", "CC-BY-NC-ND-2.0",
"AGPL-3.0", "GFDL-1.1", "GFDL-1.2", "GFDL-1.3", "GPL-1.0", "GPL-1.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",
"GPL-2.0", "GPL-2.0+", "GPL-2.0-with-autoconf-exception", "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-bison-exception", "GPL-2.0-with-classpath-exception",
"GPL-2.0-with-font-exception", "GPL-2.0-with-GCC-exception", "GPL-3.0", "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", "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+", "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", "gnuplot", "gSOAP-1.3b", "HaskellReport", "HPND", "IBM-pibs", "IPL-1.0",
"JSON", "LPPL-1.3a", "LPPL-1.0", "LPPL-1.1", "LPPL-1.2", "LPPL-1.3c", "ImageMagick", "iMatix", "Imlib2", "IJG", "Intel-ACPI", "Intel", "IPA",
"Libpng", "LPL-1.02", "LPL-1.0", "MS-PL", "MS-RL", "MirOS", "MIT", "ISC", "JasPer-2.0", "JSON", "LPPL-1.3a", "LPPL-1.0", "LPPL-1.1",
"Motosoto", "MPL-1.0", "MPL-1.1", "MPL-2.0", "LPPL-1.2", "LPPL-1.3c", "Latex2e", "BSD-3-Clause-LBNL", "Leptonica",
"MPL-2.0-no-copyleft-exception", "Multics", "NASA-1.3", "Naumen", "Libpng", "libtiff", "LPL-1.02", "LPL-1.0", "MakeIndex", "MTLL", "MS-PL",
"NBPL-1.0", "NGPL", "NOSL", "NPL-1.0", "NPL-1.1", "Nokia", "NPOSL-3.0", "MS-RL", "MirOS", "MITNFA", "MIT", "Motosoto", "MPL-1.0", "MPL-1.1",
"NTP", "OCLC-2.0", "ODbL-1.0", "PDDL-1.0", "OGTSL", "OLDAP-2.2.2", "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-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.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", "OLDAP-2.4", "OLDAP-2.5", "OLDAP-2.6", "OLDAP-2.7", "OML", "OPL-1.0",
"OSL-2.0", "OSL-2.1", "OSL-3.0", "OLDAP-2.8", "OpenSSL", "PHP-3.0", "OSL-1.0", "OSL-1.1", "OSL-2.0", "OSL-2.1", "OSL-3.0", "OLDAP-2.8",
"PHP-3.01", "PostgreSQL", "Python-2.0", "QPL-1.0", "RPSL-1.0", "RPL-1.5", "OpenSSL", "PHP-3.0", "PHP-3.01", "Plexus", "PostgreSQL", "psfrag",
"RHeCos-1.1", "RSCPL", "Ruby", "SAX-PD", "SGI-B-1.0", "SGI-B-1.1", "psutils", "Python-2.0", "QPL-1.0", "Qhull", "Rdisc", "RPSL-1.0", "RPL-1.1",
"SGI-B-2.0", "OFL-1.0", "OFL-1.1", "SimPL-2.0", "Sleepycat", "SMLNJ", "RPL-1.5", "RHeCos-1.1", "RSCPL", "Ruby", "SAX-PD", "Saxpath", "SCEA",
"SugarCRM-1.1.3", "SISSL", "SPL-1.0", "Watcom-1.0", "NCSA", "VSL-1.0", "SWL", "SGI-B-1.0", "SGI-B-1.1", "SGI-B-2.0", "OFL-1.0", "OFL-1.1",
"W3C", "WXwindows", "Xnet", "X11", "XFree86-1.1", "YPL-1.0", "YPL-1.1", "SimPL-2.0", "Sleepycat", "SNIA", "SMLNJ", "StandardML-NJ",
"Zimbra-1.3", "Zlib", "ZPL-1.1", "ZPL-2.0", "ZPL-2.1" "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"
]

View File

@ -15,6 +15,7 @@ namespace Composer\Autoload;
use Composer\Config; use Composer\Config;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
@ -34,14 +35,29 @@ class AutoloadGenerator
*/ */
private $eventDispatcher; 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->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 = '') 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 = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir')); $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
@ -49,6 +65,7 @@ class AutoloadGenerator
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir'))); $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
$useGlobalIncludePath = (bool) $config->get('use-include-path'); $useGlobalIncludePath = (bool) $config->get('use-include-path');
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
$classMapAuthoritative = $config->get('classmap-authoritative');
$targetDir = $vendorPath.'/'.$targetDir; $targetDir = $vendorPath.'/'.$targetDir;
$filesystem->ensureDirectoryExists($targetDir); $filesystem->ensureDirectoryExists($targetDir);
@ -172,7 +189,22 @@ EOF;
if (!is_dir($dir)) { if (!is_dir($dir)) {
continue; 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 ('' === $namespace || 0 === strpos($class, $namespace)) {
if (!isset($classMap[$class])) { if (!isset($classMap[$class])) {
$path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); $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 ($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); $path = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
$classMap[$class] = $path.",\n"; $classMap[$class] = $path.",\n";
} }
@ -213,7 +246,7 @@ EOF;
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile); file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
} }
file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); 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 // use stream_copy_to_stream instead of copy
// to work around https://bugs.php.net/bug.php?id=64634 // to work around https://bugs.php.net/bug.php?id=64634
@ -224,7 +257,9 @@ EOF;
fclose($targetLoader); fclose($targetLoader);
unset($sourceLoader, $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) public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@ -240,7 +275,7 @@ EOF;
$packageMap[] = array( $packageMap[] = array(
$package, $package,
$installationManager->getInstallPath($package) $installationManager->getInstallPath($package),
); );
} }
@ -370,7 +405,6 @@ EOF;
protected function getIncludeFilesFile(array $files, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode) protected function getIncludeFilesFile(array $files, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
{ {
$filesCode = ''; $filesCode = '';
$files = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($files));
foreach ($files as $functionFile) { foreach ($files as $functionFile) {
$filesCode .= ' '.$this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile).",\n"; $filesCode .= ' '.$this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile).",\n";
} }
@ -437,7 +471,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
AUTOLOAD; 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 // TODO the class ComposerAutoloaderInit should be revert to a closure
// when APC has been fixed: // when APC has been fixed:
@ -472,9 +506,6 @@ class ComposerAutoloaderInit$suffix
self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader(); self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));
\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;
HEADER; HEADER;
@ -517,6 +548,13 @@ PSR4;
CLASSMAP; CLASSMAP;
} }
if ($classMapAuthoritative) {
$file .= <<<'CLASSMAPAUTHORITATIVE'
$loader->setClassMapAuthoritative(true);
CLASSMAPAUTHORITATIVE;
}
if ($useGlobalIncludePath) { if ($useGlobalIncludePath) {
$file .= <<<'INCLUDEPATH' $file .= <<<'INCLUDEPATH'
$loader->setUseIncludePath(true); $loader->setUseIncludePath(true);
@ -530,7 +568,6 @@ INCLUDEPATH;
REGISTER_AUTOLOAD; REGISTER_AUTOLOAD;
} }
$file .= <<<REGISTER_LOADER $file .= <<<REGISTER_LOADER
@ -540,15 +577,14 @@ REGISTER_AUTOLOAD;
REGISTER_LOADER; REGISTER_LOADER;
if ($useIncludeFiles) { if ($useIncludeFiles) {
$file .= <<<'INCLUDE_FILES' $file .= <<<INCLUDE_FILES
$includeFiles = require __DIR__ . '/autoload_files.php'; \$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) { foreach (\$includeFiles as \$file) {
require $file; composerRequire$suffix(\$file);
} }
INCLUDE_FILES; INCLUDE_FILES;
} }
$file .= <<<METHOD_FOOTER $file .= <<<METHOD_FOOTER
@ -562,8 +598,12 @@ METHOD_FOOTER;
return $file . <<<FOOTER return $file . <<<FOOTER
} }
FOOTER; function composerRequire$suffix(\$file)
{
require \$file;
}
FOOTER;
} }
protected function parseAutoloadsType(array $packageMap, $type, PackageInterface $mainPackage) protected function parseAutoloadsType(array $packageMap, $type, PackageInterface $mainPackage)
@ -574,6 +614,9 @@ FOOTER;
list($package, $installPath) = $item; list($package, $installPath) = $item;
$autoload = $package->getAutoload(); $autoload = $package->getAutoload();
if ($this->devMode && $package === $mainPackage) {
$autoload = array_merge_recursive($autoload, $package->getDevAutoload());
}
// skip misconfigured packages // skip misconfigured packages
if (!isset($autoload[$type]) || !is_array($autoload[$type])) { if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
@ -585,24 +628,19 @@ FOOTER;
foreach ($autoload[$type] as $namespace => $paths) { foreach ($autoload[$type] as $namespace => $paths) {
foreach ((array) $paths as $path) { foreach ((array) $paths as $path) {
// remove target-dir from file paths of the root package if (($type === 'files' || $type === 'classmap') && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
if ($type === 'files' && $package === $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) { // remove target-dir from file paths of the root package
$targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(array('/', '\\'), '<dirsep>', $package->getTargetDir()))); if ($package === $mainPackage) {
$path = ltrim(preg_replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/'); $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 /* RKERNER
if ($type === 'files' && $package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) { * // add target-dir to classmap entries that don't have it
$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
if ($type === 'classmap' && $package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) { if ($type === 'classmap' && $package !== $mainPackage && $package->getTargetDir() && !is_readable($installPath.'/'.$path)) {
$path = $package->getTargetDir() . '/' . $path; $path = $package->getTargetDir() . '/' . $path;
} }
@ -629,6 +667,16 @@ FOOTER;
} else { } else {
$autoloads[$namespace][] = $installPath.'/'.$path; $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; 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) protected function sortPackageMap(array $packageMap)
{ {
$positions = array(); $packages = array();
$names = array(); $paths = array();
$indexes = array(); $usageList = 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;
}
foreach ($packageMap as $item) { foreach ($packageMap as $item) {
$position = $positions[$item[0]->getName()]; list($package, $path) = $item;
foreach (array_merge($item[0]->getRequires(), $item[0]->getDevRequires()) as $link) { $name = $package->getName();
$packages[$name] = $package;
$paths[$name] = $path;
foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) {
$target = $link->getTarget(); $target = $link->getTarget();
if (!isset($names[$target])) { $usageList[$target][] = $name;
continue;
}
$target = $names[$target];
if ($positions[$target] <= $position) {
continue;
}
foreach ($positions as $key => $value) {
if ($value >= $position) {
break;
}
$positions[$key]--;
}
$positions[$target] = $position - 1;
} }
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(); $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; return $sortedPackageMap;

View File

@ -54,9 +54,15 @@ class ClassLoader
private $useIncludePath = false; private $useIncludePath = false;
private $classMap = array(); private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes() 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() public function getPrefixesPsr4()
@ -143,6 +149,8 @@ class ClassLoader
* @param string $prefix The prefix/namespace, with trailing '\\' * @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-0 base directories * @param array|string $paths The PSR-0 base directories
* @param bool $prepend Whether to prepend the directories * @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/ */
public function addPsr4($prefix, $paths, $prepend = false) public function addPsr4($prefix, $paths, $prepend = false)
{ {
@ -202,10 +210,13 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, * Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace. * replacing any others previously set for this namespace.
* *
* @param string $prefix The prefix/namespace, with trailing '\\' * @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories * @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/ */
public function setPsr4($prefix, $paths) { public function setPsr4($prefix, $paths)
{
if (!$prefix) { if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths; $this->fallbackDirsPsr4 = (array) $paths;
} else { } else {
@ -239,6 +250,27 @@ class ClassLoader
return $this->useIncludePath; 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. * Registers this instance as an autoloader.
* *
@ -266,7 +298,7 @@ class ClassLoader
public function loadClass($class) public function loadClass($class)
{ {
if ($file = $this->findFile($class)) { if ($file = $this->findFile($class)) {
include $file; includeFile($file);
return true; return true;
} }
@ -290,9 +322,29 @@ class ClassLoader
if (isset($this->classMap[$class])) { if (isset($this->classMap[$class])) {
return $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 // PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0]; $first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) { if (isset($this->prefixLengthsPsr4[$first])) {
@ -321,7 +373,7 @@ class ClassLoader
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else { } else {
// PEAR-like class name // PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
} }
if (isset($this->prefixesPsr0[$first])) { if (isset($this->prefixesPsr0[$first])) {
@ -347,8 +399,15 @@ class ClassLoader
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file; 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;
}

View File

@ -12,20 +12,23 @@
*/ */
namespace Composer\Autoload; namespace Composer\Autoload;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;
/** /**
* ClassMapGenerator * ClassMapGenerator
* *
* @author Gyula Sallai <salla016@gmail.com> * @author Gyula Sallai <salla016@gmail.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
class ClassMapGenerator class ClassMapGenerator
{ {
/** /**
* Generate a class map file * Generate a class map file
* *
* @param Traversable $dirs Directories or a single path to search in * @param \Traversable $dirs Directories or a single path to search in
* @param string $file The name of the class map file * @param string $file The name of the class map file
*/ */
public static function dump($dirs, $file) public static function dump($dirs, $file)
{ {
@ -41,20 +44,23 @@ class ClassMapGenerator
/** /**
* Iterate over all files in the given directory searching for classes * Iterate over all files in the given directory searching for classes
* *
* @param Iterator|string $path The path to search in or an iterator * @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 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 * @return array A class map array
* *
* @throws \RuntimeException When the path is neither an existing file nor directory * @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_string($path)) {
if (is_file($path)) { if (is_file($path)) {
$path = array(new \SplFileInfo($path)); $path = array(new \SplFileInfo($path));
} elseif (is_dir($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 { } else {
throw new \RuntimeException( throw new \RuntimeException(
'Could not scan for classes inside "'.$path. 'Could not scan for classes inside "'.$path.
@ -68,18 +74,31 @@ class ClassMapGenerator
foreach ($path as $file) { foreach ($path as $file) {
$filePath = $file->getRealPath(); $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; continue;
} }
if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) { if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
// RKERNER: if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
continue; continue;
} }
$classes = self::findClasses($filePath); $classes = self::findClasses($filePath);
foreach ($classes as $class) { 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'; $traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait';
try { 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) { } catch (\Exception $e) {
throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $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 // 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 // strip strings
$contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s', 'null', $contents); $contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s', 'null', $contents);
// strip leading non-php code if needed // strip leading non-php code if needed
if (substr($contents, 0, 2) !== '<?') { 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 // strip non-php blocks in the file
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents); $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
@ -126,7 +156,7 @@ class ClassMapGenerator
preg_match_all('{ 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*[\{;] | \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); }ix', $contents, $matches);
@ -138,7 +168,12 @@ class ClassMapGenerator
if (!empty($matches['ns'][$i])) { if (!empty($matches['ns'][$i])) {
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\'; $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
} else { } 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, '\\');
} }
} }

View File

@ -83,7 +83,28 @@ class Cache
$this->io->write('Writing '.$this->root . $file.' into 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; return false;
@ -129,14 +150,14 @@ class Cache
public function gcIsNecessary() public function gcIsNecessary()
{ {
return (!self::$cacheCollected && !mt_rand(0, 50)); return (!self::$cacheCollected && !mt_rand(0, 50));
} }
public function remove($file) public function remove($file)
{ {
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file); $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
if ($this->enabled && file_exists($this->root . $file)) { if ($this->enabled && file_exists($this->root . $file)) {
return unlink($this->root . $file); return $this->filesystem->unlink($this->root . $file);
} }
return false; return false;
@ -144,28 +165,32 @@ class Cache
public function gc($ttl, $maxSize) public function gc($ttl, $maxSize)
{ {
$expire = new \DateTime(); if ($this->enabled) {
$expire->modify('-'.$ttl.' seconds'); $expire = new \DateTime();
$expire->modify('-'.$ttl.' seconds');
$finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
foreach ($finder as $file) { foreach ($finder as $file) {
unlink($file->getRealPath()); $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()->getRealPath();
$totalSize -= $this->filesystem->size($filepath);
unlink($filepath);
$iterator->next();
} }
$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 false;
return true;
} }
public function sha1($file) public function sha1($file)

View File

@ -40,6 +40,5 @@ EOT
See http://getcomposer.org/ for more information.</comment> See http://getcomposer.org/ for more information.</comment>
EOT EOT
); );
} }
} }

View File

@ -15,8 +15,11 @@ namespace Composer\Command;
use Composer\Factory; use Composer\Factory;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Repository\CompositeRepository; 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\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@ -37,7 +40,7 @@ class ArchiveCommand extends Command
->setDescription('Create an archive of this composer package') ->setDescription('Create an archive of this composer package')
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'), 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('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', '.'), 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) 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(), $this->getIO(),
$input->getArgument('package'), $input->getArgument('package'),
$input->getArgument('version'), $input->getArgument('version'),
$input->getOption('format'), $input->getOption('format'),
$input->getOption('dir') $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 = '.') protected function archive(IOInterface $io, $packageName = null, $version = null, $format = 'tar', $dest = '.')
@ -103,16 +119,17 @@ EOT
$pool = new Pool(); $pool = new Pool();
$pool->addRepository($repos); $pool->addRepository($repos);
$constraint = ($version) ? new VersionConstraint('>=', $version) : null; $parser = new VersionParser();
$packages = $pool->whatProvides($packageName, $constraint); $constraint = ($version) ? $parser->parseConstraints($version) : null;
$packages = $pool->whatProvides($packageName, $constraint, true);
if (count($packages) > 1) { if (count($packages) > 1) {
$package = $packages[0]; $package = reset($packages);
$io->write('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>'); $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('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>'); $io->write('<comment>Please use a more specific constraint to pick a different package.</comment>');
} elseif ($packages) { } elseif ($packages) {
$package = $packages[0]; $package = reset($packages);
$io->write('<info>Found an exact match '.$package->getPrettyString().'.</info>'); $io->write('<info>Found an exact match '.$package->getPrettyString().'.</info>');
} else { } else {
$io->write('<error>Could not find a package matching '.$packageName.'.</error>'); $io->write('<error>Could not find a package matching '.$packageName.'.</error>');

View File

@ -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>');
}
}

View File

@ -16,6 +16,8 @@ use Composer\Composer;
use Composer\Console\Application; use Composer\Console\Application;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO; use Composer\IO\NullIO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command as BaseCommand; use Symfony\Component\Console\Command\Command as BaseCommand;
/** /**
@ -68,6 +70,15 @@ abstract class Command extends BaseCommand
$this->composer = $composer; $this->composer = $composer;
} }
/**
* Removes the cached composer instance
*/
public function resetComposer()
{
$this->composer = null;
$this->getApplication()->resetComposer();
}
/** /**
* @return IOInterface * @return IOInterface
*/ */
@ -93,4 +104,16 @@ abstract class Command extends BaseCommand
{ {
$this->io = $io; $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);
}
} }

View File

@ -53,9 +53,11 @@ class ConfigCommand extends Command
->setDefinition(array( ->setDefinition(array(
new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), 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('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('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', '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-key', null, 'Setting key'),
new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
)) ))
@ -98,11 +100,13 @@ EOT
*/ */
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
{ {
parent::initialize($input, $output);
if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) { if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
throw new \RuntimeException('--file and --global can not be combined'); 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 // Get the local composer.json, global config.json, or if the user
// passed in a file to use // passed in a file to use
@ -113,15 +117,27 @@ EOT
$this->configFile = new JsonFile($configFile); $this->configFile = new JsonFile($configFile);
$this->configSource = new JsonConfigSource($this->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 // initialize the global file if it's not there
if ($input->getOption('global') && !$this->configFile->exists()) { if ($input->getOption('global') && !$this->configFile->exists()) {
touch($this->configFile->getPath()); touch($this->configFile->getPath());
$this->configFile->write(array('config' => new \ArrayObject)); $this->configFile->write(array('config' => new \ArrayObject));
@chmod($this->configFile->getPath(), 0600); @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()) { 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; return 0;
} }
if (!$input->getOption('global')) { if (!$input->getOption('global')) {
$this->config->merge($this->configFile->read()); $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 // List the configuration of the file settings
@ -203,7 +221,7 @@ EOT
$value = $data; $value = $data;
} elseif (isset($data['config'][$settingKey])) { } elseif (isset($data['config'][$settingKey])) {
$value = $data['config'][$settingKey]; $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
} else { } else {
throw new \RuntimeException($settingKey.' is not defined'); throw new \RuntimeException($settingKey.' is not defined');
} }
@ -236,16 +254,29 @@ EOT
} }
// handle github-oauth // handle github-oauth
if (preg_match('/^github-oauth\.(.+)/', $settingKey, $matches)) { if (preg_match('/^(github-oauth|http-basic)\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { 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)) { if ($matches[1] === 'github-oauth') {
throw new \RuntimeException('Too many arguments, expected only one token'); 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); }; $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 in_array($val, array('auto', 'source', 'dist'), true); },
function ($val) { return $val; } function ($val) { return $val; }
), ),
'store-auths' => array(
function ($val) { return in_array($val, array('true', 'false', 'prompt'), true); },
function ($val) {
if ('prompt' === $val) {
return 'prompt';
}
return $val !== 'false' && (bool) $val;
}
),
'notify-on-install' => array($booleanValidator, $booleanNormalizer), 'notify-on-install' => array($booleanValidator, $booleanNormalizer),
'vendor-dir' => array('is_string', function ($val) { return $val; }), 'vendor-dir' => array('is_string', function ($val) { return $val; }),
'bin-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; }), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
); );
$multiConfigValues = array( $multiConfigValues = array(
'github-protocols' => array( 'github-protocols' => array(
@ -294,8 +337,8 @@ EOT
} }
foreach ($vals as $val) { foreach ($vals as $val) {
if (!in_array($val, array('git', 'https'))) { if (!in_array($val, array('git', 'https', 'ssh'))) {
return 'valid protocols include: git, https'; return 'valid protocols include: git, https, ssh';
} }
} }
@ -320,7 +363,7 @@ EOT
); );
foreach ($uniqueConfigValues as $name => $callbacks) { foreach ($uniqueConfigValues as $name => $callbacks) {
if ($settingKey === $name) { if ($settingKey === $name) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {
return $this->configSource->removeConfigSetting($settingKey); return $this->configSource->removeConfigSetting($settingKey);
} }

View File

@ -19,9 +19,9 @@ use Composer\Installer\ProjectInstaller;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\ComposerRepository; use Composer\Repository\ComposerRepository;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\FilesystemRepository; use Composer\Repository\FilesystemRepository;
@ -56,7 +56,7 @@ class CreateProjectCommand extends Command
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'), new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'),
new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), 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('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-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
@ -69,6 +69,7 @@ class CreateProjectCommand extends Command
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), 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('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 ->setHelp(<<<EOT
The <info>create-project</info> command creates a new project from a given The <info>create-project</info> command creates a new project from a given
@ -102,22 +103,7 @@ EOT
$preferSource = false; $preferSource = false;
$preferDist = false; $preferDist = false;
switch ($config->get('preferred-install')) { $this->updatePreferredOptions($config, $input, $preferSource, $preferDist);
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');
}
if ($input->getOption('no-custom-installers')) { if ($input->getOption('no-custom-installers')) {
$output->writeln('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>'); $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('no-scripts'),
$input->getOption('keep-vcs'), $input->getOption('keep-vcs'),
$input->getOption('no-progress'), $input->getOption('no-progress'),
$input->getOption('no-install') $input->getOption('no-install'),
$input->getOption('ignore-platform-reqs'),
$input
); );
} }
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(); $oldCwd = getcwd();
// we need to manually load the configuration to pass the auth credentials to the io interface!
$io->loadConfiguration($config);
if ($packageName !== null) { if ($packageName !== null) {
$installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disablePlugins, $noScripts, $keepVcs, $noProgress); $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disablePlugins, $noScripts, $keepVcs, $noProgress);
} else { } else {
@ -161,13 +152,17 @@ EOT
$composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); $composer->getEventDispatcher()->dispatchCommandEvent(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages);
} }
$rootPackageConfig = $composer->getConfig();
$this->updatePreferredOptions($rootPackageConfig, $input, $preferSource, $preferDist);
// install dependencies of the created project // install dependencies of the created project
if ($noInstall === false) { if ($noInstall === false) {
$installer = Installer::create($io, $composer); $installer = Installer::create($io, $composer);
$installer->setPreferSource($preferSource) $installer->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode($installDevPackages) ->setDevMode($installDevPackages)
->setRunScripts( ! $noScripts); ->setRunScripts(!$noScripts)
->setIgnorePlatformRequirements($ignorePlatformReqs);
if ($disablePlugins) { if ($disablePlugins) {
$installer->disablePlugins(); $installer->disablePlugins();
@ -238,12 +233,18 @@ EOT
return 0; 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) { if (null === $repositoryUrl) {
$sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config)); $sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) { } elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION) && file_exists($repositoryUrl)) {
$sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io))); $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')) { } elseif (0 === strpos($repositoryUrl, 'http')) {
$sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config); $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
} else { } else {
@ -251,7 +252,6 @@ EOT
} }
$parser = new VersionParser(); $parser = new VersionParser();
$candidates = array();
$requirements = $parser->parseNameVersionPairs(array($packageName)); $requirements = $parser->parseNameVersionPairs(array($packageName));
$name = strtolower($requirements[0]['name']); $name = strtolower($requirements[0]['name']);
if (!$packageVersion && isset($requirements[0]['version'])) { if (!$packageVersion && isset($requirements[0]['version'])) {
@ -275,15 +275,11 @@ EOT
$pool = new Pool($stability); $pool = new Pool($stability);
$pool->addRepository($sourceRepo); $pool->addRepository($sourceRepo);
$constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null; // find the latest version if there are multiple
$candidates = $pool->whatProvides($name, $constraint); $versionSelector = new VersionSelector($pool);
foreach ($candidates as $key => $candidate) { $package = $versionSelector->findBestCandidate($name, $packageVersion);
if ($candidate->getName() !== $name) {
unset($candidates[$key]);
}
}
if (!$candidates) { if (!$package) {
throw new \InvalidArgumentException("Could not find package $name" . ($packageVersion ? " with version $packageVersion." : " with stability $stability.")); 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); $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>'); $io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>');
if ($disablePlugins) { if ($disablePlugins) {
@ -343,4 +330,34 @@ EOT
{ {
return new InstallationManager(); 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');
}
}
} }

View File

@ -29,8 +29,13 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class DiagnoseCommand extends Command class DiagnoseCommand extends Command
{ {
/** @var RemoteFileSystem */
protected $rfs; protected $rfs;
/** @var ProcessExecutor */
protected $process; protected $process;
/** @var int */
protected $failures = 0; protected $failures = 0;
protected function configure() protected function configure()
@ -48,7 +53,23 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) 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()); $this->process = new ProcessExecutor($this->getIO());
$output->write('Checking platform settings: '); $output->write('Checking platform settings: ');
@ -70,26 +91,29 @@ EOT
$this->outputResult($output, $this->checkHttpsProxyFullUriRequestParam()); $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')) { if ($oauth = $config->get('github-oauth')) {
foreach ($oauth as $domain => $token) { foreach ($oauth as $domain => $token) {
$output->write('Checking '.$domain.' oauth access: '); $output->write('Checking '.$domain.' oauth access: ');
$this->outputResult($output, $this->checkGithubOauth($domain, $token)); $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: '); $output->write('Checking disk free space: ');
@ -129,7 +153,7 @@ EOT
{ {
$this->process->execute('git config color.ui', $output); $this->process->execute('git config color.ui', $output);
if (strtolower(trim($output)) === 'always') { 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; return true;
@ -139,7 +163,7 @@ EOT
{ {
$protocol = extension_loaded('openssl') ? 'https' : 'http'; $protocol = extension_loaded('openssl') ? 'https' : 'http';
try { 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) { } catch (\Exception $e) {
return $e; return $e;
} }
@ -183,7 +207,7 @@ EOT
try { try {
$this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false))); $this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
} catch (TransportException $e) { } 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"'; 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'; $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
try { try {
$rfcResult = $this->rfs->getContents('api.github.com', $url, false); $this->rfs->getContents('github.com', $url, false);
} catch (TransportException $e) { } catch (TransportException $e) {
try { 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) { } 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"'; 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 { try {
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/user/repos' : 'https://'.$domain.'/api/v3/user/repos'; $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) { } catch (\Exception $e) {
if ($e instanceof TransportException && $e->getCode() === 401) { 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; return $e;
@ -255,7 +302,7 @@ EOT
$latest = trim($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false)); $latest = trim($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false));
if (Composer::VERSION !== $latest && Composer::VERSION !== '@package_version@') { 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; return true;
@ -271,7 +318,7 @@ EOT
if ($result instanceof \Exception) { if ($result instanceof \Exception) {
$output->writeln('['.get_class($result).'] '.$result->getMessage()); $output->writeln('['.get_class($result).'] '.$result->getMessage());
} elseif ($result) { } elseif ($result) {
$output->writeln($result); $output->writeln(trim($result));
} }
} }
} }
@ -280,7 +327,7 @@ EOT
{ {
$output = ''; $output = '';
$out = function ($msg, $style) use (&$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 // code below taken from getcomposer.org/installer, any changes should be made there and replicated here
@ -312,7 +359,7 @@ EOT
$warnings['openssl'] = true; $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; $warnings['apc_cli'] = true;
} }
@ -341,13 +388,13 @@ EOT
foreach ($errors as $error => $current) { foreach ($errors as $error => $current) {
switch ($error) { switch ($error) {
case 'php': 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; break;
case 'allow_url_fopen': 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 .= "Add the following to the end of your `php.ini`:".PHP_EOL;
$text .= " allow_url_fopen = On"; $text .= " allow_url_fopen = On";
$displayIniMessage = true; $displayIniMessage = true;
break; break;
} }
@ -361,51 +408,51 @@ EOT
foreach ($warnings as $warning => $current) { foreach ($warnings as $warning => $current) {
switch ($warning) { switch ($warning) {
case 'apc_cli': 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 .= "Add the following to the end of your `php.ini`:".PHP_EOL;
$text .= " apc.enable_cli = Off"; $text .= " apc.enable_cli = Off";
$displayIniMessage = true; $displayIniMessage = true;
break; break;
case 'sigchild': 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 .= "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; break;
case 'curlwrappers': case 'curlwrappers':
$text = PHP_EOL."PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL; $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"; $text .= " Recompile it without this flag if possible";
break; break;
case 'openssl': case 'openssl':
$text = PHP_EOL."The openssl extension is missing, which will reduce the security and stability of Composer.".PHP_EOL; $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"; $text .= " If possible you should enable it or recompile php with --with-openssl";
break; break;
case 'php': case 'php':
$text = PHP_EOL."Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL; $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."; $text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues.";
break; break;
case 'xdebug_loaded': case 'xdebug_loaded':
$text = PHP_EOL."The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; $text = "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 .= " Disabling it when using Composer is recommended.";
break; break;
case 'xdebug_profile': 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 .= "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; $displayIniMessage = true;
break; break;
} }
$out($text, 'warning'); $out($text, 'comment');
} }
} }
if ($displayIniMessage) { if ($displayIniMessage) {
$out($iniMessage, 'warning'); $out($iniMessage, 'comment');
} }
return !$warnings && !$errors ? true : $output; return !$warnings && !$errors ? true : $output;

View File

@ -30,7 +30,8 @@ class DumpAutoloadCommand extends Command
->setAliases(array('dumpautoload')) ->setAliases(array('dumpautoload'))
->setDescription('Dumps the autoloader') ->setDescription('Dumps the autoloader')
->setDefinition(array( ->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 ->setHelp(<<<EOT
<info>php composer.phar dump-autoload</info> <info>php composer.phar dump-autoload</info>
@ -51,7 +52,7 @@ EOT
$package = $composer->getPackage(); $package = $composer->getPackage();
$config = $composer->getConfig(); $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) { if ($optimize) {
$output->writeln('<info>Generating optimized autoload files</info>'); $output->writeln('<info>Generating optimized autoload files</info>');
@ -59,6 +60,8 @@ EOT
$output->writeln('<info>Generating autoload files</info>'); $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);
} }
} }

View File

@ -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;
}
}

View File

@ -12,12 +12,15 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Factory; use Composer\Factory;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -30,8 +33,10 @@ use Symfony\Component\Process\ExecutableFinder;
*/ */
class InitCommand extends Command class InitCommand extends Command
{ {
protected $repos;
private $gitConfig; private $gitConfig;
private $repos; private $pool;
public function parseAuthorString($author) public function parseAuthorString($author)
{ {
@ -101,7 +106,7 @@ EOT
} }
if (isset($options['require-dev'])) { 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']) { if (array() === $options['require-dev']) {
$options['require-dev'] = new \stdClass; $options['require-dev'] = new \stdClass;
} }
@ -224,10 +229,7 @@ EOT
$output, $output,
$dialog->getQuestion('Author', $author), $dialog->getQuestion('Author', $author),
function ($value) use ($self, $author) { function ($value) use ($self, $author) {
if (null === $value) { $value = $value ?: $author;
return $author;
}
$author = $self->parseAuthorString($value); $author = $self->parseAuthorString($value);
return sprintf('%s <%s>', $author['name'], $author['email']); return sprintf('%s <%s>', $author['name'], $author['email']);
@ -284,9 +286,11 @@ EOT
protected function findPackages($name) protected function findPackages($name)
{ {
$packages = array(); return $this->getRepos()->search($name);
}
// init repos protected function getRepos()
{
if (!$this->repos) { if (!$this->repos) {
$this->repos = new CompositeRepository(array_merge( $this->repos = new CompositeRepository(array_merge(
array(new PlatformRepository), 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()) protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
@ -306,15 +310,17 @@ EOT
$requires = $this->normalizeRequirements($requires); $requires = $this->normalizeRequirements($requires);
$result = array(); $result = array();
foreach ($requires as $key => $requirement) { foreach ($requires as $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;
}
}
if (!isset($requirement['version'])) { 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']; $result[] = $requirement['name'] . ' ' . $requirement['version'];
@ -327,17 +333,11 @@ EOT
$matches = $this->findPackages($package); $matches = $this->findPackages($package);
if (count($matches)) { if (count($matches)) {
$output->writeln(array(
'',
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
''
));
$exactMatch = null; $exactMatch = null;
$choices = array(); $choices = array();
foreach ($matches as $position => $package) { foreach ($matches as $position => $foundPackage) {
$choices[] = sprintf(' <info>%5s</info> %s', "[$position]", $package['name']); $choices[] = sprintf(' <info>%5s</info> %s', "[$position]", $foundPackage['name']);
if ($package['name'] === $package) { if ($foundPackage['name'] === $package) {
$exactMatch = true; $exactMatch = true;
break; break;
} }
@ -345,6 +345,12 @@ EOT
// no match, prompt which to pick // no match, prompt which to pick
if (!$exactMatch) { if (!$exactMatch) {
$output->writeln(array(
'',
sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
''
));
$output->writeln($choices); $output->writeln($choices);
$output->writeln(''); $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); $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, ' ')) { if (false !== $package && false === strpos($package, ' ')) {
$validator = function ($input) { $validator = function ($input) {
$input = trim($input); $input = trim($input);
@ -377,9 +383,20 @@ EOT
return $input ?: false; 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) { 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; $package .= ' '.$constraint;
@ -419,7 +436,7 @@ EOT
$finder = new ExecutableFinder(); $finder = new ExecutableFinder();
$gitBin = $finder->find('git'); $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(); $cmd->run();
if ($cmd->isSuccessful()) { if ($cmd->isSuccessful()) {
@ -504,4 +521,57 @@ EOT
return false !== filter_var($email, FILTER_VALIDATE_EMAIL); 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);
}
} }

View File

@ -17,6 +17,7 @@ use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; 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-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-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-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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump') new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
new InputOption('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 ->setHelp(<<<EOT
The <info>install</info> command reads the composer.lock file from The <info>install</info> command reads the composer.lock file from
@ -60,11 +64,21 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) 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')) { if ($input->getOption('no-custom-installers')) {
$output->writeln('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>'); $output->writeln('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
$input->setOption('no-plugins', true); $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 = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO(); $io = $this->getIO();
@ -96,7 +110,7 @@ EOT
$preferDist = $input->getOption('prefer-dist'); $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 $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -104,8 +118,10 @@ EOT
->setPreferSource($preferSource) ->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode(!$input->getOption('no-dev')) ->setDevMode(!$input->getOption('no-dev'))
->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
; ;
if ($input->getOption('no-plugins')) { if ($input->getOption('no-plugins')) {

View File

@ -16,6 +16,8 @@ use Composer\Json\JsonFile;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -33,6 +35,7 @@ class LicensesCommand extends Command
->setDescription('Show information about licenses of dependencies') ->setDescription('Show information about licenses of dependencies')
->setDefinition(array( ->setDefinition(array(
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The license command displays detailed information about the licenses of The license command displays detailed information about the licenses of
@ -55,9 +58,10 @@ EOT
$versionParser = new VersionParser; $versionParser = new VersionParser;
$packages = array(); if ($input->getOption('no-dev')) {
foreach ($repo->getPackages() as $package) { $packages = $this->filterRequiredPackages($repo, $root);
$packages[$package->getName()] = $package; } else {
$packages = $this->appendPackages($repo->getPackages(), array());
} }
ksort($packages); ksort($packages);
@ -102,4 +106,47 @@ EOT
throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); 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;
}
} }

View File

@ -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;
}
}

View File

@ -23,6 +23,8 @@ use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
/** /**
* @author Jérémy Romey <jeremy@free-agent.fr> * @author Jérémy Romey <jeremy@free-agent.fr>
@ -36,15 +38,21 @@ class RequireCommand extends InitCommand
->setName('require') ->setName('require')
->setDescription('Adds required packages to your composer.json and installs them') ->setDescription('Adds required packages to your composer.json and installs them')
->setDefinition(array( ->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('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-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
new InputOption('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 ->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 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(); $file = Factory::getComposerFile();
$newlyCreated = !file_exists($file);
if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) { if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) {
$output->writeln('<error>'.$file.' could not be created.</error>'); $output->writeln('<error>'.$file.' could not be created.</error>');
@ -74,13 +83,22 @@ EOT
} }
$json = new JsonFile($file); $json = new JsonFile($file);
$composer = $json->read(); $composerDefinition = $json->read();
$composerBackup = file_get_contents($json->getPath()); $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')); $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $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); $requirements = $this->formatRequirements($requirements);
// validate requirements format // validate requirements format
@ -89,22 +107,30 @@ EOT
$versionParser->parseConstraints($constraint); $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) { foreach ($requirements as $package => $version) {
$baseRequirements[$package] = $version; $baseRequirements[$package] = $version;
if (isset($composerDefinition[$removeKey][$package])) {
unset($composerDefinition[$removeKey][$package]);
}
} }
$composer[$requireKey] = $baseRequirements; $composerDefinition[$requireKey] = $baseRequirements;
$json->write($composer); $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')) { if ($input->getOption('no-update')) {
return 0; return 0;
} }
$updateDevMode = !$input->getOption('update-no-dev');
// Update packages // Update packages
$this->resetComposer();
$composer = $this->getComposer(); $composer = $this->getComposer();
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO(); $io = $this->getIO();
@ -118,28 +144,38 @@ EOT
->setVerbose($input->getOption('verbose')) ->setVerbose($input->getOption('verbose'))
->setPreferSource($input->getOption('prefer-source')) ->setPreferSource($input->getOption('prefer-source'))
->setPreferDist($input->getOption('prefer-dist')) ->setPreferDist($input->getOption('prefer-dist'))
->setDevMode(true) ->setDevMode($updateDevMode)
->setUpdate(true) ->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(); $status = $install->run();
if ($status !== 0) { if ($status !== 0) {
$output->writeln("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>'); if ($newlyCreated) {
file_put_contents($json->getPath(), $composerBackup); $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; 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()); $contents = file_get_contents($json->getPath());
$manipulator = new JsonManipulator($contents); $manipulator = new JsonManipulator($contents);
foreach ($new as $package => $constraint) { 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; return false;
} }
} }

View File

@ -12,6 +12,7 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\Script\CommandEvent;
use Composer\Script\ScriptEvents; use Composer\Script\ScriptEvents;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -23,6 +24,30 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
class RunScriptCommand extends Command 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() protected function configure()
{ {
$this $this
@ -30,6 +55,7 @@ class RunScriptCommand extends Command
->setDescription('Run the scripts defined in composer.json.') ->setDescription('Run the scripts defined in composer.json.')
->setDefinition(array( ->setDefinition(array(
new InputArgument('script', InputArgument::REQUIRED, 'Script name to run.'), 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('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables 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) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$script = $input->getArgument('script'); $script = $input->getArgument('script');
if (!in_array($script, array( if (!in_array($script, $this->commandEvents) && !in_array($script, $this->scriptEvents)) {
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 (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { 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" 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);
} }
} }

View File

@ -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']);
}
}

View File

@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
/** /**
* @author Igor Wiedler <igor@wiedler.ch> * @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('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 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 InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The <info>self-update</info> command checks getcomposer.org for newer The <info>self-update</info> command checks getcomposer.org for newer
@ -57,8 +59,8 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE; $baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE;
$remoteFilesystem = new RemoteFilesystem($this->getIO());
$config = Factory::createConfig(); $config = Factory::createConfig();
$remoteFilesystem = new RemoteFilesystem($this->getIO(), $config);
$cacheDir = $config->get('cache-dir'); $cacheDir = $config->get('cache-dir');
$rollbackDir = $config->get('home'); $rollbackDir = $config->get('home');
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
@ -104,7 +106,7 @@ EOT
$output->writeln(sprintf("Updating to version <info>%s</info>.", $updateVersion)); $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"); $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)) { if (!file_exists($tempFilename)) {
$output->writeln('<error>The download of the new composer version failed for an unexpected reason</error>'); $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 // remove saved installations of composer
if ($input->getOption('clean-backups')) { if ($input->getOption('clean-backups')) {
$files = $this->getOldInstallationFiles($rollbackDir); $finder = $this->getOldInstallationFinder($rollbackDir);
if (!empty($files)) { $fs = new Filesystem;
$fs = new Filesystem; foreach ($finder as $file) {
$file = (string) $file;
foreach ($files as $file) { $output->writeln('<info>Removing: '.$file.'</info>');
$output->writeln('<info>Removing: '.$file.'</info>'); $fs->remove($file);
$fs->remove($file);
}
} }
} }
@ -173,18 +173,19 @@ EOT
protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null) protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
{ {
try { try {
@chmod($newFilename, 0777 & ~umask()); @chmod($newFilename, fileperms($localFilename));
// test the phar validity if (!ini_get('phar.readonly')) {
$phar = new \Phar($newFilename); // test the phar validity
// free the variable to unlock the file $phar = new \Phar($newFilename);
unset($phar); // free the variable to unlock the file
unset($phar);
}
// copy current file into installations dir // copy current file into installations dir
if ($backupTarget && file_exists($localFilename)) { if ($backupTarget && file_exists($localFilename)) {
@copy($localFilename, $backupTarget); @copy($localFilename, $backupTarget);
} }
unset($phar);
rename($newFilename, $localFilename); rename($newFilename, $localFilename);
} catch (\Exception $e) { } catch (\Exception $e) {
if ($backupTarget) { if ($backupTarget) {
@ -200,18 +201,25 @@ EOT
protected function getLastBackupVersion($rollbackDir) protected function getLastBackupVersion($rollbackDir)
{ {
$files = $this->getOldInstallationFiles($rollbackDir); $finder = $this->getOldInstallationFinder($rollbackDir);
if (empty($files)) { $finder->sortByName();
return false; $files = iterator_to_array($finder);
if (count($files)) {
return basename(end($files), self::OLD_INSTALL_EXT);
} }
sort($files); return false;
return basename(end($files), self::OLD_INSTALL_EXT);
} }
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;
} }
} }

View File

@ -41,6 +41,7 @@ class ShowCommand extends Command
{ {
$this $this
->setName('show') ->setName('show')
->setAliases(array('info'))
->setDescription('Show information about packages') ->setDescription('Show information about packages')
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'), 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('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), 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('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The show command displays detailed information about a package, or The show command displays detailed information about a package, or
@ -193,8 +195,9 @@ EOT
$width--; $width--;
} }
$writeVersion = !$input->getOption('name-only') && $showVersion && ($nameLength + $versionLength + 3 <= $width); $writePath = !$input->getOption('name-only') && $input->getOption('path');
$writeDescription = !$input->getOption('name-only') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width); $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) { foreach ($packages[$type] as $package) {
if (is_object($package)) { if (is_object($package)) {
$output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); $output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
@ -211,6 +214,11 @@ EOT
} }
$output->write(' ' . $description); $output->write(' ' . $description);
} }
if ($writePath) {
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
$output->write(' ' . $path);
}
} else { } else {
$output->write($indent . $package); $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>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
$output->writeln('<info>names</info> : ' . implode(', ', $package->getNames())); $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()) { if ($package->getSupport()) {
$output->writeln("\n<info>support</info>"); $output->writeln("\n<info>support</info>");
foreach ($package->getSupport() as $type => $value) { foreach ($package->getSupport() as $type => $value) {

View File

@ -84,7 +84,7 @@ EOT
foreach ($errors as $path => $changes) { foreach ($errors as $path => $changes) {
if ($input->getOption('verbose')) { if ($input->getOption('verbose')) {
$indentedChanges = implode("\n", array_map(function ($line) { $indentedChanges = implode("\n", array_map(function ($line) {
return ' ' . $line; return ' ' . ltrim($line);
}, explode("\n", $changes))); }, explode("\n", $changes)));
$output->writeln('<info>'.$path.'</info>:'); $output->writeln('<info>'.$path.'</info>:');
$output->writeln($indentedChanges); $output->writeln($indentedChanges);

View File

@ -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('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-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-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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'), 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('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 ->setHelp(<<<EOT
The <info>update</info> command reads the composer.json file from the The <info>update</info> command reads the composer.json file from the
@ -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: you want to update as such:
<info>php composer.phar update vendor/package1 foo/mypackage [...]</info> <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 EOT
) )
; ;
@ -70,6 +79,10 @@ EOT
$input->setOption('no-plugins', true); $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 = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO(); $io = $this->getIO();
@ -101,7 +114,7 @@ EOT
$preferDist = $input->getOption('prefer-dist'); $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 $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -109,11 +122,15 @@ EOT
->setPreferSource($preferSource) ->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode(!$input->getOption('no-dev')) ->setDevMode(!$input->getOption('no-dev'))
->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setUpdate(true) ->setUpdate(true)
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
->setWhitelistDependencies($input->getOption('with-dependencies')) ->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')) { if ($input->getOption('no-plugins')) {

View File

@ -12,9 +12,11 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Util\ConfigValidator; use Composer\Util\ConfigValidator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
/** /**
@ -34,6 +36,7 @@ class ValidateCommand extends Command
->setName('validate') ->setName('validate')
->setDescription('Validates a composer.json') ->setDescription('Validates a composer.json')
->setDefinition(array( ->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') new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json')
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
@ -65,7 +68,8 @@ EOT
} }
$validator = new ConfigValidator($this->getIO()); $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 // output errors/warnings
if (!$errors && !$publishErrors && !$warnings) { if (!$errors && !$publishErrors && !$warnings) {

View File

@ -12,6 +12,7 @@
namespace Composer; namespace Composer;
use Composer\Json\JsonFile;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
@ -24,6 +25,7 @@ use Symfony\Component\Process\Process;
class Compiler class Compiler
{ {
private $version; private $version;
private $branchAliasVersion = '';
private $versionDate; private $versionDate;
/** /**
@ -48,13 +50,22 @@ class Compiler
if ($process->run() != 0) { 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.'); 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 = new \DateTime(trim($process->getOutput()));
$date->setTimezone(new \DateTimeZone('UTC')); $date->setTimezone(new \DateTimeZone('UTC'));
$this->versionDate = $date->format('Y-m-d H:i:s'); $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) { if ($process->run() == 0) {
$this->version = trim($process->getOutput()); $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'); $phar = new \Phar($pharFile, 0, 'composer.phar');
@ -138,6 +149,7 @@ class Compiler
if ($path === 'src/Composer/Composer.php') { if ($path === 'src/Composer/Composer.php') {
$content = str_replace('@package_version@', $this->version, $content); $content = str_replace('@package_version@', $this->version, $content);
$content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
$content = str_replace('@release_date@', $this->versionDate, $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. * 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'); Phar::mapPhar('composer.phar');
EOF; EOF;

View File

@ -29,6 +29,7 @@ use Composer\Autoload\AutoloadGenerator;
class Composer class Composer
{ {
const VERSION = '@package_version@'; const VERSION = '@package_version@';
const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
const RELEASE_DATE = '@release_date@'; const RELEASE_DATE = '@release_date@';
/** /**
@ -67,7 +68,7 @@ class Composer
private $config; private $config;
/** /**
* @var EventDispatcher\EventDispatcher * @var EventDispatcher
*/ */
private $eventDispatcher; private $eventDispatcher;
@ -190,7 +191,7 @@ class Composer
} }
/** /**
* @param EventDispatcher\EventDispatcher $eventDispatcher * @param EventDispatcher $eventDispatcher
*/ */
public function setEventDispatcher(EventDispatcher $eventDispatcher) public function setEventDispatcher(EventDispatcher $eventDispatcher)
{ {
@ -198,7 +199,7 @@ class Composer
} }
/** /**
* @return EventDispatcher\EventDispatcher * @return EventDispatcher
*/ */
public function getEventDispatcher() public function getEventDispatcher()
{ {

View File

@ -19,12 +19,14 @@ use Composer\Config\ConfigSourceInterface;
*/ */
class Config class Config
{ {
const RELATIVE_PATHS = 1;
public static $defaultConfig = array( public static $defaultConfig = array(
'process-timeout' => 300, 'process-timeout' => 300,
'use-include-path' => false, 'use-include-path' => false,
'preferred-install' => 'auto', 'preferred-install' => 'auto',
'notify-on-install' => true, 'notify-on-install' => true,
'github-protocols' => array('git', 'https'), 'github-protocols' => array('git', 'https', 'ssh'),
'vendor-dir' => 'vendor', 'vendor-dir' => 'vendor',
'bin-dir' => '{$vendor-dir}/bin', 'bin-dir' => '{$vendor-dir}/bin',
'cache-dir' => '{$home}/cache', 'cache-dir' => '{$home}/cache',
@ -37,8 +39,14 @@ class Config
'discard-changes' => false, 'discard-changes' => false,
'autoloader-suffix' => null, 'autoloader-suffix' => null,
'optimize-autoloader' => false, 'optimize-autoloader' => false,
'classmap-authoritative' => false,
'prepend-autoloader' => true, 'prepend-autoloader' => true,
'github-domains' => array('github.com'), '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( public static $defaultRepositories = array(
@ -50,14 +58,22 @@ class Config
); );
private $config; private $config;
private $baseDir;
private $repositories; private $repositories;
private $configSource; 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 // load defaults
$this->config = static::$defaultConfig; $this->config = static::$defaultConfig;
$this->repositories = static::$defaultRepositories; $this->repositories = static::$defaultRepositories;
$this->useEnvironment = (bool) $useEnvironment;
$this->baseDir = $baseDir;
} }
public function setConfigSource(ConfigSourceInterface $source) public function setConfigSource(ConfigSourceInterface $source)
@ -70,17 +86,27 @@ class Config
return $this->configSource; 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) * Merges new config values with the existing ones (overriding)
* *
* @param array $config * @param array $config
*/ */
public function merge(array $config) public function merge($config)
{ {
// override defaults with given config // override defaults with given config
if (!empty($config['config']) && is_array($config['config'])) { if (!empty($config['config']) && is_array($config['config'])) {
foreach ($config['config'] as $key => $val) { 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); $this->config[$key] = array_merge($this->config[$key], $val);
} else { } else {
$this->config[$key] = $val; $this->config[$key] = $val;
@ -99,7 +125,7 @@ class Config
} }
// disable a repository with an anonymous {"name": false} repo // 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)]); unset($this->repositories[key($repository)]);
continue; continue;
} }
@ -127,10 +153,11 @@ class Config
* Returns a setting * Returns a setting
* *
* @param string $key * @param string $key
* @param int $flags Options (see class constants)
* @throws \RuntimeException * @throws \RuntimeException
* @return mixed * @return mixed
*/ */
public function get($key) public function get($key, $flags = 0)
{ {
switch ($key) { switch ($key) {
case 'vendor-dir': 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 // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
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': case 'cache-ttl':
return (int) $this->config[$key]; return (int) $this->config[$key];
@ -179,10 +213,10 @@ class Config
return (int) $this->config['cache-ttl']; return (int) $this->config['cache-ttl'];
case 'home': case 'home':
return rtrim($this->process($this->config[$key]), '/\\'); return rtrim($this->process($this->config[$key], $flags), '/\\');
case 'discard-changes': 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)) { if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) {
throw new \RuntimeException( throw new \RuntimeException(
"Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash" "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"
@ -206,7 +240,7 @@ class Config
case 'github-protocols': case 'github-protocols':
if (reset($this->config['github-protocols']) === 'http') { 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]; return $this->config[$key];
@ -216,17 +250,17 @@ class Config
return null; 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( $all = array(
'repositories' => $this->getRepositories(), 'repositories' => $this->getRepositories(),
); );
foreach (array_keys($this->config) as $key) { foreach (array_keys($this->config) as $key) {
$all['config'][$key] = $this->get($key); $all['config'][$key] = $this->get($key, $flags);
} }
return $all; return $all;
@ -254,10 +288,11 @@ class Config
/** /**
* Replaces {$refs} inside a config string * 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 * @return string
*/ */
private function process($value) private function process($value, $flags)
{ {
$config = $this; $config = $this;
@ -265,8 +300,43 @@ class Config
return $value; return $value;
} }
return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) { return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) {
return $config->get($match[1]); return $config->get($match[1], $flags);
}, $value); }, $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;
}
} }

View File

@ -66,4 +66,11 @@ interface ConfigSourceInterface
* @param string $name Name * @param string $name Name
*/ */
public function removeLink($type, $name); public function removeLink($type, $name);
/**
* Gives a user-friendly name to this source (file path or so)
*
* @return string
*/
public function getName();
} }

View File

@ -23,17 +23,34 @@ use Composer\Json\JsonManipulator;
*/ */
class JsonConfigSource implements ConfigSourceInterface class JsonConfigSource implements ConfigSourceInterface
{ {
/**
* @var \Composer\Json\JsonFile
*/
private $file; private $file;
private $manipulator;
/**
* @var bool
*/
private $authConfig;
/** /**
* Constructor * Constructor
* *
* @param JsonFile $file * @param JsonFile $file
* @param bool $authConfig
*/ */
public function __construct(JsonFile $file) public function __construct(JsonFile $file, $authConfig = false)
{ {
$this->file = $file; $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) public function addConfigSetting($name, $value)
{ {
$this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) { $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) public function removeConfigSetting($name)
{ {
$this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) { $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()) { if ($this->file->exists()) {
$contents = file_get_contents($this->file->getPath()); $contents = file_get_contents($this->file->getPath());
} elseif ($this->authConfig) {
$contents = "{\n}\n";
} else { } else {
$contents = "{\n \"config\": {\n }\n}\n"; $contents = "{\n \"config\": {\n }\n}\n";
} }
$manipulator = new JsonManipulator($contents); $manipulator = new JsonManipulator($contents);
$newFile = !$this->file->exists(); $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 // try to update cleanly
if (call_user_func_array(array($manipulator, $method), $args)) { if (call_user_func_array(array($manipulator, $method), $args)) {
file_put_contents($this->file->getPath(), $manipulator->getContents()); file_put_contents($this->file->getPath(), $manipulator->getContents());
} else { } else {
// on failed clean update, call the fallback and rewrite the whole file // on failed clean update, call the fallback and rewrite the whole file
$config = $this->file->read(); $config = $this->file->read();
array_unshift($args, $config); $this->arrayUnshiftRef($args, $config);
call_user_func_array($fallback, $args); call_user_func_array($fallback, $args);
$this->file->write($config); $this->file->write($config);
} }
@ -127,4 +176,19 @@ class JsonConfigSource implements ConfigSourceInterface
@chmod($this->file->getPath(), 0600); @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;
}
} }

View File

@ -56,11 +56,11 @@ class Application extends BaseApplication
public function __construct() 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.show_exception_trace', false);
ini_set('xdebug.scream', false); ini_set('xdebug.scream', false);
} }
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
date_default_timezone_set(@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>'); $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 (defined('COMPOSER_DEV_WARNING_TIME')) {
if (time() > COMPOSER_DEV_WARNING_TIME) { $commandName = '';
$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 ($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); $input->setInteractive(false);
} }
if ($input->hasParameterOption('--profile')) { // switch working dir
$startTime = microtime(true);
$this->io->enableDebugging($startTime);
}
if ($newWorkDir = $this->getNewWorkingDir($input)) { if ($newWorkDir = $this->getNewWorkingDir($input)) {
$oldWorkingDir = getcwd(); $oldWorkingDir = getcwd();
chdir($newWorkDir); 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); $result = parent::doRun($input, $output);
@ -129,6 +158,7 @@ class Application extends BaseApplication
/** /**
* @param InputInterface $input * @param InputInterface $input
* @return string
* @throws \RuntimeException * @throws \RuntimeException
*/ */
private function getNewWorkingDir(InputInterface $input) private function getNewWorkingDir(InputInterface $input)
@ -147,18 +177,30 @@ class Application extends BaseApplication
public function renderException($exception, $output) public function renderException($exception, $output)
{ {
try { try {
$composer = $this->getComposer(false); $composer = $this->getComposer(false, true);
if ($composer) { if ($composer) {
$config = $composer->getConfig(); $config = $composer->getConfig();
$minSpaceFree = 1024*1024; $minSpaceFree = 1024*1024;
if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) 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 = $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>'); $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); return parent::renderException($exception, $output);
} }
@ -184,12 +226,19 @@ class Application extends BaseApplication
$message = $e->getMessage() . ':' . PHP_EOL . $errors; $message = $e->getMessage() . ':' . PHP_EOL . $errors;
throw new JsonValidationException($message); throw new JsonValidationException($message);
} }
} }
return $this->composer; return $this->composer;
} }
/**
* Removes the cached composer instance
*/
public function resetComposer()
{
$this->composer = null;
}
/** /**
* @return IOInterface * @return IOInterface
*/ */
@ -227,6 +276,9 @@ class Application extends BaseApplication
$commands[] = new Command\RunScriptCommand(); $commands[] = new Command\RunScriptCommand();
$commands[] = new Command\LicensesCommand(); $commands[] = new Command\LicensesCommand();
$commands[] = new Command\GlobalCommand(); $commands[] = new Command\GlobalCommand();
$commands[] = new Command\ClearCacheCommand();
$commands[] = new Command\RemoveCommand();
$commands[] = new Command\HomeCommand();
if ('phar:' === substr(__FILE__, 0, 5)) { if ('phar:' === substr(__FILE__, 0, 5)) {
$commands[] = new Command\SelfUpdateCommand(); $commands[] = new Command\SelfUpdateCommand();
@ -240,6 +292,16 @@ class Application extends BaseApplication
*/ */
public function getLongVersion() 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; return parent::getLongVersion() . ' ' . Composer::RELEASE_DATE;
} }

View File

@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
class DefaultPolicy implements PolicyInterface class DefaultPolicy implements PolicyInterface
{ {
private $preferStable; private $preferStable;
private $preferLowest;
public function __construct($preferStable = false) public function __construct($preferStable = false, $preferLowest = false)
{ {
$this->preferStable = $preferStable; $this->preferStable = $preferStable;
$this->preferLowest = $preferLowest;
} }
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
@ -42,11 +44,11 @@ class DefaultPolicy implements PolicyInterface
return $constraint->matchSpecific($version, true); 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(); $packages = array();
foreach ($pool->whatProvides($package->getName()) as $candidate) { foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
if ($candidate !== $package) { if ($candidate !== $package) {
$packages[] = $candidate; $packages[] = $candidate;
} }
@ -151,18 +153,18 @@ class DefaultPolicy implements PolicyInterface
} }
// priority equal, sort by package id to make reproducible // priority equal, sort by package id to make reproducible
if ($a->getId() === $b->getId()) { if ($a->id === $b->id) {
return 0; 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; return -1;
} }
if (isset($installedMap[$b->getId()])) { if (isset($installedMap[$b->id])) {
return 1; return 1;
} }
@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
protected function pruneToBestVersion(Pool $pool, $literals) protected function pruneToBestVersion(Pool $pool, $literals)
{ {
$operator = $this->preferLowest ? '<' : '>';
$bestLiterals = array($literals[0]); $bestLiterals = array($literals[0]);
$bestPackage = $pool->literalToPackage($literals[0]); $bestPackage = $pool->literalToPackage($literals[0]);
foreach ($literals as $i => $literal) { foreach ($literals as $i => $literal) {
@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
$package = $pool->literalToPackage($literal); $package = $pool->literalToPackage($literal);
if ($this->versionCompare($package, $bestPackage, '>')) { if ($this->versionCompare($package, $bestPackage, $operator)) {
$bestPackage = $package; $bestPackage = $package;
$bestLiterals = array($literal); $bestLiterals = array($literal);
} elseif ($this->versionCompare($package, $bestPackage, '==')) { } elseif ($this->versionCompare($package, $bestPackage, '==')) {
@ -215,26 +218,6 @@ class DefaultPolicy implements PolicyInterface
return $bestLiterals; 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 * Assumes that installed packages come first and then all highest priority packages
*/ */
@ -247,7 +230,7 @@ class DefaultPolicy implements PolicyInterface
foreach ($literals as $literal) { foreach ($literals as $literal) {
$package = $pool->literalToPackage($literal); $package = $pool->literalToPackage($literal);
if (isset($installedMap[$package->getId()])) { if (isset($installedMap[$package->id])) {
$selected[] = $literal; $selected[] = $literal;
continue; continue;
} }

View File

@ -15,7 +15,6 @@ namespace Composer\DependencyResolver;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Package\Link;
use Composer\Package\LinkConstraint\LinkConstraintInterface; use Composer\Package\LinkConstraint\LinkConstraintInterface;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Package\LinkConstraint\EmptyConstraint; use Composer\Package\LinkConstraint\EmptyConstraint;
@ -23,8 +22,8 @@ use Composer\Repository\RepositoryInterface;
use Composer\Repository\CompositeRepository; use Composer\Repository\CompositeRepository;
use Composer\Repository\ComposerRepository; use Composer\Repository\ComposerRepository;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\StreamableRepositoryInterface;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Package\PackageInterface;
/** /**
* A package pool contains repositories that provide packages. * A package pool contains repositories that provide packages.
@ -45,11 +44,13 @@ class Pool
protected $providerRepos = array(); protected $providerRepos = array();
protected $packages = array(); protected $packages = array();
protected $packageByName = array(); protected $packageByName = array();
protected $packageByExactName = array();
protected $acceptableStabilities; protected $acceptableStabilities;
protected $stabilityFlags; protected $stabilityFlags;
protected $versionParser; protected $versionParser;
protected $providerCache = array(); protected $providerCache = array();
protected $filterRequires; protected $filterRequires;
protected $whitelist = null;
protected $id = 1; protected $id = 1;
public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array()) public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
@ -66,6 +67,12 @@ class Pool
$this->filterRequires = $filterRequires; $this->filterRequires = $filterRequires;
} }
public function setWhitelist($whitelist)
{
$this->whitelist = $whitelist;
$this->providerCache = array();
}
/** /**
* Adds a repository and its packages to this package pool * Adds a repository and its packages to this package pool
* *
@ -89,76 +96,6 @@ class Pool
$this->providerRepos[] = $repo; $this->providerRepos[] = $repo;
$repo->setRootAliases($rootAliases); $repo->setRootAliases($rootAliases);
$repo->resetPackageIds(); $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 { } else {
foreach ($repo->getPackages() as $package) { foreach ($repo->getPackages() as $package) {
$names = $package->getNames(); $names = $package->getNames();
@ -166,6 +103,7 @@ class Pool
if ($exempt || $this->isPackageAcceptable($names, $stability)) { if ($exempt || $this->isPackageAcceptable($names, $stability)) {
$package->setId($this->id++); $package->setId($this->id++);
$this->packages[] = $package; $this->packages[] = $package;
$this->packageByExactName[$package->getName()][$package->id] = $package;
foreach ($names as $provided) { foreach ($names as $provided) {
$this->packageByName[$provided][] = $package; $this->packageByName[$provided][] = $package;
@ -184,6 +122,7 @@ class Pool
$package->getRepository()->addPackage($aliasPackage); $package->getRepository()->addPackage($aliasPackage);
$this->packages[] = $aliasPackage; $this->packages[] = $aliasPackage;
$this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
foreach ($aliasPackage->getNames() as $name) { foreach ($aliasPackage->getNames() as $name) {
$this->packageByName[$name][] = $aliasPackage; $this->packageByName[$name][] = $aliasPackage;
@ -214,44 +153,54 @@ class Pool
*/ */
public function packageById($id) 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 * Searches all packages providing the given package name and match the constraint
* *
* @param string $name The package name to be searched for * @param string $name The package name to be searched for
* @param LinkConstraintInterface $constraint A constraint that all returned * @param LinkConstraintInterface $constraint A constraint that all returned
* packages must match or null to return all * packages must match or null to return all
* @return array A set of packages * @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])) { $key = ((int) $mustMatchName).$constraint;
return $this->providerCache[$name][(string) $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 * @see whatProvides
*/ */
private function computeWhatProvides($name, $constraint) private function computeWhatProvides($name, $constraint, $mustMatchName = false)
{ {
$candidates = array(); $candidates = array();
foreach ($this->providerRepos as $repo) { foreach ($this->providerRepos as $repo) {
foreach ($repo->whatProvides($this, $name) as $candidate) { foreach ($repo->whatProvides($this, $name) as $candidate) {
$candidates[] = $candidate; $candidates[] = $candidate;
if ($candidate->getId() < 1) { if ($candidate->id < 1) {
$candidate->setId($this->id++); $candidate->setId($this->id++);
$this->packages[$this->id - 2] = $candidate; $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]); $candidates = array_merge($candidates, $this->packageByName[$name]);
} }
@ -259,6 +208,20 @@ class Pool
$nameMatch = false; $nameMatch = false;
foreach ($candidates as $candidate) { foreach ($candidates as $candidate) {
$aliasOfCandidate = null;
// alias packages are not white listed, make sure that the package
// being aliased is white listed
if ($candidate instanceof AliasPackage) {
$aliasOfCandidate = $candidate->getAliasOf();
}
if ($this->whitelist !== null && (
(!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
)) {
continue;
}
switch ($this->match($candidate, $name, $constraint)) { switch ($this->match($candidate, $name, $constraint)) {
case self::MATCH_NONE: case self::MATCH_NONE:
break; break;
@ -269,15 +232,15 @@ class Pool
case self::MATCH: case self::MATCH:
$nameMatch = true; $nameMatch = true;
$matches[] = $this->ensurePackageIsLoaded($candidate); $matches[] = $candidate;
break; break;
case self::MATCH_PROVIDE: case self::MATCH_PROVIDE:
$provideMatches[] = $this->ensurePackageIsLoaded($candidate); $provideMatches[] = $candidate;
break; break;
case self::MATCH_REPLACE: case self::MATCH_REPLACE:
$matches[] = $this->ensurePackageIsLoaded($candidate); $matches[] = $candidate;
break; break;
case self::MATCH_FILTERED: case self::MATCH_FILTERED:
@ -312,7 +275,7 @@ class Pool
{ {
$package = $this->literalToPackage($literal); $package = $this->literalToPackage($literal);
if (isset($installedMap[$package->getId()])) { if (isset($installedMap[$package->id])) {
$prefix = ($literal > 0 ? 'keep' : 'remove'); $prefix = ($literal > 0 ? 'keep' : 'remove');
} else { } else {
$prefix = ($literal > 0 ? 'install' : 'don\'t install'); $prefix = ($literal > 0 ? 'install' : 'don\'t install');
@ -338,28 +301,6 @@ class Pool
return false; 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 * Checks if the package matches the given constraint directly or through
* provided or replaced packages * provided or replaced packages
@ -371,19 +312,10 @@ class Pool
*/ */
private function match($candidate, $name, LinkConstraintInterface $constraint = null) private function match($candidate, $name, LinkConstraintInterface $constraint = null)
{ {
// handle array packages $candidateName = $candidate->getName();
if (is_array($candidate)) { $candidateVersion = $candidate->getVersion();
$candidateName = $candidate['name']; $isDev = $candidate->getStability() === 'dev';
$candidateVersion = $candidate['version']; $isAlias = $candidate instanceof AliasPackage;
$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;
}
if (!$isDev && !$isAlias && isset($this->filterRequires[$name])) { if (!$isDev && !$isAlias && isset($this->filterRequires[$name])) {
$requireFilter = $this->filterRequires[$name]; $requireFilter = $this->filterRequires[$name];
@ -401,17 +333,8 @@ class Pool
return self::MATCH_NAME; return self::MATCH_NAME;
} }
if (is_array($candidate)) { $provides = $candidate->getProvides();
$provides = isset($candidate['provide']) $replaces = $candidate->getReplaces();
? $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();
}
// aliases create multiple replaces/provides for one target so they can not use the shortcut below // aliases create multiple replaces/provides for one target so they can not use the shortcut below
if (isset($replaces[0]) || isset($provides[0])) { if (isset($replaces[0]) || isset($provides[0])) {

View File

@ -80,7 +80,13 @@ class Problem
$rule = $reason['rule']; $rule = $reason['rule'];
$job = $reason['job']; $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 // handle php extensions
if (0 === stripos($job['packageName'], 'ext-')) { if (0 === stripos($job['packageName'], 'ext-')) {
$ext = substr($job['packageName'], 4); $ext = substr($job['packageName'], 4);
@ -124,7 +130,7 @@ class Problem
$messages[] = $this->jobToText($job); $messages[] = $this->jobToText($job);
} elseif ($rule) { } elseif ($rule) {
if ($rule instanceof Rule) { if ($rule instanceof Rule) {
$messages[] = $rule->getPrettyString($installedMap); $messages[] = $rule->getPrettyString($this->pool, $installedMap);
} }
} }
} }
@ -161,18 +167,25 @@ class Problem
{ {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': 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 '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': case 'update':
return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.'; return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.';
case 'remove': case 'remove':
return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).''; 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) protected function getPackageList($packages)

View File

@ -43,22 +43,31 @@ class Request
$this->addJob($packageName, 'remove', $constraint); $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); $packageName = strtolower($packageName);
$packages = $this->pool->whatProvides($packageName, $constraint);
$this->jobs[] = array( $this->jobs[] = array(
'packages' => $packages,
'cmd' => $cmd, 'cmd' => $cmd,
'packageName' => $packageName, 'packageName' => $packageName,
'constraint' => $constraint, 'constraint' => $constraint,
'fixed' => $fixed
); );
} }
public function updateAll() public function updateAll()
{ {
$this->jobs[] = array('cmd' => 'update-all', 'packages' => array()); $this->jobs[] = array('cmd' => 'update-all');
} }
public function getJobs() public function getJobs()

View File

@ -29,10 +29,13 @@ class Rule
const RULE_LEARNED = 12; const RULE_LEARNED = 12;
const RULE_PACKAGE_ALIAS = 13; const RULE_PACKAGE_ALIAS = 13;
protected $pool; /**
* READ-ONLY: The literals this rule consists of.
* @var array
*/
public $literals;
protected $disabled; protected $disabled;
protected $literals;
protected $type; protected $type;
protected $id; protected $id;
protected $reason; protected $reason;
@ -42,10 +45,8 @@ class Rule
protected $ruleHash; 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 all packages ascending by id
sort($literals); sort($literals);
@ -160,6 +161,9 @@ class Rule
return !$this->disabled; return !$this->disabled;
} }
/**
* @deprecated Use public literals member
*/
public function getLiterals() public function getLiterals()
{ {
return $this->literals; return $this->literals;
@ -170,14 +174,14 @@ class Rule
return 1 === count($this->literals); return 1 === count($this->literals);
} }
public function getPrettyString(array $installedMap = array()) public function getPrettyString(Pool $pool, array $installedMap = array())
{ {
$ruleText = ''; $ruleText = '';
foreach ($this->literals as $i => $literal) { foreach ($this->literals as $i => $literal) {
if ($i != 0) { if ($i != 0) {
$ruleText .= '|'; $ruleText .= '|';
} }
$ruleText .= $this->pool->literalToPrettyString($literal, $installedMap); $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
} }
switch ($this->reason) { switch ($this->reason) {
@ -191,24 +195,24 @@ class Rule
return "Remove command rule ($ruleText)"; return "Remove command rule ($ruleText)";
case self::RULE_PACKAGE_CONFLICT: case self::RULE_PACKAGE_CONFLICT:
$package1 = $this->pool->literalToPackage($this->literals[0]); $package1 = $pool->literalToPackage($this->literals[0]);
$package2 = $this->pool->literalToPackage($this->literals[1]); $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: case self::RULE_PACKAGE_REQUIRES:
$literals = $this->literals; $literals = $this->literals;
$sourceLiteral = array_shift($literals); $sourceLiteral = array_shift($literals);
$sourcePackage = $this->pool->literalToPackage($sourceLiteral); $sourcePackage = $pool->literalToPackage($sourceLiteral);
$requires = array(); $requires = array();
foreach ($literals as $literal) { foreach ($literals as $literal) {
$requires[] = $this->pool->literalToPackage($literal); $requires[] = $pool->literalToPackage($literal);
} }
$text = $this->reasonData->getPrettyString($sourcePackage); $text = $this->reasonData->getPrettyString($sourcePackage);
if ($requires) { if ($requires) {
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($requires) . '.'; $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.';
} else { } else {
$targetName = $this->reasonData->getTarget(); $targetName = $this->reasonData->getTarget();
@ -235,22 +239,24 @@ class Rule
case self::RULE_INSTALLED_PACKAGE_OBSOLETES: case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
return $ruleText; return $ruleText;
case self::RULE_PACKAGE_SAME_NAME: case self::RULE_PACKAGE_SAME_NAME:
return 'Can only install one of: ' . $this->formatPackagesUnique($this->literals) . '.'; return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $this->literals) . '.';
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
return $ruleText; return $ruleText;
case self::RULE_LEARNED: case self::RULE_LEARNED:
return 'Conclusion: '.$ruleText; return 'Conclusion: '.$ruleText;
case self::RULE_PACKAGE_ALIAS: case self::RULE_PACKAGE_ALIAS:
return $ruleText; return $ruleText;
default:
return '('.$ruleText.')';
} }
} }
protected function formatPackagesUnique(array $packages) protected function formatPackagesUnique($pool, array $packages)
{ {
$prepared = array(); $prepared = array();
foreach ($packages as $package) { foreach ($packages as $package) {
if (!is_object($package)) { if (!is_object($package)) {
$package = $this->pool->literalToPackage($package); $package = $pool->literalToPackage($package);
} }
$prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['name'] = $package->getPrettyName();
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
@ -275,7 +281,7 @@ class Rule
if ($i != 0) { if ($i != 0) {
$result .= '|'; $result .= '|';
} }
$result .= $this->pool->literalToString($literal); $result .= $literal;
} }
$result .= ')'; $result .= ')';

View File

@ -22,6 +22,13 @@ class RuleSet implements \IteratorAggregate, \Countable
const TYPE_JOB = 1; const TYPE_JOB = 1;
const TYPE_LEARNED = 4; const TYPE_LEARNED = 4;
/**
* READ-ONLY: Lookup table for rule id to rule object
*
* @var Rule[]
*/
public $ruleById;
protected static $types = array( protected static $types = array(
-1 => 'UNKNOWN', -1 => 'UNKNOWN',
self::TYPE_PACKAGE => 'PACKAGE', self::TYPE_PACKAGE => 'PACKAGE',
@ -30,7 +37,6 @@ class RuleSet implements \IteratorAggregate, \Countable
); );
protected $rules; protected $rules;
protected $ruleById;
protected $nextRuleId; protected $nextRuleId;
protected $rulesByHash; protected $rulesByHash;
@ -144,17 +150,22 @@ class RuleSet implements \IteratorAggregate, \Countable
return false; return false;
} }
public function __toString() public function getPrettyString(Pool $pool = null)
{ {
$string = "\n"; $string = "\n";
foreach ($this->rules as $type => $rules) { foreach ($this->rules as $type => $rules) {
$string .= str_pad(self::$types[$type], 8, ' ') . ": "; $string .= str_pad(self::$types[$type], 8, ' ') . ": ";
foreach ($rules as $rule) { foreach ($rules as $rule) {
$string .= $rule."\n"; $string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
} }
$string .= "\n\n"; $string .= "\n\n";
} }
return $string; return $string;
} }
public function __toString()
{
return $this->getPrettyString(null);
}
} }

View File

@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Repository\PlatformRepository;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -25,6 +26,8 @@ class RuleSetGenerator
protected $rules; protected $rules;
protected $jobs; protected $jobs;
protected $installedMap; protected $installedMap;
protected $whitelistedMap;
protected $addedMap;
public function __construct(PolicyInterface $policy, Pool $pool) 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 * This rule is of the form (-A|B|C), where B and C are the providers of
* one requirement of the package A. * one requirement of the package A.
* *
* @param PackageInterface $package The package with a requirement * @param PackageInterface $package The package with a requirement
* @param array $providers The providers of the requirement * @param array $providers The providers of the requirement
* @param int $reason A RULE_* constant describing the * @param int $reason A RULE_* constant describing the
* reason for generating this rule * reason for generating this rule
* @param mixed $reasonData Any data, e.g. the requirement name, * @param mixed $reasonData Any data, e.g. the requirement name,
* that goes with the reason * that goes with the reason
* @return Rule The generated rule or null if tautological * @return Rule The generated rule or null if tautological
*/ */
protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
{ {
$literals = array(-$package->getId()); $literals = array(-$package->id);
foreach ($providers as $provider) { foreach ($providers as $provider) {
// self fulfilling rule? // self fulfilling rule?
if ($provider === $package) { if ($provider === $package) {
return null; 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 * 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. * set of packages is empty an impossible rule is generated.
* *
* @param array $packages The set of packages to choose from * @param array $packages The set of packages to choose from
* @param int $reason A RULE_* constant describing the reason for * @param int $reason A RULE_* constant describing the reason for
* generating this rule * generating this rule
* @param array $job The job this rule was created from * @param array $job The job this rule was created from
* @return Rule The generated rule * @return Rule The generated rule
*/ */
protected function createInstallOneOfRule(array $packages, $reason, $job) protected function createInstallOneOfRule(array $packages, $reason, $job)
{ {
$literals = array(); $literals = array();
foreach ($packages as $package) { 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). * The rule for a package A is (-A).
* *
* @param PackageInterface $package The package to be removed * @param PackageInterface $package The package to be removed
* @param int $reason A RULE_* constant describing the * @param int $reason A RULE_* constant describing the
* reason for generating this rule * reason for generating this rule
* @param array $job The job this rule was created from * @param array $job The job this rule was created from
* @return Rule The generated rule * @return Rule The generated rule
*/ */
protected function createRemoveRule(PackageInterface $package, $reason, $job) 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 * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
* and B the provider. * and B the provider.
* *
* @param PackageInterface $issuer The package declaring the conflict * @param PackageInterface $issuer The package declaring the conflict
* @param PackageInterface $provider The package causing the conflict * @param PackageInterface $provider The package causing the conflict
* @param int $reason A RULE_* constant describing the * @param int $reason A RULE_* constant describing the
* reason for generating this rule * reason for generating this rule
* @param mixed $reasonData Any data, e.g. the package name, that * @param mixed $reasonData Any data, e.g. the package name, that
* goes with the reason * goes with the reason
* @return Rule The generated rule * @return Rule The generated rule
*/ */
protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
{ {
@ -120,7 +123,7 @@ class RuleSetGenerator
return null; 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); $this->rules->add($newRule, $type);
} }
protected function addRulesForPackage(PackageInterface $package) protected function whitelistFromPackage(PackageInterface $package)
{ {
$workQueue = new \SplQueue; $workQueue = new \SplQueue;
$workQueue->enqueue($package); $workQueue->enqueue($package);
while (!$workQueue->isEmpty()) { while (!$workQueue->isEmpty()) {
$package = $workQueue->dequeue(); $package = $workQueue->dequeue();
if (isset($this->addedMap[$package->getId()])) { if (isset($this->whitelistedMap[$package->id])) {
continue; continue;
} }
$this->addedMap[$package->getId()] = true; $this->whitelistedMap[$package->id] = true;
foreach ($package->getRequires() as $link) { 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()); $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));
@ -173,7 +215,7 @@ class RuleSetGenerator
} }
// check obsoletes and implicit obsoletes of a package // 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) { foreach ($package->getReplaces() as $link) {
$obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
@ -221,41 +263,46 @@ class RuleSetGenerator
return $impossible; return $impossible;
} }
/** protected function whitelistFromJobs()
* 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()
{ {
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'install': case 'install':
if ($job['packages']) { $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
foreach ($job['packages'] as $package) { foreach ($packages as $package) {
if (!isset($this->installedMap[$package->getId()])) { $this->whitelistFromPackage($package);
$this->addRulesForPackage($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); $this->addRule(RuleSet::TYPE_JOB, $rule);
} }
break; break;
case 'remove': case 'remove':
// remove all packages with this name including uninstalled // remove all packages with this name including uninstalled
// ones to make sure none of them are picked as replacements // 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); $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
$this->addRule(RuleSet::TYPE_JOB, $rule); $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->jobs = $jobs;
$this->rules = new RuleSet; $this->rules = new RuleSet;
$this->installedMap = $installedMap; $this->installedMap = $installedMap;
$this->whitelistedMap = array();
foreach ($this->installedMap as $package) { foreach ($this->installedMap as $package) {
$this->addRulesForPackage($package); $this->whitelistFromPackage($package);
$this->addRulesForUpdatePackages($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; return $this->rules;
} }

View File

@ -69,11 +69,11 @@ class RuleWatchGraph
* above example the rule was (-A|+B), then A turning true means that * above example the rule was (-A|+B), then A turning true means that
* B must now be decided true as well. * B must now be decided true as well.
* *
* @param int $decidedLiteral The literal which was decided (A in our example) * @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 * @param int $level The level at which the decision took place and at which
* all resulting decisions should be made. * all resulting decisions should be made.
* @param Decisions $decisions Used to check previous decisions and to * @param Decisions $decisions Used to check previous decisions and to
* register decisions resulting from propagation * register decisions resulting from propagation
* @return Rule|null If a conflict is found the conflicting rule is returned * @return Rule|null If a conflict is found the conflicting rule is returned
*/ */
public function propagateLiteral($decidedLiteral, $level, $decisions) public function propagateLiteral($decidedLiteral, $level, $decisions)
@ -95,7 +95,7 @@ class RuleWatchGraph
$otherWatch = $node->getOtherWatch($literal); $otherWatch = $node->getOtherWatch($literal);
if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
$ruleLiterals = $node->getRule()->getLiterals(); $ruleLiterals = $node->getRule()->literals;
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) { $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
return $literal !== $ruleLiteral && return $literal !== $ruleLiteral &&

View File

@ -35,7 +35,7 @@ class RuleWatchNode
{ {
$this->rule = $rule; $this->rule = $rule;
$literals = $rule->getLiterals(); $literals = $rule->literals;
$this->watch1 = count($literals) > 0 ? $literals[0] : 0; $this->watch1 = count($literals) > 0 ? $literals[0] : 0;
$this->watch2 = count($literals) > 1 ? $literals[1] : 0; $this->watch2 = count($literals) > 1 ? $literals[1] : 0;
@ -51,10 +51,10 @@ class RuleWatchNode
*/ */
public function watch2OnHighest(Decisions $decisions) 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 there are only 2 elements, both are being watched anyway
if ($literals < 3) { if (count($literals) < 3) {
return; return;
} }

View File

@ -13,6 +13,7 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -55,13 +56,13 @@ class Solver
$rulesCount = count($this->rules); $rulesCount = count($this->rules);
for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
$rule = $this->rules->ruleById($ruleIndex); $rule = $this->rules->ruleById[$ruleIndex];
if (!$rule->isAssertion() || $rule->isDisabled()) { if (!$rule->isAssertion() || $rule->isDisabled()) {
continue; continue;
} }
$literals = $rule->getLiterals(); $literals = $rule->literals;
$literal = $literals[0]; $literal = $literals[0];
if (!$this->decisions->decided(abs($literal))) { if (!$this->decisions->decided(abs($literal))) {
@ -82,7 +83,6 @@ class Solver
$conflict = $this->decisions->decisionRule($literal); $conflict = $this->decisions->decisionRule($literal);
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
$problem = new Problem($this->pool); $problem = new Problem($this->pool);
$problem->addRule($rule); $problem->addRule($rule);
@ -104,7 +104,7 @@ class Solver
continue; continue;
} }
$assertRuleLiterals = $assertRule->getLiterals(); $assertRuleLiterals = $assertRule->literals;
$assertRuleLiteral = $assertRuleLiterals[0]; $assertRuleLiteral = $assertRuleLiterals[0];
if (abs($literal) !== abs($assertRuleLiteral)) { if (abs($literal) !== abs($assertRuleLiteral)) {
@ -125,29 +125,37 @@ class Solver
{ {
$this->installedMap = array(); $this->installedMap = array();
foreach ($this->installed->getPackages() as $package) { 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) { foreach ($this->jobs as $job) {
switch ($job['cmd']) { switch ($job['cmd']) {
case 'update': case 'update':
foreach ($job['packages'] as $package) { $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
if (isset($this->installedMap[$package->getId()])) { foreach ($packages as $package) {
$this->updateMap[$package->getId()] = true; if (isset($this->installedMap[$package->id])) {
$this->updateMap[$package->id] = true;
} }
} }
break; break;
case 'update-all': case 'update-all':
foreach ($this->installedMap as $package) { foreach ($this->installedMap as $package) {
$this->updateMap[$package->getId()] = true; $this->updateMap[$package->id] = true;
} }
break; break;
case 'install': 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 = 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; $this->problems[] = $problem;
} }
break; break;
@ -155,15 +163,14 @@ class Solver
} }
} }
public function solve(Request $request) public function solve(Request $request, $ignorePlatformReqs = false)
{ {
$this->jobs = $request->getJobs(); $this->jobs = $request->getJobs();
$this->setupInstalledMap(); $this->setupInstalledMap();
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
$this->checkForRootRequireProblems($ignorePlatformReqs);
$this->decisions = new Decisions($this->pool); $this->decisions = new Decisions($this->pool);
$this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap);
$this->watchGraph = new RuleWatchGraph; $this->watchGraph = new RuleWatchGraph;
foreach ($this->rules as $rule) { foreach ($this->rules as $rule) {
@ -349,7 +356,7 @@ class Solver
while (true) { while (true) {
$this->learnedPool[count($this->learnedPool) - 1][] = $rule; $this->learnedPool[count($this->learnedPool) - 1][] = $rule;
foreach ($rule->getLiterals() as $literal) { foreach ($rule->literals as $literal) {
// skip the one true literal // skip the one true literal
if ($this->decisions->satisfy($literal)) { if ($this->decisions->satisfy($literal)) {
continue; 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); return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
} }
@ -473,7 +480,7 @@ class Solver
$this->problems[] = $problem; $this->problems[] = $problem;
$seen = array(); $seen = array();
$literals = $conflictRule->getLiterals(); $literals = $conflictRule->literals;
foreach ($literals as $literal) { foreach ($literals as $literal) {
// skip the one true literal // skip the one true literal
@ -496,7 +503,7 @@ class Solver
$problem->addRule($why); $problem->addRule($why);
$this->analyzeUnsolvableRule($problem, $why); $this->analyzeUnsolvableRule($problem, $why);
$literals = $why->getLiterals(); $literals = $why->literals;
foreach ($literals as $literal) { foreach ($literals as $literal) {
// skip the one true literal // skip the one true literal
@ -601,7 +608,6 @@ class Solver
$installedPos = 0; $installedPos = 0;
while (true) { while (true) {
if (1 === $level) { if (1 === $level) {
$conflictRule = $this->propagate($level); $conflictRule = $this->propagate($level);
if (null !== $conflictRule) { if (null !== $conflictRule) {
@ -621,7 +627,7 @@ class Solver
$decisionQueue = array(); $decisionQueue = array();
$noneSatisfied = true; $noneSatisfied = true;
foreach ($rule->getLiterals() as $literal) { foreach ($rule->literals as $literal) {
if ($this->decisions->satisfy($literal)) { if ($this->decisions->satisfy($literal)) {
$noneSatisfied = false; $noneSatisfied = false;
break; break;
@ -650,7 +656,6 @@ class Solver
} }
if ($noneSatisfied && count($decisionQueue)) { if ($noneSatisfied && count($decisionQueue)) {
$oLevel = $level; $oLevel = $level;
$level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule); $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
@ -682,8 +687,8 @@ class Solver
$i = 0; $i = 0;
} }
$rule = $this->rules->ruleById($i); $rule = $this->rules->ruleById[$i];
$literals = $rule->getLiterals(); $literals = $rule->literals;
if ($rule->isDisabled()) { if ($rule->isDisabled()) {
continue; continue;
@ -734,7 +739,6 @@ class Solver
// minimization step // minimization step
if (count($this->branches)) { if (count($this->branches)) {
$lastLiteral = null; $lastLiteral = null;
$lastLevel = null; $lastLevel = null;
$lastBranchIndex = 0; $lastBranchIndex = 0;

View File

@ -13,7 +13,6 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\DependencyResolver\Operation;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -50,16 +49,15 @@ class Transaction
$package = $this->pool->literalToPackage($literal); $package = $this->pool->literalToPackage($literal);
// wanted & installed || !wanted & !installed // wanted & installed || !wanted & !installed
if (($literal > 0) == (isset($this->installedMap[$package->getId()]))) { if (($literal > 0) == (isset($this->installedMap[$package->id]))) {
continue; continue;
} }
if ($literal > 0) { if ($literal > 0) {
if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) { if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
$source = $installMeansUpdateMap[abs($literal)]; $source = $installMeansUpdateMap[abs($literal)];
$updateMap[$package->getId()] = array( $updateMap[$package->id] = array(
'package' => $package, 'package' => $package,
'source' => $source, 'source' => $source,
'reason' => $reason, 'reason' => $reason,
@ -67,9 +65,9 @@ class Transaction
// avoid updates to one package from multiple origins // avoid updates to one package from multiple origins
unset($installMeansUpdateMap[abs($literal)]); unset($installMeansUpdateMap[abs($literal)]);
$ignoreRemove[$source->getId()] = true; $ignoreRemove[$source->id] = true;
} else { } else {
$installMap[$package->getId()] = array( $installMap[$package->id] = array(
'package' => $package, 'package' => $package,
'reason' => $reason, 'reason' => $reason,
); );
@ -79,16 +77,16 @@ class Transaction
foreach ($this->decisions as $i => $decision) { foreach ($this->decisions as $i => $decision) {
$literal = $decision[Decisions::DECISION_LITERAL]; $literal = $decision[Decisions::DECISION_LITERAL];
$reason = $decision[Decisions::DECISION_REASON];
$package = $this->pool->literalToPackage($literal); $package = $this->pool->literalToPackage($literal);
if ($literal <= 0 && if ($literal <= 0 &&
isset($this->installedMap[$package->getId()]) && isset($this->installedMap[$package->id]) &&
!isset($ignoreRemove[$package->getId()])) { !isset($ignoreRemove[$package->id])) {
$uninstallMap[$package->getId()] = array( $uninstallMap[$package->id] = array(
'package' => $package, 'package' => $package,
'reason' => $reason, 'reason' => $reason,
); );
} }
} }
@ -109,7 +107,7 @@ class Transaction
while (!empty($queue)) { while (!empty($queue)) {
$package = array_pop($queue); $package = array_pop($queue);
$packageId = $package->getId(); $packageId = $package->id;
if (!isset($visited[$packageId])) { if (!isset($visited[$packageId])) {
array_push($queue, $package); array_push($queue, $package);
@ -126,7 +124,7 @@ class Transaction
} }
} }
$visited[$package->getId()] = true; $visited[$package->id] = true;
} else { } else {
if (isset($installMap[$packageId])) { if (isset($installMap[$packageId])) {
$this->install( $this->install(
@ -167,7 +165,7 @@ class Transaction
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint()); $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($possibleRequires as $require) { foreach ($possibleRequires as $require) {
unset($roots[$require->getId()]); unset($roots[$require->id]);
} }
} }
} }
@ -188,13 +186,13 @@ class Transaction
} }
// !wanted & installed // !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); $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
$literals = array($package->getId()); $literals = array($package->id);
foreach ($updates as $update) { foreach ($updates as $update) {
$literals[] = $update->getId(); $literals[] = $update->id;
} }
foreach ($literals as $updateLiteral) { foreach ($literals as $updateLiteral) {

View File

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Symfony\Component\Finder\Finder;
/** /**
* Base downloader for archives * Base downloader for archives
@ -47,22 +48,28 @@ abstract class ArchiveDownloader extends FileDownloader
throw $e; throw $e;
} }
unlink($fileName); $this->filesystem->unlink($fileName);
// get file list $contentDir = $this->getFolderContent($temporaryDir);
$contentDir = $this->listFiles($temporaryDir);
// only one dir in the archive, extract its contents out of it // only one dir in the archive, extract its contents out of it
if (1 === count($contentDir) && !is_file($contentDir[0])) { if (1 === count($contentDir) && is_dir(reset($contentDir))) {
$contentDir = $this->listFiles($contentDir[0]); $contentDir = $this->getFolderContent((string) reset($contentDir));
} }
// move files back out of the temp dir // move files back out of the temp dir
foreach ($contentDir as $file) { foreach ($contentDir as $file) {
$file = (string) $file;
$this->filesystem->rename($file, $path . '/' . basename($file)); $this->filesystem->rename($file, $path . '/' . basename($file));
} }
$this->filesystem->removeDirectory($temporaryDir); $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) { } catch (\Exception $e) {
// clean up // clean up
$this->filesystem->removeDirectory($path); $this->filesystem->removeDirectory($path);
@ -128,14 +135,19 @@ abstract class ArchiveDownloader extends FileDownloader
abstract protected function extract($file, $path); 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 iterator_to_array($finder);
return basename($el) !== '.' && basename($el) !== '..';
}));
} }
} }

View File

@ -13,7 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Downloader\DownloaderInterface; use Composer\IO\IOInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
/** /**
@ -23,6 +23,7 @@ use Composer\Util\Filesystem;
*/ */
class DownloadManager class DownloadManager
{ {
private $io;
private $preferDist = false; private $preferDist = false;
private $preferSource = false; private $preferSource = false;
private $filesystem; private $filesystem;
@ -31,11 +32,13 @@ class DownloadManager
/** /**
* Initializes download manager. * Initializes download manager.
* *
* @param IOInterface $io The Input Output Interface
* @param bool $preferSource prefer downloading from source * @param bool $preferSource prefer downloading from source
* @param Filesystem|null $filesystem custom Filesystem object * @param Filesystem|null $filesystem custom Filesystem object
*/ */
public function __construct($preferSource = false, Filesystem $filesystem = null) public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
{ {
$this->io = $io;
$this->preferSource = $preferSource; $this->preferSource = $preferSource;
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
} }
@ -100,7 +103,7 @@ class DownloadManager
/** /**
* Returns downloader for a specific installation type. * Returns downloader for a specific installation type.
* *
* @param string $type installation type * @param string $type installation type
* @return DownloaderInterface * @return DownloaderInterface
* *
* @throws \InvalidArgumentException if downloader for provided type is not registered * @throws \InvalidArgumentException if downloader for provided type is not registered
@ -118,12 +121,12 @@ class DownloadManager
/** /**
* Returns downloader for already installed package. * Returns downloader for already installed package.
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @return DownloaderInterface|null * @return DownloaderInterface|null
* *
* @throws \InvalidArgumentException if package has no installation source specified * @throws \InvalidArgumentException if package has no installation source specified
* @throws \LogicException if specific downloader used to load package with * @throws \LogicException if specific downloader used to load package with
* wrong type * wrong type
*/ */
public function getDownloaderForInstalledPackage(PackageInterface $package) public function getDownloaderForInstalledPackage(PackageInterface $package)
{ {
@ -161,6 +164,7 @@ class DownloadManager
* @param bool $preferSource prefer installation from source * @param bool $preferSource prefer installation from source
* *
* @throws \InvalidArgumentException if package have no urls to download from * @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException
*/ */
public function download(PackageInterface $package, $targetDir, $preferSource = null) public function download(PackageInterface $package, $targetDir, $preferSource = null)
{ {
@ -168,18 +172,48 @@ class DownloadManager
$sourceType = $package->getSourceType(); $sourceType = $package->getSourceType();
$distType = $package->getDistType(); $distType = $package->getDistType();
if ((!$package->isDev() || $this->preferDist || !$sourceType) && !($preferSource && $sourceType) && $distType) { $sources = array();
$package->setInstallationSource('dist'); if ($sourceType) {
} elseif ($sourceType) { $sources[] = 'source';
$package->setInstallationSource('source'); }
} else { if ($distType) {
$sources[] = 'dist';
}
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); 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); $this->filesystem->ensureDirectoryExists($targetDir);
$downloader = $this->getDownloaderForInstalledPackage($package); foreach ($sources as $i => $source) {
$downloader->download($package, $targetDir); 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) public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($initial); $downloader = $this->getDownloaderForInstalledPackage($initial);
if (!$downloader) {
return;
}
$installationSource = $initial->getInstallationSource(); $installationSource = $initial->getInstallationSource();
if ('dist' === $installationSource) { if ('dist' === $installationSource) {
@ -230,6 +268,8 @@ class DownloadManager
public function remove(PackageInterface $package, $targetDir) public function remove(PackageInterface $package, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($package); $downloader = $this->getDownloaderForInstalledPackage($package);
$downloader->remove($package, $targetDir); if ($downloader) {
$downloader->remove($package, $targetDir);
}
} }
} }

View File

@ -21,7 +21,6 @@ use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent; use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\GitHub;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
/** /**
@ -56,11 +55,10 @@ class FileDownloader implements DownloaderInterface
$this->io = $io; $this->io = $io;
$this->config = $config; $this->config = $config;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->rfs = $rfs ?: new RemoteFilesystem($io); $this->rfs = $rfs ?: new RemoteFilesystem($io, $config);
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
$this->cache = $cache; $this->cache = $cache;
if ($this->cache && $this->cache->gcIsNecessary()) { if ($this->cache && $this->cache->gcIsNecessary()) {
$this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); $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) public function download(PackageInterface $package, $path)
{ {
$url = $package->getDistUrl(); if (!$package->getDistUrl()) {
if (!$url) {
throw new \InvalidArgumentException('The given package is missing url information'); throw new \InvalidArgumentException('The given package is missing url information');
} }
$this->filesystem->removeDirectory($path); $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
$this->filesystem->ensureDirectoryExists($path);
$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); $fileName = $this->getFileName($package, $path);
$this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
$processedUrl = $this->processUrl($package, $url); $processedUrl = $this->processUrl($package, $url);
$hostname = parse_url($processedUrl, PHP_URL_HOST); $hostname = parse_url($processedUrl, PHP_URL_HOST);
@ -100,61 +120,39 @@ class FileDownloader implements DownloaderInterface
} }
$rfs = $preFileDownloadEvent->getRemoteFilesystem(); $rfs = $preFileDownloadEvent->getRemoteFilesystem();
if (strpos($hostname, '.github.com') === (strlen($hostname) - 11)) {
$hostname = 'github.com';
}
try { try {
$checksum = $package->getDistSha1Checksum(); $checksum = $package->getDistSha1Checksum();
$cacheKey = $this->getCacheKey($package); $cacheKey = $this->getCacheKey($package);
try { // download if we don't have it in cache or the cache is invalidated
// 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->cache || ($checksum && $checksum !== $this->cache->sha1($cacheKey)) || !$this->cache->copyTo($cacheKey, $fileName)) { if (!$this->outputProgress) {
if (!$this->outputProgress) { $this->io->write(' Downloading');
$this->io->write(' Downloading'); }
}
// try to download 3 times then fail hard // try to download 3 times then fail hard
$retries = 3; $retries = 3;
while ($retries--) { while ($retries--) {
try { try {
$rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress); $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
break; break;
} catch (TransportException $e) { } catch (TransportException $e) {
// if we got an http response with a proper code, then requesting again will probably not help, abort // if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(),array(500, 502, 503, 504))) || !$retries) { if ((0 !== $e->getCode() && !in_array($e->getCode(),array(500, 502, 503, 504))) || !$retries) {
throw $e; throw $e;
}
if ($this->io->isVerbose()) {
$this->io->write(' Download failed, retrying...');
}
usleep(500000);
} }
if ($this->io->isVerbose()) {
$this->io->write(' Download failed, retrying...');
}
usleep(500000);
} }
}
if ($this->cache) { if ($this->cache) {
$this->cache->copyFrom($cacheKey, $fileName); $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;
} }
} else {
$this->io->write(' Loading from cache');
} }
if (!file_exists($fileName)) { 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>)"); $this->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
if (!$this->filesystem->removeDirectory($path)) { if (!$this->filesystem->removeDirectory($path)) {
// retry after a bit on windows since it tends to be touchy with mass removals throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250000) && !$this->filesystem->removeDirectory($path))) {
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
}
} }
} }

View File

@ -15,6 +15,10 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\GitHub; use Composer\Util\GitHub;
use Composer\Util\Git as GitUtil; 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> * @author Jordi Boggiano <j.boggiano@seld.be>
@ -22,26 +26,37 @@ use Composer\Util\Git as GitUtil;
class GitDownloader extends VcsDownloader class GitDownloader extends VcsDownloader
{ {
private $hasStashedChanges = false; 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} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path) public function doDownload(PackageInterface $package, $path, $url)
{ {
$this->cleanEnv(); GitUtil::cleanEnv();
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
$ref = $package->getSourceReference(); $ref = $package->getSourceReference();
$flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : ''; $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); $this->io->write(" Cloning ".$ref);
$commandCallable = function($url) use ($ref, $path, $command) { $commandCallable = function ($url) use ($ref, $path, $command) {
return sprintf($command, escapeshellarg($url), escapeshellarg($path), escapeshellarg($ref)); return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($ref));
}; };
$this->runCommand($commandCallable, $package->getSourceUrl(), $path, true); $this->gitUtil->runCommand($commandCallable, $url, $path, true);
$this->setPushUrl($package, $path); 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 ($newRef = $this->updateToCommit($path, $ref, $package->getPrettyVersion(), $package->getReleaseDate())) {
if ($package->getDistReference() === $package->getSourceReference()) { if ($package->getDistReference() === $package->getSourceReference()) {
@ -54,9 +69,9 @@ class GitDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@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); $path = $this->normalizePath($path);
if (!is_dir($path.'/.git')) { if (!is_dir($path.'/.git')) {
throw new \RuntimeException('The .git directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information'); 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); $this->io->write(" Checking out ".$ref);
$command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer'; $command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer';
// capture username/password from URL if there is one $commandCallable = function ($url) use ($command) {
$this->process->execute('git remote -v', $output, $path); return sprintf($command, ProcessExecutor::escape ($url));
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));
}; };
$this->runCommand($commandCallable, $target->getSourceUrl(), $path); $this->gitUtil->runCommand($commandCallable, $url, $path);
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) { if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
if ($target->getDistReference() === $target->getSourceReference()) { if ($target->getDistReference() === $target->getSourceReference()) {
$target->setDistReference($newRef); $target->setDistReference($newRef);
@ -90,7 +99,7 @@ class GitDownloader extends VcsDownloader
*/ */
public function getLocalChanges(PackageInterface $package, $path) public function getLocalChanges(PackageInterface $package, $path)
{ {
$this->cleanEnv(); GitUtil::cleanEnv();
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
if (!is_dir($path.'/.git')) { if (!is_dir($path.'/.git')) {
return; return;
@ -109,7 +118,7 @@ class GitDownloader extends VcsDownloader
*/ */
protected function cleanChanges(PackageInterface $package, $path, $update) protected function cleanChanges(PackageInterface $package, $path, $update)
{ {
$this->cleanEnv(); GitUtil::cleanEnv();
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
if (!$changes = $this->getLocalChanges($package, $path)) { if (!$changes = $this->getLocalChanges($package, $path)) {
return; return;
@ -186,7 +195,7 @@ class GitDownloader extends VcsDownloader
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
if ($this->hasStashedChanges) { if ($this->hasStashedChanges) {
$this->hasStashedChanges = false; $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)) { if (0 !== $this->process->execute('git stash pop', $output, $path)) {
throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); 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 $path
* @param string $reference * @param string $reference
* @param string $branch * @param string $branch
* @param DateTime $date * @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 * @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) protected function updateToCommit($path, $reference, $branch, $date)
{ {
@ -218,7 +229,7 @@ class GitDownloader extends VcsDownloader
&& $branches && $branches
&& preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $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)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return;
} }
@ -231,192 +242,41 @@ class GitDownloader extends VcsDownloader
$branch = 'v' . $branch; $branch = 'v' . $branch;
} }
$command = sprintf('git checkout %s', escapeshellarg($branch)); $command = sprintf('git checkout %s', ProcessExecutor::escape($branch));
$fallbackCommand = sprintf('git checkout -B %s %s', escapeshellarg($branch), escapeshellarg('composer/'.$branch)); $fallbackCommand = sprintf('git checkout -B %s %s', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
if (0 === $this->process->execute($command, $output, $path) if (0 === $this->process->execute($command, $output, $path)
|| 0 === $this->process->execute($fallbackCommand, $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)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return;
} }
} }
} }
$command = sprintf($template, escapeshellarg($gitRef)); $command = sprintf($template, ProcessExecutor::escape($gitRef));
if (0 === $this->process->execute($command, $output, $path)) { if (0 === $this->process->execute($command, $output, $path)) {
return; return;
} }
// reference was not found (prints "fatal: reference is not a tree: $ref") // reference was not found (prints "fatal: reference is not a tree: $ref")
if ($date && false !== strpos($this->process->getErrorOutput(), $reference)) { if (false !== strpos($this->process->getErrorOutput(), $reference)) {
$date = $date->format('U'); $this->io->write(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
// 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;
}
} }
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());
} }
/** protected function setPushUrl($path, $url)
* 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)
{ {
// set push url for github projects // 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'); $protocols = $this->config->get('github-protocols');
$pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git'; $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
if ($protocols[0] !== 'git') { if ($protocols[0] !== 'git') {
$pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.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); $this->process->execute($cmd, $ignoredOutput, $path);
} }
} }
@ -462,12 +322,6 @@ class GitDownloader extends VcsDownloader
$this->hasStashedChanges = true; $this->hasStashedChanges = true;
} }
protected function cleanEnv()
{
$util = new GitUtil;
$util->cleanEnv();
}
protected function normalizePath($path) protected function normalizePath($path)
{ {
if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) { if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) {

View File

@ -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);
}
}

View File

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
/** /**
* @author Per Bernhardt <plb@webfactory.de> * @author Per Bernhardt <plb@webfactory.de>
@ -22,12 +23,12 @@ class HgDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path) public function doDownload(PackageInterface $package, $path, $url)
{ {
$url = escapeshellarg($package->getSourceUrl()); $url = ProcessExecutor::escape($url);
$ref = escapeshellarg($package->getSourceReference()); $ref = ProcessExecutor::escape($package->getSourceReference());
$this->io->write(" Cloning ".$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)) { if (0 !== $this->process->execute($command, $ignoredOutput)) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
} }
@ -40,10 +41,10 @@ class HgDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path) public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{ {
$url = escapeshellarg($target->getSourceUrl()); $url = ProcessExecutor::escape($url);
$ref = escapeshellarg($target->getSourceReference()); $ref = ProcessExecutor::escape($target->getSourceReference());
$this->io->write(" Updating to ".$target->getSourceReference()); $this->io->write(" Updating to ".$target->getSourceReference());
if (!is_dir($path.'/.hg')) { if (!is_dir($path.'/.hg')) {

View File

@ -127,19 +127,20 @@ class PearPackageExtractor
/** /**
* Builds list of copy and list of remove actions that would transform extracted PEAR tarball into installed package. * 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 string $source string path to extracted files
* @param array $roles array [role => roleRoot] relative root for files having that role * @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 * @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 * @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) * path, and target is destination of file (also relative to $source path)
* @throws \RuntimeException * @throws \RuntimeException
*/ */
private function buildCopyActions($source, array $roles, $vars) private function buildCopyActions($source, array $roles, $vars)
{ {
/** @var $package \SimpleXmlElement */ /** @var $package \SimpleXmlElement */
$package = simplexml_load_file($this->combine($source, 'package.xml')); $package = simplexml_load_file($this->combine($source, 'package.xml'));
if(false === $package) if (false === $package) {
throw new \RuntimeException('Package definition file is not valid.'); throw new \RuntimeException('Package definition file is not valid.');
}
$packageSchemaVersion = $package['version']; $packageSchemaVersion = $package['version'];
if ('1.0' == $packageSchemaVersion) { if ('1.0' == $packageSchemaVersion) {
@ -203,16 +204,16 @@ class PearPackageExtractor
/** @var $child \SimpleXMLElement */ /** @var $child \SimpleXMLElement */
if ($child->getName() == 'dir') { if ($child->getName() == 'dir') {
$dirSource = $this->combine($source, (string) $child['name']); $dirSource = $this->combine($source, (string) $child['name']);
$dirTarget = $child['baseinstalldir'] ? : $target; $dirTarget = $child['baseinstalldir'] ?: $target;
$dirRole = $child['role'] ? : $role; $dirRole = $child['role'] ?: $role;
$dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName); $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
$result = array_merge($result, $dirFiles); $result = array_merge($result, $dirFiles);
} elseif ($child->getName() == 'file') { } elseif ($child->getName() == 'file') {
$fileRole = (string) $child['role'] ? : $role; $fileRole = (string) $child['role'] ?: $role;
if (isset($targetRoles[$fileRole])) { 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); $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)) { if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
$fileTarget = $packageName . '/' . $fileTarget; $fileTarget = $packageName . '/' . $fileTarget;
} }
@ -233,15 +234,15 @@ class PearPackageExtractor
/** @var $child \SimpleXMLElement */ /** @var $child \SimpleXMLElement */
if ('dir' == $child->getName()) { if ('dir' == $child->getName()) {
$dirSource = $this->combine($source, $child['name']); $dirSource = $this->combine($source, $child['name']);
$dirTarget = $child['baseinstalldir'] ? : $target; $dirTarget = $child['baseinstalldir'] ?: $target;
$dirRole = $child['role'] ? : $role; $dirRole = $child['role'] ?: $role;
$dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName); $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
$result = array_merge($result, $dirFiles); $result = array_merge($result, $dirFiles);
} elseif ('file' == $child->getName()) { } elseif ('file' == $child->getName()) {
$fileRole = (string) $child['role'] ? : $role; $fileRole = (string) $child['role'] ?: $role;
if (isset($targetRoles[$fileRole])) { if (isset($targetRoles[$fileRole])) {
$fileSource = $this->combine($source, (string) $child['name']); $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(); $fileTasks = array();
foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) { foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) {
if ('replace' == $taskNode->getName()) { if ('replace' == $taskNode->getName()) {

View File

@ -22,18 +22,17 @@ use Composer\Util\Perforce;
class PerforceDownloader extends VcsDownloader class PerforceDownloader extends VcsDownloader
{ {
protected $perforce; protected $perforce;
protected $perforceInjected = false;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path) public function doDownload(PackageInterface $package, $path, $url)
{ {
$ref = $package->getSourceReference(); $ref = $package->getSourceReference();
$label = $package->getPrettyVersion(); $label = $this->getLabelFromSourceReference($ref);
$this->io->write(' Cloning ' . $ref); $this->io->write(' Cloning ' . $ref);
$this->initPerforce($package, $path); $this->initPerforce($package, $path, $url);
$this->perforce->setStream($ref); $this->perforce->setStream($ref);
$this->perforce->p4Login($this->io); $this->perforce->p4Login($this->io);
$this->perforce->writeP4ClientSpec(); $this->perforce->writeP4ClientSpec();
@ -42,10 +41,21 @@ class PerforceDownloader extends VcsDownloader
$this->perforce->cleanupClientSpec(); $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); $this->perforce->initializePath($path);
return; return;
} }
@ -54,7 +64,7 @@ class PerforceDownloader extends VcsDownloader
if ($repository instanceof VcsRepository) { if ($repository instanceof VcsRepository) {
$repoConfig = $this->getRepoConfig($repository); $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) private function getRepoConfig(VcsRepository $repository)
@ -65,9 +75,9 @@ class PerforceDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@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);
} }
/** /**

View File

@ -42,7 +42,7 @@ class RarDownloader extends ArchiveDownloader
// Try to use unrar on *nix // Try to use unrar on *nix
if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 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)) { if (0 === $this->process->execute($command, $ignoredOutput)) {
return; return;

View File

@ -24,10 +24,10 @@ class SvnDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path) public function doDownload(PackageInterface $package, $path, $url)
{ {
$url = $package->getSourceUrl(); SvnUtil::cleanEnv();
$ref = $package->getSourceReference(); $ref = $package->getSourceReference();
$this->io->write(" Checking out ".$package->getSourceReference()); $this->io->write(" Checking out ".$package->getSourceReference());
$this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path); $this->execute($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
@ -36,17 +36,24 @@ class SvnDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@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(); $ref = $target->getSourceReference();
if (!is_dir($path.'/.svn')) { if (!is_dir($path.'/.svn')) {
throw new \RuntimeException('The .svn directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information'); 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->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) 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 { try {
return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
@ -144,14 +151,20 @@ class SvnDownloader extends VcsDownloader
*/ */
protected function getCommitLogs($fromReference, $toReference, $path) protected function getCommitLogs($fromReference, $toReference, $path)
{ {
// strip paths from references and only keep the actual revision if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference) ) {
$fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference); // strip paths from references and only keep the actual revision
$toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference); $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)) { if (0 !== $this->process->execute($command, $output, $path)) {
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); throw new \RuntimeException(
'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
);
}
} else {
$output = "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
} }
return $output; return $output;

View File

@ -15,9 +15,10 @@ namespace Composer\Downloader;
/** /**
* @author Jordi Boggiano <j.boggiano@seld.be> * @author Jordi Boggiano <j.boggiano@seld.be>
*/ */
class TransportException extends \Exception class TransportException extends \RuntimeException
{ {
protected $headers; protected $headers;
protected $response;
public function setHeaders($headers) public function setHeaders($headers)
{ {
@ -28,4 +29,14 @@ class TransportException extends \Exception
{ {
return $this->headers; return $this->headers;
} }
public function setResponse($response)
{
$this->response = $response;
}
public function getResponse()
{
return $this->response;
}
} }

View File

@ -55,8 +55,28 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
} }
$this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)"); $this->io->write(" - Installing <info>" . $package->getName() . "</info> (<comment>" . VersionParser::formatVersion($package) . "</comment>)");
$this->filesystem->removeDirectory($path); $this->filesystem->emptyDirectory($path);
$this->doDownload($package, $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(''); $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->io->write(" - Updating <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
$this->cleanChanges($initial, $path, true); $this->cleanChanges($initial, $path, true);
try { $urls = $target->getSourceUrls();
$this->doUpdate($initial, $target, $path); while ($url = array_shift($urls)) {
} catch (\Exception $e) { try {
// in case of failed update, try to reapply the changes before aborting if (Filesystem::isLocalPath($url)) {
$this->reapplyChanges($path); $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); $this->reapplyChanges($path);
//print the commit logs if in verbose mode // print the commit logs if in verbose mode
if ($this->io->isVerbose()) { if ($this->io->isVerbose()) {
$message = 'Pulling in changes:'; $message = 'Pulling in changes:';
$logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); $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->io->write(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
$this->cleanChanges($package, $path, false); $this->cleanChanges($package, $path, false);
if (!$this->filesystem->removeDirectory($path)) { if (!$this->filesystem->removeDirectory($path)) {
// retry after a bit on windows since it tends to be touchy with mass removals throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(250) && !$this->filesystem->removeDirectory($path))) {
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 * Prompt the user to check if changes should be stashed/removed or the operation aborted
* *
* @param PackageInterface $package * @param PackageInterface $package
* @param string $path * @param string $path
* @param bool $update if true (update) the changes can be stashed and reapplied after an update, * @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 * 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 * @throws \RuntimeException in case the operation must be aborted
*/ */
protected function cleanChanges(PackageInterface $package, $path, $update) protected function cleanChanges(PackageInterface $package, $path, $update)
@ -176,8 +207,9 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @param string $path download path * @param string $path download path
* @param string $url package url
*/ */
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. * 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 $initial initial package
* @param PackageInterface $target updated package * @param PackageInterface $target updated package
* @param string $path download path * @param string $path download path
* @param string $url package url
*/ */
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 * Fetches the commit logs between two commits

View File

@ -38,12 +38,16 @@ class ZipDownloader extends ArchiveDownloader
// try to use unzip on *nix // try to use unzip on *nix
if (!defined('PHP_WINDOWS_VERSION_BUILD')) { if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$command = 'unzip '.escapeshellarg($file).' -d '.escapeshellarg($path) . ' && chmod -R u+w ' . escapeshellarg($path); $command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
if (0 === $this->process->execute($command, $ignoredOutput)) { try {
return; 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')) { if (!class_exists('ZipArchive')) {

View File

@ -24,6 +24,16 @@ class Event
*/ */
protected $name; 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 * @var boolean Whether the event should not be passed to more listeners
*/ */
@ -32,11 +42,15 @@ class Event
/** /**
* Constructor. * 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->name = $name;
$this->args = $args;
$this->flags = $flags;
} }
/** /**
@ -49,6 +63,26 @@ class Event
return $this->name; 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 * Checks if stopPropagation has been called
* *

View File

@ -12,9 +12,14 @@
namespace Composer\EventDispatcher; 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\IO\IOInterface;
use Composer\Composer; use Composer\Composer;
use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\CompositeRepository;
use Composer\Script; use Composer\Script;
use Composer\Script\CommandEvent; use Composer\Script\CommandEvent;
use Composer\Script\PackageEvent; use Composer\Script\PackageEvent;
@ -39,6 +44,7 @@ class EventDispatcher
protected $io; protected $io;
protected $loader; protected $loader;
protected $process; protected $process;
protected $listeners;
/** /**
* Constructor. * Constructor.
@ -57,8 +63,10 @@ class EventDispatcher
/** /**
* Dispatch an event * Dispatch an event
* *
* @param string $eventName An event name * @param string $eventName An event name
* @param Event $event * @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) public function dispatch($eventName, Event $event = null)
{ {
@ -66,51 +74,78 @@ class EventDispatcher
$event = new Event($eventName); $event = new Event($eventName);
} }
$this->doDispatch($event); return $this->doDispatch($event);
} }
/** /**
* Dispatch a script event. * Dispatch a script event.
* *
* @param string $eventName The constant in ScriptEvents * @param string $eventName The constant in ScriptEvents
* @param Script\Event $event * @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) { return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
$event = new Script\Event($eventName, $this->composer, $this->io);
}
$this->doDispatch($event);
} }
/** /**
* Dispatch a package event. * Dispatch a package event.
* *
* @param string $eventName The constant in ScriptEvents * @param string $eventName The constant in ScriptEvents
* @param boolean $devMode Whether or not we are in dev mode * @param boolean $devMode Whether or not we are in dev mode
* @param OperationInterface $operation The package being installed/updated/removed * @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) 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. * Dispatch a command event.
* *
* @param string $eventName The constant in ScriptEvents * @param string $eventName The constant in ScriptEvents
* @param boolean $devMode Whether or not we are in dev mode * @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. * 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 \RuntimeException
* @throws \Exception * @throws \Exception
*/ */
@ -118,9 +153,11 @@ class EventDispatcher
{ {
$listeners = $this->getListeners($event); $listeners = $this->getListeners($event);
$return = 0;
foreach ($listeners as $callable) { foreach ($listeners as $callable) {
if (!is_string($callable) && is_callable($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)) { } elseif ($this->isPhpScript($callable)) {
$className = substr($callable, 0, strpos($callable, '::')); $className = substr($callable, 0, strpos($callable, '::'));
$methodName = substr($callable, strpos($callable, '::') + 2); $methodName = substr($callable, strpos($callable, '::') + 2);
@ -135,15 +172,16 @@ class EventDispatcher
} }
try { try {
$this->executeEventPhpScript($className, $methodName, $event); $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
} catch (\Exception $e) { } catch (\Exception $e) {
$message = "Script %s handling the %s event terminated with an exception"; $message = "Script %s handling the %s event terminated with an exception";
$this->io->write('<error>'.sprintf($message, $callable, $event->getName()).'</error>'); $this->io->write('<error>'.sprintf($message, $callable, $event->getName()).'</error>');
throw $e; throw $e;
} }
} else { } else {
if (0 !== ($exitCode = $this->process->execute($callable))) { $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor','escape'), $event->getArguments()));
$event->getIO()->write(sprintf('<error>Script %s handling the %s event returned with an error</error>', $callable, $event->getName())); 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); throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
} }
@ -153,6 +191,8 @@ class EventDispatcher
break; break;
} }
} }
return $return;
} }
/** /**
@ -162,7 +202,41 @@ class EventDispatcher
*/ */
protected function executeEventPhpScript($className, $methodName, Event $event) 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()]); 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 * Finds all listeners defined as scripts in the package
* *

View File

@ -17,7 +17,7 @@ use Composer\Json\JsonFile;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\Archiver; use Composer\Package\Archiver;
use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryInterface; use Composer\Repository\WritableRepositoryInterface;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@ -36,14 +36,12 @@ use Composer\Package\Version\VersionParser;
class Factory class Factory
{ {
/** /**
* @return string
* @throws \RuntimeException * @throws \RuntimeException
* @return Config
*/ */
public static function createConfig() protected static function getHomeDir()
{ {
// determine home and cache dirs
$home = getenv('COMPOSER_HOME'); $home = getenv('COMPOSER_HOME');
$cacheDir = getenv('COMPOSER_CACHE_DIR');
if (!$home) { if (!$home) {
if (defined('PHP_WINDOWS_VERSION_MAJOR')) { if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
if (!getenv('APPDATA')) { if (!getenv('APPDATA')) {
@ -57,6 +55,18 @@ class Factory
$home = rtrim(getenv('HOME'), '/') . '/.composer'; $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 (!$cacheDir) {
if (defined('PHP_WINDOWS_VERSION_MAJOR')) { if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
if ($cacheDir = getenv('LOCALAPPDATA')) { 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 // Protect directory against web access. Since HOME could be
// the www-data's user home and be web-accessible it is a // the www-data's user home and be web-accessible it is a
// potential security risk // potential security risk
@ -82,48 +107,30 @@ class Factory
} }
} }
$config = new Config(); $config = new Config(true, $cwd);
// add dirs to the config // add dirs to the config
$config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir))); $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir)));
// load global config
$file = new JsonFile($home.'/config.json'); $file = new JsonFile($home.'/config.json');
if ($file->exists()) { if ($file->exists()) {
if ($io && $io->isDebug()) {
$io->write('Loading config file ' . $file->getPath());
}
$config->merge($file->read()); $config->merge($file->read());
} }
$config->setConfigSource(new JsonConfigSource($file)); $config->setConfigSource(new JsonConfigSource($file));
// move old cache dirs to the new locations // load global auth file
$legacyPaths = array( $file = new JsonFile($config->get('home').'/auth.json');
'cache-repo-dir' => array('/cache' => '/http*', '/cache.svn' => '/*', '/cache.github' => '/*'), if ($file->exists()) {
'cache-vcs-dir' => array('/cache.git' => '/*', '/cache.hg' => '/*'), if ($io && $io->isDebug()) {
'cache-files-dir' => array('/cache.files' => '/*'), $io->write('Loading config file ' . $file->getPath());
);
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);
}
}
} }
$config->merge(array('config' => $file->read()));
} }
$config->setAuthConfigSource(new JsonConfigSource($file, true));
return $config; return $config;
} }
@ -146,7 +153,7 @@ class Factory
$repos = array(); $repos = array();
if (!$config) { if (!$config) {
$config = static::createConfig(); $config = static::createConfig($io);
} }
if (!$rm) { if (!$rm) {
if (!$io) { if (!$io) {
@ -157,11 +164,14 @@ class Factory
} }
foreach ($config->getRepositories() as $index => $repo) { 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)) { 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'])) { 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; $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
while (isset($repos[$name])) { while (isset($repos[$name])) {
@ -176,16 +186,19 @@ class Factory
/** /**
* Creates a Composer instance * Creates a Composer instance
* *
* @param IOInterface $io IO 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 * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
* read from the default filename * read from the default filename
* @param bool $disablePlugins Whether plugins should not be loaded * @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 \InvalidArgumentException
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
* @return Composer * @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 // load Composer configuration
if (null === $localConfig) { if (null === $localConfig) {
$localConfig = static::getComposerFile(); $localConfig = static::getComposerFile();
@ -197,7 +210,7 @@ class Factory
if (!$file->exists()) { if (!$file->exists()) {
if ($localConfig === './composer.json' || $localConfig === 'composer.json') { 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 { } else {
$message = 'Composer could not find the config file: '.$localConfig; $message = 'Composer could not find the config file: '.$localConfig;
} }
@ -209,26 +222,45 @@ class Factory
$localConfig = $file->read(); $localConfig = $file->read();
} }
// Configuration defaults // Load config and override with local config/auth config
$config = static::createConfig(); $config = static::createConfig($io, $cwd);
$config->merge($localConfig); $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'); $vendorDir = $config->get('vendor-dir');
$binDir = $config->get('bin-dir'); $binDir = $config->get('bin-dir');
// setup process timeout
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
// initialize composer // initialize composer
$composer = new Composer(); $composer = new Composer();
$composer->setConfig($config); $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 // initialize event dispatcher
$dispatcher = new EventDispatcher($composer, $io); $dispatcher = new EventDispatcher($composer, $io);
$composer->setEventDispatcher($dispatcher);
// initialize repository manager // initialize repository manager
$rm = $this->createRepositoryManager($io, $config, $dispatcher); $rm = $this->createRepositoryManager($io, $config, $dispatcher);
$composer->setRepositoryManager($rm);
// load local repository // load local repository
$this->addLocalRepository($rm, $vendorDir); $this->addLocalRepository($rm, $vendorDir);
@ -237,45 +269,47 @@ class Factory
$parser = new VersionParser; $parser = new VersionParser;
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io)); $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
$package = $loader->load($localConfig); $package = $loader->load($localConfig);
$composer->setPackage($package);
// initialize installation manager // initialize installation manager
$im = $this->createInstallationManager(); $im = $this->createInstallationManager();
// Composer composition
$composer->setPackage($package);
$composer->setRepositoryManager($rm);
$composer->setInstallationManager($im); $composer->setInstallationManager($im);
// initialize download manager if ($fullLoad) {
$dm = $this->createDownloadManager($io, $config, $dispatcher); // initialize download manager
$dm = $this->createDownloadManager($io, $config, $dispatcher);
$composer->setDownloadManager($dm);
$composer->setDownloadManager($dm); // initialize autoload generator
$composer->setEventDispatcher($dispatcher); $generator = new AutoloadGenerator($dispatcher, $io);
$composer->setAutoloadGenerator($generator);
// 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();
} }
// purge packages if they have been deleted on the filesystem // add installers to the manager (must happen after download manager is created since they read it out of $composer)
$this->purgePackages($rm, $im); $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 // init locker if possible
if (isset($composerFile)) { if ($fullLoad && isset($composerFile)) {
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
? substr($composerFile, 0, -4).'lock' ? substr($composerFile, 0, -4).'lock'
: $composerFile . '.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); $composer->setLocker($locker);
} }
@ -313,22 +347,26 @@ class Factory
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json'))); $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
} }
/** /**
* @param Config $config * @param Config $config
* @param string $vendorDir * @return Composer|null
*/ */
protected function createGlobalRepository(Config $config, $vendorDir) protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins)
{ {
if ($config->get('home') == $vendorDir) { if (realpath($config->get('home')) === getcwd()) {
return null; return;
} }
$path = $config->get('home').'/vendor/composer/installed.json'; $composer = null;
if (!file_exists($path)) { try {
return null; $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_./'); $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')) { switch ($config->get('preferred-install')) {
case 'dist': case 'dist':
$dm->setPreferDist(true); $dm->setPreferDist(true);
@ -365,6 +403,7 @@ class Factory
$dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache)); $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache));
$dm->setDownloader('rar', new Downloader\RarDownloader($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('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('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache));
$dm->setDownloader('file', new Downloader\FileDownloader($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 * @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 WritableRepositoryInterface $repo repository to purge packages from
* @param Installer\InstallationManager $im * @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) { foreach ($repo->getPackages() as $package) {
if (!$im->isPackageInstalled($repo, $package)) { if (!$im->isPackageInstalled($repo, $package)) {
$repo->removePackage($package); $repo->removePackage($package);
@ -435,10 +476,10 @@ class Factory
} }
/** /**
* @param IOInterface $io IO instance * @param IOInterface $io IO instance
* @param mixed $config either a configuration array or a filename to read from, if null it will read from * @param mixed $config either a configuration array or a filename to read from, if null it will read from
* the default filename * the default filename
* @param bool $disablePlugins Whether plugins should not be loaded * @param bool $disablePlugins Whether plugins should not be loaded
* @return Composer * @return Composer
*/ */
public static function create(IOInterface $io, $config = null, $disablePlugins = false) public static function create(IOInterface $io, $config = null, $disablePlugins = false)

View File

@ -68,5 +68,12 @@ abstract class BaseIO implements IOInterface
$this->setAuthentication($domain, $token, 'x-oauth-basic'); $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']);
}
}
} }
} }

View File

@ -15,6 +15,7 @@ namespace Composer\IO;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Process\ExecutableFinder;
/** /**
* The Input/Output helper. * The Input/Output helper.
@ -95,13 +96,11 @@ class ConsoleIO extends BaseIO
public function write($messages, $newline = true) public function write($messages, $newline = true)
{ {
if (null !== $this->startTime) { if (null !== $this->startTime) {
$messages = (array) $messages; $memoryUsage = memory_get_usage() / 1024 / 1024;
$messages[0] = sprintf( $timeSpent = microtime(true) - $this->startTime;
'[%.1fMB/%.2fs] %s', $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) {
memory_get_usage() / 1024 / 1024, return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
microtime(true) - $this->startTime, }, (array) $messages);
$messages[0]
);
} }
$this->output->write($messages, $newline); $this->output->write($messages, $newline);
$this->lastMessage = join($newline ? "\n" : '', (array) $messages); $this->lastMessage = join($newline ? "\n" : '', (array) $messages);
@ -112,6 +111,14 @@ class ConsoleIO extends BaseIO
*/ */
public function overwrite($messages, $newline = true, $size = null) 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 can be an array, let's convert it to string anyway
$messages = join($newline ? "\n" : '', (array) $messages); $messages = join($newline ? "\n" : '', (array) $messages);
@ -171,6 +178,18 @@ class ConsoleIO extends BaseIO
{ {
// handle windows // handle windows
if (defined('PHP_WINDOWS_VERSION_BUILD')) { 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'; $exe = __DIR__.'\\hiddeninput.exe';
// handle code running from a phar // handle code running from a phar

View File

@ -26,11 +26,12 @@ use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager; use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
use Composer\Config; use Composer\Installer\InstallerEvents;
use Composer\Installer\NoopInstaller; use Composer\Installer\NoopInstaller;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Package\Locker; use Composer\Package\Locker;
@ -104,7 +105,16 @@ class Installer
protected $dryRun = false; protected $dryRun = false;
protected $verbose = false; protected $verbose = false;
protected $update = false; protected $update = false;
protected $dumpAutoloader = true;
protected $runScripts = 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 $updateWhitelist = null;
protected $whitelistDependencies = false; protected $whitelistDependencies = false;
@ -148,9 +158,14 @@ class Installer
* Run installation (or update) * Run installation (or update)
* *
* @return int 0 on success or a positive error code on failure * @return int 0 on success or a positive error code on failure
*
* @throws \Exception
*/ */
public function run() public function run()
{ {
gc_collect_cycles();
gc_disable();
if ($this->dryRun) { if ($this->dryRun) {
$this->verbose = true; $this->verbose = true;
$this->runScripts = false; $this->runScripts = false;
@ -218,16 +233,37 @@ class Installer
} }
$this->installationManager->notifyInstalls(); $this->installationManager->notifyInstalls();
// output suggestions // output suggestions if we're in dev mode
foreach ($this->suggestedPackages as $suggestion) { if ($this->devMode) {
$target = $suggestion['target']; foreach ($this->suggestedPackages as $suggestion) {
foreach ($installedRepo->getPackages() as $package) { $target = $suggestion['target'];
if (in_array($target, $package->getNames())) { foreach ($installedRepo->getPackages() as $package) {
continue 2; 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) { if (!$this->dryRun) {
@ -252,8 +288,10 @@ class Installer
$request->install($link->getTarget(), $link->getConstraint()); $request->install($link->getTarget(), $link->getConstraint());
} }
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request);
$solver = new Solver($policy, $pool, $installedRepo); $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) { foreach ($ops as $op) {
if ($op->getJobType() === 'uninstall') { if ($op->getJobType() === 'uninstall') {
$devPackages[] = $op->getPackage(); $devPackages[] = $op->getPackage();
@ -271,27 +309,37 @@ class Installer
$platformDevReqs, $platformDevReqs,
$aliases, $aliases,
$this->package->getMinimumStability(), $this->package->getMinimumStability(),
$this->package->getStabilityFlags() $this->package->getStabilityFlags(),
$this->preferStable || $this->package->getPreferStable(),
$this->preferLowest
); );
if ($updatedLock) { if ($updatedLock) {
$this->io->write('<info>Writing lock file</info>'); $this->io->write('<info>Writing lock file</info>');
} }
} }
// write autoloader if ($this->dumpAutoloader) {
if ($this->optimizeAutoloader) { // write autoloader
$this->io->write('<info>Generating optimized autoload files</info>'); if ($this->optimizeAutoloader) {
} else { $this->io->write('<info>Generating optimized autoload files</info>');
$this->io->write('<info>Generating 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) { if ($this->runScripts) {
// dispatch post event // dispatch post event
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
$this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode); $this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode);
} }
$vendorDir = $this->config->get('vendor-dir');
if (is_dir($vendorDir)) {
touch($vendorDir);
}
} }
return 0; return 0;
@ -361,7 +409,7 @@ class Installer
} }
if ($this->update) { 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(); $request->updateAll();
@ -412,7 +460,7 @@ class Installer
} }
} }
} elseif ($installFromLock) { } 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()) { 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>'); $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()); $request->install($link->getTarget(), $link->getConstraint());
} }
} else { } 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) { if ($withDevReqs) {
$links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); $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'); $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
// solve dependencies // solve dependencies
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $policy, $pool, $installedRepo, $request);
$solver = new Solver($policy, $pool, $installedRepo); $solver = new Solver($policy, $pool, $installedRepo);
try { 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) { } catch (SolverProblemsException $e) {
$this->io->write('<error>Your requirements could not be resolved to an installable set of packages.</error>'); $this->io->write('<error>Your requirements could not be resolved to an installable set of packages.</error>');
$this->io->write($e->getMessage()); $this->io->write($e->getMessage());
@ -468,6 +518,7 @@ class Installer
} }
$operations = $this->movePluginsToFront($operations); $operations = $this->movePluginsToFront($operations);
$operations = $this->moveUninstallsToFront($operations);
foreach ($operations as $operation) { foreach ($operations as $operation) {
// collect suggestions // 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 // not installing from lock, force dev packages' references if they're in root package refs
if (!$installFromLock) { if (!$installFromLock) {
$package = null; $package = null;
@ -501,6 +547,23 @@ class Installer
$package->setDistReference($references[$package->getName()]); $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 // output non-alias ops in dry run, output alias ops in debug verbosity
@ -520,11 +583,11 @@ class Installer
if ($reason instanceof Rule) { if ($reason instanceof Rule) {
switch ($reason->getReason()) { switch ($reason->getReason()) {
case Rule::RULE_JOB_INSTALL: 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(''); $this->io->write('');
break; break;
case Rule::RULE_PACKAGE_REQUIRES: case Rule::RULE_PACKAGE_REQUIRES:
$this->io->write(' REASON: '.$reason->getPrettyString()); $this->io->write(' REASON: '.$reason->getPrettyString($pool));
$this->io->write(''); $this->io->write('');
break; break;
} }
@ -569,15 +632,45 @@ class Installer
continue; continue;
} }
if ($package->getRequires() === array() && ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer')) { if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') {
$installerOps[] = $op; // ignore requirements to platform or composer-plugin-api
unset($operations[$idx]); $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); 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) private function createPool($withDevReqs)
{ {
$minimumStability = $this->package->getMinimumStability(); $minimumStability = $this->package->getMinimumStability();
@ -594,6 +687,10 @@ class Installer
} }
$rootConstraints = array(); $rootConstraints = array();
foreach ($requires as $req => $constraint) { 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(); $rootConstraints[$req] = $constraint->getConstraint();
} }
@ -602,7 +699,22 @@ class Installer
private function createPolicy() 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) private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
@ -631,7 +743,7 @@ class Installer
|| !isset($provided[$package->getName()]) || !isset($provided[$package->getName()])
|| !$provided[$package->getName()]->getConstraint()->matches($constraint) || !$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) { foreach ($this->updateWhitelist as $whiteListedPattern => $void) {
$cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); $patternRegexp = $this->packageNameToRegexp($whiteListedPattern);
if (preg_match($patternRegexp, $package->getName())) {
if (preg_match("{^".$cleanedWhiteListedPattern."$}i", $package->getName())) {
return true; return true;
} }
} }
@ -795,6 +906,19 @@ class Installer
return false; 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) private function extractPlatformRequirements($links)
{ {
$platformReqs = array(); $platformReqs = array();
@ -844,11 +968,27 @@ class Installer
$seen = array(); $seen = array();
$rootRequiredPackageNames = array_keys($rootRequires);
foreach ($this->updateWhitelist as $packageName => $void) { foreach ($this->updateWhitelist as $packageName => $void) {
$packageQueue = new \SplQueue; $packageQueue = new \SplQueue;
$depPackages = $pool->whatProvides($packageName); $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>'); $this->io->write('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
} }
@ -951,6 +1091,16 @@ class Installer
return $this; return $this;
} }
/**
* Checks, if this is a dry run (simulation mode).
*
* @return bool
*/
public function isDryRun()
{
return $this->dryRun;
}
/** /**
* prefer source installation * prefer source installation
* *
@ -1016,6 +1166,21 @@ class Installer
return $this; 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 * set whether to run scripts or not
* *
@ -1055,6 +1220,29 @@ class Installer
return $this; 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 * restrict the update operation to a few packages, all other packages
* that are already installed will be kept at their current version * 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? * Should dependencies of whitelisted packages be updated recursively?
* *
* @param boolean $updateDependencies * @param boolean $updateDependencies
* @return Installer * @return Installer
*/ */
public function setWhitelistDependencies($updateDependencies = true) public function setWhitelistDependencies($updateDependencies = true)
@ -1082,6 +1270,32 @@ class Installer
return $this; 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. * Disables plugins.
* *

View File

@ -14,7 +14,6 @@ namespace Composer\Installer;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Plugin\PluginInstaller;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\OperationInterface;

View File

@ -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;
}
}

View File

@ -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