diff --git a/.travis.yml b/.travis.yml
index a897f3cee..5355d5be5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,11 +5,18 @@ php:
- 5.3
- 5.4
- 5.5
+ - hhvm
-before_script:
- - echo '' > ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
+matrix:
+ allow_failures:
+ - php: hhvm
+
+before_script:
+ - sudo apt-get install parallel
+ - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
- composer install --dev --prefer-source
- git config --global user.name travis-ci
- git config --global user.email travis@example.com
-script: ./vendor/bin/phpunit -c tests/complete.phpunit.xml
+script:
+ - ls -d tests/Composer/Test/* | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml {};' || exit 1
diff --git a/README.md b/README.md
index 90b3dac90..e0e4e73b0 100644
--- a/README.md
+++ b/README.md
@@ -16,10 +16,10 @@ Installation / Usage
$ curl -sS https://getcomposer.org/installer | php
```
-
2. Create a composer.json defining your dependencies. Note that this example is
a short version for applications that are not meant to be published as packages
-themselves. To create libraries/packages please read the [guidelines](https://packagist.org/about).
+themselves. To create libraries/packages please read the
+[documentation](http://getcomposer.org/doc/02-libraries.md).
``` json
{
@@ -47,24 +47,7 @@ You can now run Composer by executing the `bin/composer` script: `php /path/to/c
Global installation of Composer (manual)
----------------------------------------
-Since Composer works with the current working directory it is possible to install it
-in a system wide way.
-
-1. Change into a directory in your path like `cd /usr/local/bin`
-2. Get Composer `curl -sS https://getcomposer.org/installer | php`
-3. Make the phar executable `chmod a+x composer.phar`
-4. Change into a project directory `cd /path/to/my/project`
-5. Use Composer as you normally would `composer.phar install`
-6. Optionally you can rename the composer.phar to composer to make it easier
-
-Global installation of Composer (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 yet: `brew tap josegonzalez/homebrew-php`
-2. Run `brew install josegonzalez/php/composer`.
-3. Use Composer with the `composer` command.
+Follow instructions [in the documentation](http://getcomposer.org/doc/00-intro.md#globally)
Updating Composer
-----------------
diff --git a/composer.json b/composer.json
index f0d4ee56e..33246e7fc 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
"seld/jsonlint": "1.*",
"symfony/console": "~2.3",
"symfony/finder": "~2.2",
- "symfony/process": "~2.1"
+ "symfony/process": "~2.1@dev"
},
"require-dev": {
"phpunit/phpunit": "~3.7.10"
diff --git a/composer.lock b/composer.lock
index cae3bad07..651ce7887 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
- "hash": "370b764a9317165e8ea7a2e1623e031b",
+ "hash": "4494d3567c8c22b1adaded932825b969",
"packages": [
{
"name": "justinrainbow/json-schema",
@@ -32,16 +32,16 @@
},
{
"name": "seld/jsonlint",
- "version": "1.1.1",
+ "version": "1.1.2",
"source": {
"type": "git",
- "url": "http://github.com/Seldaek/jsonlint",
- "reference": "1.1.1"
+ "url": "https://github.com/Seldaek/jsonlint.git",
+ "reference": "7cd4c4965e17e6e4c07f26d566619a4c76f8c672"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1.1.1",
- "reference": "1.1.1",
+ "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/7cd4c4965e17e6e4c07f26d566619a4c76f8c672",
+ "reference": "7cd4c4965e17e6e4c07f26d566619a4c76f8c672",
"shasum": ""
},
"require": {
@@ -75,21 +75,21 @@
"parser",
"validator"
],
- "time": "2013-02-11 23:03:12"
+ "time": "2013-11-04 15:41:11"
},
{
"name": "symfony/console",
- "version": "v2.3.3",
+ "version": "v2.3.7",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "v2.3.3"
+ "reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/v2.3.3",
- "reference": "v2.3.3",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/00848d3e13cf512e77c7498c2b3b0192f61f4b18",
+ "reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"shasum": ""
},
"require": {
@@ -128,21 +128,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
- "time": "2013-07-21 12:12:18"
+ "time": "2013-11-13 21:27:40"
},
{
"name": "symfony/finder",
- "version": "v2.3.3",
+ "version": "v2.3.7",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
- "reference": "v2.3.3"
+ "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Finder/zipball/v2.3.3",
- "reference": "v2.3.3",
+ "url": "https://api.github.com/repos/symfony/Finder/zipball/a175521f680b178e63c5d0ab87c6b046c0990c3f",
+ "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f",
"shasum": ""
},
"require": {
@@ -175,21 +175,21 @@
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
- "time": "2013-07-21 12:12:18"
+ "time": "2013-09-19 09:45:20"
},
{
"name": "symfony/process",
- "version": "v2.3.3",
+ "version": "dev-master",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
- "reference": "v2.3.3"
+ "reference": "88ccdd7a1fb67a23fe81355c66a09d6908ebcd80"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Process/zipball/v2.3.3",
- "reference": "v2.3.3",
+ "url": "https://api.github.com/repos/symfony/Process/zipball/88ccdd7a1fb67a23fe81355c66a09d6908ebcd80",
+ "reference": "88ccdd7a1fb67a23fe81355c66a09d6908ebcd80",
"shasum": ""
},
"require": {
@@ -198,7 +198,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.3-dev"
+ "dev-master": "2.4-dev"
}
},
"autoload": {
@@ -222,22 +222,22 @@
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
- "time": "2013-08-02 21:51:01"
+ "time": "2013-11-09 12:03:12"
}
],
"packages-dev": [
{
"name": "phpunit/php-code-coverage",
- "version": "1.2.12",
+ "version": "1.2.13",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "1.2.12"
+ "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12",
- "reference": "1.2.12",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/466e7cd2554b4e264c9e3f31216d25ac0e5f3d94",
+ "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94",
"shasum": ""
},
"require": {
@@ -285,20 +285,20 @@
"testing",
"xunit"
],
- "time": "2013-07-06 06:26:16"
+ "time": "2013-09-10 08:14:32"
},
{
"name": "phpunit/php-file-iterator",
- "version": "1.3.3",
+ "version": "1.3.4",
"source": {
"type": "git",
- "url": "git://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "1.3.3"
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb"
},
"dist": {
"type": "zip",
- "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3",
- "reference": "1.3.3",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb",
+ "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb",
"shasum": ""
},
"require": {
@@ -325,12 +325,12 @@
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
- "homepage": "http://www.phpunit.de/",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
- "time": "2012-10-11 04:44:38"
+ "time": "2013-10-10 15:34:57"
},
{
"name": "phpunit/php-text-template",
@@ -382,12 +382,12 @@
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "1.0.5"
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1.0.5",
- "reference": "1.0.5",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+ "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
"shasum": ""
},
"require": {
@@ -422,16 +422,16 @@
},
{
"name": "phpunit/php-token-stream",
- "version": "1.2.0",
+ "version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "1.2.0"
+ "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.2.0",
- "reference": "1.2.0",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/5220af2a7929aa35cf663d97c89ad3d50cf5fa3e",
+ "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e",
"shasum": ""
},
"require": {
@@ -468,20 +468,20 @@
"keywords": [
"tokenizer"
],
- "time": "2013-08-04 05:57:48"
+ "time": "2013-09-13 04:58:23"
},
{
"name": "phpunit/phpunit",
- "version": "3.7.24",
+ "version": "3.7.28",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "3.7.24"
+ "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.24",
- "reference": "3.7.24",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d",
+ "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d",
"shasum": ""
},
"require": {
@@ -542,7 +542,7 @@
"testing",
"xunit"
],
- "time": "2013-08-09 06:58:24"
+ "time": "2013-10-17 07:27:40"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -595,17 +595,17 @@
},
{
"name": "symfony/yaml",
- "version": "v2.3.3",
+ "version": "v2.3.7",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
- "reference": "v2.3.3"
+ "reference": "c1bda5b459d792cb253de12c65beba3040163b2b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.3",
- "reference": "v2.3.3",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/c1bda5b459d792cb253de12c65beba3040163b2b",
+ "reference": "c1bda5b459d792cb253de12c65beba3040163b2b",
"shasum": ""
},
"require": {
@@ -638,16 +638,16 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
- "time": "2013-07-21 12:12:18"
+ "time": "2013-10-17 11:48:01"
}
],
"aliases": [
],
"minimum-stability": "stable",
- "stability-flags": [
-
- ],
+ "stability-flags": {
+ "symfony/process": 20
+ },
"platform": {
"php": ">=5.3.2"
},
diff --git a/doc/00-intro.md b/doc/00-intro.md
index 46ec8bba8..7b62fee16 100644
--- a/doc/00-intro.md
+++ b/doc/00-intro.md
@@ -91,6 +91,18 @@ You can run these commands to easily access `composer` from anywhere on your sys
Then, just run `composer` in order to run Composer instead of `php composer.phar`.
+#### Globally (on OSX via homebrew)
+
+Composer is part of the homebrew-php project.
+
+1. Tap the homebrew-php repository into your brew installation if you haven't done
+ so yet: `brew tap josegonzalez/homebrew-php`
+2. Run `brew install josegonzalez/php/composer`.
+3. Use Composer with the `composer` command.
+
+> **Note:** If you receive an error saying PHP53 or higher is missing use this command to install php
+> `brew install php53-intl`
+
## Installation - Windows
### Using the Installer
@@ -108,7 +120,7 @@ composer.phar:
C:\Users\username>cd C:\bin
C:\bin>php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
-
+
> **Note:** If the above fails due to file_get_contents, use the `http` url or enable php_openssl.dll in php.ini
Create a new `composer.bat` file alongside `composer.phar`:
diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md
index bc4a88c1e..c353b8b6f 100644
--- a/doc/01-basic-usage.md
+++ b/doc/01-basic-usage.md
@@ -58,31 +58,31 @@ smaller decoupled parts.
### Package Versions
-We are requiring version `1.0.*` of monolog. This means any version in the `1.0`
-development branch. It would match `1.0.0`, `1.0.2` or `1.0.20`.
+In the previous example we were requiring version `1.0.*` of monolog. This
+means any version in the `1.0` development branch. It would match `1.0.0`,
+`1.0.2` or `1.0.20`.
Version constraints can be specified in a few different ways.
-* **Exact version:** You can specify the exact version of a package, for
- example `1.0.2`.
+Name | Example | Description
+-------------- | --------------------- | -----------
+Exact version | `1.0.2` | You can specify the exact version of a package.
+Range | `>=1.0` `>=1.0,<2.0` `>=1.0,<1.1 | >=1.2` | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`.
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**.
AND has higher precedence than OR.
+Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1`.
+Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2,<2.0`. For more details, read the next section below.
-* **Range:** By using comparison operators you can specify ranges of valid
- versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. An example range
- would be `>=1.0`. You can define multiple ranges, separated by a comma:
- `>=1.0,<2.0`.
+### Next Significant Release (Tilde Operator)
-* **Wildcard:** You can specify a pattern with a `*` wildcard. `1.0.*` is the
- equivalent of `>=1.0,<1.1`.
+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
+it is mostly useful for projects respecting [semantic
+versioning](http://semver.org/). A common usage would be to mark the minimum
+minor version you depend on, like `~1.2` (which allows anything up to, but not
+including, 2.0). Since in theory there should be no backwards compatibility
+breaks until 2.0, that works well. Another way of looking at it is that using
+`~` specifies a minimum version, but allows the last digit specified to go up.
-* **Next Significant Release (Tilde Operator):** 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 it is mostly useful for projects
- respecting [semantic versioning](http://semver.org/). A common usage would be
- to mark the minimum minor version you depend on, like `~1.2` (which allows
- anything up to, but not including, 2.0). Since in theory there should be no
- backwards compatibility breaks until 2.0, that works well. Another way of
- looking at it is that using `~` specifies a minimum version, but allows the
- last digit specified to go up.
+### Stability
By default only stable releases are taken into consideration. If you would like
to also get RC, beta, alpha or dev versions of your dependencies you can do
diff --git a/doc/02-libraries.md b/doc/02-libraries.md
index a2cee3a97..27428064f 100644
--- a/doc/02-libraries.md
+++ b/doc/02-libraries.md
@@ -70,8 +70,8 @@ you can just add a `version` field:
For every tag that looks like a version, a package version of that tag will be
created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix
-of `-dev`, `-patch`, `-alpha`, `-beta` or `-RC`. The patch, alpha, beta and
-RC suffixes can also be followed by a number.
+of `-patch`, `-alpha`, `-beta` or `-RC`. The suffixes can also be followed by
+a number.
Here are a few examples of valid tag names:
@@ -82,6 +82,10 @@ Here are a few examples of valid tag names:
v2.0.0-alpha
v2.0.4-p1
+> **Note:** Even if your tag is prefixed with `v`, a [version constraint](01-basic-usage.md#package-versions)
+> in a `require` statement has to be specified without prefix
+> (e.g. tag `v1.0.0` will result in version `1.0.0`).
+
### Branches
For every branch, a package development version will be created. If the branch
@@ -98,7 +102,9 @@ Here are some examples of version branch names:
1.0 (equals 1.0.x)
1.1.x
-> **Note:** When you install a dev version, it will install it from source.
+> **Note:** When you install a development version, it will be automatically
+> pulled from its `source`. See the [`install`](03-cli.md#install) command
+> for more details.
### Aliases
diff --git a/doc/03-cli.md b/doc/03-cli.md
index be0b252e8..84539b4da 100644
--- a/doc/03-cli.md
+++ b/doc/03-cli.md
@@ -82,7 +82,7 @@ resolution.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`.
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
-* **--no-custom-installers:** Disables custom installers.
+* **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters.
* **--optimize-autoloader (-o):** Convert PSR-0 autoloading to classmap to get a faster
@@ -115,14 +115,16 @@ You can also use wildcards to update a bunch of packages at once:
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`.
* **--no-scripts:** Skips execution of scripts defined in `composer.json`.
-* **--no-custom-installers:** Disables custom installers.
+* **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters.
* **--optimize-autoloader (-o):** Convert PSR-0 autoloading to classmap to get a faster
autoloader. This is recommended especially for production, but can take
a bit of time to run so it is currently not done by default.
* **--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.
+ So all packages with their dependencies are updated recursively.
## require
@@ -151,7 +153,7 @@ to the command.
## global
The global command allows you to run other commands like `install`, `require`
-or `update` as if you were running them from the [COMPOSER_HOME](#COMPOSER_HOME)
+or `update` as if you were running them from the [COMPOSER_HOME](#composer-home)
directory.
This can be used to install CLI utilities globally and if you add
@@ -267,11 +269,20 @@ command. It will replace your `composer.phar` with the latest version.
$ php composer.phar self-update
+If you would like to instead update to a specific release simply specify it:
+
+ $ composer self-update 1.0.0-alpha7
+
If you have installed composer for your entire system (see [global installation](00-intro.md#globally)),
-you have to run the command with `root` privileges
+you may have to run the command with `root` privileges
$ sudo composer self-update
+### Options
+
+* **--rollback (-r):** Rollback to the last version you had installed.
+* **--clean-backups:** Delete old backups during an update. This makes the current version of composer the only backup available after the update.
+
## config
The `config` command allows you to edit some basic composer settings in either
@@ -330,7 +341,7 @@ provide a version as third argument, otherwise the latest version is used.
If the directory does not currently exist, it will be created during installation.
- php composer.phar create-project doctrine/orm path 2.2.0
+ php composer.phar create-project doctrine/orm path 2.2.*
It is also possible to run the command without params in a directory with an
existing `composer.json` file to bootstrap a project.
@@ -346,7 +357,8 @@ By default the command checks for the packages on packagist.org.
* **--prefer-source:** Install packages from `source` when available.
* **--prefer-dist:** Install packages from `dist` when available.
* **--dev:** Install packages listed in `require-dev`.
-* **--no-custom-installers:** Disables custom installers.
+* **--no-install:** Disables installation of the vendors.
+* **--no-plugins:** Disables plugins.
* **--no-scripts:** Disables the execution of the scripts defined in the root
package.
* **--no-progress:** Removes the progress display that can mess with some
@@ -392,6 +404,20 @@ problems.
$ php composer.phar diagnose
+## archive
+
+This command is used to generate a zip/tar archive for a given package in a
+given version. It can also be used to archive your entire project without
+excluded/ignored files.
+
+ $ php composer.phar archive vendor/package 2.0.21 --format=zip
+
+### Options
+
+* **--format (-f):** Format of the resulting archive: tar or zip (default:
+ "tar")
+* **--dir:** Write the archive to this directory (default: ".")
+
## help
To get more information about a certain command, just use `help`.
diff --git a/doc/04-schema.md b/doc/04-schema.md
index dc8360cd7..56e787110 100644
--- a/doc/04-schema.md
+++ b/doc/04-schema.md
@@ -86,7 +86,7 @@ that needs some special logic, you can define a custom type. This could be a
all be specific to certain projects, and they will need to provide an
installer capable of installing packages of that type.
-Out of the box, composer supports three types:
+Out of the box, composer supports four types:
- **library:** This is the default. It will simply copy the files to `vendor`.
- **project:** This denotes a project rather than a library. For example
@@ -99,7 +99,7 @@ Out of the box, composer supports three types:
their installation, but contains no files and will not write anything to the
filesystem. As such, it does not require a dist or source key to be
installable.
-- **composer-installer:** A package of type `composer-installer` provides an
+- **composer-plugin:** A package of type `composer-plugin` may provide an
installer for other packages that have a custom type. Read more in the
[dedicated article](articles/custom-installers.md).
@@ -263,7 +263,7 @@ All links are optional fields.
These allow you to further restrict or expand the stability of a package beyond
the scope of the [minimum-stability](#minimum-stability) setting. You can apply
them to a constraint, or just apply them to an empty constraint if you want to
-allow unstable packages of a dependency's dependency for example.
+allow unstable packages of a dependency for example.
Example:
@@ -274,13 +274,22 @@ Example:
}
}
+If one of your dependencies has a dependency on an unstable package you need to
+explicitly require it as well, along with its sufficient stability flag.
+
+Example:
+
+ {
+ "require": {
+ "doctrine/doctrine-fixtures-bundle": "dev-master",
+ "doctrine/data-fixtures": "@dev"
+ }
+ }
+
`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
when you run update. These only work if you explicitly require a dev version
-and append the reference with `#[`. Note that while this is convenient at
-times, it should not really be how you use packages in the long term. You
-should always try to switch to tagged releases as soon as you can, especially
-if the project you work on will not be touched for a while.
+and append the reference with `#][`.
Example:
@@ -291,8 +300,15 @@ Example:
}
}
-It is possible to inline-alias a package constraint so that it matches a
-constraint that it otherwise would not. For more information [see the
+> **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
+> composer.json metadata will still be read from the branch name you specify
+> before the hash. Because of that in some cases it will not be a practical
+> workaround, and you should always try to switch to tagged releases as soon
+> as you can.
+
+It is also possible to inline-alias a package constraint so that it matches
+a constraint that it otherwise would not. For more information [see the
aliases article](articles/aliases.md).
#### require
@@ -640,6 +656,13 @@ The following options are supported:
dist (zip, tar, ..) packages that it downloads. When the garbage collection
is periodically ran, this is the maximum size the cache will be able to use.
Older (less used) files will be removed first until the cache fits.
+* **prepend-autoloader:** Defaults to `true`. If false, the composer autoloader
+ will not be prepended to existing autoloaders. This is sometimes required to fix
+ interoperability issues with other autoloaders.
+* **autoloader-suffix:** Defaults to `null`. String to be used as a suffix for
+ the generated Composer autoloader. When null a random one will be generated.
+* **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
+ github mode. This is used for GitHub Enterprise setups.
* **notify-on-install:** Defaults to `true`. Composer allows repositories to
define a notification URL, so that they get notified whenever a package from
that repository is installed. This option allows you to disable that behaviour.
diff --git a/doc/05-repositories.md b/doc/05-repositories.md
index dab0abb4d..ae947ea49 100644
--- a/doc/05-repositories.md
+++ b/doc/05-repositories.md
@@ -463,8 +463,8 @@ there are some use cases for hosting your own repository.
might want to keep them separate to packagist. An example of this would be
wordpress plugins.
-When hosting your own package repository it is recommended to use a `composer`
-one. This is type that is native to composer and yields the best performance.
+For hosting your own packages, a native `composer` type of repository is
+recommended, which provides the best performance.
There are a few tools that can help you create a `composer` repository.
@@ -523,7 +523,7 @@ private packages:
Each zip artifact is just a ZIP archive with `composer.json` in root folder:
- $ tar -tf acme-corp-parser-10.3.5.zip
+ $ unzip -l acme-corp-parser-10.3.5.zip
composer.json
...
diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md
index efd7cc6cb..26a9c46ab 100644
--- a/doc/articles/aliases.md
+++ b/doc/articles/aliases.md
@@ -59,8 +59,9 @@ is a dependency of your local project.
For this reason, you can alias packages in your `require` and `require-dev`
fields. Let's say you found a bug in the `monolog/monolog` package. You cloned
-Monolog on GitHub and fixed the issue in a branch named `bugfix`. Now you want
-to install that version of monolog in your local project.
+[Monolog](https://github.com/Seldaek/monolog) on GitHub and fixed the issue in
+a branch named `bugfix`. Now you want to install that version of monolog in your
+local project.
You are using `symfony/monolog-bundle` which requires `monolog/monolog` version
`1.*`. So you need your `dev-bugfix` to match that constraint.
diff --git a/doc/articles/custom-installers.md b/doc/articles/custom-installers.md
index 1eb55436e..feeebe52c 100644
--- a/doc/articles/custom-installers.md
+++ b/doc/articles/custom-installers.md
@@ -29,8 +29,8 @@ An example use-case would be:
> phpDocumentor features Templates that need to be installed outside of the
> default /vendor folder structure. As such they have chosen to adopt the
-> `phpdocumentor-template` [type][1] and create a Custom Installer to send
-> these templates to the correct folder.
+> `phpdocumentor-template` [type][1] and create a plugin providing the Custom
+> Installer to send these templates to the correct folder.
An example composer.json of such a template package would be:
@@ -38,59 +38,85 @@ An example composer.json of such a template package would be:
"name": "phpdocumentor/template-responsive",
"type": "phpdocumentor-template",
"require": {
- "phpdocumentor/template-installer": "*"
+ "phpdocumentor/template-installer-plugin": "*"
}
}
> **IMPORTANT**: to make sure that the template installer is present at the
> time the template package is installed, template packages should require
-> the installer package.
+> the plugin package.
## Creating an Installer
A Custom Installer is defined as a class that implements the
-[`Composer\Installer\InstallerInterface`][3] and is contained in a Composer
-package that has the [type][1] `composer-installer`.
+[`Composer\Installer\InstallerInterface`][3] and is usually distributed in a
+Composer Plugin.
-A basic Installer would thus compose of two files:
+A basic Installer Plugin would thus compose of three files:
1. the package file: composer.json
-2. The Installer class, e.g.: `My\Project\Composer\Installer.php`, containing a class that implements `Composer\Installer\InstallerInterface`.
+2. The Plugin class, e.g.: `My\Project\Composer\Plugin.php`, containing a class that implements `Composer\Plugin\PluginInterface`.
+3. The Installer class, e.g.: `My\Project\Composer\Installer.php`, containing a class that implements `Composer\Installer\InstallerInterface`.
### composer.json
The package file is the same as any other package file but with the following
requirements:
-1. the [type][1] attribute must be `composer-installer`.
+1. the [type][1] attribute must be `composer-plugin`.
2. the [extra][2] attribute must contain an element `class` defining the
- class name of the installer (including namespace). If a package contains
- multiple installers this can be array of class names.
+ class name of the plugin (including namespace). If a package contains
+ multiple plugins this can be array of class names.
Example:
{
- "name": "phpdocumentor/template-installer",
- "type": "composer-installer",
+ "name": "phpdocumentor/template-installer-plugin",
+ "type": "composer-plugin",
"license": "MIT",
"autoload": {
"psr-0": {"phpDocumentor\\Composer": "src/"}
},
"extra": {
- "class": "phpDocumentor\\Composer\\TemplateInstaller"
+ "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
+ },
+ "require": {
+ "composer-plugin-api": "1.0.0"
+ }
+ }
+
+### The Plugin class
+
+The class defining the Composer plugin must implement the
+[`Composer\Plugin\PluginInterface`][3]. It can then register the Custom
+Installer in its `activate()` method.
+
+The class may be placed in any location and have any name, as long as it is
+autoloadable and matches the `extra.class` element in the package definition.
+
+Example:
+
+ namespace phpDocumentor\Composer;
+
+ use Composer\Composer;
+ use Composer\IO\IOInterface;
+ use Composer\Plugin\PluginInterface;
+
+ class TemplateInstallerPlugin implements PluginInterface
+ {
+ public function activate(Composer $composer, IOInterface $io)
+ {
+ $installer = new TemplateInstaller($io, $composer);
+ $composer->getInstallationManager()->addInstaller($installer);
}
}
### The Custom Installer class
The class that executes the custom installation should implement the
-[`Composer\Installer\InstallerInterface`][3] (or extend another installer that
-implements that interface).
-
-The class may be placed in any location and have any name, as long as it is
-autoloadable and matches the `extra.class` element in the package definition.
-It will also define the [type][1] string as it will be recognized by packages
-that will use this installer in the `supports()` method.
+[`Composer\Installer\InstallerInterface`][4] (or extend another installer that
+implements that interface). It defines the [type][1] string as it will be
+recognized by packages that will use this installer in the `supports()` method.
> **NOTE**: _choose your [type][1] name carefully, it is recommended to follow
> the format: `vendor-type`_. For example: `phpdocumentor-template`.
@@ -122,7 +148,7 @@ Example:
/**
* {@inheritDoc}
*/
- public function getInstallPath(PackageInterface $package)
+ public function getPackageBasePath(PackageInterface $package)
{
$prefix = substr($package->getPrettyName(), 0, 23);
if ('phpdocumentor/template-' !== $prefix) {
@@ -146,7 +172,7 @@ Example:
}
The example demonstrates that it is quite simple to extend the
-[`Composer\Installer\LibraryInstaller`][4] 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
different installation path.
@@ -155,5 +181,6 @@ different installation path.
[1]: ../04-schema.md#type
[2]: ../04-schema.md#extra
-[3]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php
-[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php
+[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php
+[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php
+[5]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php
diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md
index 9702b55bd..0219f8108 100644
--- a/doc/articles/handling-private-packages-with-satis.md
+++ b/doc/articles/handling-private-packages-with-satis.md
@@ -124,7 +124,7 @@ Example using HTTP over SSL using a client certificate:
"url": "https://example.org",
"options": {
"ssl": {
- "local_cert": "/home/composer/.ssl/composer.pem",
+ "local_cert": "/home/composer/.ssl/composer.pem"
}
}
}
@@ -170,3 +170,19 @@ bucket or on a CDN host. A CDN would drastically improve download times and ther
Example: A `prefix-url` of `http://my-bucket.s3.amazonaws.com` (and `directory` set to `dist`) creates download URLs
which look like the following: `http://my-bucket.s3.amazonaws.com/dist/vendor-package-version-ref.zip`.
+
+
+### Resolving dependencies
+
+It is possible to make satis automatically resolve and add all dependencies for your projects. This can be used
+with the Downloads functionality to have a complete local mirror of packages. Just add the following
+to your `satis.json`:
+
+```
+{
+ "require-dependencies": true
+}
+```
+
+When searching for packages, satis will attempt to resolve all the required packages from the listed repositories.
+Therefore, if you are requiring a package from Packagist, you will need to define it in your `satis.json`.
diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md
new file mode 100644
index 000000000..75706f711
--- /dev/null
+++ b/doc/articles/plugins.md
@@ -0,0 +1,150 @@
+
+
+# Setting up and using plugins
+
+## Synopsis
+
+You may wish to alter or expand Composer's functionality with your own. For
+example if your environment poses special requirements on the behaviour of
+Composer which do not apply to the majority of its users or if you wish to
+accomplish something with composer in a way that is not desired by most users.
+
+In these cases you could consider creating a plugin to handle your
+specific logic.
+
+## Creating a Plugin
+
+A plugin is a regular composer package which ships its code as part of the
+package and may also depend on further packages.
+
+### Plugin Package
+
+The package file is the same as any other package file but with the following
+requirements:
+
+1. the [type][1] attribute must be `composer-plugin`.
+2. the [extra][2] attribute must contain an element `class` defining the
+ class name of the plugin (including namespace). If a package contains
+ multiple plugins this can be array of class names.
+
+Additionally you must require the special package called `composer-plugin-api`
+to define which composer API versions your plugin is compatible with. The
+current composer plugin API version is 1.0.0.
+
+For example
+
+ {
+ "name": "my/plugin-package",
+ "type": "composer-plugin",
+ "require": {
+ "composer-plugin-api": "1.0.0"
+ }
+ }
+
+### Plugin Class
+
+Every plugin has to supply a class which implements the
+[`Composer\Plugin\PluginInterface`][3]. The `activate()` method of the plugin
+is called after the plugin is loaded and receives an instance of
+[`Composer\Composer`][4] as well as an instance of
+[`Composer\IO\IOInterface`][5]. Using these two objects all configuration can
+be read and all internal objects and state can be manipulated as desired.
+
+Example:
+
+ namespace phpDocumentor\Composer;
+
+ use Composer\Composer;
+ use Composer\IO\IOInterface;
+ use Composer\Plugin\PluginInterface;
+
+ class TemplateInstallerPlugin implements PluginInterface
+ {
+ public function activate(Composer $composer, IOInterface $io)
+ {
+ $installer = new TemplateInstaller($io, $composer);
+ $composer->getInstallationManager()->addInstaller($installer);
+ }
+ }
+
+## Event Handler
+
+Furthermore plugins may implement the
+[`Composer\EventDispatcher\EventSubscriberInterface`][6] in order to have its
+event handlers automatically registered with the `EventDispatcher` when the
+plugin is loaded.
+
+The events available for plugins are:
+
+* **COMMAND**, is called at the beginning of all commands that load plugins.
+ It provides you with access to the input and output objects of the program.
+* **PRE_FILE_DOWNLOAD**, is triggered before files are downloaded and allows
+ you to manipulate the `RemoteFilesystem` object prior to downloading files
+ based on the URL to be downloaded.
+
+> A plugin can also subscribe to [script events][7].
+
+Example:
+
+ namespace Naderman\Composer\AWS;
+
+ 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)
+ {
+ $this->composer = $composer;
+ $this->io = $io;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ PluginEvents::PRE_FILE_DOWNLOAD => array(
+ array('onPreFileDownload', 0)
+ ),
+ );
+ }
+
+ public function onPreFileDownload(PreFileDownloadEvent $event)
+ {
+ $protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
+
+ 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
+
+Plugin packages are automatically loaded as soon as they are installed and will
+be loaded when composer starts up if they are found in the current project's
+list of installed packages. Additionally all plugin packages installed in the
+`COMPOSER_HOME` directory using the composer global command are loaded before
+local project plugins are loaded.
+
+> You may pass the `--no-plugins` option to composer commands to disable all
+> installed commands. This may be particularly helpful if any of the plugins
+> causes errors and you wish to update or uninstall it.
+
+[1]: ../04-schema.md#type
+[2]: ../04-schema.md#extra
+[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php
+[4]: https://github.com/composer/composer/blob/master/src/Composer/Composer.php
+[5]: https://github.com/composer/composer/blob/master/src/Composer/IO/IOInterface.php
+[6]: https://github.com/composer/composer/blob/master/src/Composer/EventDispatcher/EventSubscriberInterface.php
+[7]: ./scripts.md#event-names
diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md
index be777aa08..06e7c784d 100644
--- a/doc/articles/scripts.md
+++ b/doc/articles/scripts.md
@@ -41,6 +41,13 @@ Composer fires the following named events during its execution process:
- **post-create-project-cmd**: occurs after the `create-project` command is
executed.
+**NOTE: Composer makes no assumptions about the state of your dependencies
+prior to `install` or `update`. Therefore, you should not specify scripts that
+require Composer-managed dependencies in the `pre-update-cmd` or
+`pre-install-cmd` event hooks. If you need to execute scripts prior to
+`install` or `update` please make sure they are self-contained within your
+root package.**
+
## Defining scripts
The root JSON object in `composer.json` should have a property called
@@ -108,3 +115,11 @@ PHP callback. This `Event` object has getters for other contextual objects:
- `getName()`: returns the name of the event being fired as a string
- `getIO()`: returns the current input/output stream which implements
`Composer\IO\IOInterface` for writing to the console
+
+## Running scripts manually
+
+If you would like to run the scripts for an event manually, the syntax is:
+
+ $ 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.
diff --git a/doc/articles/vendor-binaries.md b/doc/articles/vendor-binaries.md
index 5f6918041..b258dccb7 100644
--- a/doc/articles/vendor-binaries.md
+++ b/doc/articles/vendor-binaries.md
@@ -58,7 +58,7 @@ Say project `my-vendor/project-b` has requirements setup like this:
{
"name": "my-vendor/project-b",
- "requires": {
+ "require": {
"my-vendor/project-a": "*"
}
}
diff --git a/res/composer-schema.json b/res/composer-schema.json
index 328794ef8..827e41f5c 100644
--- a/res/composer-schema.json
+++ b/res/composer-schema.json
@@ -9,7 +9,7 @@
"required": true
},
"type": {
- "description": "Package type, either 'library' for common packages, 'composer-installer' for custom installers, '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.",
"type": "string"
},
"target-dir": {
@@ -175,12 +175,27 @@
"discard-changes": {
"type": ["string", "boolean"],
"description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"."
+ },
+ "autoloader-suffix": {
+ "type": "string",
+ "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated."
+ },
+ "prepend-autoloader": {
+ "type": "boolean",
+ "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
+ },
+ "github-domains": {
+ "type": "array",
+ "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
+ "items": {
+ "type": "string"
+ }
}
}
},
"extra": {
"type": ["object", "array"],
- "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.",
+ "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.",
"additionalProperties": true
},
"autoload": {
diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php
index b0fe9b232..b26fb5831 100644
--- a/src/Composer/Autoload/AutoloadGenerator.php
+++ b/src/Composer/Autoload/AutoloadGenerator.php
@@ -13,12 +13,12 @@
namespace Composer\Autoload;
use Composer\Config;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
-use Composer\Script\EventDispatcher;
use Composer\Script\ScriptEvents;
/**
@@ -39,13 +39,14 @@ class AutoloadGenerator
public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '')
{
- $this->eventDispatcher->dispatch(ScriptEvents::PRE_AUTOLOAD_DUMP);
+ $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP);
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
- $basePath = $filesystem->normalizePath(getcwd());
+ $basePath = $filesystem->normalizePath(realpath(getcwd()));
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
$useGlobalIncludePath = (bool) $config->get('use-include-path');
+ $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
$targetDir = $vendorPath.'/'.$targetDir;
$filesystem->ensureDirectoryExists($targetDir);
@@ -59,7 +60,7 @@ class AutoloadGenerator
$namespacesFile = <<get('autoloader-suffix') ?: md5(uniqid('', true));
}
file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
@@ -180,7 +181,7 @@ EOF;
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
}
file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
- file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath));
+ file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader));
// use stream_copy_to_stream instead of copy
// to work around https://bugs.php.net/bug.php?id=64634
@@ -191,7 +192,7 @@ EOF;
fclose($targetLoader);
unset($sourceLoader, $targetLoader);
- $this->eventDispatcher->dispatch(ScriptEvents::POST_AUTOLOAD_DUMP);
+ $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP);
}
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -284,7 +285,7 @@ EOF;
return <<normalizePath($path);
$baseDir = '';
- if (strpos($path, $vendorPath) === 0) {
+ if (strpos($path.'/', $vendorPath.'/') === 0) {
$path = substr($path, strlen($vendorPath));
$baseDir = '$vendorDir';
@@ -355,7 +357,7 @@ EOF;
return <<register(true);
+ \$loader->register($prependAutoloader);
REGISTER_LOADER;
if ($useIncludeFiles) {
- $file .= <<whitelist.']}i', '-', $file);
@@ -157,6 +163,8 @@ class Cache
}
}
+ self::$cacheCollected = true;
+
return true;
}
diff --git a/src/Composer/Command/Command.php b/src/Composer/Command/Command.php
index 6e91ff869..862b54e58 100644
--- a/src/Composer/Command/Command.php
+++ b/src/Composer/Command/Command.php
@@ -38,16 +38,17 @@ abstract class Command extends BaseCommand
/**
* @param bool $required
+ * @param bool $disablePlugins
* @throws \RuntimeException
* @return Composer
*/
- public function getComposer($required = true)
+ public function getComposer($required = true, $disablePlugins = false)
{
if (null === $this->composer) {
$application = $this->getApplication();
if ($application instanceof Application) {
/* @var $application Application */
- $this->composer = $application->getComposer($required);
+ $this->composer = $application->getComposer($required, $disablePlugins);
} elseif ($required) {
throw new \RuntimeException(
'Could not create a Composer\Composer instance, you must inject '.
diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php
index 3459569a7..629cb7fec 100644
--- a/src/Composer/Command/ConfigCommand.php
+++ b/src/Composer/Command/ConfigCommand.php
@@ -117,7 +117,7 @@ EOT
if ($input->getOption('global') && !$this->configFile->exists()) {
touch($this->configFile->getPath());
$this->configFile->write(array('config' => new \ArrayObject));
- chmod($this->configFile->getPath(), 0600);
+ @chmod($this->configFile->getPath(), 0600);
}
if (!$this->configFile->exists()) {
@@ -254,18 +254,12 @@ EOT
// handle config values
$uniqueConfigValues = array(
'process-timeout' => array('is_numeric', 'intval'),
- 'use-include-path' => array(
- $booleanValidator,
- $booleanNormalizer
- ),
+ 'use-include-path' => array($booleanValidator, $booleanNormalizer),
'preferred-install' => array(
function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); },
function ($val) { return $val; }
),
- 'notify-on-install' => array(
- $booleanValidator,
- $booleanNormalizer
- ),
+ 'notify-on-install' => array($booleanValidator, $booleanNormalizer),
'vendor-dir' => array('is_string', function ($val) { return $val; }),
'bin-dir' => array('is_string', function ($val) { return $val; }),
'cache-dir' => array('is_string', function ($val) { return $val; }),
@@ -288,6 +282,8 @@ EOT
return $val !== 'false' && (bool) $val;
}
),
+ 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
+ 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
);
$multiConfigValues = array(
'github-protocols' => array(
@@ -308,6 +304,18 @@ EOT
return $vals;
}
),
+ 'github-domains' => array(
+ function ($vals) {
+ if (!is_array($vals)) {
+ return 'array expected';
+ }
+
+ return true;
+ },
+ function ($vals) {
+ return $vals;
+ }
+ ),
);
foreach ($uniqueConfigValues as $name => $callbacks) {
diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php
index 222add8e5..5aab270ce 100644
--- a/src/Composer/Command/CreateProjectCommand.php
+++ b/src/Composer/Command/CreateProjectCommand.php
@@ -44,6 +44,7 @@ use Composer\Package\Version\VersionParser;
* @author Benjamin Eberlei
* @author Jordi Boggiano
* @author Tobias Munk
+ * @author Nils Adermann
*/
class CreateProjectCommand extends Command
{
@@ -56,16 +57,18 @@ class CreateProjectCommand extends Command
new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'),
new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
new InputArgument('version', InputArgument::OPTIONAL, 'Version, will defaults to latest'),
- new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).', 'stable'),
+ new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'Pick a different repository url to look for the package.'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
- new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Whether to disable custom installers.'),
+ new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'),
+ new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('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.'),
))
->setHelp(<<create-project command creates a new project from a given
@@ -116,6 +119,11 @@ EOT
$preferDist = $input->getOption('prefer-dist');
}
+ if ($input->getOption('no-custom-installers')) {
+ $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.');
+ $input->setOption('no-plugins', true);
+ }
+
return $this->installProject(
$this->getIO(),
$config,
@@ -127,24 +135,26 @@ EOT
$preferDist,
!$input->getOption('no-dev'),
$input->getOption('repository-url'),
- $input->getOption('no-custom-installers'),
+ $input->getOption('no-plugins'),
$input->getOption('no-scripts'),
$input->getOption('keep-vcs'),
- $input->getOption('no-progress')
+ $input->getOption('no-progress'),
+ $input->getOption('no-install')
);
}
- public function installProject(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false, $keepVcs = false, $noProgress = false)
+ 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)
{
$oldCwd = getcwd();
if ($packageName !== null) {
- $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disableCustomInstallers, $noScripts, $keepVcs, $noProgress);
+ $installedFromVcs = $this->installRootPackage($io, $config, $packageName, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositoryUrl, $disablePlugins, $noScripts, $keepVcs, $noProgress);
} else {
$installedFromVcs = false;
}
- $composer = Factory::create($io);
+ $composer = Factory::create($io, null, $disablePlugins);
+ $fs = new Filesystem();
if ($noScripts === false) {
// dispatch event
@@ -152,18 +162,21 @@ EOT
}
// install dependencies of the created project
- $installer = Installer::create($io, $composer);
- $installer->setPreferSource($preferSource)
- ->setPreferDist($preferDist)
- ->setDevMode($installDevPackages)
- ->setRunScripts( ! $noScripts);
+ if ($noInstall === false) {
+ $installer = Installer::create($io, $composer);
+ $installer->setPreferSource($preferSource)
+ ->setPreferDist($preferDist)
+ ->setDevMode($installDevPackages)
+ ->setRunScripts( ! $noScripts);
- if ($disableCustomInstallers) {
- $installer->disableCustomInstallers();
- }
+ if ($disablePlugins) {
+ $installer->disablePlugins();
+ }
- if (!$installer->run()) {
- return 1;
+ $status = $installer->run();
+ if (0 !== $status) {
+ return $status;
+ }
}
$hasVcs = $installedFromVcs;
@@ -180,7 +193,6 @@ EOT
}
try {
- $fs = new Filesystem();
$dirs = iterator_to_array($finder);
unset($finder);
foreach ($dirs as $dir) {
@@ -215,10 +227,10 @@ EOT
chdir($oldCwd);
$vendorComposerDir = $composer->getConfig()->get('vendor-dir').'/composer';
- if (is_dir($vendorComposerDir) && glob($vendorComposerDir.'/*') === array() && count(glob($vendorComposerDir.'/.*')) === 2) {
+ if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) {
@rmdir($vendorComposerDir);
$vendorDir = $composer->getConfig()->get('vendor-dir');
- if (is_dir($vendorDir) && glob($vendorDir.'/*') === array() && count(glob($vendorDir.'/.*')) === 2) {
+ if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) {
@rmdir($vendorDir);
}
}
@@ -226,16 +238,8 @@ EOT
return 0;
}
- protected function installRootPackage(IOInterface $io, $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false, $keepVcs = false, $noProgress = false)
+ 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)
{
- $stability = strtolower($stability);
- if ($stability === 'rc') {
- $stability = 'RC';
- }
- if (!isset(BasePackage::$stabilities[$stability])) {
- throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
- }
-
if (null === $repositoryUrl) {
$sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) {
@@ -254,10 +258,24 @@ EOT
$packageVersion = $requirements[0]['version'];
}
- $pool = new Pool($packageVersion ? 'dev' : $stability);
+ if (null === $stability) {
+ if (preg_match('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::$stabilities)).')$}i', $packageVersion, $match)) {
+ $stability = $match[1];
+ } else {
+ $stability = VersionParser::parseStability($packageVersion);
+ }
+ }
+
+ $stability = VersionParser::normalizeStability($stability);
+
+ if (!isset(BasePackage::$stabilities[$stability])) {
+ throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities)));
+ }
+
+ $pool = new Pool($stability);
$pool->addRepository($sourceRepo);
- $constraint = $packageVersion ? new VersionConstraint('=', $parser->normalize($packageVersion)) : null;
+ $constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null;
$candidates = $pool->whatProvides($name, $constraint);
foreach ($candidates as $key => $candidate) {
if ($candidate->getName() !== $name) {
@@ -275,7 +293,7 @@ EOT
}
// select highest version if we have many
- $package = $candidates[0];
+ $package = reset($candidates);
foreach ($candidates as $candidate) {
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
$package = $candidate;
@@ -285,8 +303,8 @@ EOT
$io->write('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')');
- if ($disableCustomInstallers) {
- $io->write('Custom installers have been disabled.');
+ if ($disablePlugins) {
+ $io->write('Plugins have been disabled.');
}
if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) {
diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php
index 5603a17c0..755b40b90 100644
--- a/src/Composer/Command/DependsCommand.php
+++ b/src/Composer/Command/DependsCommand.php
@@ -13,6 +13,8 @@
namespace Composer\Command;
use Composer\DependencyResolver\Pool;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -50,7 +52,12 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
- $repo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
+ $composer = $this->getComposer();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
+ $repo = $composer->getRepositoryManager()->getLocalRepository();
$needle = $input->getArgument('package');
$pool = new Pool();
diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php
index 57ed3a003..879e139d2 100644
--- a/src/Composer/Command/DiagnoseCommand.php
+++ b/src/Composer/Command/DiagnoseCommand.php
@@ -15,6 +15,8 @@ namespace Composer\Command;
use Composer\Composer;
use Composer\Factory;
use Composer\Downloader\TransportException;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator;
use Composer\Util\RemoteFilesystem;
use Composer\Util\StreamContextFactory;
@@ -64,6 +66,9 @@ EOT
$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());
}
@@ -295,6 +300,12 @@ EOT
$warnings['apc_cli'] = true;
}
+ if (ini_get('xdebug.profiler_enabled')) {
+ $warnings['xdebug_profile'] = true;
+ } elseif (extension_loaded('xdebug')) {
+ $warnings['xdebug_loaded'] = true;
+ }
+
ob_start();
phpinfo(INFO_GENERAL);
$phpinfo = ob_get_clean();
@@ -360,6 +371,18 @@ EOT
$text = PHP_EOL."Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL;
$text .= "Composer works with 5.3.2+ for most people, but there might be edge case issues.";
break;
+
+ case 'xdebug_loaded':
+ $text = PHP_EOL."The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
+ $text .= "Disabling it when using Composer is recommended, but should not cause issues beyond slowness.";
+ break;
+
+ case 'xdebug_profile':
+ $text = PHP_EOL."The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
+ $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL;
+ $text .= " xdebug.profiler_enabled = 0";
+ $displayIniMessage = true;
+ break;
}
$out($text, 'warning');
}
diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php
index 4855a409d..3e1541590 100644
--- a/src/Composer/Command/DumpAutoloadCommand.php
+++ b/src/Composer/Command/DumpAutoloadCommand.php
@@ -12,6 +12,8 @@
namespace Composer\Command;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -42,6 +44,10 @@ EOT
$output->writeln('Generating autoload files');
$composer = $this->getComposer();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$installationManager = $composer->getInstallationManager();
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$package = $composer->getPackage();
diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php
index d37958939..15f1fff08 100644
--- a/src/Composer/Command/GlobalCommand.php
+++ b/src/Composer/Command/GlobalCommand.php
@@ -12,7 +12,6 @@
namespace Composer\Command;
-use Composer\Installer;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
@@ -43,6 +42,9 @@ is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.
COMPOSER_HOME is c:\Users\\AppData\Roaming\Composer on Windows
and /home//.composer on unix systems.
+Note: This path may vary depending on customizations to bin-dir in
+composer.json or the environmental variable COMPOSER_BIN_DIR.
+
EOT
)
;
@@ -70,10 +72,11 @@ EOT
// change to global dir
$config = Factory::createConfig();
chdir($config->get('home'));
+ $output->writeln('Changed current directory to '.$config->get('home').'');
// create new input without "global" command prefix
$input = new StringInput(preg_replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));
- return $this->getApplication()->get($args[1])->run($input, $output);
+ return $this->getApplication()->run($input, $output);
}
}
diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php
index 1071e949d..a44546b73 100644
--- a/src/Composer/Command/InitCommand.php
+++ b/src/Composer/Command/InitCommand.php
@@ -208,7 +208,8 @@ EOT
$description = $input->getOption('description') ?: false;
$description = $dialog->ask(
$output,
- $dialog->getQuestion('Description', $description)
+ $dialog->getQuestion('Description', $description),
+ $description
);
$input->setOption('description', $description);
@@ -258,7 +259,8 @@ EOT
$license = $input->getOption('license') ?: false;
$license = $dialog->ask(
$output,
- $dialog->getQuestion('License', $license)
+ $dialog->getQuestion('License', $license),
+ $license
);
$input->setOption('license', $license);
diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php
index 05d3f37d9..a163d5ad5 100644
--- a/src/Composer/Command/InstallCommand.php
+++ b/src/Composer/Command/InstallCommand.php
@@ -13,6 +13,8 @@
namespace Composer\Command;
use Composer\Installer;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -21,6 +23,7 @@ use Symfony\Component\Console\Output\OutputInterface;
* @author Jordi Boggiano
* @author Ryan Weaver
* @author Konstantin Kudryashov
+ * @author Nils Adermann
*/
class InstallCommand extends Command
{
@@ -35,7 +38,8 @@ class InstallCommand extends Command
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
- new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'),
+ 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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
@@ -56,9 +60,18 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
- $composer = $this->getComposer();
+ if ($input->getOption('no-custom-installers')) {
+ $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.');
+ $input->setOption('no-plugins', true);
+ }
+
+ $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$install = Installer::create($io, $composer);
$preferSource = false;
@@ -90,10 +103,10 @@ EOT
->setOptimizeAutoloader($input->getOption('optimize-autoloader'))
;
- if ($input->getOption('no-custom-installers')) {
- $install->disableCustomInstallers();
+ if ($input->getOption('no-plugins')) {
+ $install->disablePlugins();
}
- return $install->run() ? 0 : 1;
+ return $install->run();
}
}
diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php
index e30e371c2..861b889a0 100644
--- a/src/Composer/Command/LicensesCommand.php
+++ b/src/Composer/Command/LicensesCommand.php
@@ -12,12 +12,12 @@
namespace Composer\Command;
-use Composer\Package\PackageInterface;
use Composer\Json\JsonFile;
use Composer\Package\Version\VersionParser;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -46,6 +46,10 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
$composer = $this->getComposer();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$root = $composer->getPackage();
$repo = $composer->getRepositoryManager()->getLocalRepository();
diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php
index f47445a4e..f33c2fd00 100644
--- a/src/Composer/Command/RequireCommand.php
+++ b/src/Composer/Command/RequireCommand.php
@@ -20,6 +20,9 @@ use Composer\Factory;
use Composer\Installer;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
+use Composer\Package\Version\VersionParser;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
/**
* @author Jérémy Romey
@@ -80,6 +83,12 @@ EOT
$baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
$requirements = $this->formatRequirements($requirements);
+ // validate requirements format
+ $versionParser = new VersionParser();
+ foreach ($requirements as $constraint) {
+ $versionParser->parseConstraints($constraint);
+ }
+
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
foreach ($requirements as $package => $version) {
$baseRequirements[$package] = $version;
@@ -99,6 +108,10 @@ EOT
$composer = $this->getComposer();
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$install = Installer::create($io, $composer);
$install
@@ -110,14 +123,13 @@ EOT
->setUpdateWhitelist(array_keys($requirements));
;
- if (!$install->run()) {
+ $status = $install->run();
+ if ($status !== 0) {
$output->writeln("\n".'Installation failed, reverting '.$file.' to its original content.');
file_put_contents($json->getPath(), $composerBackup);
-
- return 1;
}
- return 0;
+ return $status;
}
private function updateFileCleanly($json, array $base, array $new, $requireKey)
diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php
index a212eb329..b9aaa8d74 100644
--- a/src/Composer/Command/SearchCommand.php
+++ b/src/Composer/Command/SearchCommand.php
@@ -20,6 +20,8 @@ use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Factory;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
/**
* @author Robert Schönthal
@@ -65,6 +67,11 @@ EOT
$repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
}
+ if ($composer) {
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+ }
+
$onlyName = $input->getOption('only-name');
$flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT;
diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php
index 07ed9eae1..b6830650d 100644
--- a/src/Composer/Command/SelfUpdateCommand.php
+++ b/src/Composer/Command/SelfUpdateCommand.php
@@ -13,22 +13,36 @@
namespace Composer\Command;
use Composer\Composer;
+use Composer\Factory;
+use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
use Composer\Downloader\FilesystemException;
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 Igor Wiedler
+ * @author Kevin Ran
+ * @author Jordi Boggiano
*/
class SelfUpdateCommand extends Command
{
+ const HOMEPAGE = 'getcomposer.org';
+ const OLD_INSTALL_EXT = '-old.phar';
+
protected function configure()
{
$this
->setName('self-update')
->setAliases(array('selfupdate'))
->setDescription('Updates composer.phar to the latest version.')
+ ->setDefinition(array(
+ new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
+ new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
+ new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
+ ))
->setHelp(<<self-update command checks getcomposer.org for newer
versions of composer and if found, installs the latest.
@@ -42,52 +56,162 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $baseUrl = (extension_loaded('openssl') ? 'https' : 'http') . '://' . self::HOMEPAGE;
+ $remoteFilesystem = new RemoteFilesystem($this->getIO());
+ $config = Factory::createConfig();
+ $cacheDir = $config->get('cache-dir');
+ $rollbackDir = $config->get('home');
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
- $tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar').'-temp.phar';
+
+ // check if current dir is writable and if not try the cache dir from settings
+ $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;
// check for permissions in local filesystem before start connection process
- if (!is_writable($tempDirectory = dirname($tempFilename))) {
- throw new FilesystemException('Composer update failed: the "'.$tempDirectory.'" directory used to download the temp file could not be written');
+ if (!is_writable($tmpDir)) {
+ throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
}
-
if (!is_writable($localFilename)) {
- throw new FilesystemException('Composer update failed: the "'.$localFilename. '" file could not be written');
+ throw new FilesystemException('Composer update failed: the "'.$localFilename.'" file could not be written');
}
- $protocol = extension_loaded('openssl') ? 'https' : 'http';
- $rfs = new RemoteFilesystem($this->getIO());
- $latest = trim($rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false));
+ if ($input->getOption('rollback')) {
+ return $this->rollback($output, $rollbackDir, $localFilename);
+ }
- if (Composer::VERSION !== $latest) {
- $output->writeln(sprintf("Updating to version %s.", $latest));
+ $latestVersion = trim($remoteFilesystem->getContents(self::HOMEPAGE, $baseUrl. '/version', false));
+ $updateVersion = $input->getArgument('version') ?: $latestVersion;
- $remoteFilename = $protocol . '://getcomposer.org/composer.phar';
+ if (preg_match('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) {
+ $output->writeln('You can not update to a specific SHA-1 as those phars are not available for download');
- $rfs->copy('getcomposer.org', $remoteFilename, $tempFilename);
+ return 1;
+ }
- if (!file_exists($tempFilename)) {
- $output->writeln('The download of the new composer version failed for an unexpected reason');
+ if (Composer::VERSION === $updateVersion) {
+ $output->writeln('You are already using composer version '.$updateVersion.'.');
- return 1;
- }
+ return 0;
+ }
- try {
- chmod($tempFilename, 0777 & ~umask());
- // test the phar validity
- $phar = new \Phar($tempFilename);
- // free the variable to unlock the file
- unset($phar);
- rename($tempFilename, $localFilename);
- } catch (\Exception $e) {
- @unlink($tempFilename);
- if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
- throw $e;
+ $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp.phar';
+ $backupFile = sprintf(
+ '%s/%s-%s%s',
+ $rollbackDir,
+ strtr(Composer::RELEASE_DATE, ' :', '_-'),
+ preg_replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION),
+ self::OLD_INSTALL_EXT
+ );
+
+ $output->writeln(sprintf("Updating to version %s.", $updateVersion));
+ $remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar");
+ $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename);
+ if (!file_exists($tempFilename)) {
+ $output->writeln('The download of the new composer version failed for an unexpected reason');
+
+ return 1;
+ }
+
+ // remove saved installations of composer
+ if ($input->getOption('clean-backups')) {
+ $files = $this->getOldInstallationFiles($rollbackDir);
+
+ if (!empty($files)) {
+ $fs = new Filesystem;
+
+ foreach ($files as $file) {
+ $output->writeln('Removing: '.$file);
+ $fs->remove($file);
}
- $output->writeln('The download is corrupted ('.$e->getMessage().').');
- $output->writeln('Please re-run the self-update command to try again.');
}
+ }
+
+ if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
+ $output->writeln('The file is corrupted ('.$err->getMessage().').');
+ $output->writeln('Please re-run the self-update command to try again.');
+
+ return 1;
+ }
+
+ if (file_exists($backupFile)) {
+ $output->writeln('Use composer self-update --rollback to return to version '.Composer::VERSION);
} else {
- $output->writeln("You are using the latest composer version.");
+ $output->writeln('A backup of the current version could not be written to '.$backupFile.', no rollback possible');
}
}
+
+ protected function rollback(OutputInterface $output, $rollbackDir, $localFilename)
+ {
+ $rollbackVersion = $this->getLastBackupVersion($rollbackDir);
+ if (!$rollbackVersion) {
+ throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
+ }
+
+ if (!is_writable($rollbackDir)) {
+ throw new FilesystemException('Composer rollback failed: the "'.$rollbackDir.'" dir could not be written to');
+ }
+
+ $old = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;
+
+ if (!is_file($old)) {
+ throw new FilesystemException('Composer rollback failed: "'.$old.'" could not be found');
+ }
+ if (!is_readable($old)) {
+ throw new FilesystemException('Composer rollback failed: "'.$old.'" could not be read');
+ }
+
+ $oldFile = $rollbackDir . "/{$rollbackVersion}" . self::OLD_INSTALL_EXT;
+ $output->writeln(sprintf("Rolling back to version %s.", $rollbackVersion));
+ if ($err = $this->setLocalPhar($localFilename, $oldFile)) {
+ $output->writeln('The backup file was corrupted ('.$err->getMessage().') and has been removed.');
+
+ return 1;
+ }
+
+ return 0;
+ }
+
+ protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
+ {
+ try {
+ @chmod($newFilename, 0777 & ~umask());
+ // test the phar validity
+ $phar = new \Phar($newFilename);
+ // free the variable to unlock the file
+ unset($phar);
+
+ // copy current file into installations dir
+ if ($backupTarget && file_exists($localFilename)) {
+ @copy($localFilename, $backupTarget);
+ }
+
+ unset($phar);
+ rename($newFilename, $localFilename);
+ } catch (\Exception $e) {
+ if ($backupTarget) {
+ @unlink($newFilename);
+ }
+ if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
+ throw $e;
+ }
+
+ return $e;
+ }
+ }
+
+ protected function getLastBackupVersion($rollbackDir)
+ {
+ $files = $this->getOldInstallationFiles($rollbackDir);
+ if (empty($files)) {
+ return false;
+ }
+
+ sort($files);
+
+ return basename(end($files), self::OLD_INSTALL_EXT);
+ }
+
+ protected function getOldInstallationFiles($rollbackDir)
+ {
+ return glob($rollbackDir . '/*' . self::OLD_INSTALL_EXT) ?: array();
+ }
}
diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php
index a54de99c7..97e315a5b 100644
--- a/src/Composer/Command/ShowCommand.php
+++ b/src/Composer/Command/ShowCommand.php
@@ -12,12 +12,13 @@
namespace Composer\Command;
-use Composer\Composer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\Factory;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -66,8 +67,9 @@ EOT
// init repos
$platformRepo = new PlatformRepository;
+ $composer = $this->getComposer(false);
if ($input->getOption('self')) {
- $package = $this->getComposer(false)->getPackage();
+ $package = $this->getComposer()->getPackage();
$repos = $installedRepo = new ArrayRepository(array($package));
} elseif ($input->getOption('platform')) {
$repos = $installedRepo = $platformRepo;
@@ -75,15 +77,14 @@ EOT
$repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
} elseif ($input->getOption('available')) {
$installedRepo = $platformRepo;
- if ($composer = $this->getComposer(false)) {
+ if ($composer) {
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
} else {
$defaultRepos = Factory::createDefaultRepositories($this->getIO());
$repos = new CompositeRepository($defaultRepos);
$output->writeln('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
}
- } elseif ($composer = $this->getComposer(false)) {
- $composer = $this->getComposer();
+ } elseif ($composer) {
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
$installedRepo = new CompositeRepository(array($localRepo, $platformRepo));
$repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories()));
@@ -94,6 +95,11 @@ EOT
$repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
}
+ if ($composer) {
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+ }
+
// show single package or single version
if ($input->getArgument('package') || !empty($package)) {
$versions = array();
@@ -178,6 +184,11 @@ EOT
}
}
list($width) = $this->getApplication()->getTerminalDimensions();
+ if (null === $width) {
+ // In case the width is not detected, we're probably running the command
+ // outside of a real terminal, use space without a limit
+ $width = PHP_INT_MAX;
+ }
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$width--;
}
@@ -290,7 +301,7 @@ EOT
if ($type === 'psr-0') {
foreach ($autoloads as $name => $path) {
- $output->writeln(($name ?: '*') . ' => ' . ($path ?: '.'));
+ $output->writeln(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
}
} elseif ($type === 'classmap') {
$output->writeln(implode(', ', $autoloads));
diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php
index 5151d734b..a125bdd3c 100644
--- a/src/Composer/Command/StatusCommand.php
+++ b/src/Composer/Command/StatusCommand.php
@@ -16,7 +16,8 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Downloader\ChangeReportInterface;
-use Composer\Downloader\VcsDownloader;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
/**
@@ -46,6 +47,10 @@ EOT
{
// init repos
$composer = $this->getComposer();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
$dm = $composer->getDownloadManager();
diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php
index dc103cf99..7a1471a65 100644
--- a/src/Composer/Command/UpdateCommand.php
+++ b/src/Composer/Command/UpdateCommand.php
@@ -13,6 +13,8 @@
namespace Composer\Command;
use Composer\Installer;
+use Composer\Plugin\CommandEvent;
+use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
@@ -20,6 +22,7 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jordi Boggiano
+ * @author Nils Adermann
*/
class UpdateCommand extends Command
{
@@ -36,11 +39,13 @@ class UpdateCommand extends Command
new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
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-custom-installers', null, InputOption::VALUE_NONE, 'Disables all custom installers.'),
+ 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-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
+ new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
- new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump')
+ new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.')
))
->setHelp(<<update command reads the composer.json file from the
@@ -60,9 +65,18 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
{
- $composer = $this->getComposer();
+ if ($input->getOption('no-custom-installers')) {
+ $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.');
+ $input->setOption('no-plugins', true);
+ }
+
+ $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO();
+
+ $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
+ $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
+
$install = Installer::create($io, $composer);
$preferSource = false;
@@ -94,12 +108,13 @@ EOT
->setOptimizeAutoloader($input->getOption('optimize-autoloader'))
->setUpdate(true)
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
+ ->setWhitelistDependencies($input->getOption('with-dependencies'))
;
- if ($input->getOption('no-custom-installers')) {
- $install->disableCustomInstallers();
+ if ($input->getOption('no-plugins')) {
+ $install->disablePlugins();
}
- return $install->run() ? 0 : 1;
+ return $install->run();
}
}
diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php
index 94f1a15ff..c81e300b6 100644
--- a/src/Composer/Compiler.php
+++ b/src/Composer/Compiler.php
@@ -24,6 +24,7 @@ use Symfony\Component\Process\Process;
class Compiler
{
private $version;
+ private $versionDate;
/**
* Compiles composer into a single phar file
@@ -43,6 +44,14 @@ class Compiler
}
$this->version = trim($process->getOutput());
+ $process = new Process('git log -n1 --pretty=%ci HEAD', __DIR__);
+ if ($process->run() != 0) {
+ throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
+ }
+ $date = new \DateTime(trim($process->getOutput()));
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->versionDate = $date->format('Y-m-d H:i:s');
+
$process = new Process('git describe --tags HEAD');
if ($process->run() == 0) {
$this->version = trim($process->getOutput());
@@ -117,7 +126,7 @@ class Compiler
private function addFile($phar, $file, $strip = true)
{
- $path = str_replace(dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR, '', $file->getRealPath());
+ $path = strtr(str_replace(dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR, '', $file->getRealPath()), '\\', '/');
$content = file_get_contents($file);
if ($strip) {
@@ -126,7 +135,10 @@ class Compiler
$content = "\n".$content."\n";
}
- $content = str_replace('@package_version@', $this->version, $content);
+ if ($path === 'src/Composer/Composer.php') {
+ $content = str_replace('@package_version@', $this->version, $content);
+ $content = str_replace('@release_date@', $this->versionDate, $content);
+ }
$phar->addFromString($path, $content);
}
diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php
index 8c5d0785f..0d1e0aa89 100644
--- a/src/Composer/Composer.php
+++ b/src/Composer/Composer.php
@@ -16,17 +16,20 @@ use Composer\Package\RootPackageInterface;
use Composer\Package\Locker;
use Composer\Repository\RepositoryManager;
use Composer\Installer\InstallationManager;
+use Composer\Plugin\PluginManager;
use Composer\Downloader\DownloadManager;
-use Composer\Script\EventDispatcher;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
/**
* @author Jordi Boggiano
* @author Konstantin Kudryashiv
+ * @author Nils Adermann
*/
class Composer
{
const VERSION = '@package_version@';
+ const RELEASE_DATE = '@release_date@';
/**
* @var Package\RootPackageInterface
@@ -53,13 +56,18 @@ class Composer
*/
private $installationManager;
+ /**
+ * @var Plugin\PluginManager
+ */
+ private $pluginManager;
+
/**
* @var Config
*/
private $config;
/**
- * @var Script\EventDispatcher
+ * @var EventDispatcher\EventDispatcher
*/
private $eventDispatcher;
@@ -166,7 +174,23 @@ class Composer
}
/**
- * @param Script\EventDispatcher $eventDispatcher
+ * @param Plugin\PluginManager $manager
+ */
+ public function setPluginManager(PluginManager $manager)
+ {
+ $this->pluginManager = $manager;
+ }
+
+ /**
+ * @return Plugin\PluginManager
+ */
+ public function getPluginManager()
+ {
+ return $this->pluginManager;
+ }
+
+ /**
+ * @param EventDispatcher\EventDispatcher $eventDispatcher
*/
public function setEventDispatcher(EventDispatcher $eventDispatcher)
{
@@ -174,7 +198,7 @@ class Composer
}
/**
- * @return Script\EventDispatcher
+ * @return EventDispatcher\EventDispatcher
*/
public function getEventDispatcher()
{
diff --git a/src/Composer/Config.php b/src/Composer/Config.php
index 601a21344..c54bc498c 100644
--- a/src/Composer/Config.php
+++ b/src/Composer/Config.php
@@ -35,6 +35,9 @@ class Config
'cache-files-ttl' => null, // fallback to cache-ttl
'cache-files-maxsize' => '300MiB',
'discard-changes' => false,
+ 'autoloader-suffix' => null,
+ 'prepend-autoloader' => true,
+ 'github-domains' => array('github.com'),
);
public static $defaultRepositories = array(
diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php
index 27d922853..5223eb5d2 100644
--- a/src/Composer/Config/JsonConfigSource.php
+++ b/src/Composer/Config/JsonConfigSource.php
@@ -124,7 +124,7 @@ class JsonConfigSource implements ConfigSourceInterface
}
if ($newFile) {
- chmod($this->file->getPath(), 0600);
+ @chmod($this->file->getPath(), 0600);
}
}
}
diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php
index 0d1e45500..9d622cf67 100644
--- a/src/Composer/Console/Application.php
+++ b/src/Composer/Console/Application.php
@@ -165,14 +165,15 @@ class Application extends BaseApplication
/**
* @param bool $required
+ * @param bool $disablePlugins
* @throws JsonValidationException
* @return \Composer\Composer
*/
- public function getComposer($required = true)
+ public function getComposer($required = true, $disablePlugins = false)
{
if (null === $this->composer) {
try {
- $this->composer = Factory::create($this->io);
+ $this->composer = Factory::create($this->io, null, $disablePlugins);
} catch (\InvalidArgumentException $e) {
if ($required) {
$this->io->write($e->getMessage());
@@ -234,6 +235,14 @@ class Application extends BaseApplication
return $commands;
}
+ /**
+ * {@inheritDoc}
+ */
+ public function getLongVersion()
+ {
+ return parent::getLongVersion() . ' ' . Composer::RELEASE_DATE;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php
index 309c6e471..96198f4d6 100644
--- a/src/Composer/DependencyResolver/Pool.php
+++ b/src/Composer/DependencyResolver/Pool.php
@@ -377,6 +377,7 @@ class Pool
if ($constraint === null) {
return self::MATCH;
}
+
return $constraint->matches(new VersionConstraint('==', $candidateVersion)) ? self::MATCH : self::MATCH_NAME;
}
diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php
index ac4541eb6..56ac867c4 100644
--- a/src/Composer/DependencyResolver/Problem.php
+++ b/src/Composer/DependencyResolver/Problem.php
@@ -84,7 +84,7 @@ class Problem
// handle php extensions
if (0 === stripos($job['packageName'], 'ext-')) {
$ext = substr($job['packageName'], 4);
- $error = extension_loaded($ext) ? 'has the wrong version ('.phpversion($ext).') installed' : 'is missing from your system';
+ $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'.';
}
diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php
index 672fe2f4a..a47038431 100644
--- a/src/Composer/DependencyResolver/Rule.php
+++ b/src/Composer/DependencyResolver/Rule.php
@@ -215,7 +215,7 @@ class Rule
// handle php extensions
if (0 === strpos($targetName, 'ext-')) {
$ext = substr($targetName, 4);
- $error = extension_loaded($ext) ? 'has the wrong version ('.phpversion($ext).') installed' : 'is missing from your system';
+ $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
$text .= ' -> the requested PHP extension '.$ext.' '.$error.'.';
} elseif (0 === strpos($targetName, 'lib-')) {
diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php
index f65f6e0e2..0fc860c72 100644
--- a/src/Composer/DependencyResolver/Solver.php
+++ b/src/Composer/DependencyResolver/Solver.php
@@ -756,7 +756,6 @@ class Solver
if ($lastLiteral) {
unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]);
- array_values($this->branches[$lastBranchIndex][self::BRANCH_LITERALS]);
$level = $lastLevel;
$this->revert($level);
diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php
index f53798a00..bfe174fb7 100644
--- a/src/Composer/Downloader/ArchiveDownloader.php
+++ b/src/Composer/Downloader/ArchiveDownloader.php
@@ -69,7 +69,7 @@ abstract class ArchiveDownloader extends FileDownloader
$this->filesystem->removeDirectory($temporaryDir);
// retry downloading if we have an invalid zip file
- if ($retries && $e instanceof \UnexpectedValueException && $e->getCode() === \ZipArchive::ER_NOZIP) {
+ if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
$this->io->write(' Invalid zip file, retrying...');
usleep(500000);
continue;
@@ -132,7 +132,7 @@ abstract class ArchiveDownloader extends FileDownloader
*/
private function listFiles($dir)
{
- $files = array_merge(glob($dir . '/.*'), glob($dir . '/*'));
+ $files = array_merge(glob($dir . '/.*') ?: array(), glob($dir . '/*') ?: array());
return array_values(array_filter($files, function ($el) {
return basename($el) !== '.' && basename($el) !== '..';
diff --git a/src/Composer/Downloader/ChangeReportInterface.php b/src/Composer/Downloader/ChangeReportInterface.php
index e60615431..3fb1dc5d0 100644
--- a/src/Composer/Downloader/ChangeReportInterface.php
+++ b/src/Composer/Downloader/ChangeReportInterface.php
@@ -24,9 +24,9 @@ interface ChangeReportInterface
/**
* Checks for changes to the local copy
*
- * @param PackageInterface $package package instance
- * @param string $path package directory
- * @return string|null changes or null
+ * @param PackageInterface $package package instance
+ * @param string $path package directory
+ * @return string|null changes or null
*/
public function getLocalChanges(PackageInterface $package, $path);
}
diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php
index 38b2fe304..0fcc31188 100644
--- a/src/Composer/Downloader/FileDownloader.php
+++ b/src/Composer/Downloader/FileDownloader.php
@@ -17,6 +17,9 @@ use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
+use Composer\Plugin\PluginEvents;
+use Composer\Plugin\PreFileDownloadEvent;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\GitHub;
use Composer\Util\RemoteFilesystem;
@@ -27,10 +30,10 @@ use Composer\Util\RemoteFilesystem;
* @author Kirill chEbba Chebunin
* @author Jordi Boggiano
* @author François Pluchino
+ * @author Nils Adermann
*/
class FileDownloader implements DownloaderInterface
{
- private static $cacheCollected = false;
protected $io;
protected $config;
protected $rfs;
@@ -41,24 +44,26 @@ class FileDownloader implements DownloaderInterface
/**
* Constructor.
*
- * @param IOInterface $io The IO instance
- * @param Config $config The config
- * @param Cache $cache Optional cache instance
- * @param RemoteFilesystem $rfs The remote filesystem
- * @param Filesystem $filesystem The filesystem
+ * @param IOInterface $io The IO instance
+ * @param Config $config The config
+ * @param EventDispatcher $eventDispatcher The event dispatcher
+ * @param Cache $cache Optional cache instance
+ * @param RemoteFilesystem $rfs The remote filesystem
+ * @param Filesystem $filesystem The filesystem
*/
- public function __construct(IOInterface $io, Config $config, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
+ public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
{
$this->io = $io;
$this->config = $config;
+ $this->eventDispatcher = $eventDispatcher;
$this->rfs = $rfs ?: new RemoteFilesystem($io);
$this->filesystem = $filesystem ?: new Filesystem();
$this->cache = $cache;
- if ($this->cache && !self::$cacheCollected && !mt_rand(0, 50)) {
- $this->cache->gc($config->get('cache-ttl'), $config->get('cache-files-maxsize'));
+
+ if ($this->cache && $this->cache->gcIsNecessary()) {
+ $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
}
- self::$cacheCollected = true;
}
/**
@@ -79,6 +84,7 @@ class FileDownloader implements DownloaderInterface
throw new \InvalidArgumentException('The given package is missing url information');
}
+ $this->filesystem->removeDirectory($path);
$this->filesystem->ensureDirectoryExists($path);
$fileName = $this->getFileName($package, $path);
@@ -88,6 +94,12 @@ class FileDownloader implements DownloaderInterface
$processedUrl = $this->processUrl($package, $url);
$hostname = parse_url($processedUrl, PHP_URL_HOST);
+ $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl);
+ if ($this->eventDispatcher) {
+ $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
+ }
+ $rfs = $preFileDownloadEvent->getRemoteFilesystem();
+
if (strpos($hostname, '.github.com') === (strlen($hostname) - 11)) {
$hostname = 'github.com';
}
@@ -103,11 +115,11 @@ class FileDownloader implements DownloaderInterface
$retries = 3;
while ($retries--) {
try {
- $this->rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getOptions());
+ $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getOptions());
break;
} catch (TransportException $e) {
// if we got an http response with a proper code, then requesting again will probably not help, abort
- if ((0 !== $e->getCode() && 500 !== $e->getCode()) || !$retries) {
+ if ((0 !== $e->getCode() && !in_array($e->getCode(),array(500, 502, 503, 504))) || !$retries) {
throw $e;
}
if ($this->io->isVerbose()) {
@@ -124,15 +136,18 @@ class FileDownloader implements DownloaderInterface
$this->io->write(' Loading from cache');
}
} catch (TransportException $e) {
- if (in_array($e->getCode(), array(404, 403)) && 'github.com' === $hostname && !$this->io->hasAuthentication($hostname)) {
+ 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, $this->rfs);
+ $gitHubUtil = new GitHub($this->io, $this->config, null, $rfs);
if (!$gitHubUtil->authorizeOAuth($hostname)
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($hostname, $message))
) {
throw $e;
}
- $this->rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getOptions());
+ $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getOptions());
} else {
throw $e;
}
diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php
index f320cf931..322d3f883 100644
--- a/src/Composer/Downloader/GitDownloader.php
+++ b/src/Composer/Downloader/GitDownloader.php
@@ -293,7 +293,7 @@ class GitDownloader extends VcsDownloader
}
// public github, autoswitch protocols
- if (preg_match('{^(?:https?|git)(://github.com/.*)}', $url, $match)) {
+ 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));
@@ -317,7 +317,7 @@ class GitDownloader extends VcsDownloader
$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@(github.com):(.+?)\.git$}i', $url, $match)) {
+ 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';
@@ -368,6 +368,11 @@ class GitDownloader extends VcsDownloader
}
}
+ 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)) {
@@ -379,17 +384,17 @@ class GitDownloader extends VcsDownloader
protected function sanitizeUrl($message)
{
- return preg_replace('{://(.+?):.+?@}', '://$1:***@', $message);
+ return preg_replace('{://([^@]+?):.+?@}', '://$1:***@', $message);
}
protected function setPushUrl(PackageInterface $package, $path)
{
// set push url for github projects
- if (preg_match('{^(?:https?|git)://github.com/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) {
+ if (preg_match('{^(?:https?|git)://'.$this->getGitHubDomainsRegex().'/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) {
$protocols = $this->config->get('github-protocols');
- $pushUrl = 'git@github.com:'.$match[1].'/'.$match[2].'.git';
+ $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
if ($protocols[0] !== 'git') {
- $pushUrl = 'https://github.com/'.$match[1].'/'.$match[2].'.git';
+ $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
}
$cmd = sprintf('git remote set-url --push origin %s', escapeshellarg($pushUrl));
$this->process->execute($cmd, $ignoredOutput, $path);
diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php
new file mode 100644
index 000000000..2bb1ba619
--- /dev/null
+++ b/src/Composer/Downloader/PerforceDownloader.php
@@ -0,0 +1,97 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Downloader;
+
+use Composer\Package\PackageInterface;
+use Composer\Repository\VcsRepository;
+use Composer\Util\Perforce;
+
+/**
+ * @author Matt Whittom
+ */
+class PerforceDownloader extends VcsDownloader
+{
+ protected $perforce;
+ protected $perforceInjected = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function doDownload(PackageInterface $package, $path)
+ {
+ $ref = $package->getSourceReference();
+ $label = $package->getPrettyVersion();
+
+ $this->io->write(' Cloning ' . $ref);
+ $this->initPerforce($package, $path);
+ $this->perforce->setStream($ref);
+ $this->perforce->p4Login($this->io);
+ $this->perforce->writeP4ClientSpec();
+ $this->perforce->connectClient();
+ $this->perforce->syncCodeBase($label);
+ $this->perforce->cleanupClientSpec();
+ }
+
+ public function initPerforce($package, $path)
+ {
+ if ($this->perforce) {
+ $this->perforce->initializePath($path);
+ return;
+ }
+
+ $repository = $package->getRepository();
+ $repoConfig = null;
+ if ($repository instanceof VcsRepository) {
+ $repoConfig = $this->getRepoConfig($repository);
+ }
+ $this->perforce = Perforce::create($repoConfig, $package->getSourceUrl(), $path);
+ }
+
+ private function getRepoConfig(VcsRepository $repository)
+ {
+ return $repository->getRepoConfig();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
+ {
+ $this->doDownload($target, $path);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocalChanges(PackageInterface $package, $path)
+ {
+ $this->io->write('Perforce driver does not check for local changes before overriding', true);
+
+ return;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getCommitLogs($fromReference, $toReference, $path)
+ {
+ $commitLogs = $this->perforce->getCommitLogs($fromReference, $toReference);
+
+ return $commitLogs;
+ }
+
+ public function setPerforce($perforce)
+ {
+ $this->perforce = $perforce;
+ }
+}
diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php
new file mode 100644
index 000000000..bb62ee0a8
--- /dev/null
+++ b/src/Composer/Downloader/RarDownloader.php
@@ -0,0 +1,94 @@
+
+ * Jordi Boggiano
+ *
+ * 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\Util\ProcessExecutor;
+use Composer\IO\IOInterface;
+use RarArchive;
+
+/**
+ * RAR archive downloader.
+ *
+ * Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
+ *
+ * @author Derrick Nelson
+ */
+class RarDownloader 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)
+ {
+ $processError = null;
+
+ // Try to use unrar on *nix
+ if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $command = 'unrar x ' . escapeshellarg($file) . ' ' . escapeshellarg($path) . ' && chmod -R u+w ' . escapeshellarg($path);
+
+ if (0 === $this->process->execute($command, $ignoredOutput)) {
+ return;
+ }
+
+ $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
+ }
+
+ if (!class_exists('RarArchive')) {
+ // php.ini path is added to the error message to help users find the correct file
+ $iniPath = php_ini_loaded_file();
+
+ if ($iniPath) {
+ $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath;
+ } else {
+ $iniMessage = 'A php.ini file does not exist. You will have to create one.';
+ }
+
+ $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
+ . $iniMessage . "\n" . $processError;
+
+ if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
+ }
+
+ throw new \RuntimeException($error);
+ }
+
+ $rarArchive = RarArchive::open($file);
+
+ if (false === $rarArchive) {
+ throw new \UnexpectedValueException('Could not open RAR archive: ' . $file);
+ }
+
+ $entries = $rarArchive->getEntries();
+
+ if (false === $entries) {
+ throw new \RuntimeException('Could not retrieve RAR archive entries');
+ }
+
+ foreach ($entries as $entry) {
+ if (false === $entry->extract($path)) {
+ throw new \RuntimeException('Could not extract entry');
+ }
+ }
+
+ $rarArchive->close();
+ }
+}
diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php
index d3253d956..4e06d63f8 100644
--- a/src/Composer/Downloader/VcsDownloader.php
+++ b/src/Composer/Downloader/VcsDownloader.php
@@ -148,8 +148,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* Prompt the user to check if changes should be stashed/removed or the operation aborted
*
* @param PackageInterface $package
- * @param string $path
- * @param bool $update if true (update) the changes can be stashed and reapplied after an update,
+ * @param string $path
+ * @param bool $update if true (update) the changes can be stashed and reapplied after an update,
* if false (remove) the changes should be assumed to be lost if the operation is not aborted
* @throws \RuntimeException in case the operation must be aborted
*/
diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php
index 80bc60272..71958948d 100644
--- a/src/Composer/Downloader/ZipDownloader.php
+++ b/src/Composer/Downloader/ZipDownloader.php
@@ -14,6 +14,7 @@ namespace Composer\Downloader;
use Composer\Config;
use Composer\Cache;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use ZipArchive;
@@ -25,10 +26,10 @@ class ZipDownloader extends ArchiveDownloader
{
protected $process;
- public function __construct(IOInterface $io, Config $config, Cache $cache = null, ProcessExecutor $process = null)
+ 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, $cache);
+ parent::__construct($io, $config, $eventDispatcher, $cache);
}
protected function extract($file, $path)
@@ -37,7 +38,7 @@ class ZipDownloader extends ArchiveDownloader
// try to use unzip on *nix
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
- $command = 'unzip '.escapeshellarg($file).' -d '.escapeshellarg($path);
+ $command = 'unzip '.escapeshellarg($file).' -d '.escapeshellarg($path) . ' && chmod -R u+w ' . escapeshellarg($path);
if (0 === $this->process->execute($command, $ignoredOutput)) {
return;
}
diff --git a/src/Composer/EventDispatcher/Event.php b/src/Composer/EventDispatcher/Event.php
new file mode 100644
index 000000000..0b3c9c951
--- /dev/null
+++ b/src/Composer/EventDispatcher/Event.php
@@ -0,0 +1,69 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\EventDispatcher;
+
+/**
+ * The base event class
+ *
+ * @author Nils Adermann
+ */
+class Event
+{
+ /**
+ * @var string This event's name
+ */
+ protected $name;
+
+ /**
+ * @var boolean Whether the event should not be passed to more listeners
+ */
+ private $propagationStopped = false;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name
+ */
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Returns the event's name.
+ *
+ * @return string The event name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Checks if stopPropagation has been called
+ *
+ * @return boolean Whether propagation has been stopped
+ */
+ public function isPropagationStopped()
+ {
+ return $this->propagationStopped;
+ }
+
+ /**
+ * Prevents the event from being passed to further listeners
+ */
+ public function stopPropagation()
+ {
+ $this->propagationStopped = true;
+ }
+}
diff --git a/src/Composer/Script/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php
similarity index 65%
rename from src/Composer/Script/EventDispatcher.php
rename to src/Composer/EventDispatcher/EventDispatcher.php
index 46d3d94d7..f1e94e6d5 100644
--- a/src/Composer/Script/EventDispatcher.php
+++ b/src/Composer/EventDispatcher/EventDispatcher.php
@@ -10,11 +10,14 @@
* file that was distributed with this source code.
*/
-namespace Composer\Script;
+namespace Composer\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\DependencyResolver\Operation\OperationInterface;
+use Composer\Script;
+use Composer\Script\CommandEvent;
+use Composer\Script\PackageEvent;
use Composer\Util\ProcessExecutor;
/**
@@ -28,6 +31,7 @@ use Composer\Util\ProcessExecutor;
*
* @author François Pluchino
* @author Jordi Boggiano
+ * @author Nils Adermann
*/
class EventDispatcher
{
@@ -51,15 +55,30 @@ class EventDispatcher
}
/**
- * Dispatch a script event.
+ * Dispatch an event
*
- * @param string $eventName The constant in ScriptEvents
+ * @param string $eventName An event name
* @param Event $event
*/
public function dispatch($eventName, Event $event = null)
{
if (null == $event) {
- $event = new Event($eventName, $this->composer, $this->io);
+ $event = new Event($eventName);
+ }
+
+ $this->doDispatch($event);
+ }
+
+ /**
+ * Dispatch a script event.
+ *
+ * @param string $eventName The constant in ScriptEvents
+ * @param Script\Event $event
+ */
+ public function dispatchScript($eventName, Script\Event $event = null)
+ {
+ if (null == $event) {
+ $event = new Script\Event($eventName, $this->composer, $this->io);
}
$this->doDispatch($event);
@@ -100,7 +119,9 @@ class EventDispatcher
$listeners = $this->getListeners($event);
foreach ($listeners as $callable) {
- if ($this->isPhpScript($callable)) {
+ if (!is_string($callable) && is_callable($callable)) {
+ call_user_func($callable, $event);
+ } elseif ($this->isPhpScript($callable)) {
$className = substr($callable, 0, strpos($callable, '::'));
$methodName = substr($callable, strpos($callable, '::') + 2);
@@ -127,6 +148,10 @@ class EventDispatcher
throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
}
}
+
+ if ($event->isPropagationStopped()) {
+ break;
+ }
}
}
@@ -141,10 +166,67 @@ class EventDispatcher
}
/**
+ * Add a listener for a particular event
+ *
+ * @param string $eventName The event name - typically a constant
+ * @param Callable $listener A callable expecting an event argument
+ * @param integer $priority A higher value represents a higher priority
+ */
+ protected function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->listeners[$eventName][$priority][] = $listener;
+ }
+
+ /**
+ * Adds object methods as listeners for the events in getSubscribedEvents
+ *
+ * @see EventSubscriberInterface
+ *
+ * @param EventSubscriberInterface $subscriber
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (is_string($params)) {
+ $this->addListener($eventName, array($subscriber, $params));
+ } elseif (is_string($params[0])) {
+ $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieves all listeners for a given event
+ *
+ * @param Event $event
+ * @return array All listeners: callables and scripts
+ */
+ protected function getListeners(Event $event)
+ {
+ $scriptListeners = $this->getScriptListeners($event);
+
+ if (!isset($this->listeners[$event->getName()][0])) {
+ $this->listeners[$event->getName()][0] = array();
+ }
+ krsort($this->listeners[$event->getName()]);
+
+ $listeners = $this->listeners;
+ $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);
+
+ return call_user_func_array('array_merge', $listeners[$event->getName()]);
+ }
+
+ /**
+ * Finds all listeners defined as scripts in the package
+ *
* @param Event $event Event object
* @return array Listeners
*/
- protected function getListeners(Event $event)
+ protected function getScriptListeners(Event $event)
{
$package = $this->composer->getPackage();
$scripts = $package->getScripts();
diff --git a/src/Composer/EventDispatcher/EventSubscriberInterface.php b/src/Composer/EventDispatcher/EventSubscriberInterface.php
new file mode 100644
index 000000000..6b0c4ca06
--- /dev/null
+++ b/src/Composer/EventDispatcher/EventSubscriberInterface.php
@@ -0,0 +1,48 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\EventDispatcher;
+
+/**
+ * An EventSubscriber knows which events it is interested in.
+ *
+ * If an EventSubscriber is added to an EventDispatcher, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ */
+interface EventSubscriberInterface
+{
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * The array keys are event names and the value can be:
+ *
+ * * The method name to call (priority defaults to 0)
+ * * An array composed of the method name to call and the priority
+ * * An array of arrays composed of the method names to call and respective
+ * priorities, or 0 if unset
+ *
+ * For instance:
+ *
+ * * array('eventName' => 'methodName')
+ * * array('eventName' => array('methodName', $priority))
+ * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
+ *
+ * @return array The event names to listen to
+ */
+ public static function getSubscribedEvents();
+}
diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php
index b63ad13f1..f829cb459 100644
--- a/src/Composer/Factory.php
+++ b/src/Composer/Factory.php
@@ -16,12 +16,12 @@ use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Package\Archiver;
-use Composer\Repository\ComposerRepository;
use Composer\Repository\RepositoryManager;
+use Composer\Repository\RepositoryInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
-use Composer\Script\EventDispatcher;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Version\VersionParser;
@@ -31,6 +31,7 @@ use Composer\Package\Version\VersionParser;
* @author Ryan Weaver
* @author Jordi Boggiano
* @author Igor Wiedler
+ * @author Nils Adermann
*/
class Factory
{
@@ -117,7 +118,9 @@ class Factory
@rename($child, $dir.'/'.basename($child));
}
}
- @rmdir($oldPath);
+ if ($config->get('cache-dir') != $oldPath) {
+ @rmdir($oldPath);
+ }
}
}
}
@@ -176,11 +179,12 @@ class Factory
* @param IOInterface $io IO instance
* @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
* read from the default filename
+ * @param bool $disablePlugins Whether plugins should not be loaded
* @throws \InvalidArgumentException
* @throws \UnexpectedValueException
* @return Composer
*/
- public function createComposer(IOInterface $io, $localConfig = null)
+ public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false)
{
// load Composer configuration
if (null === $localConfig) {
@@ -216,8 +220,15 @@ class Factory
// setup process timeout
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
+ // initialize composer
+ $composer = new Composer();
+ $composer->setConfig($config);
+
+ // initialize event dispatcher
+ $dispatcher = new EventDispatcher($composer, $io);
+
// initialize repository manager
- $rm = $this->createRepositoryManager($io, $config);
+ $rm = $this->createRepositoryManager($io, $config, $dispatcher);
// load local repository
$this->addLocalRepository($rm, $vendorDir);
@@ -227,22 +238,18 @@ class Factory
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
$package = $loader->load($localConfig);
- // initialize download manager
- $dm = $this->createDownloadManager($io, $config);
-
// initialize installation manager
$im = $this->createInstallationManager();
- // initialize composer
- $composer = new Composer();
- $composer->setConfig($config);
+ // Composer composition
$composer->setPackage($package);
$composer->setRepositoryManager($rm);
- $composer->setDownloadManager($dm);
$composer->setInstallationManager($im);
- // initialize event dispatcher
- $dispatcher = new EventDispatcher($composer, $io);
+ // initialize download manager
+ $dm = $this->createDownloadManager($io, $config, $dispatcher);
+
+ $composer->setDownloadManager($dm);
$composer->setEventDispatcher($dispatcher);
// initialize autoload generator
@@ -252,6 +259,14 @@ class Factory
// 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
$this->purgePackages($rm, $im);
@@ -270,17 +285,19 @@ class Factory
/**
* @param IOInterface $io
* @param Config $config
+ * @param EventDispatcher $eventDispatcher
* @return Repository\RepositoryManager
*/
- protected function createRepositoryManager(IOInterface $io, Config $config)
+ protected function createRepositoryManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
{
- $rm = new RepositoryManager($io, $config);
+ $rm = new RepositoryManager($io, $config, $eventDispatcher);
$rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
$rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
$rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
$rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
@@ -296,12 +313,31 @@ class Factory
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
}
+ /**
+ * @param Config $config
+ * @param string $vendorDir
+ */
+ protected function createGlobalRepository(Config $config, $vendorDir)
+ {
+ if ($config->get('home') == $vendorDir) {
+ return null;
+ }
+
+ $path = $config->get('home').'/vendor/composer/installed.json';
+ if (!file_exists($path)) {
+ return null;
+ }
+
+ return new Repository\InstalledFilesystemRepository(new JsonFile($path));
+ }
+
/**
* @param IO\IOInterface $io
* @param Config $config
+ * @param EventDispatcher $eventDispatcher
* @return Downloader\DownloadManager
*/
- public function createDownloadManager(IOInterface $io, Config $config)
+ public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
{
$cache = null;
if ($config->get('cache-files-ttl') > 0) {
@@ -325,10 +361,12 @@ class Factory
$dm->setDownloader('git', new Downloader\GitDownloader($io, $config));
$dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config));
$dm->setDownloader('hg', new Downloader\HgDownloader($io, $config));
- $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $cache));
- $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $cache));
- $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $cache));
- $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $cache));
+ $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
+ $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache));
+ $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache));
+ $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache));
+ $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache));
+ $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache));
return $dm;
}
@@ -342,7 +380,9 @@ class Factory
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
{
if (null === $dm) {
- $dm = $this->createDownloadManager(new IO\NullIO(), $config);
+ $io = new IO\NullIO();
+ $io->loadConfiguration($config);
+ $dm = $this->createDownloadManager($io, $config);
}
$am = new Archiver\ArchiveManager($dm);
@@ -351,6 +391,14 @@ class Factory
return $am;
}
+ /**
+ * @return Plugin\PluginManager
+ */
+ protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
+ {
+ return new Plugin\PluginManager($composer, $io, $globalRepository);
+ }
+
/**
* @return Installer\InstallationManager
*/
@@ -368,7 +416,7 @@ class Factory
{
$im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
$im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
- $im->addInstaller(new Installer\InstallerInstaller($io, $composer));
+ $im->addInstaller(new Installer\PluginInstaller($io, $composer));
$im->addInstaller(new Installer\MetapackageInstaller($io));
}
@@ -390,12 +438,13 @@ class Factory
* @param IOInterface $io IO instance
* @param mixed $config either a configuration array or a filename to read from, if null it will read from
* the default filename
+ * @param bool $disablePlugins Whether plugins should not be loaded
* @return Composer
*/
- public static function create(IOInterface $io, $config = null)
+ public static function create(IOInterface $io, $config = null, $disablePlugins = false)
{
$factory = new static();
- return $factory->createComposer($io, $config);
+ return $factory->createComposer($io, $config, $disablePlugins);
}
}
diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php
index a9cae3b49..29cae4f07 100644
--- a/src/Composer/IO/BaseIO.php
+++ b/src/Composer/IO/BaseIO.php
@@ -12,9 +12,6 @@
namespace Composer\IO;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Helper\HelperSet;
use Composer\Config;
abstract class BaseIO implements IOInterface
diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php
index b17da6128..f117ce974 100644
--- a/src/Composer/IO/IOInterface.php
+++ b/src/Composer/IO/IOInterface.php
@@ -71,7 +71,7 @@ interface IOInterface
* @param bool $newline Whether to add a newline or not
* @param integer $size The size of line
*/
- public function overwrite($messages, $newline = true, $size = 80);
+ public function overwrite($messages, $newline = true, $size = null);
/**
* Asks a question to the user.
diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php
index 8f424cc39..2bbcefe7d 100644
--- a/src/Composer/Installer.php
+++ b/src/Composer/Installer.php
@@ -24,6 +24,7 @@ use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\Config;
use Composer\Installer\NoopInstaller;
@@ -41,13 +42,13 @@ use Composer\Repository\InstalledFilesystemRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
-use Composer\Script\EventDispatcher;
use Composer\Script\ScriptEvents;
/**
* @author Jordi Boggiano
* @author Beau Simensen
* @author Konstantin Kudryashov
+ * @author Nils Adermann
*/
class Installer
{
@@ -105,6 +106,7 @@ class Installer
protected $update = false;
protected $runScripts = true;
protected $updateWhitelist = null;
+ protected $whitelistDependencies = false;
/**
* @var array
@@ -144,6 +146,8 @@ class Installer
/**
* Run installation (or update)
+ *
+ * @return int 0 on success or a positive error code on failure
*/
public function run()
{
@@ -169,13 +173,15 @@ class Installer
unset($devRepo, $package);
// end BC
- if ($this->preferSource) {
- $this->downloadManager->setPreferSource(true);
- }
- if ($this->preferDist) {
- $this->downloadManager->setPreferDist(true);
+ if ($this->runScripts) {
+ // dispatch pre event
+ $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
+ $this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode);
}
+ $this->downloadManager->setPreferSource($this->preferSource);
+ $this->downloadManager->setPreferDist($this->preferDist);
+
// clone root package to have one in the installed repo that does not require anything
// we don't want it to be uninstallable, but its requirements should not conflict
// with the lock file for example
@@ -199,16 +205,11 @@ class Installer
$aliases = $this->getRootAliases();
$this->aliasPlatformPackages($platformRepo, $aliases);
- if ($this->runScripts) {
- // dispatch pre event
- $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
- $this->eventDispatcher->dispatchCommandEvent($eventName, $this->devMode);
- }
-
try {
$this->suggestedPackages = array();
- if (!$this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $this->devMode)) {
- return false;
+ $res = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $this->devMode);
+ if ($res !== 0) {
+ return $res;
}
} catch (\Exception $e) {
$this->installationManager->notifyInstalls();
@@ -288,7 +289,7 @@ class Installer
}
}
- return true;
+ return 0;
}
protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $withDevReqs)
@@ -450,7 +451,7 @@ class Installer
$this->io->write('Your requirements could not be resolved to an installable set of packages.');
$this->io->write($e->getMessage());
- return false;
+ return max(1, $e->getCode());
}
// force dev packages to be updated if we update or install from a (potentially new) lock
@@ -461,7 +462,7 @@ class Installer
$this->io->write('Nothing to install or update');
}
- $operations = $this->moveCustomInstallersToFront($operations);
+ $operations = $this->movePluginsToFront($operations);
foreach ($operations as $operation) {
// collect suggestions
@@ -535,12 +536,11 @@ class Installer
}
}
- return true;
+ return 0;
}
-
/**
- * Workaround: if your packages depend on custom installers, we must be sure
+ * Workaround: if your packages depend on plugins, we must be sure
* that those are installed / updated first; else it would lead to packages
* being installed multiple times in different folders, when running Composer
* twice.
@@ -549,22 +549,22 @@ class Installer
* it at least fixes the symptoms and makes usage of composer possible (again)
* in such scenarios.
*
- * @param OperationInterface[] $operations
+ * @param OperationInterface[] $operations
* @return OperationInterface[] reordered operation list
*/
- private function moveCustomInstallersToFront(array $operations)
+ private function movePluginsToFront(array $operations)
{
$installerOps = array();
foreach ($operations as $idx => $op) {
if ($op instanceof InstallOperation) {
$package = $op->getPackage();
- } else if ($op instanceof UpdateOperation) {
+ } elseif ($op instanceof UpdateOperation) {
$package = $op->getTargetPackage();
} else {
continue;
}
- if ($package->getRequires() === array() && $package->getType() === 'composer-installer') {
+ if ($package->getRequires() === array() && ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer')) {
$installerOps[] = $op;
unset($operations[$idx]);
}
@@ -835,7 +835,7 @@ class Installer
$depPackages = $pool->whatProvides($packageName);
if (count($depPackages) == 0 && !in_array($packageName, $requiredPackageNames) && !in_array($packageName, array('nothing', 'lock'))) {
- $this->io->write('Package "' . $packageName . '" listed for update is not installed. Ignoring.');
+ $this->io->write('Package "' . $packageName . '" listed for update is not installed. Ignoring.');
}
foreach ($depPackages as $depPackage) {
@@ -851,11 +851,12 @@ class Installer
$seen[$package->getId()] = true;
$this->updateWhitelist[$package->getName()] = true;
- $requires = $package->getRequires();
- if ($devMode) {
- $requires = array_merge($requires, $package->getDevRequires());
+ if (!$this->whitelistDependencies) {
+ continue;
}
+ $requires = $package->getRequires();
+
foreach ($requires as $require) {
$requirePackages = $pool->whatProvides($require->getTarget());
@@ -1055,7 +1056,20 @@ class Installer
}
/**
- * Disables custom installers.
+ * Should dependencies of whitelisted packages be updated recursively?
+ *
+ * @param boolean $updateDependencies
+ * @return Installer
+ */
+ public function setWhitelistDependencies($updateDependencies = true)
+ {
+ $this->whitelistDependencies = (boolean) $updateDependencies;
+
+ return $this;
+ }
+
+ /**
+ * Disables plugins.
*
* Call this if you want to ensure that third-party code never gets
* executed. The default is to automatically install, and execute
@@ -1063,9 +1077,9 @@ class Installer
*
* @return Installer
*/
- public function disableCustomInstallers()
+ public function disablePlugins()
{
- $this->installationManager->disableCustomInstallers();
+ $this->installationManager->disablePlugins();
return $this;
}
diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php
index 406ee1166..21b16e2fd 100644
--- a/src/Composer/Installer/InstallationManager.php
+++ b/src/Composer/Installer/InstallationManager.php
@@ -14,6 +14,7 @@ namespace Composer\Installer;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
+use Composer\Plugin\PluginInstaller;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
@@ -29,6 +30,7 @@ use Composer\Util\StreamContextFactory;
*
* @author Konstantin Kudryashov
* @author Jordi Boggiano
+ * @author Nils Adermann
*/
class InstallationManager
{
@@ -66,16 +68,16 @@ class InstallationManager
}
/**
- * Disables custom installers.
+ * Disables plugins.
*
- * We prevent any custom installers from being instantiated by simply
+ * We prevent any plugins from being instantiated by simply
* deactivating the installer for them. This ensure that no third-party
* code is ever executed.
*/
- public function disableCustomInstallers()
+ public function disablePlugins()
{
foreach ($this->installers as $i => $installer) {
- if (!$installer instanceof InstallerInstaller) {
+ if (!$installer instanceof PluginInstaller) {
continue;
}
diff --git a/src/Composer/Installer/InstallerInstaller.php b/src/Composer/Installer/InstallerInstaller.php
deleted file mode 100644
index a833b68d2..000000000
--- a/src/Composer/Installer/InstallerInstaller.php
+++ /dev/null
@@ -1,104 +0,0 @@
-
- * Jordi Boggiano
- *
- * 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\Package\Package;
-use Composer\IO\IOInterface;
-use Composer\Repository\InstalledRepositoryInterface;
-use Composer\Package\PackageInterface;
-
-/**
- * Installer installation manager.
- *
- * @author Jordi Boggiano
- */
-class InstallerInstaller extends LibraryInstaller
-{
- private $installationManager;
- private static $classCounter = 0;
-
- /**
- * Initializes Installer installer.
- *
- * @param IOInterface $io
- * @param Composer $composer
- * @param string $type
- */
- public function __construct(IOInterface $io, Composer $composer, $type = 'library')
- {
- parent::__construct($io, $composer, 'composer-installer');
- $this->installationManager = $composer->getInstallationManager();
-
- $repo = $composer->getRepositoryManager()->getLocalRepository();
- foreach ($repo->getPackages() as $package) {
- if ('composer-installer' === $package->getType()) {
- $this->registerInstaller($package);
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
- {
- $extra = $package->getExtra();
- if (empty($extra['class'])) {
- throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.');
- }
-
- parent::install($repo, $package);
- $this->registerInstaller($package);
- }
-
- /**
- * {@inheritDoc}
- */
- public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
- {
- $extra = $target->getExtra();
- if (empty($extra['class'])) {
- throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.');
- }
-
- parent::update($repo, $initial, $target);
- $this->registerInstaller($target);
- }
-
- private function registerInstaller(PackageInterface $package)
- {
- $downloadPath = $this->getInstallPath($package);
-
- $extra = $package->getExtra();
- $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']);
-
- $generator = $this->composer->getAutoloadGenerator();
- $map = $generator->parseAutoloads(array(array($package, $downloadPath)), new Package('dummy', '1.0.0.0', '1.0.0'));
- $classLoader = $generator->createLoader($map);
- $classLoader->register();
-
- foreach ($classes as $class) {
- if (class_exists($class, false)) {
- $code = file_get_contents($classLoader->findFile($class));
- $code = preg_replace('{^(\s*)class\s+(\S+)}mi', '$1class $2_composer_tmp'.self::$classCounter, $code);
- eval('?>'.$code);
- $class .= '_composer_tmp'.self::$classCounter;
- self::$classCounter++;
- }
-
- $installer = new $class($this->io, $this->composer);
- $this->installationManager->addInstaller($installer);
- }
- }
-}
diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php
index 281ffa11a..b1677cec2 100644
--- a/src/Composer/Installer/LibraryInstaller.php
+++ b/src/Composer/Installer/LibraryInstaller.php
@@ -14,7 +14,6 @@ namespace Composer\Installer;
use Composer\Composer;
use Composer\IO\IOInterface;
-use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
@@ -41,15 +40,16 @@ class LibraryInstaller implements InstallerInterface
* @param IOInterface $io
* @param Composer $composer
* @param string $type
+ * @param Filesystem $filesystem
*/
- public function __construct(IOInterface $io, Composer $composer, $type = 'library')
+ public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null)
{
$this->composer = $composer;
$this->downloadManager = $composer->getDownloadManager();
$this->io = $io;
$this->type = $type;
- $this->filesystem = new Filesystem();
+ $this->filesystem = $filesystem ?: new Filesystem();
$this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
$this->binDir = rtrim($composer->getConfig()->get('bin-dir'), '/');
}
@@ -157,8 +157,23 @@ class LibraryInstaller implements InstallerInterface
protected function updateCode(PackageInterface $initial, PackageInterface $target)
{
- $downloadPath = $this->getInstallPath($initial);
- $this->downloadManager->update($initial, $target, $downloadPath);
+ $initialDownloadPath = $this->getInstallPath($initial);
+ $targetDownloadPath = $this->getInstallPath($target);
+ if ($targetDownloadPath !== $initialDownloadPath) {
+ // if the target and initial dirs intersect, we force a remove + install
+ // to avoid the rename wiping the target dir as part of the initial dir cleanup
+ if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath
+ || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath
+ ) {
+ $this->removeCode($initial);
+ $this->installCode($target);
+
+ return;
+ }
+
+ $this->filesystem->rename($initialDownloadPath, $targetDownloadPath);
+ }
+ $this->downloadManager->update($initial, $target, $targetDownloadPath);
}
protected function removeCode(PackageInterface $package)
@@ -185,6 +200,12 @@ class LibraryInstaller implements InstallerInterface
continue;
}
+ // in case a custom installer returned a relative path for the
+ // $package, we can now safely turn it into a absolute path (as we
+ // already checked the binary's existence). The following helpers
+ // will require absolute paths to work properly.
+ $binPath = realpath($binPath);
+
$this->initializeBinDir();
$link = $this->binDir.'/'.basename($bin);
if (file_exists($link)) {
@@ -192,7 +213,7 @@ class LibraryInstaller implements InstallerInterface
// likely leftover from a previous install, make sure
// that the target is still executable in case this
// is a fresh install of the vendor.
- chmod($link, 0777 & ~umask());
+ @chmod($link, 0777 & ~umask());
}
$this->io->write(' Skipped installation of '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
continue;
@@ -201,7 +222,7 @@ class LibraryInstaller implements InstallerInterface
// add unixy support for cygwin and similar environments
if ('.bat' !== substr($binPath, -4)) {
file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
- chmod($link, 0777 & ~umask());
+ @chmod($link, 0777 & ~umask());
$link .= '.bat';
if (file_exists($link)) {
$this->io->write(' Skipped installation of '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed');
@@ -225,7 +246,7 @@ class LibraryInstaller implements InstallerInterface
}
chdir($cwd);
}
- chmod($link, 0777 & ~umask());
+ @chmod($link, 0777 & ~umask());
}
}
diff --git a/src/Composer/Installer/PearInstaller.php b/src/Composer/Installer/PearInstaller.php
index 1a8f674af..b44f61f14 100644
--- a/src/Composer/Installer/PearInstaller.php
+++ b/src/Composer/Installer/PearInstaller.php
@@ -99,9 +99,9 @@ class PearInstaller extends LibraryInstaller
{
parent::initializeBinDir();
file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode());
- chmod($this->binDir.'/composer-php', 0777);
+ @chmod($this->binDir.'/composer-php', 0777);
file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode());
- chmod($this->binDir.'/composer-php.bat', 0777);
+ @chmod($this->binDir.'/composer-php.bat', 0777);
}
protected function generateWindowsProxyCode($bin, $link)
diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php
new file mode 100644
index 000000000..61c5a2823
--- /dev/null
+++ b/src/Composer/Installer/PluginInstaller.php
@@ -0,0 +1,81 @@
+
+ * Jordi Boggiano
+ *
+ * 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\Package\Package;
+use Composer\IO\IOInterface;
+use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Package\PackageInterface;
+
+/**
+ * Installer for plugin packages
+ *
+ * @author Jordi Boggiano
+ * @author Nils Adermann
+ */
+class PluginInstaller extends LibraryInstaller
+{
+ private $installationManager;
+ private static $classCounter = 0;
+
+ /**
+ * Initializes Plugin installer.
+ *
+ * @param IOInterface $io
+ * @param Composer $composer
+ * @param string $type
+ */
+ public function __construct(IOInterface $io, Composer $composer, $type = 'library')
+ {
+ parent::__construct($io, $composer, 'composer-plugin');
+ $this->installationManager = $composer->getInstallationManager();
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function supports($packageType)
+ {
+ return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
+ {
+ $extra = $package->getExtra();
+ if (empty($extra['class'])) {
+ throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
+ }
+
+ parent::install($repo, $package);
+ $this->composer->getPluginManager()->registerPackage($package);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+ {
+ $extra = $target->getExtra();
+ if (empty($extra['class'])) {
+ throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
+ }
+
+ parent::update($repo, $initial, $target);
+ $this->composer->getPluginManager()->registerPackage($target);
+ }
+}
diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php
index 5d98722c8..c79238b36 100644
--- a/src/Composer/Installer/ProjectInstaller.php
+++ b/src/Composer/Installer/ProjectInstaller.php
@@ -15,6 +15,7 @@ namespace Composer\Installer;
use Composer\Package\PackageInterface;
use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Util\Filesystem;
/**
* Project Installer is used to install a single package into a directory as
@@ -26,11 +27,13 @@ class ProjectInstaller implements InstallerInterface
{
private $installPath;
private $downloadManager;
+ private $filesystem;
public function __construct($installPath, DownloadManager $dm)
{
$this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/';
$this->downloadManager = $dm;
+ $this->filesystem = new Filesystem;
}
/**
@@ -58,7 +61,7 @@ class ProjectInstaller implements InstallerInterface
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
$installPath = $this->installPath;
- if (file_exists($installPath) && (count(glob($installPath.'*')) || (count(glob($installPath.'.*')) > 2))) {
+ if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
throw new \InvalidArgumentException("Project directory $installPath is not empty.");
}
if (!is_dir($installPath)) {
diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php
index f7e1a2126..70b97b18d 100644
--- a/src/Composer/Json/JsonFile.php
+++ b/src/Composer/Json/JsonFile.php
@@ -12,7 +12,6 @@
namespace Composer\Json;
-use Composer\Composer;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
@@ -117,7 +116,21 @@ class JsonFile
);
}
}
- file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : ''));
+
+ $retries = 3;
+ while ($retries--) {
+ try {
+ file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : ''));
+ break;
+ } catch (\Exception $e) {
+ if ($retries) {
+ usleep(500000);
+ continue;
+ }
+
+ throw $e;
+ }
+ }
}
/**
diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php
index 6c6be7fda..80865245e 100644
--- a/src/Composer/Package/Archiver/ArchiveManager.php
+++ b/src/Composer/Package/Archiver/ArchiveManager.php
@@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackage;
use Composer\Util\Filesystem;
+use Composer\Json\JsonFile;
/**
* @author Matthieu Moquet
@@ -83,9 +84,11 @@ class ArchiveManager
$nameParts[] = substr(sha1($package->getSourceReference()), 0, 6);
}
- return implode('-', array_filter($nameParts, function ($p) {
+ $name = implode('-', array_filter($nameParts, function ($p) {
return !empty($p);
}));
+
+ return str_replace('/', '-', $name);
}
/**
@@ -139,12 +142,21 @@ class ArchiveManager
// Download sources
$this->downloadManager->download($package, $sourcePath);
+
+ // Check exclude from downloaded composer.json
+ if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
+ $jsonFile = new JsonFile($composerJsonPath);
+ $jsonData = $jsonFile->read();
+ if (!empty($jsonData['archive']['exclude'])) {
+ $package->setArchiveExcludes($jsonData['archive']['exclude']);
+ }
+ }
}
// Create the archive
$archivePath = $usableArchiver->archive($sourcePath, $target, $format, $package->getArchiveExcludes());
- //cleanup temporary download
+ // cleanup temporary download
if (!$package instanceof RootPackage) {
$filesystem->removeDirectory($sourcePath);
}
diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php
index 0fea4578d..1754aae73 100644
--- a/src/Composer/Package/Dumper/ArrayDumper.php
+++ b/src/Composer/Package/Dumper/ArrayDumper.php
@@ -16,7 +16,6 @@ use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackageInterface;
-use Composer\Package\Link;
/**
* @author Konstantin Kudryashiv
diff --git a/src/Composer/Package/LinkConstraint/VersionConstraint.php b/src/Composer/Package/LinkConstraint/VersionConstraint.php
index 0e8a8c3b7..cd2336227 100644
--- a/src/Composer/Package/LinkConstraint/VersionConstraint.php
+++ b/src/Composer/Package/LinkConstraint/VersionConstraint.php
@@ -66,6 +66,22 @@ class VersionConstraint extends SpecificConstraint
* @return bool
*/
public function matchSpecific(VersionConstraint $provider, $compareBranches = false)
+ {
+ static $cache = array();
+ if (isset($cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches])) {
+ return $cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches];
+ }
+
+ return $cache[$this->operator][$this->version][$provider->operator][$provider->version][$compareBranches] =
+ $this->doMatchSpecific($provider, $compareBranches);
+ }
+
+ /**
+ * @param VersionConstraint $provider
+ * @param bool $compareBranches
+ * @return bool
+ */
+ private function doMatchSpecific(VersionConstraint $provider, $compareBranches = false)
{
$noEqualOp = str_replace('=', '', $this->operator);
$providerNoEqualOp = str_replace('=', '', $provider->operator);
diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php
index f39e21d0f..ee13b77a5 100644
--- a/src/Composer/Package/Version/VersionParser.php
+++ b/src/Composer/Package/Version/VersionParser.php
@@ -275,7 +275,14 @@ class VersionParser
// version, to ensure that unstable instances of the current version are allowed.
// however, if a stability suffix is added to the constraint, then a >= match on the current version is
// used instead
- if (preg_match('{^~(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex.'?$}i', $constraint, $matches)) {
+ if (preg_match('{^~>?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex.'?$}i', $constraint, $matches)) {
+ if (substr($constraint, 0, 2) === '~>') {
+ throw new \UnexpectedValueException(
+ 'Could not parse version constraint '.$constraint.': '.
+ 'Invalid operator "~>", you probably meant to use the "~" operator'
+ );
+ }
+
// Work out which position in the version we are operating at
if (isset($matches[4]) && '' !== $matches[4]) {
$position = 4;
@@ -368,9 +375,10 @@ class VersionParser
*
* Support function for {@link parseConstraint()}
*
- * @param array $matches Array with version parts in array indexes 1,2,3,4
- * @param int $position 1,2,3,4 - which segment of the version to decrement
- * @param string $pad The string to pad version parts after $position
+ * @param array $matches Array with version parts in array indexes 1,2,3,4
+ * @param int $position 1,2,3,4 - which segment of the version to decrement
+ * @param int $increment
+ * @param string $pad The string to pad version parts after $position
* @return string The new version
*/
private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
@@ -378,7 +386,7 @@ class VersionParser
for ($i = 4; $i > 0; $i--) {
if ($i > $position) {
$matches[$i] = $pad;
- } else if ($i == $position && $increment) {
+ } elseif ($i == $position && $increment) {
$matches[$i] += $increment;
// If $matches[$i] was 0, carry the decrement
if ($matches[$i] < 0) {
diff --git a/src/Composer/Plugin/CommandEvent.php b/src/Composer/Plugin/CommandEvent.php
new file mode 100644
index 000000000..0f75bed9e
--- /dev/null
+++ b/src/Composer/Plugin/CommandEvent.php
@@ -0,0 +1,86 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+use Composer\EventDispatcher\Event;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * An event for all commands.
+ *
+ * @author Nils Adermann
+ */
+class CommandEvent extends Event
+{
+ /**
+ * @var string
+ */
+ private $commandName;
+
+ /**
+ * @var InputInterface
+ */
+ private $input;
+
+ /**
+ * @var OutputInterface
+ */
+ private $output;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name
+ * @param string $commandName The command name
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ public function __construct($name, $commandName, $input, $output)
+ {
+ parent::__construct($name);
+ $this->commandName = $commandName;
+ $this->input = $input;
+ $this->output = $output;
+ }
+
+ /**
+ * Returns the command input interface
+ *
+ * @return InputInterface
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Retrieves the command output interface
+ *
+ * @return OutputInterface
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * Retrieves the name of the command being run
+ *
+ * @return string
+ */
+ public function getCommandName()
+ {
+ return $this->commandName;
+ }
+}
diff --git a/src/Composer/Plugin/PluginEvents.php b/src/Composer/Plugin/PluginEvents.php
new file mode 100644
index 000000000..ce9efdef2
--- /dev/null
+++ b/src/Composer/Plugin/PluginEvents.php
@@ -0,0 +1,41 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+/**
+ * The Plugin Events.
+ *
+ * @author Nils Adermann
+ */
+class PluginEvents
+{
+ /**
+ * The COMMAND event occurs as a command begins
+ *
+ * The event listener method receives a
+ * Composer\Plugin\CommandEvent instance.
+ *
+ * @var string
+ */
+ const COMMAND = 'command';
+
+ /**
+ * The PRE_FILE_DOWNLOAD event occurs before downloading a file
+ *
+ * The event listener method receives a
+ * Composer\Plugin\PreFileDownloadEvent instance.
+ *
+ * @var string
+ */
+ const PRE_FILE_DOWNLOAD = 'pre-file-download';
+}
diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php
new file mode 100644
index 000000000..dea5828c1
--- /dev/null
+++ b/src/Composer/Plugin/PluginInterface.php
@@ -0,0 +1,39 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+use Composer\Composer;
+use Composer\IO\IOInterface;
+
+/**
+ * Plugin interface
+ *
+ * @author Nils Adermann
+ */
+interface PluginInterface
+{
+ /**
+ * Version number of the fake composer-plugin-api package
+ *
+ * @var string
+ */
+ const PLUGIN_API_VERSION = '1.0.0';
+
+ /**
+ * Apply plugin modifications to composer
+ *
+ * @param Composer $composer
+ * @param IOInterface $io
+ */
+ public function activate(Composer $composer, IOInterface $io);
+}
diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php
new file mode 100644
index 000000000..d7c3ae07a
--- /dev/null
+++ b/src/Composer/Plugin/PluginManager.php
@@ -0,0 +1,259 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+use Composer\Composer;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\IO\IOInterface;
+use Composer\Package\Package;
+use Composer\Package\Version\VersionParser;
+use Composer\Repository\RepositoryInterface;
+use Composer\Package\AliasPackage;
+use Composer\Package\PackageInterface;
+use Composer\Package\Link;
+use Composer\Package\LinkConstraint\VersionConstraint;
+use Composer\DependencyResolver\Pool;
+
+/**
+ * Plugin manager
+ *
+ * @author Nils Adermann
+ */
+class PluginManager
+{
+ protected $composer;
+ protected $io;
+ protected $globalRepository;
+ protected $versionParser;
+
+ protected $plugins = array();
+
+ private static $classCounter = 0;
+
+ /**
+ * Initializes plugin manager
+ *
+ * @param Composer $composer
+ * @param IOInterface $io
+ * @param RepositoryInterface $globalRepository
+ */
+ public function __construct(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
+ {
+ $this->composer = $composer;
+ $this->io = $io;
+ $this->globalRepository = $globalRepository;
+ $this->versionParser = new VersionParser();
+ }
+
+ /**
+ * Loads all plugins from currently installed plugin packages
+ */
+ public function loadInstalledPlugins()
+ {
+ $repo = $this->composer->getRepositoryManager()->getLocalRepository();
+
+ if ($repo) {
+ $this->loadRepository($repo);
+ }
+ if ($this->globalRepository) {
+ $this->loadRepository($this->globalRepository);
+ }
+ }
+
+ /**
+ * Adds a plugin, activates it and registers it with the event dispatcher
+ *
+ * @param PluginInterface $plugin plugin instance
+ */
+ public function addPlugin(PluginInterface $plugin)
+ {
+ $this->plugins[] = $plugin;
+ $plugin->activate($this->composer, $this->io);
+
+ if ($plugin instanceof EventSubscriberInterface) {
+ $this->composer->getEventDispatcher()->addSubscriber($plugin);
+ }
+ }
+
+ /**
+ * Gets all currently active plugin instances
+ *
+ * @return array plugins
+ */
+ public function getPlugins()
+ {
+ return $this->plugins;
+ }
+
+ /**
+ * Load all plugins and installers from a repository
+ *
+ * Note that plugins in the specified repository that rely on events that
+ * have fired prior to loading will be missed. This means you likely want to
+ * call this method as early as possible.
+ *
+ * @param RepositoryInterface $repo Repository to scan for plugins to install
+ */
+ public function loadRepository(RepositoryInterface $repo)
+ {
+ foreach ($repo->getPackages() as $package) {
+ if ($package instanceof AliasPackage) {
+ continue;
+ }
+ if ('composer-plugin' === $package->getType()) {
+ $requiresComposer = null;
+ foreach ($package->getRequires() as $link) {
+ if ($link->getTarget() == 'composer-plugin-api') {
+ $requiresComposer = $link->getConstraint();
+ }
+ }
+
+ if (!$requiresComposer) {
+ throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package.");
+ }
+
+ if (!$requiresComposer->matches(new VersionConstraint('==', $this->versionParser->normalize(PluginInterface::PLUGIN_API_VERSION)))) {
+ $this->io->write("The plugin ".$package->getName()." requires a version of composer-plugin-api that does not match your composer installation. You may need to run composer update with the '--no-plugins' option.");
+ }
+
+ $this->registerPackage($package);
+ }
+ // Backward compatibility
+ if ('composer-installer' === $package->getType()) {
+ $this->registerPackage($package);
+ }
+ }
+ }
+
+ /**
+ * Recursively generates a map of package names to packages for all deps
+ *
+ * @param Pool $pool Package pool of installed packages
+ * @param array $collected Current state of the map for recursion
+ * @param PackageInterface $package The package to analyze
+ *
+ * @return array Map of package names to packages
+ */
+ protected function collectDependencies(Pool $pool, array $collected, PackageInterface $package)
+ {
+ $requires = array_merge(
+ $package->getRequires(),
+ $package->getDevRequires()
+ );
+
+ foreach ($requires as $requireLink) {
+ $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink);
+ if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) {
+ $collected[$requiredPackage->getName()] = $requiredPackage;
+ $collected = $this->collectDependencies($pool, $collected, $requiredPackage);
+ }
+ }
+
+ return $collected;
+ }
+
+ /**
+ * Resolves a package link to a package in the installed pool
+ *
+ * Since dependencies are already installed this should always find one.
+ *
+ * @param Pool $pool Pool of installed packages only
+ * @param Link $link Package link to look up
+ *
+ * @return PackageInterface|null The found package
+ */
+ protected function lookupInstalledPackage(Pool $pool, Link $link)
+ {
+ $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint());
+
+ return (!empty($packages)) ? $packages[0] : null;
+ }
+
+ /**
+ * Register a plugin package, activate it etc.
+ *
+ * If it's of type composer-installer it is registered as an installer
+ * instead for BC
+ *
+ * @param PackageInterface $package
+ */
+ public function registerPackage(PackageInterface $package)
+ {
+ $oldInstallerPlugin = ($package->getType() === 'composer-installer');
+
+ $extra = $package->getExtra();
+ if (empty($extra['class'])) {
+ throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
+ }
+ $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']);
+
+ $pool = new Pool('dev');
+ $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
+ $pool->addRepository($localRepo);
+ if ($this->globalRepository) {
+ $pool->addRepository($this->globalRepository);
+ }
+
+ $autoloadPackages = array($package->getName() => $package);
+ $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package);
+
+ $generator = $this->composer->getAutoloadGenerator();
+ $autoloads = array();
+ foreach ($autoloadPackages as $autoloadPackage) {
+ $downloadPath = $this->getInstallPath($autoloadPackage, ($this->globalRepository && $this->globalRepository->hasPackage($autoloadPackage)));
+ $autoloads[] = array($autoloadPackage, $downloadPath);
+ }
+
+ $map = $generator->parseAutoloads($autoloads, new Package('dummy', '1.0.0.0', '1.0.0'));
+ $classLoader = $generator->createLoader($map);
+ $classLoader->register();
+
+ foreach ($classes as $class) {
+ if (class_exists($class, false)) {
+ $code = file_get_contents($classLoader->findFile($class));
+ $code = preg_replace('{^(\s*)class\s+(\S+)}mi', '$1class $2_composer_tmp'.self::$classCounter, $code);
+ eval('?>'.$code);
+ $class .= '_composer_tmp'.self::$classCounter;
+ self::$classCounter++;
+ }
+
+ if ($oldInstallerPlugin) {
+ $installer = new $class($this->io, $this->composer);
+ $this->composer->getInstallationManager()->addInstaller($installer);
+ } else {
+ $plugin = new $class();
+ $this->addPlugin($plugin);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the path a package is installed to.
+ *
+ * @param PackageInterface $package
+ * @param bool $global Whether this is a global package
+ *
+ * @return string Install path
+ */
+ public function getInstallPath(PackageInterface $package, $global = false)
+ {
+ if (!$global) {
+ return $this->composer->getInstallationManager()->getInstallPath($package);
+ }
+
+ $targetDir = $package->getTargetDir();
+ $vendorDir = $this->composer->getConfig()->get('home').'/vendor';
+
+ return ($vendorDir ? $vendorDir.'/' : '').$package->getPrettyName().($targetDir ? '/'.$targetDir : '');
+ }
+}
diff --git a/src/Composer/Plugin/PreFileDownloadEvent.php b/src/Composer/Plugin/PreFileDownloadEvent.php
new file mode 100644
index 000000000..7ae6821ce
--- /dev/null
+++ b/src/Composer/Plugin/PreFileDownloadEvent.php
@@ -0,0 +1,78 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Plugin;
+
+use Composer\EventDispatcher\Event;
+use Composer\Util\RemoteFilesystem;
+
+/**
+ * The pre file download event.
+ *
+ * @author Nils Adermann
+ */
+class PreFileDownloadEvent extends Event
+{
+ /**
+ * @var RemoteFilesystem
+ */
+ private $rfs;
+
+ /**
+ * @var string
+ */
+ private $processedUrl;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name
+ * @param RemoteFilesystem $rfs
+ * @param string $processedUrl
+ */
+ public function __construct($name, RemoteFilesystem $rfs, $processedUrl)
+ {
+ parent::__construct($name);
+ $this->rfs = $rfs;
+ $this->processedUrl = $processedUrl;
+ }
+
+ /**
+ * Returns the remote filesystem
+ *
+ * @return RemoteFilesystem
+ */
+ public function getRemoteFilesystem()
+ {
+ return $this->rfs;
+ }
+
+ /**
+ * Sets the remote filesystem
+ *
+ * @param RemoteFilesystem $rfs
+ */
+ public function setRemoteFilesystem(RemoteFilesystem $rfs)
+ {
+ $this->rfs = $rfs;
+ }
+
+ /**
+ * Retrieves the processed URL this remote filesystem will be used for
+ *
+ * @return string
+ */
+ public function getProcessedUrl()
+ {
+ return $this->processedUrl;
+ }
+}
diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php
index 2b6dd2385..c3b5b2b34 100644
--- a/src/Composer/Repository/ComposerRepository.php
+++ b/src/Composer/Repository/ComposerRepository.php
@@ -23,6 +23,9 @@ use Composer\Cache;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Util\RemoteFilesystem;
+use Composer\Plugin\PluginEvents;
+use Composer\Plugin\PreFileDownloadEvent;
+use Composer\EventDispatcher\EventDispatcher;
/**
* @author Jordi Boggiano
@@ -46,12 +49,13 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
protected $loader;
protected $rootAliases;
protected $allowSslDowngrade = false;
+ protected $eventDispatcher;
private $rawData;
private $minimalPackages;
private $degradedMode = false;
private $rootData;
- public function __construct(array $repoConfig, IOInterface $io, Config $config)
+ public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
{
if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) {
// assume http as the default protocol
@@ -64,7 +68,7 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
}
$urlBits = parse_url($repoConfig['url']);
- if (empty($urlBits['scheme']) || empty($urlBits['host'])) {
+ if ($urlBits === false || empty($urlBits['scheme'])) {
throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
}
@@ -83,6 +87,7 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
$this->loader = new ArrayLoader();
$this->rfs = new RemoteFilesystem($this->io, $this->options);
+ $this->eventDispatcher = $eventDispatcher;
}
public function setRootAliases(array $rootAliases)
@@ -559,7 +564,11 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
$retries = 3;
while ($retries--) {
try {
- $json = $this->rfs->getContents($filename, $filename, false);
+ $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename);
+ if ($this->eventDispatcher) {
+ $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
+ }
+ $json = $preFileDownloadEvent->getRemoteFilesystem()->getContents($filename, $filename, false);
if ($sha256 && $sha256 !== hash('sha256', $json)) {
if ($retries) {
usleep(100000);
diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php
index 82097e26a..a106385a5 100644
--- a/src/Composer/Repository/PearRepository.php
+++ b/src/Composer/Repository/PearRepository.php
@@ -17,6 +17,7 @@ use Composer\Package\Version\VersionParser;
use Composer\Repository\Pear\ChannelReader;
use Composer\Package\CompletePackage;
use Composer\Repository\Pear\ChannelInfo;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\Link;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Util\RemoteFilesystem;
@@ -43,7 +44,7 @@ class PearRepository extends ArrayRepository
*/
private $vendorAlias;
- public function __construct(array $repoConfig, IOInterface $io, Config $config, RemoteFilesystem $rfs = null)
+ public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null)
{
if (!preg_match('{^https?://}', $repoConfig['url'])) {
$repoConfig['url'] = 'http://'.$repoConfig['url'];
diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php
index 3f208aae0..2b462fca9 100644
--- a/src/Composer/Repository/PlatformRepository.php
+++ b/src/Composer/Repository/PlatformRepository.php
@@ -14,6 +14,7 @@ namespace Composer\Repository;
use Composer\Package\CompletePackage;
use Composer\Package\Version\VersionParser;
+use Composer\Plugin\PluginInterface;
/**
* @author Jordi Boggiano
@@ -28,6 +29,12 @@ class PlatformRepository extends ArrayRepository
$versionParser = new VersionParser();
+ $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
+ $version = $versionParser->normalize($prettyVersion);
+ $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion);
+ $composerPluginApi->setDescription('The Composer Plugin API');
+ parent::addPackage($composerPluginApi);
+
try {
$prettyVersion = PHP_VERSION;
$version = $versionParser->normalize($prettyVersion);
@@ -63,7 +70,8 @@ class PlatformRepository extends ArrayRepository
$version = $versionParser->normalize($prettyVersion);
}
- $ext = new CompletePackage('ext-'.$name, $version, $prettyVersion);
+ $packageName = $this->buildPackageName($name);
+ $ext = new CompletePackage($packageName, $version, $prettyVersion);
$ext->setDescription('The '.$name.' PHP extension');
parent::addPackage($ext);
}
@@ -137,5 +145,25 @@ class PlatformRepository extends ArrayRepository
$lib->setDescription('The '.$name.' PHP library');
parent::addPackage($lib);
}
+
+ if (defined('HPHP_VERSION')) {
+ try {
+ $prettyVersion = HPHP_VERSION;
+ $version = $versionParser->normalize($prettyVersion);
+ } catch (\UnexpectedValueException $e) {
+ $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', HPHP_VERSION);
+ $version = $versionParser->normalize($prettyVersion);
+ }
+
+ $hhvm = new CompletePackage('hhvm', $version, $prettyVersion);
+ $hhvm->setDescription('The HHVM Runtime (64bit)');
+ parent::addPackage($hhvm);
+ }
+ }
+
+
+ private function buildPackageName($name)
+ {
+ return 'ext-' . str_replace(' ', '-', $name);
}
}
diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php
index ed915707f..1a9054f14 100644
--- a/src/Composer/Repository/RepositoryManager.php
+++ b/src/Composer/Repository/RepositoryManager.php
@@ -14,6 +14,7 @@ namespace Composer\Repository;
use Composer\IO\IOInterface;
use Composer\Config;
+use Composer\EventDispatcher\EventDispatcher;
/**
* Repositories manager.
@@ -29,11 +30,13 @@ class RepositoryManager
private $repositoryClasses = array();
private $io;
private $config;
+ private $eventDispatcher;
- public function __construct(IOInterface $io, Config $config)
+ public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
{
$this->io = $io;
$this->config = $config;
+ $this->eventDispatcher = $eventDispatcher;
}
/**
@@ -98,7 +101,7 @@ class RepositoryManager
$class = $this->repositoryClasses[$type];
- return new $class($config, $this->io, $this->config);
+ return new $class($config, $this->io, $this->config, $this->eventDispatcher);
}
/**
diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php
index 950115a79..12eba6d3a 100644
--- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php
+++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php
@@ -12,6 +12,7 @@
namespace Composer\Repository\Vcs;
+use Composer\Config;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
@@ -140,7 +141,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
return false;
diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php
index b220ea573..ecfad77db 100644
--- a/src/Composer/Repository/Vcs/GitDriver.php
+++ b/src/Composer/Repository/Vcs/GitDriver.php
@@ -17,12 +17,15 @@ use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
+use Composer\Cache;
+use Composer\Config;
/**
* @author Jordi Boggiano
*/
class GitDriver extends VcsDriver
{
+ protected $cache;
protected $tags;
protected $branches;
protected $rootIdentifier;
@@ -77,6 +80,8 @@ class GitDriver extends VcsDriver
$this->getTags();
$this->getBranches();
+
+ $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url));
}
/**
@@ -132,6 +137,10 @@ class GitDriver extends VcsDriver
*/
public function getComposerInformation($identifier)
{
+ if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) {
+ $this->infoCache[$identifier] = JsonFile::parseJson($res);
+ }
+
if (!isset($this->infoCache[$identifier])) {
$resource = sprintf('%s:composer.json', escapeshellarg($identifier));
$this->process->execute(sprintf('git show %s', $resource), $composer, $this->repoDir);
@@ -147,6 +156,11 @@ class GitDriver extends VcsDriver
$date = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
$composer['time'] = $date->format('Y-m-d H:i:s');
}
+
+ if (preg_match('{[a-f0-9]{40}}i', $identifier)) {
+ $this->cache->write($identifier, json_encode($composer));
+ }
+
$this->infoCache[$identifier] = $composer;
}
@@ -198,7 +212,7 @@ class GitDriver extends VcsDriver
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (preg_match('#(^git://|\.git$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
return true;
diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php
index 5efc82629..3cf2befb5 100644
--- a/src/Composer/Repository/Vcs/GitHubDriver.php
+++ b/src/Composer/Repository/Vcs/GitHubDriver.php
@@ -12,11 +12,11 @@
namespace Composer\Repository\Vcs;
+use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\IO\IOInterface;
-use Composer\Util\RemoteFilesystem;
use Composer\Util\GitHub;
/**
@@ -46,10 +46,10 @@ class GitHubDriver extends VcsDriver
*/
public function initialize()
{
- preg_match('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match);
- $this->owner = $match[1];
- $this->repository = $match[2];
- $this->originUrl = 'github.com';
+ preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match);
+ $this->owner = $match[3];
+ $this->repository = $match[4];
+ $this->originUrl = !empty($match[1]) ? $match[1] : $match[2];
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
$this->fetchRootIdentifier();
@@ -76,7 +76,21 @@ class GitHubDriver extends VcsDriver
return $this->gitDriver->getUrl();
}
- return 'https://github.com/'.$this->owner.'/'.$this->repository.'.git';
+ return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getApiUrl()
+ {
+ if ('github.com' === $this->originUrl) {
+ $apiUrl = 'api.github.com';
+ } else {
+ $apiUrl = $this->originUrl . '/api/v3';
+ }
+
+ return 'https://' . $apiUrl;
}
/**
@@ -106,7 +120,8 @@ class GitHubDriver extends VcsDriver
if ($this->gitDriver) {
return $this->gitDriver->getDist($identifier);
}
- $url = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;
+
+ $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;
return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
}
@@ -128,7 +143,7 @@ class GitHubDriver extends VcsDriver
$notFoundRetries = 2;
while ($notFoundRetries) {
try {
- $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/contents/composer.json?ref='.urlencode($identifier);
+ $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/composer.json?ref='.urlencode($identifier);
$composer = JsonFile::parseJson($this->getContents($resource));
if (empty($composer['content']) || $composer['encoding'] !== 'base64' || !($composer = base64_decode($composer['content']))) {
throw new \RuntimeException('Could not retrieve composer.json from '.$resource);
@@ -150,16 +165,16 @@ class GitHubDriver extends VcsDriver
$composer = JsonFile::parseJson($composer, $resource);
if (!isset($composer['time'])) {
- $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
+ $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
$commit = JsonFile::parseJson($this->getContents($resource), $resource);
$composer['time'] = $commit['commit']['committer']['date'];
}
if (!isset($composer['support']['source'])) {
$label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
- $composer['support']['source'] = sprintf('https://github.com/%s/%s/tree/%s', $this->owner, $this->repository, $label);
+ $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label);
}
if (!isset($composer['support']['issues']) && $this->hasIssues) {
- $composer['support']['issues'] = sprintf('https://github.com/%s/%s/issues', $this->owner, $this->repository);
+ $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
}
}
@@ -182,7 +197,7 @@ class GitHubDriver extends VcsDriver
return $this->gitDriver->getTags();
}
if (null === $this->tags) {
- $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags';
+ $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags';
$tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
$this->tags = array();
foreach ($tagsData as $tag) {
@@ -202,7 +217,7 @@ class GitHubDriver extends VcsDriver
return $this->gitDriver->getBranches();
}
if (null === $this->branches) {
- $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads';
+ $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads';
$branchData = JsonFile::parseJson($this->getContents($resource), $resource);
$this->branches = array();
foreach ($branchData as $branch) {
@@ -217,9 +232,14 @@ class GitHubDriver extends VcsDriver
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
- if (!preg_match('#^((?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url)) {
+ if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git)?$#', $url, $matches)) {
+ return false;
+ }
+
+ $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
+ if (!in_array($originUrl, $config->get('github-domains'))) {
return false;
}
@@ -241,7 +261,7 @@ class GitHubDriver extends VcsDriver
*/
protected function generateSshUrl()
{
- return 'git@github.com:'.$this->owner.'/'.$this->repository.'.git';
+ return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
}
/**
@@ -358,7 +378,7 @@ class GitHubDriver extends VcsDriver
*/
protected function fetchRootIdentifier()
{
- $repoDataUrl = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository;
+ $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;
$repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl);
if (null === $repoData && null !== $this->gitDriver) {
diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php
index 80683a465..c6eac73b9 100644
--- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php
+++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php
@@ -12,6 +12,7 @@
namespace Composer\Repository\Vcs;
+use Composer\Config;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
@@ -150,7 +151,7 @@ class HgBitbucketDriver extends VcsDriver
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (!preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
return false;
diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php
index 36ed205f2..060a77e45 100644
--- a/src/Composer/Repository/Vcs/HgDriver.php
+++ b/src/Composer/Repository/Vcs/HgDriver.php
@@ -12,6 +12,7 @@
namespace Composer\Repository\Vcs;
+use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
@@ -48,14 +49,14 @@ class HgDriver extends VcsDriver
// update the repo if it is a valid hg repository
if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) {
- if (0 !== $this->process->execute('hg pull -u', $output, $this->repoDir)) {
+ if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) {
$this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')');
}
} else {
// clean up directory and do a fresh clone into it
$fs->removeDirectory($this->repoDir);
- if (0 !== $this->process->execute(sprintf('hg clone %s %s', escapeshellarg($this->url), escapeshellarg($this->repoDir)), $output, $cacheDir)) {
+ if (0 !== $this->process->execute(sprintf('hg clone --noupdate %s %s', escapeshellarg($this->url), escapeshellarg($this->repoDir)), $output, $cacheDir)) {
$output = $this->process->getErrorOutput();
if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
@@ -124,7 +125,7 @@ class HgDriver extends VcsDriver
$composer = JsonFile::parseJson($composer, $identifier);
if (!isset($composer['time'])) {
- $this->process->execute(sprintf('hg log --template "{date|rfc822date}" -r %s', escapeshellarg($identifier)), $output, $this->repoDir);
+ $this->process->execute(sprintf('hg log --template "{date|rfc3339date}" -r %s', escapeshellarg($identifier)), $output, $this->repoDir);
$date = new \DateTime(trim($output), new \DateTimeZone('UTC'));
$composer['time'] = $date->format('Y-m-d H:i:s');
}
@@ -189,7 +190,7 @@ class HgDriver extends VcsDriver
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
return true;
diff --git a/src/Composer/Repository/Vcs/PerforceDriver.php b/src/Composer/Repository/Vcs/PerforceDriver.php
new file mode 100644
index 000000000..79500f1d6
--- /dev/null
+++ b/src/Composer/Repository/Vcs/PerforceDriver.php
@@ -0,0 +1,194 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository\Vcs;
+
+use Composer\Config;
+use Composer\IO\IOInterface;
+use Composer\Util\ProcessExecutor;
+use Composer\Util\Perforce;
+
+/**
+ * @author Matt Whittom
+ */
+class PerforceDriver extends VcsDriver
+{
+ protected $depot;
+ protected $branch;
+ protected $perforce;
+ protected $composerInfo;
+ protected $composerInfoIdentifier;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function initialize()
+ {
+ $this->depot = $this->repoConfig['depot'];
+ $this->branch = '';
+ if (isset($this->repoConfig['branch'])) {
+ $this->branch = $this->repoConfig['branch'];
+ }
+
+ $this->initPerforce($this->repoConfig);
+ $this->perforce->p4Login($this->io);
+ $this->perforce->checkStream($this->depot);
+
+ $this->perforce->writeP4ClientSpec();
+ $this->perforce->connectClient();
+
+ return true;
+ }
+
+ private function initPerforce($repoConfig)
+ {
+ if (isset($this->perforce)) {
+ return;
+ }
+
+ $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
+ $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getComposerInformation($identifier)
+ {
+ if (isset($this->composerInfoIdentifier)) {
+ if (strcmp($identifier, $this->composerInfoIdentifier) === 0) {
+ return $this->composerInfo;
+ }
+ }
+ $composer_info = $this->perforce->getComposerInformation($identifier);
+
+ return $composer_info;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getRootIdentifier()
+ {
+ return $this->branch;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getBranches()
+ {
+ $branches = $this->perforce->getBranches();
+
+ return $branches;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTags()
+ {
+ $tags = $this->perforce->getTags();
+
+ return $tags;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getDist($identifier)
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSource($identifier)
+ {
+ $source = array(
+ 'type' => 'perforce',
+ 'url' => $this->repoConfig['url'],
+ 'reference' => $identifier,
+ 'p4user' => $this->perforce->getUser()
+ );
+
+ return $source;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasComposerFile($identifier)
+ {
+ $this->composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);
+ $this->composerInfoIdentifier = $identifier;
+ $result = false;
+ if (isset($this->composerInfo)) {
+ $result = count($this->composerInfo) > 0;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getContents($url)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
+ {
+ if ($deep || preg_match('#\b(perforce|p4)\b#i', $url)) {
+ return Perforce::checkServerExists($url, new ProcessExecutor($io));
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function cleanup()
+ {
+ $this->perforce->cleanupClientSpec();
+ $this->perforce = null;
+ }
+
+ public function getDepot()
+ {
+ return $this->depot;
+ }
+
+ public function getBranch()
+ {
+ return $this->branch;
+ }
+
+ public function setPerforce(Perforce $perforce)
+ {
+ $this->perforce = $perforce;
+ }
+}
diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php
index b979c2e4b..d69230cce 100644
--- a/src/Composer/Repository/Vcs/SvnDriver.php
+++ b/src/Composer/Repository/Vcs/SvnDriver.php
@@ -13,6 +13,7 @@
namespace Composer\Repository\Vcs;
use Composer\Cache;
+use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
@@ -193,7 +194,13 @@ class SvnDriver extends VcsDriver
if (null === $this->branches) {
$this->branches = array();
- $output = $this->execute('svn ls --verbose', $this->baseUrl . '/');
+ if (false === strpos($this->trunkPath, '/')) {
+ $trunkParent = $this->baseUrl . '/';
+ } else {
+ $trunkParent = $this->baseUrl . '/' . dirname($this->trunkPath) . '/';
+ }
+
+ $output = $this->execute('svn ls --verbose', $trunkParent);
if ($output) {
foreach ($this->process->splitLines($output) as $line) {
$line = trim($line);
@@ -235,7 +242,7 @@ class SvnDriver extends VcsDriver
/**
* {@inheritDoc}
*/
- public static function supports(IOInterface $io, $url, $deep = false)
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{
$url = self::normalizeUrl($url);
if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php
index f6d428802..f1112074e 100644
--- a/src/Composer/Repository/Vcs/VcsDriver.php
+++ b/src/Composer/Repository/Vcs/VcsDriver.php
@@ -101,8 +101,22 @@ abstract class VcsDriver implements VcsDriverInterface
return $this->remoteFilesystem->getContents($this->originUrl, $url, false);
}
+ /**
+ * Return if current repository url is local
+ *
+ * @param string $url
+ * @return boolean Repository url is local
+ */
protected static function isLocalUrl($url)
{
return (bool) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function cleanup()
+ {
+ return;
+ }
}
diff --git a/src/Composer/Repository/Vcs/VcsDriverInterface.php b/src/Composer/Repository/Vcs/VcsDriverInterface.php
index 44486f007..dd30baacd 100644
--- a/src/Composer/Repository/Vcs/VcsDriverInterface.php
+++ b/src/Composer/Repository/Vcs/VcsDriverInterface.php
@@ -12,6 +12,7 @@
namespace Composer\Repository\Vcs;
+use Composer\Config;
use Composer\IO\IOInterface;
/**
@@ -81,13 +82,20 @@ interface VcsDriverInterface
*/
public function hasComposerFile($identifier);
+ /**
+ * Performs any cleanup necessary as the driver is not longer needed
+ *
+ */
+ public function cleanup();
+
/**
* Checks if this driver can handle a given url
*
- * @param IOInterface $io IO instance
- * @param string $url
- * @param bool $deep unless true, only shallow checks (url matching typically) should be done
+ * @param IOInterface $io IO instance
+ * @param Config $config current $config
+ * @param string $url URL to validate/check
+ * @param bool $deep unless true, only shallow checks (url matching typically) should be done
* @return bool
*/
- public static function supports(IOInterface $io, $url, $deep = false);
+ public static function supports(IOInterface $io, Config $config, $url, $deep = false);
}
diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php
index f8fb84005..62e7acef5 100644
--- a/src/Composer/Repository/VcsRepository.php
+++ b/src/Composer/Repository/VcsRepository.php
@@ -19,6 +19,7 @@ use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Package\Loader\LoaderInterface;
+use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Config;
@@ -38,7 +39,7 @@ class VcsRepository extends ArrayRepository
protected $repoConfig;
protected $branchErrorOccurred = false;
- public function __construct(array $repoConfig, IOInterface $io, Config $config, array $drivers = null)
+ public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null)
{
$this->drivers = $drivers ?: array(
'github' => 'Composer\Repository\Vcs\GitHubDriver',
@@ -46,6 +47,7 @@ class VcsRepository extends ArrayRepository
'git' => 'Composer\Repository\Vcs\GitDriver',
'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver',
'hg' => 'Composer\Repository\Vcs\HgDriver',
+ 'perforce' => 'Composer\Repository\Vcs\PerforceDriver',
// svn must be last because identifying a subversion server for sure is practically impossible
'svn' => 'Composer\Repository\Vcs\SvnDriver',
);
@@ -58,6 +60,11 @@ class VcsRepository extends ArrayRepository
$this->repoConfig = $repoConfig;
}
+ public function getRepoConfig()
+ {
+ return $this->repoConfig;
+ }
+
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
@@ -74,7 +81,7 @@ class VcsRepository extends ArrayRepository
}
foreach ($this->drivers as $driver) {
- if ($driver::supports($this->io, $this->url)) {
+ if ($driver::supports($this->io, $this->config, $this->url)) {
$driver = new $driver($this->repoConfig, $this->io, $this->config);
$driver->initialize();
@@ -83,7 +90,7 @@ class VcsRepository extends ArrayRepository
}
foreach ($this->drivers as $driver) {
- if ($driver::supports($this->io, $this->url, true)) {
+ if ($driver::supports($this->io, $this->config, $this->url, true)) {
$driver = new $driver($this->repoConfig, $this->io, $this->config);
$driver->initialize();
@@ -247,6 +254,7 @@ class VcsRepository extends ArrayRepository
continue;
}
}
+ $driver->cleanup();
if (!$verbose) {
$this->io->overwrite('', false);
diff --git a/src/Composer/Script/CommandEvent.php b/src/Composer/Script/CommandEvent.php
index 5d8f732c9..48ea2246a 100644
--- a/src/Composer/Script/CommandEvent.php
+++ b/src/Composer/Script/CommandEvent.php
@@ -12,8 +12,6 @@
namespace Composer\Script;
-use Composer\Composer;
-
/**
* The Command Event.
*
diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php
index cafea2948..40b109b2d 100644
--- a/src/Composer/Script/Event.php
+++ b/src/Composer/Script/Event.php
@@ -16,17 +16,13 @@ use Composer\Composer;
use Composer\IO\IOInterface;
/**
- * The base event class
+ * The script event class
*
* @author François Pluchino
+ * @author Nils Adermann
*/
-class Event
+class Event extends \Composer\EventDispatcher\Event
{
- /**
- * @var string This event's name
- */
- private $name;
-
/**
* @var Composer The composer instance
*/
@@ -52,22 +48,12 @@ class Event
*/
public function __construct($name, Composer $composer, IOInterface $io, $devMode = false)
{
- $this->name = $name;
+ parent::__construct($name);
$this->composer = $composer;
$this->io = $io;
$this->devMode = $devMode;
}
- /**
- * Returns the event's name.
- *
- * @return string The event name
- */
- public function getName()
- {
- return $this->name;
- }
-
/**
* Returns the composer instance.
*
diff --git a/src/Composer/Util/ConfigValidator.php b/src/Composer/Util/ConfigValidator.php
index 0c234a245..82648d6d3 100644
--- a/src/Composer/Util/ConfigValidator.php
+++ b/src/Composer/Util/ConfigValidator.php
@@ -104,6 +104,15 @@ class ConfigValidator
);
}
+ if (!empty($manifest['type']) && $manifest['type'] == 'composer-installer') {
+ $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See http://getcomposer.org/doc/articles/plugins.md for plugin documentation.";
+ }
+
+ $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']);
+ if (!empty($requireOverrides)) {
+ $warnings[] = implode(', ', array_keys($requireOverrides)). " is required both in require and require-dev, this can lead to unexpected behavior";
+ }
+
try {
$loader = new ValidatingArrayLoader(new ArrayLoader());
if (!isset($manifest['version'])) {
diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php
index 3dade2ce9..8771776fc 100644
--- a/src/Composer/Util/Filesystem.php
+++ b/src/Composer/Util/Filesystem.php
@@ -41,6 +41,19 @@ class Filesystem
return false;
}
+ /**
+ * Checks if a directory is empty
+ *
+ * @param string $dir
+ * @return bool
+ */
+ public function isDirEmpty($dir)
+ {
+ $dir = rtrim($dir, '/\\');
+
+ return count(glob($dir.'/*') ?: array()) === 0 && count(glob($dir.'/.*') ?: array()) === 2;
+ }
+
/**
* Recursively remove a directory
*
@@ -56,6 +69,10 @@ class Filesystem
return true;
}
+ if (preg_match('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) {
+ throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.');
+ }
+
if (!function_exists('proc_open')) {
return $this->removeDirectoryPhp($directory);
}
@@ -129,15 +146,12 @@ class Filesystem
{
$it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
-
- if (!file_exists($target)) {
- mkdir($target, 0777, true);
- }
+ $this->ensureDirectoryExists($target);
foreach ($ri as $file) {
$targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
if ($file->isDir()) {
- mkdir($targetPath);
+ $this->ensureDirectoryExists($targetPath);
} else {
copy($file->getPathname(), $targetPath);
}
@@ -212,12 +226,12 @@ class Filesystem
return './'.basename($to);
}
- $commonPath = $to.'/';
- while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
+ $commonPath = $to;
+ while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath)) {
$commonPath = strtr(dirname($commonPath), '\\', '/');
}
- if (0 !== strpos($from, $commonPath) || '/' === $commonPath || '.' === $commonPath) {
+ if (0 !== strpos($from, $commonPath) || '/' === $commonPath) {
return $to;
}
@@ -250,8 +264,8 @@ class Filesystem
return $directories ? '__DIR__' : '__FILE__';
}
- $commonPath = $to.'/';
- while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
+ $commonPath = $to;
+ while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
$commonPath = strtr(dirname($commonPath), '\\', '/');
}
diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php
index 7df6b1d95..4b6fcd6f2 100644
--- a/src/Composer/Util/Git.php
+++ b/src/Composer/Util/Git.php
@@ -12,8 +12,6 @@
namespace Composer\Util;
-use Composer\IO\IOInterface;
-
/**
* @author Jordi Boggiano
*/
diff --git a/src/Composer/Util/GitHub.php b/src/Composer/Util/GitHub.php
index 93894cc76..49e56f8c9 100644
--- a/src/Composer/Util/GitHub.php
+++ b/src/Composer/Util/GitHub.php
@@ -51,7 +51,7 @@ class GitHub
*/
public function authorizeOAuth($originUrl)
{
- if ('github.com' !== $originUrl) {
+ if (!in_array($originUrl, $this->config->get('github-domains'))) {
return false;
}
@@ -78,6 +78,8 @@ class GitHub
{
$attemptCounter = 0;
+ $apiUrl = ('github.com' === $originUrl) ? 'api.github.com' : $originUrl . '/api/v3';
+
if ($message) {
$this->io->write($message);
}
@@ -95,7 +97,7 @@ class GitHub
$appName .= ' on ' . trim($output);
}
- $contents = JsonFile::parseJson($this->remoteFilesystem->getContents($originUrl, 'https://api.github.com/authorizations', false, array(
+ $contents = JsonFile::parseJson($this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl . '/authorizations', false, array(
'http' => array(
'method' => 'POST',
'follow_location' => false,
diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php
index 930d12e22..533dbc19c 100644
--- a/src/Composer/Util/NoProxyPattern.php
+++ b/src/Composer/Util/NoProxyPattern.php
@@ -73,7 +73,14 @@ class NoProxyPattern
if (strpos($ruleHost, '/') === false) {
$match = $ip === $ruleHost;
} else {
- $match = self::inCIDRBlock($ruleHost, $ip);
+ // gethostbyname() failed to resolve $host to an ip, so we assume
+ // it must be proxied to let the proxy's DNS resolve it
+ if ($ip === $host) {
+ $match = false;
+ } else {
+ // match resolved IP against the rule
+ $match = self::inCIDRBlock($ruleHost, $ip);
+ }
}
} else {
// match end of domain
@@ -100,12 +107,12 @@ class NoProxyPattern
}
/**
- * Check an IP adress against a CIDR
+ * Check an IP address against a CIDR
*
* http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php
*
* @param string $cidr IPv4 block in CIDR notation
- * @param string $ip IPv4 address
+ * @param string $ip IPv4 address
*
* @return boolean
*/
@@ -134,7 +141,7 @@ class NoProxyPattern
$check = ($a << 24) + ($b << 16) + ($c << 8) + $d;
// If the ip is within the range, including highest/lowest values,
- // then it's witin the CIDR range
+ // then it's within the CIDR range
return $check >= $low && $check <= $high;
}
}
diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php
new file mode 100644
index 000000000..47558557b
--- /dev/null
+++ b/src/Composer/Util/Perforce.php
@@ -0,0 +1,544 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Util;
+
+use Composer\IO\IOInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Matt Whittom
+ */
+class Perforce
+{
+ protected $path;
+ protected $p4Depot;
+ protected $p4Client;
+ protected $p4User;
+ protected $p4Password;
+ protected $p4Port;
+ protected $p4Stream;
+ protected $p4ClientSpec;
+ protected $p4DepotType;
+ protected $p4Branch;
+ protected $process;
+ protected $uniquePerforceClientName;
+ protected $windowsFlag;
+ protected $commandResult;
+
+ public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows)
+ {
+ $this->windowsFlag = $isWindows;
+ $this->p4Port = $port;
+ $this->initializePath($path);
+ $this->process = $process;
+ $this->initialize($repoConfig);
+ }
+
+ public static function create($repoConfig, $port, $path, ProcessExecutor $process = null)
+ {
+ if (!isset($process)) {
+ $process = new ProcessExecutor;
+ }
+ $isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
+
+ $perforce = new Perforce($repoConfig, $port, $path, $process, $isWindows);
+
+ return $perforce;
+ }
+
+ public function initialize($repoConfig)
+ {
+ $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
+ if (null == $repoConfig) {
+ return;
+ }
+ if (isset($repoConfig['unique_perforce_client_name'])) {
+ $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
+ }
+
+ if (isset($repoConfig['depot'])) {
+ $this->p4Depot = $repoConfig['depot'];
+ }
+ if (isset($repoConfig['branch'])) {
+ $this->p4Branch = $repoConfig['branch'];
+ }
+ if (isset($repoConfig['p4user'])) {
+ $this->p4User = $repoConfig['p4user'];
+ } else {
+ $this->p4User = $this->getP4variable('P4USER');
+ }
+ if (isset($repoConfig['p4password'])) {
+ $this->p4Password = $repoConfig['p4password'];
+ }
+ }
+
+ public function initializeDepotAndBranch($depot, $branch)
+ {
+ if (isset($depot)) {
+ $this->p4Depot = $depot;
+ }
+ if (isset($branch)) {
+ $this->p4Branch = $branch;
+ }
+ }
+
+ public function generateUniquePerforceClientName()
+ {
+ return gethostname() . "_" . time();
+ }
+
+ public function cleanupClientSpec()
+ {
+ $client = $this->getClient();
+ $command = 'p4 client -d $client';
+ $this->executeCommand($command);
+ $clientSpec = $this->getP4ClientSpec();
+ $fileSystem = new FileSystem($this->process);
+ $fileSystem->remove($clientSpec);
+ }
+
+ protected function executeCommand($command)
+ {
+ $this->commandResult = "";
+ $exit_code = $this->process->execute($command, $this->commandResult);
+ return $exit_code;
+ }
+
+ public function getClient()
+ {
+ if (!isset($this->p4Client)) {
+ $cleanStreamName = str_replace('@', '', str_replace('/', '_', str_replace('//', '', $this->getStream())));
+ $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
+ }
+
+ return $this->p4Client;
+ }
+
+ protected function getPath()
+ {
+ return $this->path;
+ }
+
+ public function initializePath($path)
+ {
+ $this->path = $path;
+ $fs = new Filesystem();
+ $fs->ensureDirectoryExists($path);
+ }
+
+ protected function getPort()
+ {
+ return $this->p4Port;
+ }
+
+ public function setStream($stream)
+ {
+ $this->p4Stream = $stream;
+ $index = strrpos($stream, '/');
+ //Stream format is //depot/stream, while non-streaming depot is //depot
+ if ($index > 2) {
+ $this->p4DepotType = 'stream';
+ }
+ }
+
+ public function isStream()
+ {
+ return (strcmp($this->p4DepotType, 'stream') === 0);
+ }
+
+ public function getStream()
+ {
+ if (!isset($this->p4Stream)) {
+ if ($this->isStream()) {
+ $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
+ } else {
+ $this->p4Stream = '//' . $this->p4Depot;
+ }
+ }
+
+ return $this->p4Stream;
+ }
+
+ public function getStreamWithoutLabel($stream)
+ {
+ $index = strpos($stream, '@');
+ if ($index === false) {
+ return $stream;
+ }
+
+ return substr($stream, 0, $index);
+ }
+
+ public function getP4ClientSpec()
+ {
+ $p4clientSpec = $this->path . '/' . $this->getClient() . '.p4.spec';
+
+ return $p4clientSpec;
+ }
+
+ public function getUser()
+ {
+ return $this->p4User;
+ }
+
+ public function queryP4User(IOInterface $io)
+ {
+ $this->getUser();
+ if (strlen($this->p4User) > 0) {
+ return;
+ }
+ $this->p4User = $this->getP4variable('P4USER');
+ if (strlen($this->p4User) > 0) {
+ return;
+ }
+ $this->p4User = $io->ask('Enter P4 User:');
+ if ($this->windowsFlag) {
+ $command = 'p4 set P4USER=' . $this->p4User;
+ } else {
+ $command = 'export P4USER=' . $this->p4User;
+ }
+ $this->executeCommand($command);
+ }
+
+ protected function getP4variable($name)
+ {
+ if ($this->windowsFlag) {
+ $command = 'p4 set';
+ $this->executeCommand($command);
+ $result = trim($this->commandResult);
+ $resArray = explode(PHP_EOL, $result);
+ foreach ($resArray as $line) {
+ $fields = explode('=', $line);
+ if (strcmp($name, $fields[0]) == 0) {
+ $index = strpos($fields[1], ' ');
+ if ($index === false) {
+ $value = $fields[1];
+ } else {
+ $value = substr($fields[1], 0, $index);
+ }
+ $value = trim($value);
+
+ return $value;
+ }
+ }
+ } else {
+ $command = 'echo $' . $name;
+ $this->executeCommand($command);
+ $result = trim($this->commandResult);
+ return $result;
+ }
+ }
+
+ public function queryP4Password(IOInterface $io)
+ {
+ if (isset($this->p4Password)) {
+ return $this->p4Password;
+ }
+ $password = $this->getP4variable('P4PASSWD');
+ if (strlen($password) <= 0) {
+ $password = $io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
+ }
+ $this->p4Password = $password;
+
+ return $password;
+ }
+
+ public function generateP4Command($command, $useClient = true)
+ {
+ $p4Command = 'p4 ';
+ $p4Command = $p4Command . '-u ' . $this->getUser() . ' ';
+ if ($useClient) {
+ $p4Command = $p4Command . '-c ' . $this->getClient() . ' ';
+ }
+ $p4Command = $p4Command . '-p ' . $this->getPort() . ' ';
+ $p4Command = $p4Command . $command;
+
+ return $p4Command;
+ }
+
+ public function isLoggedIn()
+ {
+ $command = $this->generateP4Command('login -s', false);
+ $exitCode = $this->executeCommand($command);
+ if ($exitCode){
+ $errorOutput = $this->process->getErrorOutput();
+ $index = strpos($errorOutput, $this->getUser());
+ if ($index === false){
+ $index = strpos($errorOutput, 'p4');
+ if ($index===false){
+ return false;
+ }
+ throw new \Exception('p4 command not found in path: ' . $errorOutput);
+ }
+ throw new \Exception('Invalid user name: ' . $this->getUser() );
+ }
+ return true;
+ }
+
+ public function connectClient()
+ {
+ $p4CreateClientCommand = $this->generateP4Command('client -i < ' . $this->getP4ClientSpec());
+ $this->executeCommand($p4CreateClientCommand);
+ }
+
+ public function syncCodeBase($label)
+ {
+ $prevDir = getcwd();
+ chdir($this->path);
+
+ $p4SyncCommand = $this->generateP4Command('sync -f ');
+ if (isset($label)) {
+ if (strcmp($label, 'dev-master') != 0) {
+ $p4SyncCommand = $p4SyncCommand . '@' . $label;
+ }
+ }
+ $this->executeCommand($p4SyncCommand);
+
+ chdir($prevDir);
+ }
+
+ public function writeClientSpecToFile($spec)
+ {
+ fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
+ fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'Description:' . PHP_EOL);
+ fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL);
+ fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL);
+ if ($this->isStream()) {
+ fwrite($spec, 'Stream:' . PHP_EOL);
+ fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
+ } else {
+ fwrite(
+ $spec,
+ 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL
+ );
+ }
+ }
+
+ public function writeP4ClientSpec()
+ {
+ $clientSpec = $this->getP4ClientSpec();
+ $spec = fopen($clientSpec, 'w');
+ try {
+ $this->writeClientSpecToFile($spec);
+ } catch (\Exception $e) {
+ fclose($spec);
+ throw $e;
+ }
+ fclose($spec);
+ }
+
+ protected function read($pipe, $name)
+ {
+ if (feof($pipe)) {
+ return;
+ }
+ $line = fgets($pipe);
+ while ($line != false) {
+ $line = fgets($pipe);
+ }
+
+ return;
+ }
+
+ public function windowsLogin($password)
+ {
+ $command = $this->generateP4Command(' login -a');
+ $process = new Process($command, null, null, $password);
+
+ return $process->run();
+ }
+
+ public function p4Login(IOInterface $io)
+ {
+ $this->queryP4User($io);
+ if (!$this->isLoggedIn()) {
+ $password = $this->queryP4Password($io);
+ if ($this->windowsFlag) {
+ $this->windowsLogin($password);
+ } else {
+ $command = 'echo ' . $password . ' | ' . $this->generateP4Command(' login -a', false);
+ $exitCode = $this->executeCommand($command);
+ $result = trim($this->commandResult);
+ if ($exitCode){
+ throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
+ }
+ }
+ }
+ }
+
+ public static function checkServerExists($url, ProcessExecutor $processExecutor)
+ {
+ $output = null;
+ return 0 === $processExecutor->execute('p4 -p ' . $url . ' info -s', $output);
+ }
+
+ public function getComposerInformation($identifier)
+ {
+ $index = strpos($identifier, '@');
+ if ($index === false) {
+ $composerJson = $identifier. '/composer.json';
+
+ return $this->getComposerInformationFromPath($composerJson);
+ }
+
+ return $this->getComposerInformationFromLabel($identifier, $index);
+ }
+
+ public function getComposerInformationFromPath($composerJson)
+ {
+ $command = $this->generateP4Command(' print ' . $composerJson);
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+ $index = strpos($result, '{');
+ if ($index === false) {
+ return '';
+ }
+ if ($index >= 0) {
+ $rawData = substr($result, $index);
+ $composer_info = json_decode($rawData, true);
+
+ return $composer_info;
+ }
+
+ return '';
+ }
+
+ public function getComposerInformationFromLabel($identifier, $index)
+ {
+ $composerJsonPath = substr($identifier, 0, $index) . '/composer.json' . substr($identifier, $index);
+ $command = $this->generateP4Command(' files ' . $composerJsonPath, false);
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+ $index2 = strpos($result, 'no such file(s).');
+ if ($index2 === false) {
+ $index3 = strpos($result, 'change');
+ if (!($index3 === false)) {
+ $phrase = trim(substr($result, $index3));
+ $fields = explode(' ', $phrase);
+ $id = $fields[1];
+ $composerJson = substr($identifier, 0, $index) . '/composer.json@' . $id;
+
+ return $this->getComposerInformationFromPath($composerJson);
+ }
+ }
+
+ return "";
+ }
+
+ public function getBranches()
+ {
+ $possibleBranches = array();
+ if (!$this->isStream()) {
+ $possibleBranches[$this->p4Branch] = $this->getStream();
+ } else {
+ $command = $this->generateP4Command('streams //' . $this->p4Depot . '/...');
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+ $resArray = explode(PHP_EOL, $result);
+ foreach ($resArray as $line) {
+ $resBits = explode(' ', $line);
+ if (count($resBits) > 4) {
+ $branch = preg_replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
+ $possibleBranches[$branch] = $resBits[1];
+ }
+ }
+ }
+ $branches = array();
+ $branches['master'] = $possibleBranches[$this->p4Branch];
+
+ return $branches;
+ }
+
+ public function getTags()
+ {
+ $command = $this->generateP4Command('labels');
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+ $resArray = explode(PHP_EOL, $result);
+ $tags = array();
+ foreach ($resArray as $line) {
+ $index = strpos($line, 'Label');
+ if (!($index === false)) {
+ $fields = explode(' ', $line);
+ $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
+ }
+ }
+
+ return $tags;
+ }
+
+ public function checkStream()
+ {
+ $command = $this->generateP4Command('depots', false);
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+ $resArray = explode(PHP_EOL, $result);
+ foreach ($resArray as $line) {
+ $index = strpos($line, 'Depot');
+ if (!($index === false)) {
+ $fields = explode(' ', $line);
+ if (strcmp($this->p4Depot, $fields[1]) === 0) {
+ $this->p4DepotType = $fields[3];
+
+ return $this->isStream();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected function getChangeList($reference)
+ {
+ $index = strpos($reference, '@');
+ if ($index === false) {
+ return;
+ }
+ $label = substr($reference, $index);
+ $command = $this->generateP4Command(' changes -m1 ' . $label);
+ $this->executeCommand($command);
+ $changes = $this->commandResult;
+ if (strpos($changes, 'Change') !== 0) {
+ return;
+ }
+ $fields = explode(' ', $changes);
+ $changeList = $fields[1];
+
+ return $changeList;
+ }
+
+ public function getCommitLogs($fromReference, $toReference)
+ {
+ $fromChangeList = $this->getChangeList($fromReference);
+ if ($fromChangeList == null) {
+ return;
+ }
+ $toChangeList = $this->getChangeList($toReference);
+ if ($toChangeList == null) {
+ return;
+ }
+ $index = strpos($fromReference, '@');
+ $main = substr($fromReference, 0, $index) . '/...';
+ $command = $this->generateP4Command('filelog ' . $main . '@' . $fromChangeList. ',' . $toChangeList);
+ $this->executeCommand($command);
+ $result = $this->commandResult;
+
+ return $result;
+ }
+}
diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php
index 3db55ab6d..7c26bd290 100644
--- a/src/Composer/Util/RemoteFilesystem.php
+++ b/src/Composer/Util/RemoteFilesystem.php
@@ -19,6 +19,7 @@ use Composer\Downloader\TransportException;
/**
* @author François Pluchino
* @author Jordi Boggiano
+ * @author Nils Adermann
*/
class RemoteFilesystem
{
@@ -76,6 +77,16 @@ class RemoteFilesystem
return $this->get($originUrl, $fileUrl, $options, null, $progress);
}
+ /**
+ * Retrieve the options set in the constructor
+ *
+ * @return array Options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
/**
* Get file content or copy action.
*
diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php
index 892016674..1c3c20e44 100644
--- a/src/Composer/Util/StreamContextFactory.php
+++ b/src/Composer/Util/StreamContextFactory.php
@@ -91,9 +91,9 @@ final class StreamContextFactory
}
if (isset($proxy['user'])) {
- $auth = $proxy['user'];
+ $auth = urldecode($proxy['user']);
if (isset($proxy['pass'])) {
- $auth .= ':' . $proxy['pass'];
+ $auth .= ':' . urldecode($proxy['pass']);
}
$auth = base64_encode($auth);
@@ -120,7 +120,7 @@ final class StreamContextFactory
}
/**
- * A bug in PHP prevents the headers from correctly beeing sent when a content-type header is present and
+ * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and
* NOT at the end of the array
*
* This method fixes the array by moving the content-type header to the end
diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php
index fcfac6d7e..510b45daa 100644
--- a/src/Composer/Util/Svn.php
+++ b/src/Composer/Util/Svn.php
@@ -92,6 +92,9 @@ class Svn
if ($type !== 'out') {
return;
}
+ if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
+ return;
+ }
$output .= $buffer;
if ($verbose) {
$io->write($buffer, false);
@@ -108,7 +111,9 @@ class Svn
// the error is not auth-related
if (false === stripos($output, 'Could not authenticate to server:')
- && false === stripos($output, 'svn: E170001:')) {
+ && false === stripos($output, 'authorization failed')
+ && false === stripos($output, 'svn: E170001:')
+ && false === stripos($output, 'svn: E215004:')) {
throw new \RuntimeException($output);
}
diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
index dc1fa529e..7a1609926 100644
--- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
+++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
@@ -17,7 +17,7 @@ use Composer\Package\Link;
use Composer\Util\Filesystem;
use Composer\Package\AliasPackage;
use Composer\Package\Package;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\Script\ScriptEvents;
class AutoloadGeneratorTest extends TestCase
@@ -72,7 +72,7 @@ class AutoloadGeneratorTest extends TestCase
}));
$this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
- $this->eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $this->eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->disableOriginalConstructor()
->getMock();
@@ -475,7 +475,7 @@ class AutoloadGeneratorTest extends TestCase
$expectedNamespace = <<eventDispatcher
->expects($this->at(0))
- ->method('dispatch')
+ ->method('dispatchScript')
->with(ScriptEvents::PRE_AUTOLOAD_DUMP, false);
$this->eventDispatcher
->expects($this->at(1))
- ->method('dispatch')
+ ->method('dispatchScript')
->with(ScriptEvents::POST_AUTOLOAD_DUMP, false);
$package = new Package('a', '1.0', '1.0');
@@ -724,7 +724,7 @@ EOF;
$expectedNamespace = <<<'EOF'
assertEquals($expectedClassmap, file_get_contents($this->vendorDir.'/composer/autoload_classmap.php'));
}
+ public function testVendorSubstringPath()
+ {
+ $package = new Package('a', '1.0', '1.0');
+ $package->setAutoload(array(
+ 'psr-0' => array('Foo' => 'composer-test-autoload-src/src'),
+ ));
+
+ $this->repository->expects($this->once())
+ ->method('getCanonicalPackages')
+ ->will($this->returnValue(array()));
+
+ $this->fs->ensureDirectoryExists($this->vendorDir.'/a');
+
+ $expectedNamespace = <<<'EOF'
+ array($baseDir . '/composer-test-autoload-src/src'),
+);
+
+EOF;
+
+ $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'VendorSubstring');
+ $this->assertEquals($expectedNamespace, file_get_contents($this->vendorDir.'/composer/autoload_namespaces.php'));
+ }
+
private function assertAutoloadFiles($name, $dir, $type = 'namespaces')
{
$a = __DIR__.'/Fixtures/autoload_'.$name.'.php';
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php
index d74b484d8..b7b2a9656 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php
@@ -1,6 +1,6 @@
register(true);
- foreach (require __DIR__ . '/autoload_files.php' as $file) {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ foreach ($includeFiles as $file) {
require $file;
}
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
index 3ddbc9ca9..dc09bc0ee 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_functions.php
@@ -1,6 +1,6 @@
register(true);
- foreach (require __DIR__ . '/autoload_files.php' as $file) {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ foreach ($includeFiles as $file) {
require $file;
}
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
index e24d4752c..3c92a5a2c 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_real_include_path.php
@@ -1,6 +1,6 @@
register(true);
- foreach (require __DIR__ . '/autoload_files.php' as $file) {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ foreach ($includeFiles as $file) {
require $file;
}
diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php b/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
index aa4b6725d..63ef49f61 100644
--- a/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
+++ b/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
@@ -1,6 +1,6 @@
getMock('Composer\IO\IOInterface');
$config = $config ?: $this->getMock('Composer\Config');
$rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock();
- return new FileDownloader($io, $config, null, $rfs);
+ return new FileDownloader($io, $config, $eventDispatcher, $cache, $rfs, $filesystem);
}
/**
@@ -127,6 +127,37 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
}
}
+ public function testCacheGarbageCollectionIsCalled()
+ {
+ $expectedTtl = '99999999';
+
+ $configMock = $this->getMock('Composer\Config');
+ $configMock
+ ->expects($this->at(0))
+ ->method('get')
+ ->with('cache-files-ttl')
+ ->will($this->returnValue($expectedTtl));
+ $configMock
+ ->expects($this->at(1))
+ ->method('get')
+ ->with('cache-files-maxsize')
+ ->will($this->returnValue('500M'));
+
+ $cacheMock = $this->getMockBuilder('Composer\Cache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cacheMock
+ ->expects($this->any())
+ ->method('gcIsNecessary')
+ ->will($this->returnValue(true));
+ $cacheMock
+ ->expects($this->once())
+ ->method('gc')
+ ->with($expectedTtl, $this->anything());
+
+ $downloader = $this->getDownloader(null, $configMock, null, $cacheMock, null, null);
+ }
+
public function testDownloadFileWithInvalidChecksum()
{
$packageMock = $this->getMock('Composer\Package\PackageInterface');
@@ -142,12 +173,13 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
->method('getDistSha1Checksum')
->will($this->returnValue('invalid'))
;
+ $filesystem = $this->getMock('Composer\Util\Filesystem');
do {
$path = sys_get_temp_dir().'/'.md5(time().mt_rand());
} while (file_exists($path));
- $downloader = $this->getDownloader();
+ $downloader = $this->getDownloader(null, null, null, null, null, $filesystem);
// make sure the file expected to be downloaded is on disk already
mkdir($path, 0777, true);
diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php
index 1d093574a..7a0816eaf 100644
--- a/tests/Composer/Test/Downloader/GitDownloaderTest.php
+++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php
@@ -24,10 +24,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
$executor = $executor ?: $this->getMock('Composer\Util\ProcessExecutor');
$filesystem = $filesystem ?: $this->getMock('Composer\Util\Filesystem');
if (!$config) {
- $config = $this->getMock('Composer\Config');
- $config->expects($this->any())
- ->method('has')
- ->will($this->returnValue(false));
+ $config = new Config();
}
return new GitDownloader($io, $config, $executor, $filesystem);
diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php
new file mode 100644
index 000000000..8a20f67cc
--- /dev/null
+++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php
@@ -0,0 +1,100 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Downloader;
+
+use Composer\Downloader\PerforceDownloader;
+use Composer\Config;
+use Composer\Repository\VcsRepository;
+
+/**
+ * @author Matt Whittom
+ */
+class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase
+{
+ private $io;
+ private $config;
+ private $testPath;
+ public static $repository;
+
+ protected function setUp()
+ {
+ $this->testPath = sys_get_temp_dir() . '/composer-test';
+ $this->config = new Config();
+ $this->config->merge(
+ array(
+ 'config' => array(
+ 'home' => $this->testPath,
+ ),
+ )
+ );
+ $this->io = $this->getMock('Composer\IO\IOInterface');
+ }
+
+ public function testInitPerforceGetRepoConfig()
+ {
+ $downloader = new PerforceDownloader($this->io, $this->config);
+ $package = $this->getMock('Composer\Package\PackageInterface');
+ $repoConfig = array('url' => 'TEST_URL', 'p4user' => 'TEST_USER');
+ $repository = $this->getMock(
+ 'Composer\Repository\VcsRepository',
+ array('getRepoConfig'),
+ array($repoConfig, $this->io, $this->config)
+ );
+ $package->expects($this->at(0))
+ ->method('getRepository')
+ ->will($this->returnValue($repository));
+ $repository->expects($this->at(0))
+ ->method('getRepoConfig');
+ $path = $this->testPath;
+ $downloader->initPerforce($package, $path, 'SOURCE_REF');
+ }
+
+ public function testDoDownload()
+ {
+ $downloader = new PerforceDownloader($this->io, $this->config);
+ $repoConfig = array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH', 'p4user' => 'TEST_USER');
+ $port = 'TEST_PORT';
+ $path = 'TEST_PATH';
+ $process = $this->getmock('Composer\Util\ProcessExecutor');
+ $perforce = $this->getMock(
+ 'Composer\Util\Perforce',
+ array('setStream', 'queryP4User', 'writeP4ClientSpec', 'connectClient', 'syncCodeBase'),
+ array($repoConfig, $port, $path, $process, true, 'TEST')
+ );
+ $ref = 'SOURCE_REF';
+ $label = 'LABEL';
+ $perforce->expects($this->at(0))
+ ->method('setStream')
+ ->with($this->equalTo($ref));
+ $perforce->expects($this->at(1))
+ ->method('queryP4User')
+ ->with($this->io);
+ $perforce->expects($this->at(2))
+ ->method('writeP4ClientSpec');
+ $perforce->expects($this->at(3))
+ ->method('connectClient');
+ $perforce->expects($this->at(4))
+ ->method('syncCodeBase')
+ ->with($this->equalTo($label));
+ $downloader->setPerforce($perforce);
+ $package = $this->getMock('Composer\Package\PackageInterface');
+ $package->expects($this->at(0))
+ ->method('getSourceReference')
+ ->will($this->returnValue($ref));
+ $package->expects($this->at(1))
+ ->method('getPrettyVersion')
+ ->will($this->returnValue($label));
+ $path = $this->testPath;
+ $downloader->doDownload($package, $path);
+ }
+}
diff --git a/tests/Composer/Test/Script/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
similarity index 83%
rename from tests/Composer/Test/Script/EventDispatcherTest.php
rename to tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
index cd8f8e76f..fd26b0a3c 100644
--- a/tests/Composer/Test/Script/EventDispatcherTest.php
+++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
@@ -10,11 +10,12 @@
* file that was distributed with this source code.
*/
-namespace Composer\Test\Script;
+namespace Composer\Test\EventDispatcher;
-use Composer\Test\TestCase;
-use Composer\Script\Event;
-use Composer\Script\EventDispatcher;
+use Composer\EventDispatcher\Event;
+use Composer\EventDispatcher\EventDispatcher;
+use Composer\TestCase;
+use Composer\Script;
use Composer\Util\ProcessExecutor;
class EventDispatcherTest extends TestCase
@@ -26,12 +27,12 @@ class EventDispatcherTest extends TestCase
{
$io = $this->getMock('Composer\IO\IOInterface');
$dispatcher = $this->getDispatcherStubForListenersTest(array(
- "Composer\Test\Script\EventDispatcherTest::call"
+ "Composer\Test\EventDispatcher\EventDispatcherTest::call"
), $io);
$io->expects($this->once())
->method('write')
- ->with('Script Composer\Test\Script\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception');
+ ->with('Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception');
$dispatcher->dispatchCommandEvent("post-install-cmd", false);
}
@@ -43,7 +44,7 @@ class EventDispatcherTest extends TestCase
public function testDispatcherCanExecuteSingleCommandLineScript($command)
{
$process = $this->getMock('Composer\Util\ProcessExecutor');
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->getMock('Composer\Composer'),
$this->getMock('Composer\IO\IOInterface'),
@@ -68,7 +69,7 @@ class EventDispatcherTest extends TestCase
public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack()
{
$process = $this->getMock('Composer\Util\ProcessExecutor');
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->getMock('Composer\Composer'),
$this->getMock('Composer\IO\IOInterface'),
@@ -86,7 +87,7 @@ class EventDispatcherTest extends TestCase
$listeners = array(
'echo -n foo',
- 'Composer\\Test\\Script\\EventDispatcherTest::someMethod',
+ 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod',
'echo -n bar',
);
$dispatcher->expects($this->atLeastOnce())
@@ -95,7 +96,7 @@ class EventDispatcherTest extends TestCase
$dispatcher->expects($this->once())
->method('executeEventPhpScript')
- ->with('Composer\Test\Script\EventDispatcherTest', 'someMethod')
+ ->with('Composer\Test\EventDispatcher\EventDispatcherTest', 'someMethod')
->will($this->returnValue(true));
$dispatcher->dispatchCommandEvent("post-install-cmd", false);
@@ -103,7 +104,7 @@ class EventDispatcherTest extends TestCase
private function getDispatcherStubForListenersTest($listeners, $io)
{
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->getMock('Composer\Composer'),
$io,
@@ -129,7 +130,7 @@ class EventDispatcherTest extends TestCase
public function testDispatcherOutputsCommands()
{
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->getMock('Composer\Composer'),
$this->getMock('Composer\IO\IOInterface'),
@@ -150,7 +151,7 @@ class EventDispatcherTest extends TestCase
public function testDispatcherOutputsErrorOnFailedCommand()
{
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$this->getMock('Composer\Composer'),
$io = $this->getMock('Composer\IO\IOInterface'),
diff --git a/tests/Composer/Test/Fixtures/functional/create-project-command.test b/tests/Composer/Test/Fixtures/functional/create-project-command.test
index f5d330912..2e4b2762a 100644
--- a/tests/Composer/Test/Fixtures/functional/create-project-command.test
+++ b/tests/Composer/Test/Fixtures/functional/create-project-command.test
@@ -3,7 +3,7 @@ create-project seld/jsonlint %testDir% 1.0.0 --prefer-source -n
--EXPECT--
Installing seld/jsonlint (1.0.0)
- Installing seld/jsonlint (1.0.0)
- Cloning 1.0.0
+ Cloning 3b4bc2a96ff5d3fe6866bfe9dd0c845246705791
Created project in %testDir%
Loading composer repositories with package information
diff --git a/tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test
similarity index 90%
rename from tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test
rename to tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test
index dd9d26d98..c57a36d35 100644
--- a/tests/Composer/Test/Fixtures/installer/custom-installers-are-installed-first.test
+++ b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test
@@ -8,8 +8,8 @@ Composer installers are installed first if they have no requirements
"package": [
{ "name": "pkg", "version": "1.0.0" },
{ "name": "pkg2", "version": "1.0.0" },
- { "name": "inst", "version": "1.0.0", "type": "composer-installer" },
- { "name": "inst2", "version": "1.0.0", "type": "composer-installer", "require": { "pkg2": "*" } }
+ { "name": "inst", "version": "1.0.0", "type": "composer-plugin" },
+ { "name": "inst2", "version": "1.0.0", "type": "composer-plugin", "require": { "pkg2": "*" } }
]
}
],
diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-locked-require.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-locked-require.test
index 6586e461f..381416af1 100644
--- a/tests/Composer/Test/Fixtures/installer/update-whitelist-locked-require.test
+++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-locked-require.test
@@ -1,5 +1,5 @@
--TEST--
-Update with a package whitelist only updates those packages and their dependencies if they are not present in composer.json
+Update with a package whitelist only updates those packages if they are not present in composer.json
--COMPOSER--
{
"repositories": [
@@ -30,7 +30,7 @@ Update with a package whitelist only updates those packages and their dependenci
{ "name": "fixed-sub-dependency", "version": "1.0.0" }
]
--RUN--
-update whitelisted
+update whitelisted dependency
--EXPECT--
Updating dependency (1.0.0) to dependency (1.1.0)
Updating whitelisted (1.0.0) to whitelisted (1.1.0)
diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependencies.test
new file mode 100644
index 000000000..bb2e04193
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependencies.test
@@ -0,0 +1,40 @@
+--TEST--
+Update with a package whitelist only updates those packages and their dependencies listed as command arguments
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "fixed", "version": "1.1.0" },
+ { "name": "fixed", "version": "1.0.0" },
+ { "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.1.0" } },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "dependency", "version": "1.1.0" },
+ { "name": "dependency", "version": "1.0.0" },
+ { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated-dependency", "version": "1.1.0" },
+ { "name": "unrelated-dependency", "version": "1.0.0" }
+ ]
+ }
+ ],
+ "require": {
+ "fixed": "1.*",
+ "whitelisted": "1.*",
+ "unrelated": "1.*"
+ }
+}
+--INSTALLED--
+[
+ { "name": "fixed", "version": "1.0.0" },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "dependency", "version": "1.0.0" },
+ { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated-dependency", "version": "1.0.0" }
+]
+--RUN--
+update whitelisted --with-dependencies
+--EXPECT--
+Updating dependency (1.0.0) to dependency (1.1.0)
+Updating whitelisted (1.0.0) to whitelisted (1.1.0)
diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependency-conflict.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependency-conflict.test
new file mode 100644
index 000000000..f63229fbc
--- /dev/null
+++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-with-dependency-conflict.test
@@ -0,0 +1,38 @@
+--TEST--
+Update with a package whitelist only updates whitelisted packages if no dependency conflicts
+--COMPOSER--
+{
+ "repositories": [
+ {
+ "type": "package",
+ "package": [
+ { "name": "fixed", "version": "1.1.0" },
+ { "name": "fixed", "version": "1.0.0" },
+ { "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.1.0" } },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "dependency", "version": "1.1.0" },
+ { "name": "dependency", "version": "1.0.0" },
+ { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated-dependency", "version": "1.1.0" },
+ { "name": "unrelated-dependency", "version": "1.0.0" }
+ ]
+ }
+ ],
+ "require": {
+ "fixed": "1.*",
+ "whitelisted": "1.*",
+ "unrelated": "1.*"
+ }
+}
+--INSTALLED--
+[
+ { "name": "fixed", "version": "1.0.0" },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "dependency", "version": "1.0.0" },
+ { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
+ { "name": "unrelated-dependency", "version": "1.0.0" }
+]
+--RUN--
+update whitelisted
+--EXPECT--
diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist.test b/tests/Composer/Test/Fixtures/installer/update-whitelist.test
index 3d7ca30af..751d79e70 100644
--- a/tests/Composer/Test/Fixtures/installer/update-whitelist.test
+++ b/tests/Composer/Test/Fixtures/installer/update-whitelist.test
@@ -1,5 +1,5 @@
--TEST--
-Update with a package whitelist only updates those packages and their dependencies listed as command arguments
+Update with a package whitelist only updates those packages listed as command arguments
--COMPOSER--
{
"repositories": [
@@ -8,8 +8,8 @@ Update with a package whitelist only updates those packages and their dependenci
"package": [
{ "name": "fixed", "version": "1.1.0" },
{ "name": "fixed", "version": "1.0.0" },
- { "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.1.0" } },
- { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "whitelisted", "version": "1.1.0", "require": { "dependency": "1.*" } },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.*" } },
{ "name": "dependency", "version": "1.1.0" },
{ "name": "dependency", "version": "1.0.0" },
{ "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } },
@@ -28,7 +28,7 @@ Update with a package whitelist only updates those packages and their dependenci
--INSTALLED--
[
{ "name": "fixed", "version": "1.0.0" },
- { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.0.0" } },
+ { "name": "whitelisted", "version": "1.0.0", "require": { "dependency": "1.*" } },
{ "name": "dependency", "version": "1.0.0" },
{ "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } },
{ "name": "unrelated-dependency", "version": "1.0.0" }
@@ -36,5 +36,4 @@ Update with a package whitelist only updates those packages and their dependenci
--RUN--
update whitelisted
--EXPECT--
-Updating dependency (1.0.0) to dependency (1.1.0)
Updating whitelisted (1.0.0) to whitelisted (1.1.0)
diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php
index 0d76758d4..3a4313f69 100644
--- a/tests/Composer/Test/IO/ConsoleIOTest.php
+++ b/tests/Composer/Test/IO/ConsoleIOTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\IO;
use Composer\IO\ConsoleIO;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class ConsoleIOTest extends TestCase
{
diff --git a/tests/Composer/Test/IO/NullIOTest.php b/tests/Composer/Test/IO/NullIOTest.php
index cb2023d49..feb586f95 100644
--- a/tests/Composer/Test/IO/NullIOTest.php
+++ b/tests/Composer/Test/IO/NullIOTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\IO;
use Composer\IO\NullIO;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class NullIOTest extends TestCase
{
diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php
deleted file mode 100644
index bfad4a88a..000000000
--- a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php
+++ /dev/null
@@ -1,19 +0,0 @@
-io, $this->composer);
+ $filesystem = $this->getMockBuilder('Composer\Util\Filesystem')
+ ->getMock();
+ $filesystem
+ ->expects($this->once())
+ ->method('rename')
+ ->with($this->vendorDir.'/package1/oldtarget', $this->vendorDir.'/package1/newtarget');
+
$initial = $this->createPackageMock();
$target = $this->createPackageMock();
$initial
- ->expects($this->any())
+ ->expects($this->once())
->method('getPrettyName')
->will($this->returnValue('package1'));
+ $initial
+ ->expects($this->once())
+ ->method('getTargetDir')
+ ->will($this->returnValue('oldtarget'));
+
+ $target
+ ->expects($this->once())
+ ->method('getPrettyName')
+ ->will($this->returnValue('package1'));
+
+ $target
+ ->expects($this->once())
+ ->method('getTargetDir')
+ ->will($this->returnValue('newtarget'));
+
$this->repository
->expects($this->exactly(3))
->method('hasPackage')
@@ -148,7 +169,7 @@ class LibraryInstallerTest extends TestCase
$this->dm
->expects($this->once())
->method('update')
- ->with($initial, $target, $this->vendorDir.'/package1');
+ ->with($initial, $target, $this->vendorDir.'/package1/newtarget');
$this->repository
->expects($this->once())
@@ -160,6 +181,7 @@ class LibraryInstallerTest extends TestCase
->method('addPackage')
->with($target);
+ $library = new LibraryInstaller($this->io, $this->composer, 'library', $filesystem);
$library->update($this->repository, $initial, $target);
$this->assertFileExists($this->vendorDir, 'Vendor dir should be created');
$this->assertFileExists($this->binDir, 'Bin dir should be created');
diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php
index 7c3791791..0cc17cfb0 100644
--- a/tests/Composer/Test/InstallerTest.php
+++ b/tests/Composer/Test/InstallerTest.php
@@ -27,6 +27,7 @@ use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
use Composer\Test\Mock\InstallationManagerMock;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
+use Composer\TestCase;
class InstallerTest extends TestCase
{
@@ -66,12 +67,12 @@ class InstallerTest extends TestCase
$locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
$installationManager = new InstallationManagerMock();
- $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock();
+ $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock();
$installer = new Installer($io, $config, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
$result = $installer->run();
- $this->assertTrue($result);
+ $this->assertSame(0, $result);
$expectedInstalled = isset($options['install']) ? $options['install'] : array();
$expectedUpdated = isset($options['update']) ? $options['update'] : array();
@@ -189,7 +190,7 @@ class InstallerTest extends TestCase
$locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), md5(json_encode($composerConfig)));
$composer->setLocker($locker);
- $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock();
+ $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator', array(), array($eventDispatcher));
$composer->setAutoloadGenerator($autoloadGenerator);
$composer->setEventDispatcher($eventDispatcher);
@@ -205,7 +206,7 @@ class InstallerTest extends TestCase
->setDevMode($input->getOption('dev'))
->setDryRun($input->getOption('dry-run'));
- return $installer->run() ? 0 : 1;
+ return $installer->run();
});
$application->get('update')->setCode(function ($input, $output) use ($installer) {
@@ -213,9 +214,10 @@ class InstallerTest extends TestCase
->setDevMode($input->getOption('dev'))
->setUpdate(true)
->setDryRun($input->getOption('dry-run'))
- ->setUpdateWhitelist($input->getArgument('packages'));
+ ->setUpdateWhitelist($input->getArgument('packages'))
+ ->setWhitelistDependencies($input->getOption('with-dependencies'));
- return $installer->run() ? 0 : 1;
+ return $installer->run();
});
if (!preg_match('{^(install|update)\b}', $run)) {
diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php
index 427b0d70c..5a9f8c2d8 100644
--- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php
+++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php
@@ -49,7 +49,7 @@ class ArchiveManagerTest extends ArchiverTest
$target = $this->getTargetName($package, 'tar');
$this->assertFileExists($target);
-
+
$tmppath = sys_get_temp_dir().'/composer_archiver/'.$this->manager->getPackageFilename($package);
$this->assertFileNotExists($tmppath);
@@ -79,13 +79,13 @@ class ArchiveManagerTest extends ArchiverTest
throw new \RuntimeException('Could not init: '.$this->process->getErrorOutput());
}
- $result = file_put_contents('b', 'a');
+ $result = file_put_contents('composer.json', '{"name":"faker/faker", "description": "description", "license": "MIT"}');
if (false === $result) {
chdir($currentWorkDir);
throw new \RuntimeException('Could not save file.');
}
- $result = $this->process->execute('git add b && git commit -m "commit b" -q', $output, $this->testDir);
+ $result = $this->process->execute('git add composer.json && git commit -m "commit composer.json" -q', $output, $this->testDir);
if ($result > 0) {
chdir($currentWorkDir);
throw new \RuntimeException('Could not commit: '.$this->process->getErrorOutput());
diff --git a/tests/Composer/Test/Package/BasePackageTest.php b/tests/Composer/Test/Package/BasePackageTest.php
index 6e9f8f05a..1fe0ece84 100644
--- a/tests/Composer/Test/Package/BasePackageTest.php
+++ b/tests/Composer/Test/Package/BasePackageTest.php
@@ -25,7 +25,7 @@ class BasePackageTest extends \PHPUnit_Framework_TestCase
try {
$package->setRepository($repository);
} catch (\Exception $e) {
- $this->fail('Set againt the same repository is allowed.');
+ $this->fail('Set against the same repository is allowed.');
}
}
diff --git a/tests/Composer/Test/Package/CompletePackageTest.php b/tests/Composer/Test/Package/CompletePackageTest.php
index b6f90928c..de119ebaa 100644
--- a/tests/Composer/Test/Package/CompletePackageTest.php
+++ b/tests/Composer/Test/Package/CompletePackageTest.php
@@ -14,7 +14,7 @@ namespace Composer\Test\Package;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class CompletePackageTest extends TestCase
{
diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php
index e16cdc10c..18b5c493f 100644
--- a/tests/Composer/Test/Package/Version/VersionParserTest.php
+++ b/tests/Composer/Test/Package/Version/VersionParserTest.php
@@ -181,6 +181,16 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
$this->assertSame((string) new VersionConstraint('=', '1.0.0.0'), (string) $parser->parseConstraints('1.0#trunk/@123'));
}
+ /**
+ * @expectedException UnexpectedValueException
+ * @expectedExceptionMessage Invalid operator "~>", you probably meant to use the "~" operator
+ */
+ public function testParseConstraintsNudgesRubyDevsTowardsThePathOfRighteousness()
+ {
+ $parser = new VersionParser;
+ $parser->parseConstraints('~>1.2');
+ }
+
/**
* @dataProvider simpleConstraints
*/
diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php
new file mode 100644
index 000000000..f80acd325
--- /dev/null
+++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php
@@ -0,0 +1,16 @@
+packages = array();
+ $this->directory = sys_get_temp_dir() . '/' . uniqid();
for ($i = 1; $i <= 4; $i++) {
- $this->packages[] = $loader->load(__DIR__.'/Fixtures/installer-v'.$i.'/composer.json');
+ $filename = '/Fixtures/plugin-v'.$i.'/composer.json';
+ mkdir(dirname($this->directory . $filename), 0777, TRUE);
+ $this->packages[] = $loader->load(__DIR__ . $filename);
}
$dm = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->disableOriginalConstructor()
->getMock();
- $this->im = $this->getMockBuilder('Composer\Installer\InstallationManager')
- ->disableOriginalConstructor()
- ->getMock();
-
$this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
$rm = $this->getMockBuilder('Composer\Repository\RepositoryManager')
@@ -54,129 +56,112 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
->method('getLocalRepository')
->will($this->returnValue($this->repository));
+ $im = $this->getMock('Composer\Installer\InstallationManager');
+ $im->expects($this->any())
+ ->method('getInstallPath')
+ ->will($this->returnCallback(function ($package) {
+ return __DIR__.'/Fixtures/'.$package->getPrettyName();
+ }));
+
$this->io = $this->getMock('Composer\IO\IOInterface');
- $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock();
+ $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$this->autoloadGenerator = new AutoloadGenerator($dispatcher);
$this->composer = new Composer();
$config = new Config();
$this->composer->setConfig($config);
$this->composer->setDownloadManager($dm);
- $this->composer->setInstallationManager($this->im);
$this->composer->setRepositoryManager($rm);
+ $this->composer->setInstallationManager($im);
$this->composer->setAutoloadGenerator($this->autoloadGenerator);
+ $this->pm = new PluginManager($this->composer, $this->io);
+ $this->composer->setPluginManager($this->pm);
+
$config->merge(array(
'config' => array(
- 'vendor-dir' => __DIR__.'/Fixtures/',
- 'bin-dir' => __DIR__.'/Fixtures/bin',
+ 'vendor-dir' => $this->directory.'/Fixtures/',
+ 'home' => $this->directory.'/Fixtures',
+ 'bin-dir' => $this->directory.'/Fixtures/bin',
),
));
}
- public function testInstallNewInstaller()
+ protected function tearDown()
{
- $this->repository
- ->expects($this->once())
- ->method('getPackages')
- ->will($this->returnValue(array()));
- $installer = new InstallerInstallerMock($this->io, $this->composer);
-
- $test = $this;
- $this->im
- ->expects($this->once())
- ->method('addInstaller')
- ->will($this->returnCallback(function ($installer) use ($test) {
- $test->assertEquals('installer-v1', $installer->version);
- }));
-
- $installer->install($this->repository, $this->packages[0]);
+ $filesystem = new Filesystem();
+ $filesystem->removeDirectory($this->directory);
}
- public function testInstallMultipleInstallers()
+ public function testInstallNewPlugin()
{
$this->repository
- ->expects($this->once())
+ ->expects($this->exactly(2))
->method('getPackages')
->will($this->returnValue(array()));
+ $installer = new PluginInstaller($this->io, $this->composer);
+ $this->pm->loadInstalledPlugins();
- $installer = new InstallerInstallerMock($this->io, $this->composer);
+ $installer->install($this->repository, $this->packages[0]);
- $test = $this;
+ $plugins = $this->pm->getPlugins();
+ $this->assertEquals('installer-v1', $plugins[0]->version);
+ }
- $this->im
- ->expects($this->at(0))
- ->method('addInstaller')
- ->will($this->returnCallback(function ($installer) use ($test) {
- $test->assertEquals('custom1', $installer->name);
- $test->assertEquals('installer-v4', $installer->version);
- }));
-
- $this->im
- ->expects($this->at(1))
- ->method('addInstaller')
- ->will($this->returnCallback(function ($installer) use ($test) {
- $test->assertEquals('custom2', $installer->name);
- $test->assertEquals('installer-v4', $installer->version);
- }));
+ public function testInstallMultiplePlugins()
+ {
+ $this->repository
+ ->expects($this->exactly(2))
+ ->method('getPackages')
+ ->will($this->returnValue(array()));
+ $installer = new PluginInstaller($this->io, $this->composer);
+ $this->pm->loadInstalledPlugins();
$installer->install($this->repository, $this->packages[3]);
+
+ $plugins = $this->pm->getPlugins();
+ $this->assertEquals('plugin1', $plugins[0]->name);
+ $this->assertEquals('installer-v4', $plugins[0]->version);
+ $this->assertEquals('plugin2', $plugins[1]->name);
+ $this->assertEquals('installer-v4', $plugins[1]->version);
}
public function testUpgradeWithNewClassName()
{
$this->repository
- ->expects($this->once())
+ ->expects($this->exactly(3))
->method('getPackages')
->will($this->returnValue(array($this->packages[0])));
$this->repository
->expects($this->exactly(2))
->method('hasPackage')
->will($this->onConsecutiveCalls(true, false));
- $installer = new InstallerInstallerMock($this->io, $this->composer);
-
- $test = $this;
- $this->im
- ->expects($this->once())
- ->method('addInstaller')
- ->will($this->returnCallback(function ($installer) use ($test) {
- $test->assertEquals('installer-v2', $installer->version);
- }));
+ $installer = new PluginInstaller($this->io, $this->composer);
+ $this->pm->loadInstalledPlugins();
$installer->update($this->repository, $this->packages[0], $this->packages[1]);
+
+ $plugins = $this->pm->getPlugins();
+ $this->assertEquals('installer-v2', $plugins[1]->version);
}
public function testUpgradeWithSameClassName()
{
$this->repository
- ->expects($this->once())
+ ->expects($this->exactly(3))
->method('getPackages')
->will($this->returnValue(array($this->packages[1])));
$this->repository
->expects($this->exactly(2))
->method('hasPackage')
->will($this->onConsecutiveCalls(true, false));
- $installer = new InstallerInstallerMock($this->io, $this->composer);
-
- $test = $this;
- $this->im
- ->expects($this->once())
- ->method('addInstaller')
- ->will($this->returnCallback(function ($installer) use ($test) {
- $test->assertEquals('installer-v3', $installer->version);
- }));
+ $installer = new PluginInstaller($this->io, $this->composer);
+ $this->pm->loadInstalledPlugins();
$installer->update($this->repository, $this->packages[1], $this->packages[2]);
- }
-}
-
-class InstallerInstallerMock extends InstallerInstaller
-{
- public function getInstallPath(PackageInterface $package)
- {
- $version = $package->getVersion();
-
- return __DIR__.'/Fixtures/installer-v'.$version[0].'/';
+
+ $plugins = $this->pm->getPlugins();
+ $this->assertEquals('installer-v3', $plugins[1]->version);
}
}
diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php
index 6852f7dd6..434b3da99 100644
--- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php
+++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\Repository;
use Composer\Repository\ArrayRepository;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class ArrayRepositoryTest extends TestCase
{
diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php
index 5ffae515a..109b53bfb 100644
--- a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php
+++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\IO\NullIO;
use Composer\Config;
use Composer\Package\BasePackage;
diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php
index bad24d6d4..5109ee41f 100644
--- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php
+++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php
@@ -15,7 +15,7 @@ namespace Composer\Test\Repository;
use Composer\Repository\ComposerRepository;
use Composer\IO\NullIO;
use Composer\Test\Mock\FactoryMock;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionParser;
diff --git a/tests/Composer/Test/Repository/CompositeRepositoryTest.php b/tests/Composer/Test/Repository/CompositeRepositoryTest.php
index 978587133..d9f8b70e3 100644
--- a/tests/Composer/Test/Repository/CompositeRepositoryTest.php
+++ b/tests/Composer/Test/Repository/CompositeRepositoryTest.php
@@ -14,7 +14,7 @@ namespace Composer\Test\Repository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\ArrayRepository;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class CompositeRepositoryTest extends TestCase
{
diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php
index f80a91889..fa6214dee 100644
--- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php
+++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php
@@ -13,7 +13,7 @@
namespace Composer\Repository;
use Composer\Repository\FilesystemRepository;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class FilesystemRepositoryTest extends TestCase
{
diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
index 127c5689c..214d7b702 100644
--- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
+++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository\Pear;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\Package\Version\VersionParser;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Package\Link;
diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php
index ac4f377be..299eae37b 100644
--- a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php
+++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository\Pear;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\Test\Mock\RemoteFilesystemMock;
class ChannelRest10ReaderTest extends TestCase
diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php
index 58105a5eb..08420786e 100644
--- a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php
+++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository\Pear;
-use Composer\Test\TestCase;
+use Composer\TestCase;
use Composer\Test\Mock\RemoteFilesystemMock;
class ChannelRest11ReaderTest extends TestCase
diff --git a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php
index c2d77aef0..4f43d39f0 100644
--- a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php
+++ b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository\Pear;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class PackageDependencyParserTest extends TestCase
{
diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php
index 35e05fc2c..a42c8e0b3 100644
--- a/tests/Composer/Test/Repository/PearRepositoryTest.php
+++ b/tests/Composer/Test/Repository/PearRepositoryTest.php
@@ -12,7 +12,7 @@
namespace Composer\Repository;
-use Composer\Test\TestCase;
+use Composer\TestCase;
/**
* @group slow
diff --git a/tests/Composer/Test/Repository/RepositoryManagerTest.php b/tests/Composer/Test/Repository/RepositoryManagerTest.php
new file mode 100644
index 000000000..94acc8bad
--- /dev/null
+++ b/tests/Composer/Test/Repository/RepositoryManagerTest.php
@@ -0,0 +1,57 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository;
+
+use Composer\TestCase;
+
+class RepositoryManagerTest extends TestCase
+{
+ /**
+ * @dataProvider creationCases
+ */
+ public function testRepoCreation($type, $config)
+ {
+ $rm = new RepositoryManager(
+ $this->getMock('Composer\IO\IOInterface'),
+ $this->getMock('Composer\Config'),
+ $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock()
+ );
+ $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
+ $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
+ $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
+ $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
+
+ $rm->createRepository('composer', array('url' => 'http://example.org'));
+ $rm->createRepository('composer', array('url' => 'http://example.org'));
+ $rm->createRepository('composer', array('url' => 'http://example.org'));
+ }
+
+ public function creationCases()
+ {
+ return array(
+ array('composer', array('url' => 'http://example.org')),
+ array('vcs', array('url' => 'http://github.com/foo/bar')),
+ array('git', array('url' => 'http://github.com/foo/bar')),
+ array('git', array('url' => 'git@example.org:foo/bar.git')),
+ array('svn', array('url' => 'svn://example.org/foo/bar')),
+ array('pear', array('url' => 'http://pear.example.org/foo')),
+ array('artifact', array('url' => '/path/to/zips')),
+ array('package', array()),
+ );
+ }
+}
diff --git a/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php
new file mode 100644
index 000000000..36cd69ebc
--- /dev/null
+++ b/tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php
@@ -0,0 +1,146 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Repository\Vcs;
+
+use Composer\Repository\Vcs\PerforceDriver;
+use Composer\Util\Filesystem;
+use Composer\Config;
+
+/**
+ * @author Matt Whittom
+ */
+class PerforceDriverTest extends \PHPUnit_Framework_TestCase
+{
+ private $config;
+ private $io;
+ private $process;
+ private $remoteFileSystem;
+ private $testPath;
+
+ public function setUp()
+ {
+ $this->testPath = sys_get_temp_dir() . '/composer-test';
+ $this->config = new Config();
+ $this->config->merge(
+ array(
+ 'config' => array(
+ 'home' => $this->testPath,
+ ),
+ )
+ );
+
+ $this->io = $this->getMock('Composer\IO\IOInterface');
+ $this->process = $this->getMock('Composer\Util\ProcessExecutor');
+ $this->remoteFileSystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function tearDown()
+ {
+ $fs = new Filesystem;
+ $fs->removeDirectory($this->testPath);
+ }
+
+ public function testInitializeCapturesVariablesFromRepoConfig()
+ {
+ $this->setUp();
+ $repoConfig = array(
+ 'url' => 'TEST_PERFORCE_URL',
+ 'depot' => 'TEST_DEPOT_CONFIG',
+ 'branch' => 'TEST_BRANCH_CONFIG'
+ );
+ $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+ $process = $this->getMock('Composer\Util\ProcessExecutor');
+ $arguments = array(
+ array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH'),
+ 'port' => 'TEST_PORT',
+ 'path' => $this->testPath,
+ $process,
+ true,
+ 'TEST'
+ );
+ $perforce = $this->getMock('Composer\Util\Perforce', null, $arguments);
+ $driver->setPerforce($perforce);
+ $driver->initialize();
+ $this->assertEquals('TEST_PERFORCE_URL', $driver->getUrl());
+ $this->assertEquals('TEST_DEPOT_CONFIG', $driver->getDepot());
+ $this->assertEquals('TEST_BRANCH_CONFIG', $driver->getBranch());
+ }
+
+ public function testInitializeLogsInAndConnectsClient()
+ {
+ $this->setUp();
+ $repoConfig = array(
+ 'url' => 'TEST_PERFORCE_URL',
+ 'depot' => 'TEST_DEPOT_CONFIG',
+ 'branch' => 'TEST_BRANCH_CONFIG'
+ );
+ $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+ $perforce = $this->getMockBuilder('Composer\Util\Perforce')->disableOriginalConstructor()->getMock();
+ $perforce->expects($this->at(0))
+ ->method('p4Login')
+ ->with($this->io);
+ $perforce->expects($this->at(1))
+ ->method('checkStream')
+ ->with($this->equalTo('TEST_DEPOT_CONFIG'));
+ $perforce->expects($this->at(2))
+ ->method('writeP4ClientSpec');
+ $perforce->expects($this->at(3))
+ ->method('connectClient');
+
+ $driver->setPerforce($perforce);
+ $driver->initialize();
+ }
+
+ public function testHasComposerFile()
+ {
+ $repoConfig = array(
+ 'url' => 'TEST_PERFORCE_URL',
+ 'depot' => 'TEST_DEPOT_CONFIG',
+ 'branch' => 'TEST_BRANCH_CONFIG'
+ );
+ $driver = new PerforceDriver($repoConfig, $this->io, $this->config, $this->process, $this->remoteFileSystem);
+ $process = $this->getMock('Composer\Util\ProcessExecutor');
+ $arguments = array(
+ array('depot' => 'TEST_DEPOT', 'branch' => 'TEST_BRANCH'),
+ 'port' => 'TEST_PORT',
+ 'path' => $this->testPath,
+ $process,
+ true,
+ 'TEST'
+ );
+ $perforce = $this->getMock('Composer\Util\Perforce', array('getComposerInformation'), $arguments);
+ $perforce->expects($this->at(0))
+ ->method('getComposerInformation')
+ ->with($this->equalTo('//TEST_DEPOT_CONFIG/TEST_IDENTIFIER'))
+ ->will($this->returnValue('Some json stuff'));
+ $driver->setPerforce($perforce);
+ $driver->initialize();
+ $identifier = 'TEST_IDENTIFIER';
+ $result = $driver->hasComposerFile($identifier);
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Test that supports() simply return false.
+ *
+ * @covers \Composer\Repository\Vcs\PerforceDriver::supports
+ *
+ * @return void
+ */
+ public function testSupportsReturnsFalseNoDeepCheck()
+ {
+ $this->expectOutputString('');
+ $this->assertFalse(PerforceDriver::supports($this->io, $this->config, 'existing.url'));
+ }
+}
diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
index d9bd53321..2f698565f 100644
--- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
+++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
@@ -80,10 +80,8 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
*/
public function testSupport($url, $assertion)
{
- if ($assertion === true) {
- $this->assertTrue(SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $url));
- } else {
- $this->assertFalse(SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $url));
- }
+ $config = new Config();
+ $result = SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $config, $url);
+ $this->assertEquals($assertion, $result);
}
}
diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php
index b98be6fc2..eaedc82a9 100644
--- a/tests/Composer/Test/Repository/VcsRepositoryTest.php
+++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php
@@ -25,12 +25,14 @@ use Composer\Config;
*/
class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
{
+ private static $composerHome;
private static $gitRepo;
private $skipped;
protected function initialize()
{
$oldCwd = getcwd();
+ self::$composerHome = sys_get_temp_dir() . '/composer-home-'.mt_rand().'/';
self::$gitRepo = sys_get_temp_dir() . '/composer-git-'.mt_rand().'/';
$locator = new ExecutableFinder();
@@ -125,6 +127,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
public static function tearDownAfterClass()
{
$fs = new Filesystem;
+ $fs->removeDirectory(self::$composerHome);
$fs->removeDirectory(self::$gitRepo);
}
@@ -140,7 +143,13 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
'dev-master' => true,
);
- $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, new Config());
+ $config = new Config();
+ $config->merge(array(
+ 'config' => array(
+ 'home' => self::$composerHome,
+ ),
+ ));
+ $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, $config);
$packages = $repo->getPackages();
$dumper = new ArrayDumper();
diff --git a/tests/Composer/Test/Util/ErrorHandlerTest.php b/tests/Composer/Test/Util/ErrorHandlerTest.php
index e24fe3f39..cb16a1e13 100644
--- a/tests/Composer/Test/Util/ErrorHandlerTest.php
+++ b/tests/Composer/Test/Util/ErrorHandlerTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\Util;
use Composer\Util\ErrorHandler;
-use Composer\Test\TestCase;
+use Composer\TestCase;
/**
* ErrorHandler test case
@@ -38,7 +38,7 @@ class ErrorHandlerTest extends TestCase
*/
public function testErrorHandlerCaptureWarning()
{
- $this->setExpectedException('\ErrorException', 'array_merge(): Argument #2 is not an array');
+ $this->setExpectedException('\ErrorException', 'array_merge');
ErrorHandler::register();
diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php
index 88fad9289..c74e84a5f 100644
--- a/tests/Composer/Test/Util/FilesystemTest.php
+++ b/tests/Composer/Test/Util/FilesystemTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\Util;
use Composer\Util\Filesystem;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class FilesystemTest extends TestCase
{
@@ -60,6 +60,8 @@ class FilesystemTest extends TestCase
array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'d:/test'"),
array('/foo/bar', '/foo/bar_vendor', true, "dirname(__DIR__).'/bar_vendor'"),
array('/foo/bar_vendor', '/foo/bar', true, "dirname(__DIR__).'/bar'"),
+ array('/foo/bar_vendor', '/foo/bar/src', true, "dirname(__DIR__).'/bar/src'"),
+ array('/foo/bar_vendor/src2', '/foo/bar/src/lib', true, "dirname(dirname(__DIR__)).'/bar/src/lib'"),
);
}
@@ -104,9 +106,12 @@ class FilesystemTest extends TestCase
array('/tmp/test/.././vendor', '/tmp/test', '../test', true),
array('C:/Temp', 'c:\Temp\..\..\test', "../test", true),
array('C:/Temp/../..', 'c:\Temp\..\..\test', "./test", true),
+ array('C:/Temp/../..', 'D:\Temp\..\..\test', "d:/test", true),
array('/tmp', '/tmp/../../test', '/test', true),
array('/foo/bar', '/foo/bar_vendor', '../bar_vendor', true),
array('/foo/bar_vendor', '/foo/bar', '../bar', true),
+ array('/foo/bar_vendor', '/foo/bar/src', '../bar/src', true),
+ array('/foo/bar_vendor/src2', '/foo/bar/src/lib', '../../bar/src/lib', true),
);
}
diff --git a/tests/Composer/Test/Util/PerforceTest.php b/tests/Composer/Test/Util/PerforceTest.php
new file mode 100644
index 000000000..f2eeb1964
--- /dev/null
+++ b/tests/Composer/Test/Util/PerforceTest.php
@@ -0,0 +1,695 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Util;
+
+use Composer\Util\Perforce;
+use Composer\Util\ProcessExecutor;
+
+/**
+ * @author Matt Whittom
+ */
+class PerforceTest extends \PHPUnit_Framework_TestCase
+{
+ protected $perforce;
+ protected $processExecutor;
+
+ public function setUp()
+ {
+ $this->processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+ $repoConfig = array(
+ 'depot' => 'depot',
+ 'branch' => 'branch',
+ 'p4user' => 'user',
+ 'unique_perforce_client_name' => 'TEST'
+ );
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true);
+ }
+
+ public function testGetClientWithoutStream()
+ {
+ $client = $this->perforce->getClient();
+ $hostname = gethostname();
+ $timestamp = time();
+
+ $expected = 'composer_perforce_TEST_depot';
+ $this->assertEquals($expected, $client);
+ }
+
+ public function testGetClientFromStream()
+ {
+ $this->setPerforceToStream();
+
+ $client = $this->perforce->getClient();
+
+ $expected = 'composer_perforce_TEST_depot_branch';
+ $this->assertEquals($expected, $client);
+ }
+
+ public function testGetStreamWithoutStream()
+ {
+ $stream = $this->perforce->getStream();
+ $this->assertEquals("//depot", $stream);
+ }
+
+ public function testGetStreamWithStream()
+ {
+ $this->setPerforceToStream();
+
+ $stream = $this->perforce->getStream();
+ $this->assertEquals('//depot/branch', $stream);
+ }
+
+ public function testGetStreamWithoutLabelWithStreamWithoutLabel()
+ {
+ $stream = $this->perforce->getStreamWithoutLabel('//depot/branch');
+ $this->assertEquals('//depot/branch', $stream);
+ }
+
+ public function testGetStreamWithoutLabelWithStreamWithLabel()
+ {
+ $stream = $this->perforce->getStreamWithoutLabel('//depot/branching@label');
+ $this->assertEquals('//depot/branching', $stream);
+ }
+
+ public function testGetClientSpec()
+ {
+ $clientSpec = $this->perforce->getP4ClientSpec();
+ $expected = 'path/composer_perforce_TEST_depot.p4.spec';
+ $this->assertEquals($expected, $clientSpec);
+ }
+
+ public function testGenerateP4Command()
+ {
+ $command = 'do something';
+ $p4Command = $this->perforce->generateP4Command($command);
+ $expected = 'p4 -u user -c composer_perforce_TEST_depot -p port do something';
+ $this->assertEquals($expected, $p4Command);
+ }
+
+ public function testQueryP4UserWithUserAlreadySet()
+ {
+ $io = $this->getMock('Composer\IO\IOInterface');
+
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch', 'p4user' => 'TEST_USER');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+ $this->perforce->queryP4user($io);
+ $this->assertEquals('TEST_USER', $this->perforce->getUser());
+ }
+
+ public function testQueryP4UserWithUserSetInP4VariablesWithWindowsOS()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedCommand = 'p4 set';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'P4USER=TEST_P4VARIABLE_USER' . PHP_EOL ;
+
+ return true;
+ }
+ )
+ );
+
+ $this->perforce->queryP4user($io);
+ $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser());
+ }
+
+ public function testQueryP4UserWithUserSetInP4VariablesNotWindowsOS()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedCommand = 'echo $P4USER';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'TEST_P4VARIABLE_USER' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $this->perforce->queryP4user($io);
+ $this->assertEquals('TEST_P4VARIABLE_USER', $this->perforce->getUser());
+ }
+
+ public function testQueryP4UserQueriesForUser()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedQuestion = 'Enter P4 User:';
+ $io->expects($this->at(0))
+ ->method('ask')
+ ->with($this->equalTo($expectedQuestion))
+ ->will($this->returnValue('TEST_QUERY_USER'));
+
+ $this->perforce->queryP4user($io);
+ $this->assertEquals('TEST_QUERY_USER', $this->perforce->getUser());
+ }
+
+ public function testQueryP4UserStoresResponseToQueryForUserWithWindows()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, true, 'TEST');
+
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedQuestion = 'Enter P4 User:';
+ $io->expects($this->at(0))
+ ->method('ask')
+ ->with($this->equalTo($expectedQuestion))
+ ->will($this->returnValue('TEST_QUERY_USER'));
+ $expectedCommand = 'p4 set P4USER=TEST_QUERY_USER';
+ $this->processExecutor->expects($this->at(1))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will($this->returnValue(0));
+
+ $this->perforce->queryP4user($io);
+ }
+
+ public function testQueryP4UserStoresResponseToQueryForUserWithoutWindows()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedQuestion = 'Enter P4 User:';
+ $io->expects($this->at(0))
+ ->method('ask')
+ ->with($this->equalTo($expectedQuestion))
+ ->will($this->returnValue('TEST_QUERY_USER'));
+ $expectedCommand = 'export P4USER=TEST_QUERY_USER';
+ $this->processExecutor->expects($this->at(1))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will($this->returnValue(0));
+
+ $this->perforce->queryP4user($io);
+ }
+
+ public function testQueryP4PasswordWithPasswordAlreadySet()
+ {
+ $repoConfig = array(
+ 'depot' => 'depot',
+ 'branch' => 'branch',
+ 'p4user' => 'user',
+ 'p4password' => 'TEST_PASSWORD'
+ );
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+ $io = $this->getMock('Composer\IO\IOInterface');
+
+ $password = $this->perforce->queryP4Password($io);
+ $this->assertEquals('TEST_PASSWORD', $password);
+ }
+
+ public function testQueryP4PasswordWithPasswordSetInP4VariablesWithWindowsOS()
+ {
+ $io = $this->getMock('Composer\IO\IOInterface');
+
+ $expectedCommand = 'p4 set';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'P4PASSWD=TEST_P4VARIABLE_PASSWORD' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $password = $this->perforce->queryP4Password($io);
+ $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password);
+ }
+
+ public function testQueryP4PasswordWithPasswordSetInP4VariablesNotWindowsOS()
+ {
+ $repoConfig = array('depot' => 'depot', 'branch' => 'branch', 'p4user' => 'user');
+ $this->perforce = new Perforce($repoConfig, 'port', 'path', $this->processExecutor, false, 'TEST');
+
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedCommand = 'echo $P4PASSWD';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'TEST_P4VARIABLE_PASSWORD' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $password = $this->perforce->queryP4Password($io);
+ $this->assertEquals('TEST_P4VARIABLE_PASSWORD', $password);
+ }
+
+ public function testQueryP4PasswordQueriesForPassword()
+ {
+ $io = $this->getMock('Composer\IO\IOInterface');
+ $expectedQuestion = 'Enter password for Perforce user user: ';
+ $io->expects($this->at(0))
+ ->method('askAndHideAnswer')
+ ->with($this->equalTo($expectedQuestion))
+ ->will($this->returnValue('TEST_QUERY_PASSWORD'));
+
+ $password = $this->perforce->queryP4Password($io);
+ $this->assertEquals('TEST_QUERY_PASSWORD', $password);
+ }
+
+ public function testWriteP4ClientSpecWithoutStream()
+ {
+ $stream = fopen('php://memory', 'w+');
+ $this->perforce->writeClientSpecToFile($stream);
+
+ rewind($stream);
+
+ $expectedArray = $this->getExpectedClientSpec(false);
+ try {
+ foreach ($expectedArray as $expected) {
+ $this->assertStringStartsWith($expected, fgets($stream));
+ }
+ $this->assertFalse(fgets($stream));
+ } catch (Exception $e) {
+ fclose($stream);
+ throw $e;
+ }
+ fclose($stream);
+ }
+
+ public function testWriteP4ClientSpecWithStream()
+ {
+ $this->setPerforceToStream();
+ $stream = fopen('php://memory', 'w+');
+
+ $this->perforce->writeClientSpecToFile($stream);
+ rewind($stream);
+
+ $expectedArray = $this->getExpectedClientSpec(true);
+ try {
+ foreach ($expectedArray as $expected) {
+ $this->assertStringStartsWith($expected, fgets($stream));
+ }
+ $this->assertFalse(fgets($stream));
+ } catch (Exception $e) {
+ fclose($stream);
+ throw $e;
+ }
+ fclose($stream);
+ }
+
+ public function testIsLoggedIn()
+ {
+ $expectedCommand = 'p4 -u user -p port login -s';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+ ->will($this->returnValue(0));
+
+ $this->perforce->isLoggedIn();
+ }
+
+ public function testConnectClient()
+ {
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port client -i < path/composer_perforce_TEST_depot.p4.spec';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+ ->will($this->returnValue(0));
+
+ $this->perforce->connectClient();
+ }
+
+ public function testGetBranchesWithStream()
+ {
+ $this->setPerforceToStream();
+
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port streams //depot/...';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'Stream //depot/branch mainline none \'branch\'' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $branches = $this->perforce->getBranches();
+ $this->assertEquals('//depot/branch', $branches['master']);
+ }
+
+ public function testGetBranchesWithoutStream()
+ {
+ $branches = $this->perforce->getBranches();
+ $this->assertEquals('//depot', $branches['master']);
+ }
+
+ public function testGetTagsWithoutStream()
+ {
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port labels';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'Label 0.0.1 2013/07/31 \'First Label!\'' . PHP_EOL . 'Label 0.0.2 2013/08/01 \'Second Label!\'' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $tags = $this->perforce->getTags();
+ $this->assertEquals('//depot@0.0.1', $tags['0.0.1']);
+ $this->assertEquals('//depot@0.0.2', $tags['0.0.2']);
+ }
+
+ public function testGetTagsWithStream()
+ {
+ $this->setPerforceToStream();
+
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port labels';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'Label 0.0.1 2013/07/31 \'First Label!\'' . PHP_EOL . 'Label 0.0.2 2013/08/01 \'Second Label!\'' . PHP_EOL;
+
+ return true;
+ }
+ )
+ );
+
+ $tags = $this->perforce->getTags();
+ $this->assertEquals('//depot/branch@0.0.1', $tags['0.0.1']);
+ $this->assertEquals('//depot/branch@0.0.2', $tags['0.0.2']);
+ }
+
+ public function testCheckStreamWithoutStream()
+ {
+ $result = $this->perforce->checkStream('depot');
+ $this->assertFalse($result);
+ $this->assertFalse($this->perforce->isStream());
+ }
+
+ public function testCheckStreamWithStream()
+ {
+ $this->processExecutor->expects($this->any())->method('execute')
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = 'Depot depot 2013/06/25 stream /p4/1/depots/depot/... \'Created by Me\'';
+
+ return true;
+ }
+ )
+ );
+ $result = $this->perforce->checkStream('depot');
+ $this->assertTrue($result);
+ $this->assertTrue($this->perforce->isStream());
+ }
+
+ public function testGetComposerInformationWithoutLabelWithoutStream()
+ {
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print //depot/composer.json';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = PerforceTest::getComposerJson();
+
+ return true;
+ }
+ )
+ );
+
+ $result = $this->perforce->getComposerInformation('//depot');
+ $expected = array(
+ 'name' => 'test/perforce',
+ 'description' => 'Basic project for testing',
+ 'minimum-stability' => 'dev',
+ 'autoload' => array('psr-0' => array())
+ );
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testGetComposerInformationWithLabelWithoutStream()
+ {
+ $expectedCommand = 'p4 -u user -p port files //depot/composer.json@0.0.1';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = '//depot/composer.json#1 - branch change 10001 (text)';
+
+ return true;
+ }
+ )
+ );
+
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port print //depot/composer.json@10001';
+ $this->processExecutor->expects($this->at(1))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = PerforceTest::getComposerJson();
+
+ return true;
+ }
+ )
+ );
+
+ $result = $this->perforce->getComposerInformation('//depot@0.0.1');
+
+ $expected = array(
+ 'name' => 'test/perforce',
+ 'description' => 'Basic project for testing',
+ 'minimum-stability' => 'dev',
+ 'autoload' => array('psr-0' => array())
+ );
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testGetComposerInformationWithoutLabelWithStream()
+ {
+ $this->setPerforceToStream();
+
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print //depot/branch/composer.json';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = PerforceTest::getComposerJson();
+
+ return true;
+ }
+ )
+ );
+
+ $result = $this->perforce->getComposerInformation('//depot/branch');
+
+ $expected = array(
+ 'name' => 'test/perforce',
+ 'description' => 'Basic project for testing',
+ 'minimum-stability' => 'dev',
+ 'autoload' => array('psr-0' => array())
+ );
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testGetComposerInformationWithLabelWithStream()
+ {
+ $this->setPerforceToStream();
+ $expectedCommand = 'p4 -u user -p port files //depot/branch/composer.json@0.0.1';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = '//depot/composer.json#1 - branch change 10001 (text)';
+
+ return true;
+ }
+ )
+ );
+
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port print //depot/branch/composer.json@10001';
+ $this->processExecutor->expects($this->at(1))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will(
+ $this->returnCallback(
+ function ($command, &$output) {
+ $output = PerforceTest::getComposerJson();
+
+ return true;
+ }
+ )
+ );
+
+ $result = $this->perforce->getComposerInformation('//depot/branch@0.0.1');
+
+ $expected = array(
+ 'name' => 'test/perforce',
+ 'description' => 'Basic project for testing',
+ 'minimum-stability' => 'dev',
+ 'autoload' => array('psr-0' => array())
+ );
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testSyncCodeBaseWithoutStream()
+ {
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot -p port sync -f @label';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+ ->will($this->returnValue(0));
+
+ $this->perforce->syncCodeBase('label');
+ }
+
+ public function testSyncCodeBaseWithStream()
+ {
+ $this->setPerforceToStream();
+ $expectedCommand = 'p4 -u user -c composer_perforce_TEST_depot_branch -p port sync -f @label';
+ $this->processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand))
+ ->will($this->returnValue(0));
+
+ $this->perforce->syncCodeBase('label');
+ }
+
+ public function testCheckServerExists()
+ {
+ $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+
+ $expectedCommand = 'p4 -p perforce.does.exist:port info -s';
+ $processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+ ->will($this->returnValue(0));
+
+ $result = $this->perforce->checkServerExists('perforce.does.exist:port', $processExecutor);
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Test if "p4" command is missing.
+ *
+ * @covers \Composer\Util\Perforce::checkServerExists
+ *
+ * @return void
+ */
+ public function testCheckServerClientError()
+ {
+ $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+
+ $expectedCommand = 'p4 -p perforce.does.exist:port info -s';
+ $processExecutor->expects($this->at(0))
+ ->method('execute')
+ ->with($this->equalTo($expectedCommand), $this->equalTo(null))
+ ->will($this->returnValue(127));
+
+ $result = $this->perforce->checkServerExists('perforce.does.exist:port', $processExecutor);
+ $this->assertFalse($result);
+ }
+
+ public static function getComposerJson()
+ {
+ $composer_json = array(
+ '{',
+ '"name": "test/perforce",',
+ '"description": "Basic project for testing",',
+ '"minimum-stability": "dev",',
+ '"autoload": {',
+ '"psr-0" : {',
+ '}',
+ '}',
+ '}'
+ );
+
+ return implode($composer_json);
+ }
+
+ private function getExpectedClientSpec($withStream)
+ {
+ $expectedArray = array(
+ 'Client: composer_perforce_TEST_depot',
+ PHP_EOL,
+ 'Update:',
+ PHP_EOL,
+ 'Access:',
+ 'Owner: user',
+ PHP_EOL,
+ 'Description:',
+ ' Created by user from composer.',
+ PHP_EOL,
+ 'Root: path',
+ PHP_EOL,
+ 'Options: noallwrite noclobber nocompress unlocked modtime rmdir',
+ PHP_EOL,
+ 'SubmitOptions: revertunchanged',
+ PHP_EOL,
+ 'LineEnd: local',
+ PHP_EOL
+ );
+ if ($withStream) {
+ $expectedArray[] = 'Stream:';
+ $expectedArray[] = ' //depot/branch';
+ } else {
+ $expectedArray[] = 'View: //depot/... //composer_perforce_TEST_depot/...';
+ }
+
+ return $expectedArray;
+ }
+
+ private function setPerforceToStream()
+ {
+ $this->perforce->setStream('//depot/branch');
+ }
+}
diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php
index 716a2daa7..b15a2763f 100644
--- a/tests/Composer/Test/Util/ProcessExecutorTest.php
+++ b/tests/Composer/Test/Util/ProcessExecutorTest.php
@@ -13,7 +13,7 @@
namespace Composer\Test\Util;
use Composer\Util\ProcessExecutor;
-use Composer\Test\TestCase;
+use Composer\TestCase;
class ProcessExecutorTest extends TestCase
{
diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php
index eb8ebc07e..eabfe9ed5 100644
--- a/tests/Composer/Test/Util/RemoteFilesystemTest.php
+++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php
@@ -144,7 +144,6 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
}
}
-
public function testCaptureAuthenticationParamsFromUrl()
{
$io = $this->getMock('Composer\IO\IOInterface');
@@ -158,7 +157,6 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
} catch (\Exception $e) {
$this->assertInstanceOf('Composer\Downloader\TransportException', $e);
$this->assertEquals(404, $e->getCode());
- $this->assertContains('404 Not Found', $e->getMessage());
}
}
diff --git a/tests/Composer/Test/Util/SpdxLicenseIdentifierTest.php b/tests/Composer/Test/Util/SpdxLicenseIdentifierTest.php
index 2ed7c1819..b6cee4ec5 100644
--- a/tests/Composer/Test/Util/SpdxLicenseIdentifierTest.php
+++ b/tests/Composer/Test/Util/SpdxLicenseIdentifierTest.php
@@ -1,7 +1,7 @@
array('method' => 'GET')));
@@ -69,7 +69,7 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
'proxy' => 'tcp://proxyserver.net:3128',
'request_fulluri' => true,
'method' => 'GET',
- 'header' => array("Proxy-Authorization: Basic " . base64_encode('username:password')),
+ 'header' => array("Proxy-Authorization: Basic " . base64_encode('username:p@ssword')),
'max_redirects' => 20,
'follow_location' => 1,
)), $options);
@@ -192,6 +192,6 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
);
$context = StreamContextFactory::getContext('http://example.org', $options);
$ctxoptions = stream_context_get_options($context);
- $this->assertEquals(join("\n", $ctxoptions['http']['header']), join("\n", $expectedOptions['http']['header']));
+ $this->assertEquals(end($ctxoptions['http']['header']), end($expectedOptions['http']['header']));
}
}
diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/TestCase.php
similarity index 98%
rename from tests/Composer/Test/TestCase.php
rename to tests/Composer/TestCase.php
index bb4e0f14b..760b57291 100644
--- a/tests/Composer/Test/TestCase.php
+++ b/tests/Composer/TestCase.php
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
-namespace Composer\Test;
+namespace Composer;
use Composer\Package\Version\VersionParser;
use Composer\Package\Package;
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 1974415d3..c5e16d625 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -14,3 +14,5 @@ error_reporting(E_ALL);
$loader = require __DIR__.'/../src/bootstrap.php';
$loader->add('Composer\Test', __DIR__);
+
+require __DIR__.'/Composer/TestCase.php';
]