Merge remote-tracking branch 'upstream/master'

Conflicts:
	src/Composer/Downloader/FileDownloader.php
main
Luís Otávio Cobucci Oblonczyk 11 years ago
commit 0f2c0ab389

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

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

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

112
composer.lock generated

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

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

@ -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`.
* **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`.
* **Wildcard:** You can specify a pattern with a `*` wildcard. `1.0.*` is the
equivalent of `>=1.0,<1.1`.
* **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.
Name | Example | Description
-------------- | --------------------- | -----------
Exact version | `1.0.2` | You can specify the exact version of a package.
Range | `>=1.0` `>=1.0,<2.0` `>=1.0,<1.1 | >=1.2` | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. <br />You can define multiple ranges, separated by a comma, which will be treated as a **logical AND**. A pipe symbol `|` will be treated as a **logical OR**. <br />AND has higher precedence than OR.
Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1`.
Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2,<2.0`. For more details, read the next section below.
### 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

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

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

@ -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 `#<ref>`. 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 `#<ref>`.
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.

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

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

@ -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 Custom Installer class
### The Plugin 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 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.
It will also define the [type][1] string as it will be recognized by packages
that will use this installer in the `supports()` method.
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`][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

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

@ -0,0 +1,150 @@
<!--
tagline: Modify and extend Composer's functionality
-->
# 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

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

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

@ -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": {

@ -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 = <<<EOF
<?php
// autoload_namespaces.php generated by Composer
// autoload_namespaces.php @generated by Composer
\$vendorDir = $vendorPathCode52;
\$baseDir = $appBaseDirCode;
@ -85,7 +86,7 @@ EOF;
$classmapFile = <<<EOF
<?php
// autoload_classmap.php generated by Composer
// autoload_classmap.php @generated by Composer
\$vendorDir = $vendorPathCode52;
\$baseDir = $appBaseDirCode;
@ -168,7 +169,7 @@ EOF;
$classmapFile .= ");\n";
if (!$suffix) {
$suffix = md5(uniqid('', true));
$suffix = $config->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 <<<EOF
<?php
// include_paths.php generated by Composer
// include_paths.php @generated by Composer
\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;
@ -310,13 +311,14 @@ EOF;
return <<<EOF
<?php
// autoload_files.php generated by Composer
// autoload_files.php @generated by Composer
\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;
return array(
$filesCode);
EOF;
}
@ -328,7 +330,7 @@ EOF;
$path = $filesystem->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 <<<AUTOLOAD
<?php
// autoload.php generated by Composer
// autoload.php @generated by Composer
require_once $vendorPathToTargetDirCode . '/autoload_real.php';
@ -364,7 +366,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
AUTOLOAD;
}
protected function getAutoloadRealFile($usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath)
protected function getAutoloadRealFile($usePSR0, $useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)
{
// TODO the class ComposerAutoloaderInit should be revert to a closure
// when APC has been fixed:
@ -376,7 +378,7 @@ AUTOLOAD;
$file = <<<HEADER
<?php
// autoload_real.php generated by Composer
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit$suffix
{
@ -395,7 +397,7 @@ class ComposerAutoloaderInit$suffix
return self::\$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader);
self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));
@ -454,15 +456,16 @@ REGISTER_AUTOLOAD;
}
$file .= <<<REGISTER_LOADER
\$loader->register(true);
\$loader->register($prependAutoloader);
REGISTER_LOADER;
if ($useIncludeFiles) {
$file .= <<<INCLUDE_FILES
foreach (require __DIR__ . '/autoload_files.php' as \$file) {
require \$file;
$file .= <<<'INCLUDE_FILES'
$includeFiles = require __DIR__ . '/autoload_files.php';
foreach ($includeFiles as $file) {
require $file;
}

@ -23,6 +23,7 @@ use Symfony\Component\Finder\Finder;
*/
class Cache
{
private static $cacheCollected = false;
private $io;
private $root;
private $enabled = true;
@ -126,6 +127,11 @@ class Cache
return false;
}
public function gcIsNecessary()
{
return (!self::$cacheCollected && !mt_rand(0, 50));
}
public function remove($file)
{
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
@ -157,6 +163,8 @@ class Cache
}
}
self::$cacheCollected = true;
return true;
}

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

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

@ -44,6 +44,7 @@ use Composer\Package\Version\VersionParser;
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Tobias Munk <schmunk@usrbin.de>
* @author Nils Adermann <naderman@naderman.de>
*/
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(<<<EOT
The <info>create-project</info> 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('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
$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 ($disableCustomInstallers) {
$installer->disableCustomInstallers();
}
if ($noInstall === false) {
$installer = Installer::create($io, $composer);
$installer->setPreferSource($preferSource)
->setPreferDist($preferDist)
->setDevMode($installDevPackages)
->setRunScripts( ! $noScripts);
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('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>');
if ($disableCustomInstallers) {
$io->write('<info>Custom installers have been disabled.</info>');
if ($disablePlugins) {
$io->write('<info>Plugins have been disabled.</info>');
}
if (0 === strpos($package->getPrettyVersion(), 'dev-') && in_array($package->getSourceType(), array('git', 'hg'))) {

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

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

@ -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('<info>Generating autoload files</info>');
$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();

@ -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\<user>\AppData\Roaming\Composer on Windows
and /home/<user>/.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('<info>Changed current directory to '.$config->get('home').'</info>');
// 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);
}
}

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

@ -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 <j.boggiano@seld.be>
* @author Ryan Weaver <ryan@knplabs.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Nils Adermann <naderman@naderman.de>
*/
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('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
$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();
}
}

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

@ -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 <jeremy@free-agent.fr>
@ -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".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
file_put_contents($json->getPath(), $composerBackup);
return 1;
}
return 0;
return $status;
}
private function updateFileCleanly($json, array $base, array $new, $requireKey)

@ -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 <seroscho@googlemail.com>
@ -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;

@ -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 <igor@wiedler.ch>
* @author Kevin Ran <kran@adobe.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
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(<<<EOT
The <info>self-update</info> 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 <info>%s</info>.", $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('<error>You can not update to a specific SHA-1 as those phars are not available for download</error>');
$rfs->copy('getcomposer.org', $remoteFilename, $tempFilename);
return 1;
}
if (!file_exists($tempFilename)) {
$output->writeln('<error>The download of the new composer version failed for an unexpected reason');
if (Composer::VERSION === $updateVersion) {
$output->writeln('<info>You are already using composer version '.$updateVersion.'.</info>');
return 1;
}
return 0;
}
$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 <info>%s</info>.", $updateVersion));
$remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar");
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename);
if (!file_exists($tempFilename)) {
$output->writeln('<error>The download of the new composer version failed for an unexpected reason');
return 1;
}
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;
// 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('<info>Removing: '.$file);
$fs->remove($file);
}
$output->writeln('<error>The download is corrupted ('.$e->getMessage().').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
}
}
if ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
$output->writeln('<error>The file is corrupted ('.$err->getMessage().').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
return 1;
}
if (file_exists($backupFile)) {
$output->writeln('Use <info>composer self-update --rollback</info> to return to version '.Composer::VERSION);
} else {
$output->writeln("<info>You are using the latest composer version.</info>");
$output->writeln('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
}
}
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 <info>%s</info>.", $rollbackVersion));
if ($err = $this->setLocalPhar($localFilename, $oldFile)) {
$output->writeln('<error>The backup file was corrupted ('.$err->getMessage().') and has been removed.</error>');
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();
}
}

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

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

@ -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 <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>
*/
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(<<<EOT
The <info>update</info> 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('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
$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();
}
}

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

@ -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 <j.boggiano@seld.be>
* @author Konstantin Kudryashiv <ever.zet@gmail.com>
* @author Nils Adermann <naderman@naderman.de>
*/
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()
{

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

@ -124,7 +124,7 @@ class JsonConfigSource implements ConfigSourceInterface
}
if ($newFile) {
chmod($this->file->getPath(), 0600);
@chmod($this->file->getPath(), 0600);
}
}
}

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

@ -377,6 +377,7 @@ class Pool
if ($constraint === null) {
return self::MATCH;
}
return $constraint->matches(new VersionConstraint('==', $candidateVersion)) ? self::MATCH : self::MATCH_NAME;
}

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

@ -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-')) {

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

@ -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) !== '..';

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

@ -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 <iam@chebba.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author François Pluchino <francois.pluchino@opendisplay.com>
* @author Nils Adermann <naderman@naderman.de>
*/
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;
}

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

@ -0,0 +1,97 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Downloader;
use Composer\Package\PackageInterface;
use Composer\Repository\VcsRepository;
use Composer\Util\Perforce;
/**
* @author Matt Whittom <Matt.Whittom@veteransunited.com>
*/
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;
}
}

@ -0,0 +1,94 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Downloader;
use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use RarArchive;
/**
* RAR archive downloader.
*
* Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
*
* @author Derrick Nelson <drrcknlsn@gmail.com>
*/
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();
}
}

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

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

@ -0,0 +1,69 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\EventDispatcher;
/**
* The base event class
*
* @author Nils Adermann <naderman@naderman.de>
*/
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;
}
}

@ -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 <francois.pluchino@opendisplay.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>
*/
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();

@ -0,0 +1,48 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\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 <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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();
}

@ -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 <ryan@knplabs.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Igor Wiedler <igor@wiedler.ch>
* @author Nils Adermann <naderman@naderman.de>
*/
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);
}
}

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

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

@ -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 <j.boggiano@seld.be>
* @author Beau Simensen <beau@dflydev.com>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Nils Adermann <naderman@naderman.de>
*/
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('<error>Your requirements could not be resolved to an installable set of packages.</error>');
$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('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.<warning>');
$this->io->write('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
}
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;
}

@ -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 <ever.zet@gmail.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>
*/
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;
}

@ -1,104 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Installer;
use Composer\Composer;
use Composer\Package\Package;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
/**
* Installer installation manager.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
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);
}
}
}

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

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

@ -0,0 +1,81 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Installer;
use Composer\Composer;
use Composer\Package\Package;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
/**
* Installer for plugin packages
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>
*/
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);
}
}

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

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

@ -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 <matthieu@moquet.net>
@ -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);
}

@ -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 <ever.zet@gmail.com>

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

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

@ -0,0 +1,86 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\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 <naderman@naderman.de>
*/
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;
}
}

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Plugin;
/**
* The Plugin Events.
*
* @author Nils Adermann <naderman@naderman.de>
*/
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';
}

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Plugin;
use Composer\Composer;
use Composer\IO\IOInterface;
/**
* Plugin interface
*
* @author Nils Adermann <naderman@naderman.de>
*/
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);
}

@ -0,0 +1,259 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\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 <naderman@naderman.de>
*/
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("<warning>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.</warning>");
}
$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 : '');
}
}

@ -0,0 +1,78 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Plugin;
use Composer\EventDispatcher\Event;
use Composer\Util\RemoteFilesystem;
/**
* The pre file download event.
*
* @author Nils Adermann <naderman@naderman.de>
*/
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;
}
}

@ -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 <j.boggiano@seld.be>
@ -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);

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

@ -14,6 +14,7 @@ namespace Composer\Repository;
use Composer\Package\CompletePackage;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginInterface;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
@ -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);
}
}

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

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

@ -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 <j.boggiano@seld.be>
*/
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;

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

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

@ -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('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
}
} 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;

@ -0,0 +1,194 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Repository\Vcs;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Perforce;
/**
* @author Matt Whittom <Matt.Whittom@veteransunited.com>
*/
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;
}
}

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

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

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

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

@ -12,8 +12,6 @@
namespace Composer\Script;
use Composer\Composer;
/**
* The Command Event.
*

@ -16,17 +16,13 @@ use Composer\Composer;
use Composer\IO\IOInterface;
/**
* The base event class
* The script event class
*
* @author François Pluchino <francois.pluchino@opendisplay.com>
* @author Nils Adermann <naderman@naderman.de>
*/
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.
*

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

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

@ -12,8 +12,6 @@
namespace Composer\Util;
use Composer\IO\IOInterface;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/

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

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

@ -0,0 +1,544 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Util;
use Composer\IO\IOInterface;
use Symfony\Component\Process\Process;
/**
* @author Matt Whittom <Matt.Whittom@veteransunited.com>
*/
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;
}
}

@ -19,6 +19,7 @@ use Composer\Downloader\TransportException;
/**
* @author François Pluchino <francois.pluchino@opendisplay.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Nils Adermann <naderman@naderman.de>
*/
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.
*

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

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save