Merge remote-tracking branch 'parent/master'

Conflicts:
	src/Composer/Factory.php
main
Nicolas Toniazzi 10 years ago
commit 865eab602f

@ -0,0 +1,29 @@
Contributing to Composer
========================
Installation from Source
------------------------
Prior to contributing to Composer, you must use be able to run the tests.
To achieve this, you must use the sources and not the phar file.
1. Run `git clone https://github.com/composer/composer.git`
2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
Contributing policy
-------------------
All code contributions - including those of people having commit access -
must go through a pull request and approved by a core developer before being
merged. This is to ensure proper review of all the code.
Fork the project, create a feature branch, and send us a pull request.
To ensure a consistent code base, you should make sure the code follows
the [Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html)
which we borrowed from Symfony.
If you would like to help, take a look at the [list of issues](http://github.com/composer/composer/issues).

@ -32,18 +32,6 @@ themselves. To create libraries/packages please read the
3. Run Composer: `php composer.phar install` 3. Run Composer: `php composer.phar install`
4. Browse for more packages on [Packagist](https://packagist.org). 4. Browse for more packages on [Packagist](https://packagist.org).
Installation from Source
------------------------
To run tests, or develop Composer itself, you must use the sources and not the phar
file as described above.
1. Run `git clone https://github.com/composer/composer.git`
2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
Global installation of Composer (manual) Global installation of Composer (manual)
---------------------------------------- ----------------------------------------
@ -55,20 +43,6 @@ Updating Composer
Running `php composer.phar self-update` or equivalent will update a phar Running `php composer.phar self-update` or equivalent will update a phar
install with the latest version. install with the latest version.
Contributing
------------
All code contributions - including those of people having commit access -
must go through a pull request and approved by a core developer before being
merged. This is to ensure proper review of all the code.
Fork the project, create a feature branch, and send us a pull request.
To ensure a consistent code base, you should make sure the code follows
the [Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html)
which we borrowed from Symfony.
If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues).
Community Community
--------- ---------

@ -23,7 +23,7 @@
}, },
"require": { "require": {
"php": ">=5.3.2", "php": ">=5.3.2",
"justinrainbow/json-schema": "~1.1", "justinrainbow/json-schema": "~1.3",
"seld/jsonlint": "~1.0", "seld/jsonlint": "~1.0",
"symfony/console": "~2.3", "symfony/console": "~2.3",
"symfony/finder": "~2.2", "symfony/finder": "~2.2",

33
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "0430383b5ba00e406ce4a44253f49fe7", "hash": "2bc9cc8aa706b68d611d7058e4eb8de7",
"packages": [ "packages": [
{ {
"name": "justinrainbow/json-schema", "name": "justinrainbow/json-schema",
@ -327,16 +327,16 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "2.0.13", "version": "2.0.14",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5" "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca158276c1200cc27f5409a5e338486bc0b4fc94",
"reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -388,7 +388,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2014-12-03 06:41:44" "time": "2014-12-26 13:28:33"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -574,16 +574,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "4.4.0", "version": "4.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0" "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a5e49a86ce5e33b8d0657abe145057fc513543a",
"reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -641,7 +641,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2014-12-05 06:49:03" "time": "2014-12-28 07:57:05"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
@ -982,16 +982,16 @@
}, },
{ {
"name": "sebastian/version", "name": "sebastian/version",
"version": "1.0.3", "version": "1.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/version.git", "url": "https://github.com/sebastianbergmann/version.git",
"reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/a77d9123f8e809db3fbdea15038c27a95da4058b",
"reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -1013,7 +1013,7 @@
], ],
"description": "Library that helps with managing the version number of Git-hosted PHP projects", "description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version", "homepage": "https://github.com/sebastianbergmann/version",
"time": "2014-03-07 15:35:33" "time": "2014-12-15 14:25:24"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
@ -1067,6 +1067,7 @@
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": [],
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false,
"platform": { "platform": {
"php": ">=5.3.2" "php": ">=5.3.2"
}, },

@ -107,6 +107,8 @@ mv composer.phar /usr/local/bin/composer
> **Note:** If the above fails due to permissions, run the `mv` line > **Note:** If the above fails due to permissions, run the `mv` line
> again with sudo. > again with sudo.
> **Note:** In OSX Yosemite the `/usr` directory does not exist by default. If you receive the error "/usr/local/bin/composer: No such file or directory" then you must create `/usr/local/bin/` manually before proceeding.
Then, just run `composer` in order to run Composer instead of `php composer.phar`. Then, just run `composer` in order to run Composer instead of `php composer.phar`.
## Installation - Windows ## Installation - Windows

@ -2,7 +2,7 @@
## Installing ## Installing
If you have not yet installed Composer, refer to to the [Intro](00-intro.md) chapter. If you have not yet installed Composer, refer to the [Intro](00-intro.md) chapter.
## `composer.json`: Project Setup ## `composer.json`: Project Setup
@ -213,7 +213,7 @@ You define a mapping from namespaces to directories. The `src` directory would
be in your project root, on the same level as `vendor` directory is. An example be in your project root, on the same level as `vendor` directory is. An example
filename would be `src/Foo.php` containing an `Acme\Foo` class. filename would be `src/Foo.php` containing an `Acme\Foo` class.
After adding the `autoload` field, you have to re-run `install` to re-generate After adding the `autoload` field, you have to re-run `dump-autoload` to re-generate
the `vendor/autoload.php` file. the `vendor/autoload.php` file.
Including that file will also return the autoloader instance, so you can store Including that file will also return the autoloader instance, so you can store

@ -77,8 +77,8 @@ you can just add a `version` field:
For every tag that looks like a version, a package version of that tag will be For every tag that looks like a version, a package version of that tag will be
created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix
of `-patch`, `-alpha`, `-beta` or `-RC`. The suffixes can also be followed by of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes
a number. can also be followed by a number.
Here are a few examples of valid tag names: Here are a few examples of valid tag names:

@ -87,7 +87,8 @@ resolution.
installing a package, you can use `--dry-run`. This will simulate the installing a package, you can use `--dry-run`. This will simulate the
installation and show you what would happen. installation and show you what would happen.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior). * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`. * **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
* **--no-autoloader:** Skips autoloader generation.
* **--no-scripts:** Skips execution of scripts defined in `composer.json`. * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
* **--no-plugins:** Disables plugins. * **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some
@ -129,7 +130,8 @@ php composer.phar update vendor/*
fulfill these. fulfill these.
* **--dry-run:** Simulate the command without actually doing anything. * **--dry-run:** Simulate the command without actually doing anything.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior). * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`. * **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
* **--no-autoloader:** Skips autoloader generation.
* **--no-scripts:** Skips execution of scripts defined in `composer.json`. * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
* **--no-plugins:** Disables plugins. * **--no-plugins:** Disables plugins.
* **--no-progress:** Removes the progress display that can mess with some * **--no-progress:** Removes the progress display that can mess with some
@ -397,16 +399,18 @@ options.
### Options ### Options
* **--global (-g):** Operate on the global config file located at * **--global (-g):** Operate on the global config file located at
`$COMPOSER_HOME/config.json` by default. Without this option, this command `$COMPOSER_HOME/config.json` by default. Without this option, this command
affects the local composer.json file or a file specified by `--file`. affects the local composer.json file or a file specified by `--file`.
* **--editor (-e):** Open the local composer.json file using in a text editor as * **--editor (-e):** Open the local composer.json file using in a text editor as
defined by the `EDITOR` env variable. With the `--global` option, this opens defined by the `EDITOR` env variable. With the `--global` option, this opens
the global config file. the global config file.
* **--unset:** Remove the configuration element named by `setting-key`. * **--unset:** Remove the configuration element named by `setting-key`.
* **--list (-l):** Show the list of current config variables. With the `--global` * **--list (-l):** Show the list of current config variables. With the `--global`
option this lists the global configuration only. option this lists the global configuration only.
* **--file="..." (-f):** Operate on a specific file instead of composer.json. Note * **--file="..." (-f):** Operate on a specific file instead of composer.json. Note
that this cannot be used in conjunction with the `--global` option. that this cannot be used in conjunction with the `--global` option.
* **--absolute:** Returns absolute paths when fetching *-dir config values
instead of relative.
### Modifying Repositories ### Modifying Repositories
@ -463,6 +467,9 @@ By default the command checks for the packages on packagist.org.
* **--keep-vcs:** Skip the deletion of the VCS metadata for the created * **--keep-vcs:** Skip the deletion of the VCS metadata for the created
project. This is mostly useful if you run the command in non-interactive project. This is mostly useful if you run the command in non-interactive
mode. mode.
* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
requirements and force the installation even if the local machine does not
fulfill these.
## dump-autoload ## dump-autoload

@ -54,8 +54,8 @@ The version of the package. In most cases this is not required and should
be omitted (see below). be omitted (see below).
This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix
of `-dev`, `-patch`, `-alpha`, `-beta` or `-RC`. The patch, alpha, beta and of `-dev`, `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`.
RC suffixes can also be followed by a number. The patch, alpha, beta and RC suffixes can also be followed by a number.
Examples: Examples:
@ -67,6 +67,7 @@ Examples:
- 1.0.0-alpha3 - 1.0.0-alpha3
- 1.0.0-beta2 - 1.0.0-beta2
- 1.0.0-RC5 - 1.0.0-RC5
- v2.0.4-p1
Optional if the package repository can infer the version from somewhere, such Optional if the package repository can infer the version from somewhere, such
as the VCS tag name in the VCS repository. In that case it is also recommended as the VCS tag name in the VCS repository. In that case it is also recommended
@ -375,7 +376,7 @@ useful for common interfaces. A package could depend on some virtual
`logger` package, any library that implements this logger interface would `logger` package, any library that implements this logger interface would
simply list it in `provide`. simply list it in `provide`.
### suggest #### suggest
Suggested packages that can enhance or work well with this package. These are Suggested packages that can enhance or work well with this package. These are
just informational and are displayed after the package is installed, to give just informational and are displayed after the package is installed, to give
@ -790,6 +791,9 @@ The following options are supported:
the generated Composer autoloader. When null a random one will be generated. the generated Composer autoloader. When null a random one will be generated.
* **optimize-autoloader** Defaults to `false`. Always optimize when dumping * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
the autoloader. the autoloader.
* **classmap-authoritative:** Defaults to `false`. If true, the composer
autoloader will not scan the filesystem for classes that are not found in
the class map. Implies 'optimize-autoloader'.
* **github-domains:** Defaults to `["github.com"]`. A list of domains to use in * **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
github mode. This is used for GitHub Enterprise setups. github mode. This is used for GitHub Enterprise setups.
* **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth * **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth

@ -122,7 +122,7 @@ JSON request body:
```json ```json
{ {
"downloads": [ "downloads": [
{"name": "monolog/monolog", "version": "1.2.1.0"}, {"name": "monolog/monolog", "version": "1.2.1.0"}
] ]
} }
``` ```

@ -38,10 +38,14 @@ specifying a `branch-alias` field under `extra` in `composer.json`:
} }
``` ```
The branch version must begin with `dev-` (non-comparable version), the alias If you alias a non-comparible version (such as dev-develop) `dev-` must prefix the
must be a comparable dev version (i.e. start with numbers, and end with branch name. You may also alias a comparible version (i.e. start with numbers,
`.x-dev`). The `branch-alias` must be present on the branch that it references. and end with `.x-dev`), but only as a more specific version.
For `dev-master`, you need to commit it on the `master` branch. For example, 1.x-dev could be aliased as 1.2.x-dev.
The alias must be a comparable dev version, and the `branch-alias` must be present on
the branch that it references. For `dev-master`, you need to commit it on the
`master` branch.
As a result, anyone can now require `1.0.*` and it will happily install As a result, anyone can now require `1.0.*` and it will happily install
`dev-master`. `dev-master`.

@ -66,7 +66,7 @@ constraint if you want really specific versions.
} }
``` ```
Once you did this, you just run `php bin/satis build <configuration file> <build dir>`. Once you've done this, you just run `php bin/satis build <configuration file> <build dir>`.
For example `php bin/satis build config.json web/` would read the `config.json` For example `php bin/satis build config.json web/` would read the `config.json`
file and build a static repository inside the `web/` directory. file and build a static repository inside the `web/` directory.

@ -40,7 +40,7 @@ username/password pairs, for example:
```json ```json
{ {
"basic-auth": [ "basic-auth": {
"repo.example1.org": { "repo.example1.org": {
"username": "my-username1", "username": "my-username1",
"password": "my-secret-password1" "password": "my-secret-password1"
@ -49,7 +49,7 @@ username/password pairs, for example:
"username": "my-username2", "username": "my-username2",
"password": "my-secret-password2" "password": "my-secret-password2"
} }
] }
} }
``` ```

@ -114,8 +114,9 @@ php -d memory_limit=-1 composer.phar <...>
## "The system cannot find the path specified" (Windows) ## "The system cannot find the path specified" (Windows)
1. Open regedit. 1. Open regedit.
2. Search for an ```AutoRun``` key inside ```HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor``` 2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`,
or ```HKEY_CURRENT_USER\Software\Microsoft\Command Processor```. `HKEY_CURRENT_USER\Software\Microsoft\Command Processor`
or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`.
3. Check if it contains any path to non-existent file, if it's the case, just remove them. 3. Check if it contains any path to non-existent file, if it's the case, just remove them.
## API rate limit and OAuth tokens ## API rate limit and OAuth tokens

@ -1,12 +1,13 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#",
"name": "Package", "name": "Package",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "name", "description" ],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Package name, including 'vendor-name/' prefix.", "description": "Package name, including 'vendor-name/' prefix."
"required": true
}, },
"type": { "type": {
"description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
@ -18,8 +19,7 @@
}, },
"description": { "description": {
"type": "string", "type": "string",
"description": "Short package description.", "description": "Short package description."
"required": true
}, },
"keywords": { "keywords": {
"type": "array", "type": "array",
@ -51,11 +51,11 @@
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "name"],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Full name of the author.", "description": "Full name of the author."
"required": true
}, },
"email": { "email": {
"type": "string", "type": "string",
@ -197,6 +197,10 @@
"type": "boolean", "type": "boolean",
"description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
}, },
"classmap-authoritative": {
"type": "boolean",
"description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
},
"github-domains": { "github-domains": {
"type": "array", "type": "array",
"description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",

@ -63,6 +63,7 @@ class AutoloadGenerator
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir'))); $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
$useGlobalIncludePath = (bool) $config->get('use-include-path'); $useGlobalIncludePath = (bool) $config->get('use-include-path');
$prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
$classMapAuthoritative = $config->get('classmap-authoritative');
$targetDir = $vendorPath.'/'.$targetDir; $targetDir = $vendorPath.'/'.$targetDir;
$filesystem->ensureDirectoryExists($targetDir); $filesystem->ensureDirectoryExists($targetDir);
@ -226,7 +227,7 @@ EOF;
file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile); file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
} }
file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)); file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative));
// use stream_copy_to_stream instead of copy // use stream_copy_to_stream instead of copy
// to work around https://bugs.php.net/bug.php?id=64634 // to work around https://bugs.php.net/bug.php?id=64634
@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
AUTOLOAD; AUTOLOAD;
} }
protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader) protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)
{ {
// TODO the class ComposerAutoloaderInit should be revert to a closure // TODO the class ComposerAutoloaderInit should be revert to a closure
// when APC has been fixed: // when APC has been fixed:
@ -520,6 +521,13 @@ PSR4;
CLASSMAP; CLASSMAP;
} }
if ($classMapAuthoritative) {
$file .= <<<'CLASSMAPAUTHORITATIVE'
$loader->setClassMapAuthoritative(true);
CLASSMAPAUTHORITATIVE;
}
if ($useGlobalIncludePath) { if ($useGlobalIncludePath) {
$file .= <<<'INCLUDEPATH' $file .= <<<'INCLUDEPATH'
$loader->setUseIncludePath(true); $loader->setUseIncludePath(true);

@ -54,6 +54,8 @@ class ClassLoader
private $useIncludePath = false; private $useIncludePath = false;
private $classMap = array(); private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes() public function getPrefixes()
{ {
if (!empty($this->prefixesPsr0)) { if (!empty($this->prefixesPsr0)) {
@ -248,6 +250,27 @@ class ClassLoader
return $this->useIncludePath; return $this->useIncludePath;
} }
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/** /**
* Registers this instance as an autoloader. * Registers this instance as an autoloader.
* *
@ -299,6 +322,9 @@ class ClassLoader
if (isset($this->classMap[$class])) { if (isset($this->classMap[$class])) {
return $this->classMap[$class]; return $this->classMap[$class];
} }
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php'); $file = $this->findFileWithExtension($class, '.php');

@ -16,6 +16,8 @@ use Composer\Composer;
use Composer\Console\Application; use Composer\Console\Application;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO; use Composer\IO\NullIO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command as BaseCommand; use Symfony\Component\Console\Command\Command as BaseCommand;
/** /**
@ -102,4 +104,16 @@ abstract class Command extends BaseCommand
{ {
$this->io = $io; $this->io = $io;
} }
/**
* {@inheritDoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) {
$input->setOption('no-progress', true);
}
parent::initialize($input, $output);
}
} }

@ -57,6 +57,7 @@ class ConfigCommand extends Command
new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'),
new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-key', null, 'Setting key'),
new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
)) ))
@ -99,6 +100,8 @@ EOT
*/ */
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
{ {
parent::initialize($input, $output);
if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) { if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
throw new \RuntimeException('--file and --global can not be combined'); throw new \RuntimeException('--file and --global can not be combined');
} }
@ -134,7 +137,7 @@ EOT
} }
if (!$this->configFile->exists()) { if (!$this->configFile->exists()) {
throw new \RuntimeException('No composer.json found in the current directory'); throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
} }
} }
@ -218,7 +221,7 @@ EOT
$value = $data; $value = $data;
} elseif (isset($data['config'][$settingKey])) { } elseif (isset($data['config'][$settingKey])) {
$value = $data['config'][$settingKey]; $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
} else { } else {
throw new \RuntimeException($settingKey.' is not defined'); throw new \RuntimeException($settingKey.' is not defined');
} }
@ -322,6 +325,7 @@ EOT
), ),
'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
); );

@ -69,6 +69,7 @@ class CreateProjectCommand extends Command
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The <info>create-project</info> command creates a new project from a given The <info>create-project</info> command creates a new project from a given
@ -125,11 +126,12 @@ EOT
$input->getOption('keep-vcs'), $input->getOption('keep-vcs'),
$input->getOption('no-progress'), $input->getOption('no-progress'),
$input->getOption('no-install'), $input->getOption('no-install'),
$input->getOption('ignore-platform-reqs'),
$input $input
); );
} }
public function installProject(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, InputInterface $input) public function installProject(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, InputInterface $input)
{ {
$oldCwd = getcwd(); $oldCwd = getcwd();
@ -159,7 +161,8 @@ EOT
$installer->setPreferSource($preferSource) $installer->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode($installDevPackages) ->setDevMode($installDevPackages)
->setRunScripts( ! $noScripts); ->setRunScripts(!$noScripts)
->setIgnorePlatformRequirements($ignorePlatformReqs);
if ($disablePlugins) { if ($disablePlugins) {
$installer->disablePlugins(); $installer->disablePlugins();

@ -52,7 +52,7 @@ EOT
$package = $composer->getPackage(); $package = $composer->getPackage();
$config = $composer->getConfig(); $config = $composer->getConfig();
$optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
if ($optimize) { if ($optimize) {
$output->writeln('<info>Generating optimized autoload files</info>'); $output->writeln('<info>Generating optimized autoload files</info>');

@ -40,12 +40,14 @@ class HomeCommand extends Command
->setDefinition(array( ->setDefinition(array(
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'),
new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The home command opens a package's repository URL or The home command opens or shows a package's repository URL or
homepage in your default browser. homepage in your default browser.
To open the homepage by default, use -H or --homepage. To open the homepage by default, use -H or --homepage.
To show instead of open the repository or homepage URL, use -s or --show.
EOT EOT
); );
} }
@ -55,7 +57,7 @@ EOT
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$repo = $this->initializeRepo($input, $output); $repo = $this->initializeRepo();
$return = 0; $return = 0;
foreach ($input->getArgument('packages') as $packageName) { foreach ($input->getArgument('packages') as $packageName) {
@ -81,7 +83,11 @@ EOT
continue; continue;
} }
$this->openBrowser($url); if ($input->getOption('show')) {
$output->writeln(sprintf('<info>%s</info>', $url));
} else {
$this->openBrowser($url);
}
} }
return $return; return $return;
@ -138,13 +144,11 @@ EOT
} }
/** /**
* initializes the repo * Initializes the repo
* *
* @param InputInterface $input
* @param OutputInterface $output
* @return CompositeRepository * @return CompositeRepository
*/ */
private function initializeRepo(InputInterface $input, OutputInterface $output) private function initializeRepo()
{ {
$composer = $this->getComposer(false); $composer = $this->getComposer(false);

@ -41,6 +41,7 @@ class InstallCommand extends Command
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
@ -74,6 +75,10 @@ EOT
$input->setOption('no-plugins', true); $input->setOption('no-plugins', true);
} }
if ($input->getOption('dev')) {
$output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
}
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO(); $io = $this->getIO();
@ -105,7 +110,7 @@ EOT
$preferDist = $input->getOption('prefer-dist'); $preferDist = $input->getOption('prefer-dist');
} }
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
$install $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -113,6 +118,7 @@ EOT
->setPreferSource($preferSource) ->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode(!$input->getOption('no-dev')) ->setDevMode(!$input->getOption('no-dev'))
->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))

@ -38,7 +38,7 @@ class RequireCommand extends InitCommand
->setName('require') ->setName('require')
->setDescription('Adds required packages to your composer.json and installs them') ->setDescription('Adds required packages to your composer.json and installs them')
->setDefinition(array( ->setDefinition(array(
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package name optionally including a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
@ -50,7 +50,9 @@ class RequireCommand extends InitCommand
new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
)) ))
->setHelp(<<<EOT ->setHelp(<<<EOT
The require command adds required packages to your composer.json and installs them The require command adds required packages to your composer.json and installs them.
If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.
If you do not want to install the new dependencies immediately you can call it with --no-update If you do not want to install the new dependencies immediately you can call it with --no-update

@ -173,7 +173,7 @@ EOT
protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null) protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
{ {
try { try {
@chmod($newFilename, 0777 & ~umask()); @chmod($newFilename, fileperms($localFilename));
if (!ini_get('phar.readonly')) { if (!ini_get('phar.readonly')) {
// test the phar validity // test the phar validity
$phar = new \Phar($newFilename); $phar = new \Phar($newFilename);

@ -41,6 +41,7 @@ class ShowCommand extends Command
{ {
$this $this
->setName('show') ->setName('show')
->setAliases(array('info'))
->setDescription('Show information about packages') ->setDescription('Show information about packages')
->setDefinition(array( ->setDefinition(array(
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'), new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'),

@ -41,6 +41,7 @@ class UpdateCommand extends Command
new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'), new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
@ -78,6 +79,10 @@ EOT
$input->setOption('no-plugins', true); $input->setOption('no-plugins', true);
} }
if ($input->getOption('dev')) {
$output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
}
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO(); $io = $this->getIO();
@ -109,7 +114,7 @@ EOT
$preferDist = $input->getOption('prefer-dist'); $preferDist = $input->getOption('prefer-dist');
} }
$optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
$install $install
->setDryRun($input->getOption('dry-run')) ->setDryRun($input->getOption('dry-run'))
@ -117,6 +122,7 @@ EOT
->setPreferSource($preferSource) ->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setDevMode(!$input->getOption('no-dev')) ->setDevMode(!$input->getOption('no-dev'))
->setDumpAutoloader(!$input->getOption('no-autoloader'))
->setRunScripts(!$input->getOption('no-scripts')) ->setRunScripts(!$input->getOption('no-scripts'))
->setOptimizeAutoloader($optimize) ->setOptimizeAutoloader($optimize)
->setUpdate(true) ->setUpdate(true)

@ -19,6 +19,8 @@ use Composer\Config\ConfigSourceInterface;
*/ */
class Config class Config
{ {
const RELATIVE_PATHS = 1;
public static $defaultConfig = array( public static $defaultConfig = array(
'process-timeout' => 300, 'process-timeout' => 300,
'use-include-path' => false, 'use-include-path' => false,
@ -38,6 +40,7 @@ class Config
'discard-changes' => false, 'discard-changes' => false,
'autoloader-suffix' => null, 'autoloader-suffix' => null,
'optimize-autoloader' => false, 'optimize-autoloader' => false,
'classmap-authoritative' => false,
'prepend-autoloader' => true, 'prepend-autoloader' => true,
'github-domains' => array('github.com'), 'github-domains' => array('github.com'),
'github-expose-hostname' => true, 'github-expose-hostname' => true,
@ -56,6 +59,7 @@ class Config
); );
private $config; private $config;
private $baseDir;
private $repositories; private $repositories;
private $configSource; private $configSource;
private $authConfigSource; private $authConfigSource;
@ -64,12 +68,13 @@ class Config
/** /**
* @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings * @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings
*/ */
public function __construct($useEnvironment = true) public function __construct($useEnvironment = true, $baseDir = null)
{ {
// load defaults // load defaults
$this->config = static::$defaultConfig; $this->config = static::$defaultConfig;
$this->repositories = static::$defaultRepositories; $this->repositories = static::$defaultRepositories;
$this->useEnvironment = (bool) $useEnvironment; $this->useEnvironment = (bool) $useEnvironment;
$this->baseDir = $baseDir;
} }
public function setConfigSource(ConfigSourceInterface $source) public function setConfigSource(ConfigSourceInterface $source)
@ -121,7 +126,7 @@ class Config
} }
// disable a repository with an anonymous {"name": false} repo // disable a repository with an anonymous {"name": false} repo
if (1 === count($repository) && false === current($repository)) { if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
unset($this->repositories[key($repository)]); unset($this->repositories[key($repository)]);
continue; continue;
} }
@ -149,10 +154,11 @@ class Config
* Returns a setting * Returns a setting
* *
* @param string $key * @param string $key
* @param int $flags Options (see class constants)
* @throws \RuntimeException * @throws \RuntimeException
* @return mixed * @return mixed
*/ */
public function get($key) public function get($key, $flags = 0)
{ {
switch ($key) { switch ($key) {
case 'vendor-dir': case 'vendor-dir':
@ -166,10 +172,14 @@ class Config
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
$val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key]), '/\\'); $val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key], $flags), '/\\');
$val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val); $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val);
return $val; if (substr($key, -4) !== '-dir') {
return $val;
}
return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val);
case 'cache-ttl': case 'cache-ttl':
return (int) $this->config[$key]; return (int) $this->config[$key];
@ -205,7 +215,7 @@ class Config
return (int) $this->config['cache-ttl']; return (int) $this->config['cache-ttl'];
case 'home': case 'home':
return rtrim($this->process($this->config[$key]), '/\\'); return rtrim($this->process($this->config[$key], $flags), '/\\');
case 'discard-changes': case 'discard-changes':
if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) { if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
@ -242,17 +252,17 @@ class Config
return null; return null;
} }
return $this->process($this->config[$key]); return $this->process($this->config[$key], $flags);
} }
} }
public function all() public function all($flags = 0)
{ {
$all = array( $all = array(
'repositories' => $this->getRepositories(), 'repositories' => $this->getRepositories(),
); );
foreach (array_keys($this->config) as $key) { foreach (array_keys($this->config) as $key) {
$all['config'][$key] = $this->get($key); $all['config'][$key] = $this->get($key, $flags);
} }
return $all; return $all;
@ -281,9 +291,10 @@ class Config
* Replaces {$refs} inside a config string * Replaces {$refs} inside a config string
* *
* @param string $value a config string that can contain {$refs-to-other-config} * @param string $value a config string that can contain {$refs-to-other-config}
* @param int $flags Options (see class constants)
* @return string * @return string
*/ */
private function process($value) private function process($value, $flags)
{ {
$config = $this; $config = $this;
@ -291,11 +302,28 @@ class Config
return $value; return $value;
} }
return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) { return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) {
return $config->get($match[1]); return $config->get($match[1], $flags);
}, $value); }, $value);
} }
/**
* Turns relative paths in absolute paths without realpath()
*
* Since the dirs might not exist yet we can not call realpath or it will fail.
*
* @param string $path
* @return string
*/
private function realpath($path)
{
if (substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':') {
return $path;
}
return $this->baseDir . '/' . $path;
}
/** /**
* Reads the value of a Composer environment variable * Reads the value of a Composer environment variable
* *

@ -184,6 +184,7 @@ class Application extends BaseApplication
$minSpaceFree = 1024*1024; $minSpaceFree = 1024*1024;
if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
|| (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
|| (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
) { ) {
$output->writeln('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>'); $output->writeln('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>');
} }

@ -90,10 +90,10 @@ class FileDownloader implements DownloaderInterface
} catch (\Exception $e) { } catch (\Exception $e) {
if ($this->io->isDebug()) { if ($this->io->isDebug()) {
$this->io->write(''); $this->io->write('');
$this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage()); $this->io->write('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
} elseif (count($urls)) { } elseif (count($urls)) {
$this->io->write(''); $this->io->write('');
$this->io->write(' Failed, trying the next URL'); $this->io->write(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
} }
if (!count($urls)) { if (!count($urls)) {

@ -44,6 +44,7 @@ class EventDispatcher
protected $io; protected $io;
protected $loader; protected $loader;
protected $process; protected $process;
protected $listeners;
/** /**
* Constructor. * Constructor.

@ -17,10 +17,9 @@ use Composer\Json\JsonFile;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\Archiver; use Composer\Package\Archiver;
use Composer\Repository\RepositoryManager; use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryInterface; use Composer\Repository\WritableRepositoryInterface;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem; use Composer\Util\RemoteFilesystem;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator; use Composer\Autoload\AutoloadGenerator;
@ -36,7 +35,6 @@ use Composer\Package\Version\VersionParser;
*/ */
class Factory class Factory
{ {
/** /**
* @return string * @return string
* @throws \RuntimeException * @throws \RuntimeException
@ -73,9 +71,9 @@ class Factory
if (!$xdgConfig) { if (!$xdgConfig) {
$xdgConfig = $userDir . '/.config'; $xdgConfig = $userDir . '/.config';
} }
$home = $xdgConfig . '/composer'; $home = $xdgConfig . '/composer';
} else { } else {
$home = $userDir . '/.composer'; $home = $userDir . '/.composer';
} }
} }
} }
@ -144,8 +142,10 @@ class Factory
* @param IOInterface|null $io * @param IOInterface|null $io
* @return Config * @return Config
*/ */
public static function createConfig(IOInterface $io = null) public static function createConfig(IOInterface $io = null, $cwd = null)
{ {
$cwd = $cwd ?: getcwd();
// determine home and cache dirs // determine home and cache dirs
$home = self::getHomeDir(); $home = self::getHomeDir();
$cacheDir = self::getCacheDir($home); $cacheDir = self::getCacheDir($home);
@ -188,7 +188,7 @@ class Factory
} }
} }
$config = new Config(); $config = new Config(true, $cwd);
// add dirs to the config // add dirs to the config
$config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir))); $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir)));
@ -218,14 +218,14 @@ class Factory
public static function getComposerFile() public static function getComposerFile()
{ {
return trim(getenv('COMPOSER')) ? : './composer.json'; return trim(getenv('COMPOSER')) ?: './composer.json';
} }
public static function createAdditionalStyles() public static function createAdditionalStyles()
{ {
return array( return array(
'highlight' => new OutputFormatterStyle('red'), 'highlight' => new OutputFormatterStyle('red'),
'warning' => new OutputFormatterStyle('black', 'yellow'), 'warning' => new OutputFormatterStyle('black', 'yellow'),
); );
} }
@ -241,15 +241,18 @@ class Factory
throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
} }
$factory = new static; $factory = new static;
$rm = $factory->createRepositoryManager($io, $config); $rm = $factory->createRepositoryManager($io, $config);
} }
foreach ($config->getRepositories() as $index => $repo) { foreach ($config->getRepositories() as $index => $repo) {
if (is_string($repo)) {
throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
}
if (!is_array($repo)) { if (!is_array($repo)) {
throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') should be an array, ' . gettype($repo) . ' given'); throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
} }
if (!isset($repo['type'])) { if (!isset($repo['type'])) {
throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') must have a type defined'); throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
} }
$name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index; $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
while (isset($repos[$name])) { while (isset($repos[$name])) {
@ -268,12 +271,15 @@ class Factory
* @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
* read from the default filename * read from the default filename
* @param bool $disablePlugins Whether plugins should not be loaded * @param bool $disablePlugins Whether plugins should not be loaded
* @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer)
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
* @return Composer * @return Composer
*/ */
public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false) public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true)
{ {
$cwd = $cwd ?: getcwd();
// load Composer configuration // load Composer configuration
if (null === $localConfig) { if (null === $localConfig) {
$localConfig = static::getComposerFile(); $localConfig = static::getComposerFile();
@ -281,16 +287,16 @@ class Factory
if (is_string($localConfig)) { if (is_string($localConfig)) {
$composerFile = $localConfig; $composerFile = $localConfig;
$file = new JsonFile($localConfig, new RemoteFilesystem($io)); $file = new JsonFile($localConfig, new RemoteFilesystem($io));
if (!$file->exists()) { if (!$file->exists()) {
if ($localConfig === './composer.json' || $localConfig === 'composer.json') { if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
$message = 'Composer could not find a composer.json file in ' . getcwd(); $message = 'Composer could not find a composer.json file in '.$cwd;
} else { } else {
$message = 'Composer could not find the config file: ' . $localConfig; $message = 'Composer could not find the config file: '.$localConfig;
} }
$instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section'; $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section';
throw new \InvalidArgumentException($message . PHP_EOL . $instructions); throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
} }
$file->validateSchema(JsonFile::LAX_SCHEMA); $file->validateSchema(JsonFile::LAX_SCHEMA);
@ -298,7 +304,7 @@ class Factory
} }
// Load config and override with local config/auth config // Load config and override with local config/auth config
$config = static::createConfig($io); $config = static::createConfig($io, $cwd);
$config->merge($localConfig); $config->merge($localConfig);
if (isset($composerFile)) { if (isset($composerFile)) {
if ($io && $io->isDebug()) { if ($io && $io->isDebug()) {
@ -314,69 +320,77 @@ class Factory
} }
} }
// load auth configs into the IO instance
$io->loadConfiguration($config);
$vendorDir = $config->get('vendor-dir'); $vendorDir = $config->get('vendor-dir');
$binDir = $config->get('bin-dir'); $binDir = $config->get('bin-dir');
// setup process timeout
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
// initialize composer // initialize composer
$composer = new Composer(); $composer = new Composer();
$composer->setConfig($config); $composer->setConfig($config);
if ($fullLoad) {
// load auth configs into the IO instance
$io->loadConfiguration($config);
// setup process timeout
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
}
// initialize event dispatcher // initialize event dispatcher
$dispatcher = new EventDispatcher($composer, $io); $dispatcher = new EventDispatcher($composer, $io);
$composer->setEventDispatcher($dispatcher);
// initialize repository manager // initialize repository manager
$rm = $this->createRepositoryManager($io, $config, $dispatcher); $rm = $this->createRepositoryManager($io, $config, $dispatcher);
$composer->setRepositoryManager($rm);
// load local repository // load local repository
$this->addLocalRepository($rm, $vendorDir); $this->addLocalRepository($rm, $vendorDir);
// load package // load package
$parser = new VersionParser; $parser = new VersionParser;
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io)); $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
$package = $loader->load($localConfig); $package = $loader->load($localConfig);
$composer->setPackage($package);
// initialize installation manager // initialize installation manager
$im = $this->createInstallationManager(); $im = $this->createInstallationManager();
// Composer composition
$composer->setPackage($package);
$composer->setRepositoryManager($rm);
$composer->setInstallationManager($im); $composer->setInstallationManager($im);
// initialize download manager if ($fullLoad) {
$dm = $this->createDownloadManager($io, $config, $dispatcher); // initialize download manager
$dm = $this->createDownloadManager($io, $config, $dispatcher);
$composer->setDownloadManager($dm); $composer->setDownloadManager($dm);
$composer->setEventDispatcher($dispatcher);
// initialize autoload generator // initialize autoload generator
$generator = new AutoloadGenerator($dispatcher, $io); $generator = new AutoloadGenerator($dispatcher, $io);
$composer->setAutoloadGenerator($generator); $composer->setAutoloadGenerator($generator);
}
// add installers to the manager // add installers to the manager (must happen after download manager is created since they read it out of $composer)
$this->createDefaultInstallers($im, $composer, $io); $this->createDefaultInstallers($im, $composer, $io);
$globalRepository = $this->createGlobalRepository($config, $vendorDir); if ($fullLoad) {
$pm = $this->createPluginManager($composer, $io, $globalRepository); $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
$composer->setPluginManager($pm); $pm = $this->createPluginManager($io, $composer, $globalComposer);
$composer->setPluginManager($pm);
if (!$disablePlugins) { if (!$disablePlugins) {
$pm->loadInstalledPlugins(); $pm->loadInstalledPlugins();
} }
// purge packages if they have been deleted on the filesystem // once we have plugins and custom installers we can
$this->purgePackages($rm, $im); // purge packages from local repos if they have been deleted on the filesystem
if ($rm->getLocalRepository()) {
$this->purgePackages($rm->getLocalRepository(), $im);
}
}
// init locker if possible // init locker if possible
if (isset($composerFile)) { if ($fullLoad && isset($composerFile)) {
$lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4) . 'lock' : $composerFile . '.lock'; $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
$locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile)); ? substr($composerFile, 0, -4).'lock'
: $composerFile . '.lock';
$locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
$composer->setLocker($locker); $composer->setLocker($locker);
} }
@ -411,26 +425,29 @@ class Factory
*/ */
protected function addLocalRepository(RepositoryManager $rm, $vendorDir) protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
{ {
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir . '/composer/installed.json'))); $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
} }
/** /**
* @param Config $config * @param Config $config
* @param string $vendorDir * @return Composer|null
* @return Repository\InstalledFilesystemRepository|null
*/ */
protected function createGlobalRepository(Config $config, $vendorDir) protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins)
{ {
if ($config->get('home') == $vendorDir) { if (realpath($config->get('home')) === getcwd()) {
return null; return;
} }
$path = $config->get('home') . '/vendor/composer/installed.json'; $composer = null;
if (!file_exists($path)) { try {
return null; $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false);
} catch (\Exception $e) {
if ($io->isDebug()) {
$io->write('Failed to initialize global composer: '.$e->getMessage());
}
} }
return new Repository\InstalledFilesystemRepository(new JsonFile($path)); return $composer;
} }
/** /**
@ -495,14 +512,14 @@ class Factory
} }
/** /**
* @param Composer $composer
* @param IOInterface $io * @param IOInterface $io
* @param RepositoryInterface $globalRepository * @param Composer $composer
* @param Composer $globalComposer
* @return Plugin\PluginManager * @return Plugin\PluginManager
*/ */
protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null) protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null)
{ {
return new Plugin\PluginManager($io, $composer, $globalRepository); return new Plugin\PluginManager($io, $composer, $globalComposer);
} }
/** /**
@ -527,12 +544,11 @@ class Factory
} }
/** /**
* @param Repository\RepositoryManager $rm * @param WritableRepositoryInterface $repo repository to purge packages from
* @param Installer\InstallationManager $im * @param Installer\InstallationManager $im manager to check whether packages are still installed
*/ */
protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im) protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
{ {
$repo = $rm->getLocalRepository();
foreach ($repo->getPackages() as $package) { foreach ($repo->getPackages() as $package) {
if (!$im->isPackageInstalled($repo, $package)) { if (!$im->isPackageInstalled($repo, $package)) {
$repo->removePackage($package); $repo->removePackage($package);
@ -553,5 +569,4 @@ class Factory
return $factory->createComposer($io, $config, $disablePlugins); return $factory->createComposer($io, $config, $disablePlugins);
} }
} }

@ -96,13 +96,11 @@ class ConsoleIO extends BaseIO
public function write($messages, $newline = true) public function write($messages, $newline = true)
{ {
if (null !== $this->startTime) { if (null !== $this->startTime) {
$messages = (array) $messages; $memoryUsage = memory_get_usage() / 1024 / 1024;
$messages[0] = sprintf( $timeSpent = microtime(true) - $this->startTime;
'[%.1fMB/%.2fs] %s', $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) {
memory_get_usage() / 1024 / 1024, return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
microtime(true) - $this->startTime, }, (array) $messages);
$messages[0]
);
} }
$this->output->write($messages, $newline); $this->output->write($messages, $newline);
$this->lastMessage = join($newline ? "\n" : '', (array) $messages); $this->lastMessage = join($newline ? "\n" : '', (array) $messages);
@ -113,6 +111,14 @@ class ConsoleIO extends BaseIO
*/ */
public function overwrite($messages, $newline = true, $size = null) public function overwrite($messages, $newline = true, $size = null)
{ {
if (!$this->output->isDecorated()) {
if (!$messages) {
return;
}
return $this->write($messages, count($messages) === 1 || $newline);
}
// messages can be an array, let's convert it to string anyway // messages can be an array, let's convert it to string anyway
$messages = join($newline ? "\n" : '', (array) $messages); $messages = join($newline ? "\n" : '', (array) $messages);

@ -105,6 +105,7 @@ class Installer
protected $dryRun = false; protected $dryRun = false;
protected $verbose = false; protected $verbose = false;
protected $update = false; protected $update = false;
protected $dumpAutoloader = true;
protected $runScripts = true; protected $runScripts = true;
protected $ignorePlatformReqs = false; protected $ignorePlatformReqs = false;
protected $preferStable = false; protected $preferStable = false;
@ -317,15 +318,17 @@ class Installer
} }
} }
// write autoloader if ($this->dumpAutoloader) {
if ($this->optimizeAutoloader) { // write autoloader
$this->io->write('<info>Generating optimized autoload files</info>'); if ($this->optimizeAutoloader) {
} else { $this->io->write('<info>Generating optimized autoload files</info>');
$this->io->write('<info>Generating autoload files</info>'); } else {
} $this->io->write('<info>Generating autoload files</info>');
}
$this->autoloadGenerator->setDevMode($this->devMode); $this->autoloadGenerator->setDevMode($this->devMode);
$this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
}
if ($this->runScripts) { if ($this->runScripts) {
// dispatch post event // dispatch post event
@ -1163,6 +1166,21 @@ class Installer
return $this; return $this;
} }
/**
* set whether to run autoloader or not
*
* @param boolean $dumpAutoloader
* @return Installer
*/
public function setDumpAutoloader($dumpAutoloader = true)
{
$this->dumpAutoloader = (boolean) $dumpAutoloader;
return $this;
}
/** /**
* set whether to run scripts or not * set whether to run scripts or not
* *

@ -61,7 +61,7 @@ class PluginInstaller extends LibraryInstaller
} }
parent::install($repo, $package); parent::install($repo, $package);
$this->composer->getPluginManager()->registerPackage($package); $this->composer->getPluginManager()->registerPackage($package, true);
} }
/** /**
@ -75,6 +75,6 @@ class PluginInstaller extends LibraryInstaller
} }
parent::update($repo, $initial, $target); parent::update($repo, $initial, $target);
$this->composer->getPluginManager()->registerPackage($target); $this->composer->getPluginManager()->registerPackage($target, true);
} }
} }

@ -154,8 +154,7 @@ class JsonFile
if ($schema === self::LAX_SCHEMA) { if ($schema === self::LAX_SCHEMA) {
$schemaData->additionalProperties = true; $schemaData->additionalProperties = true;
$schemaData->properties->name->required = false; $schemaData->required = array();
$schemaData->properties->description->required = false;
} }
$validator = new Validator(); $validator = new Validator();

@ -21,10 +21,10 @@ class JsonValidationException extends Exception
{ {
protected $errors; protected $errors;
public function __construct($message, $errors = array()) public function __construct($message, $errors = array(), \Exception $previous = null)
{ {
$this->errors = $errors; $this->errors = $errors;
parent::__construct($message); parent::__construct($message, 0, $previous);
} }
public function getErrors() public function getErrors()

@ -78,6 +78,6 @@ class MultiConstraint implements LinkConstraintInterface
$constraints[] = $constraint->__toString(); $constraints[] = $constraint->__toString();
} }
return '['.implode($this->conjunctive ? ', ' : ' | ', $constraints).']'; return '['.implode($this->conjunctive ? ' ' : ' || ', $constraints).']';
} }
} }

@ -224,7 +224,7 @@ class ArrayLoader implements LoaderInterface
*/ */
public function getBranchAlias(array $config) public function getBranchAlias(array $config)
{ {
if ('dev-' !== substr($config['version'], 0, 4) if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
|| !isset($config['extra']['branch-alias']) || !isset($config['extra']['branch-alias'])
|| !is_array($config['extra']['branch-alias']) || !is_array($config['extra']['branch-alias'])
) { ) {
@ -248,6 +248,14 @@ class ArrayLoader implements LoaderInterface
continue; continue;
} }
// If using numeric aliases ensure the alias is a valid subversion
if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
&& ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
&& (stripos($targetPrefix, $sourcePrefix) !== 0)
) {
continue;
}
return $validatedTargetBranch; return $validatedTargetBranch;
} }
} }

@ -131,7 +131,7 @@ class RootPackageLoader extends ArrayLoader
$minimumStability = $stabilities[$minimumStability]; $minimumStability = $stabilities[$minimumStability];
foreach ($requires as $reqName => $reqVersion) { foreach ($requires as $reqName => $reqVersion) {
// parse explicit stability flags to the most unstable // parse explicit stability flags to the most unstable
if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) { if (preg_match('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
$name = strtolower($reqName); $name = strtolower($reqName);
$stability = $stabilities[VersionParser::normalizeStability($match[1])]; $stability = $stabilities[VersionParser::normalizeStability($match[1])];

@ -251,6 +251,17 @@ class ValidatingArrayLoader implements LoaderInterface
if ('-dev' !== substr($validatedTargetBranch, -4)) { if ('-dev' !== substr($validatedTargetBranch, -4)) {
$this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev'; $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
unset($this->config['extra']['branch-alias'][$sourceBranch]); unset($this->config['extra']['branch-alias'][$sourceBranch]);
continue;
}
// If using numeric aliases ensure the alias is a valid subversion
if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
&& ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
&& (stripos($targetPrefix, $sourcePrefix) !== 0)
) {
$this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
unset($this->config['extra']['branch-alias'][$sourceBranch]);
} }
} }
} }

@ -169,6 +169,22 @@ class VersionParser
throw new \UnexpectedValueException('Invalid version string "'.$version.'"'.$extraMessage); throw new \UnexpectedValueException('Invalid version string "'.$version.'"'.$extraMessage);
} }
/**
* Extract numeric prefix from alias, if it is in numeric format, suitable for
* version comparison
*
* @param string $branch Branch name (e.g. 2.1.x-dev)
* @return string|false Numeric prefix if present (e.g. 2.1.) or false
*/
public function parseNumericAliasPrefix($branch)
{
if (preg_match('/^(?P<version>(\d+\\.)*\d+)(?:\.x)?-dev$/i', $branch, $matches)) {
return $matches['version'].".";
}
return false;
}
/** /**
* Normalizes a branch name to be able to perform comparisons on it * Normalizes a branch name to be able to perform comparisons on it
* *

@ -190,10 +190,11 @@ class PluginManager
* instead for BC * instead for BC
* *
* @param PackageInterface $package * @param PackageInterface $package
* @param bool $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception
* *
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
*/ */
public function registerPackage(PackageInterface $package) public function registerPackage(PackageInterface $package, $failOnMissingClasses = false)
{ {
$oldInstallerPlugin = ($package->getType() === 'composer-installer'); $oldInstallerPlugin = ($package->getType() === 'composer-installer');
@ -242,10 +243,12 @@ class PluginManager
if ($oldInstallerPlugin) { if ($oldInstallerPlugin) {
$installer = new $class($this->io, $this->composer); $installer = new $class($this->io, $this->composer);
$this->composer->getInstallationManager()->addInstaller($installer); $this->composer->getInstallationManager()->addInstaller($installer);
} else { } elseif (class_exists($class)) {
$plugin = new $class(); $plugin = new $class();
$this->addPlugin($plugin); $this->addPlugin($plugin);
$this->registeredPlugins[] = $package->getName(); $this->registeredPlugins[] = $package->getName();
} elseif ($failOnMissingClasses) {
throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
} }
} }
} }

@ -53,8 +53,6 @@ class ComposerRepository extends ArrayRepository
protected $eventDispatcher; protected $eventDispatcher;
protected $sourceMirrors; protected $sourceMirrors;
protected $distMirrors; protected $distMirrors;
private $rawData;
private $minimalPackages;
private $degradedMode = false; private $degradedMode = false;
private $rootData; private $rootData;
@ -206,6 +204,11 @@ class ComposerRepository extends ArrayRepository
$this->loadProviderListings($this->loadRootServerFile()); $this->loadProviderListings($this->loadRootServerFile());
} }
if ($this->lazyProvidersUrl) {
// Can not determine list of provided packages for lazy repositories
return array();
}
if ($this->providersUrl) { if ($this->providersUrl) {
return array_keys($this->providerListing); return array_keys($this->providerListing);
} }

@ -160,6 +160,7 @@ class PearRepository extends ArrayRepository
$package = new CompletePackage($composerPackageName, $normalizedVersion, $version); $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
$package->setType('pear-library'); $package->setType('pear-library');
$package->setDescription($packageDefinition->getDescription()); $package->setDescription($packageDefinition->getDescription());
$package->setLicense(array($packageDefinition->getLicense()));
$package->setDistType('file'); $package->setDistType('file');
$package->setDistUrl($distUrl); $package->setDistUrl($distUrl);
$package->setAutoload(array('classmap' => array(''))); $package->setAutoload(array('classmap' => array('')));

@ -33,7 +33,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/ */
public function initialize() public function initialize()
{ {
preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match); preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
$this->owner = $match[1]; $this->owner = $match[1];
$this->repository = $match[2]; $this->repository = $match[2];
$this->originUrl = 'bitbucket.org'; $this->originUrl = 'bitbucket.org';
@ -143,7 +143,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{ {
if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
return false; return false;
} }

@ -202,7 +202,7 @@ class GitDriver extends VcsDriver
$this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir);
foreach ($this->process->splitLines($output) as $branch) { foreach ($this->process->splitLines($output) as $branch) {
if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) { if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) { if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) {
$branches[$match[1]] = $match[2]; $branches[$match[1]] = $match[2];
} }
} }
@ -241,7 +241,11 @@ class GitDriver extends VcsDriver
return false; return false;
} }
// TODO try to connect to the server $process = new ProcessExecutor($io);
if($process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url)) === 0) {
return true;
}
return false; return false;
} }
} }

@ -33,7 +33,7 @@ class HgBitbucketDriver extends VcsDriver
*/ */
public function initialize() public function initialize()
{ {
preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match); preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
$this->owner = $match[1]; $this->owner = $match[1];
$this->repository = $match[2]; $this->repository = $match[2];
$this->originUrl = 'bitbucket.org'; $this->originUrl = 'bitbucket.org';
@ -153,7 +153,7 @@ class HgBitbucketDriver extends VcsDriver
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{ {
if (!preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
return false; return false;
} }

@ -169,6 +169,9 @@ class Git
if (getenv('GIT_WORK_TREE')) { if (getenv('GIT_WORK_TREE')) {
putenv('GIT_WORK_TREE'); putenv('GIT_WORK_TREE');
} }
// clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
putenv("DYLD_LIBRARY_PATH");
} }
public static function getGitHubDomainsRegex(Config $config) public static function getGitHubDomainsRegex(Config $config)

@ -26,7 +26,6 @@ class RemoteFilesystem
{ {
private $io; private $io;
private $config; private $config;
private $firstCall;
private $bytesMax; private $bytesMax;
private $originUrl; private $originUrl;
private $fileUrl; private $fileUrl;
@ -344,7 +343,7 @@ class RemoteFilesystem
{ {
if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) { if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
$message = "\n".'Could not fetch '.$this->fileUrl.', enter your GitHub credentials '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit'); $message = "\n".'Could not fetch '.$this->fileUrl.', enter your GitHub credentials '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit');
$gitHubUtil = new GitHub($this->io, $this->config, null, $this); $gitHubUtil = new GitHub($this->io, $this->config, null);
if (!$gitHubUtil->authorizeOAuth($this->originUrl) if (!$gitHubUtil->authorizeOAuth($this->originUrl)
&& (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
) { ) {

@ -67,6 +67,17 @@ class AutoloadGeneratorTest extends TestCase
*/ */
private $eventDispatcher; private $eventDispatcher;
/**
* Map of setting name => return value configuration for the stub Config
* object.
*
* Note: must be public for compatibility with PHP 5.3 runtimes where
* closures cannot access private members of the classes they are created
* in.
* @var array
*/
public $configValueMap;
protected function setUp() protected function setUp()
{ {
$this->fs = new Filesystem; $this->fs = new Filesystem;
@ -79,18 +90,23 @@ class AutoloadGeneratorTest extends TestCase
$this->config = $this->getMock('Composer\Config'); $this->config = $this->getMock('Composer\Config');
$this->config->expects($this->at(0)) $this->configValueMap = array(
->method('get') 'vendor-dir' => function () use ($that) {
->with($this->equalTo('vendor-dir'))
->will($this->returnCallback(function () use ($that) {
return $that->vendorDir; return $that->vendorDir;
})); },
);
$this->config->expects($this->at(1)) $this->config->expects($this->atLeastOnce())
->method('get') ->method('get')
->with($this->equalTo('vendor-dir')) ->will($this->returnCallback(function ($arg) use ($that) {
->will($this->returnCallback(function () use ($that) { $ret = null;
return $that->vendorDir; if (isset($that->configValueMap[$arg])) {
$ret = $that->configValueMap[$arg];
if (is_callable($ret)) {
$ret = $ret();
}
}
return $ret;
})); }));
$this->origDir = getcwd(); $this->origDir = getcwd();
@ -483,6 +499,48 @@ class AutoloadGeneratorTest extends TestCase
include $this->vendorDir.'/composer/autoload_classmap.php' include $this->vendorDir.'/composer/autoload_classmap.php'
); );
$this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
$this->assertNotContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
}
public function testClassMapAutoloadingAuthoritative()
{
$package = new Package('a', '1.0', '1.0');
$packages = array();
$packages[] = $a = new Package('a/a', '1.0', '1.0');
$packages[] = $b = new Package('b/b', '1.0', '1.0');
$packages[] = $c = new Package('c/c', '1.0', '1.0');
$a->setAutoload(array('classmap' => array('')));
$b->setAutoload(array('classmap' => array('test.php')));
$c->setAutoload(array('classmap' => array('./')));
$this->repository->expects($this->once())
->method('getCanonicalPackages')
->will($this->returnValue($packages));
$this->configValueMap['classmap-authoritative'] = true;
$this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
$this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
$this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');
$this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo');
file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}');
file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}');
$this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7');
$this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
$this->assertEquals(
array(
'ClassMapBar' => $this->vendorDir.'/b/b/test.php',
'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php',
'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php',
),
include $this->vendorDir.'/composer/autoload_classmap.php'
);
$this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
$this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
} }
public function testFilesAutoloadGeneration() public function testFilesAutoloadGeneration()
@ -829,10 +887,7 @@ EOF;
->method('getCanonicalPackages') ->method('getCanonicalPackages')
->will($this->returnValue(array())); ->will($this->returnValue(array()));
$this->config->expects($this->at(2)) $this->configValueMap['use-include-path'] = true;
->method('get')
->with($this->equalTo('use-include-path'))
->will($this->returnValue(true));
$this->fs->ensureDirectoryExists($this->vendorDir.'/a'); $this->fs->ensureDirectoryExists($this->vendorDir.'/a');

@ -97,6 +97,18 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
), ),
); );
$data['incorrect local config does not cause ErrorException'] = array(
array(
'packagist' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true),
'type' => 'vcs',
'url' => 'http://example.com',
),
array(
'type' => 'vcs',
'url' => 'http://example.com',
),
);
return $data; return $data;
} }
@ -121,6 +133,35 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($home.'/foo', $config->get('cache-dir')); $this->assertEquals($home.'/foo', $config->get('cache-dir'));
} }
public function testRealpathReplacement()
{
$config = new Config(false, '/foo/bar');
$config->merge(array('config' => array(
'bin-dir' => '$HOME/foo',
'cache-dir' => '/baz/',
'vendor-dir' => 'vendor'
)));
$home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/');
$this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir'));
$this->assertEquals($home.'/foo', $config->get('bin-dir'));
$this->assertEquals('/baz', $config->get('cache-dir'));
}
public function testFetchingRelativePaths()
{
$config = new Config(false, '/foo/bar');
$config->merge(array('config' => array(
'bin-dir' => '{$vendor-dir}/foo',
'vendor-dir' => 'vendor'
)));
$this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir'));
$this->assertEquals('/foo/bar/vendor/foo', $config->get('bin-dir'));
$this->assertEquals('vendor', $config->get('vendor-dir', Config::RELATIVE_PATHS));
$this->assertEquals('vendor/foo', $config->get('bin-dir', Config::RELATIVE_PATHS));
}
public function testOverrideGithubProtocols() public function testOverrideGithubProtocols()
{ {
$config = new Config(false); $config = new Config(false);

@ -49,6 +49,30 @@ class ConsoleIOTest extends TestCase
$consoleIO->write('some information about something', false); $consoleIO->write('some information about something', false);
} }
public function testWriteWithMultipleLineStringWhenDebugging()
{
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
$outputMock->expects($this->once())
->method('write')
->with(
$this->callback(function($messages){
$result = preg_match("[(.*)/(.*) First line]", $messages[0]) > 0;
$result &= preg_match("[(.*)/(.*) Second line]", $messages[1]) > 0;
return $result;
}),
$this->equalTo(false)
);
$helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet');
$consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock);
$startTime = microtime(true);
$consoleIO->enableDebugging($startTime);
$example = explode('\n', 'First line\nSecond lines');
$consoleIO->write($example, false);
}
public function testOverwrite() public function testOverwrite()
{ {
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
@ -58,21 +82,27 @@ class ConsoleIOTest extends TestCase
->method('write') ->method('write')
->with($this->equalTo('something (<question>strlen = 23</question>)')); ->with($this->equalTo('something (<question>strlen = 23</question>)'));
$outputMock->expects($this->at(1)) $outputMock->expects($this->at(1))
->method('isDecorated')
->willReturn(true);
$outputMock->expects($this->at(2))
->method('write') ->method('write')
->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
$outputMock->expects($this->at(2)) $outputMock->expects($this->at(3))
->method('write') ->method('write')
->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false)); ->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
$outputMock->expects($this->at(3)) $outputMock->expects($this->at(4))
->method('write') ->method('write')
->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
$outputMock->expects($this->at(4)) $outputMock->expects($this->at(5))
->method('write') ->method('write')
->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false)); ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
$outputMock->expects($this->at(5)) $outputMock->expects($this->at(6))
->method('isDecorated')
->willReturn(true);
$outputMock->expects($this->at(7))
->method('write') ->method('write')
->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)); ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
$outputMock->expects($this->at(6)) $outputMock->expects($this->at(8))
->method('write') ->method('write')
->with($this->equalTo('something longer than initial (<info>34</info>)')); ->with($this->equalTo('something longer than initial (<info>34</info>)'));

@ -241,10 +241,10 @@ class InstallerTest extends TestCase
} }
$installationManager = $composer->getInstallationManager(); $installationManager = $composer->getInstallationManager();
$this->assertSame($expect, implode("\n", $installationManager->getTrace())); $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace()));
if ($expectOutput) { if ($expectOutput) {
$this->assertEquals($expectOutput, $output); $this->assertEquals(rtrim($expectOutput), rtrim($output));
} }
} }
@ -258,21 +258,7 @@ class InstallerTest extends TestCase
continue; continue;
} }
$test = file_get_contents($file->getRealpath()); $testData = $this->readTestFile($file, $fixturesDir);
$content = '(?:.(?!--[A-Z]))+';
$pattern = '{^
--TEST--\s*(?P<test>.*?)\s*
(?:--CONDITION--\s*(?P<condition>'.$content.'))?\s*
--COMPOSER--\s*(?P<composer>'.$content.')\s*
(?:--LOCK--\s*(?P<lock>'.$content.'))?\s*
(?:--INSTALLED--\s*(?P<installed>'.$content.'))?\s*
--RUN--\s*(?P<run>.*?)\s*
(?:--EXPECT-LOCK--\s*(?P<expectLock>'.$content.'))?\s*
(?:--EXPECT-OUTPUT--\s*(?P<expectOutput>'.$content.'))?\s*
(?:--EXPECT-EXIT-CODE--\s*(?P<expectExitCode>\d+))?\s*
--EXPECT--\s*(?P<expect>.*?)\s*
$}xs';
$installed = array(); $installed = array();
$installedDev = array(); $installedDev = array();
@ -280,48 +266,44 @@ class InstallerTest extends TestCase
$expectLock = array(); $expectLock = array();
$expectExitCode = 0; $expectExitCode = 0;
if (preg_match($pattern, $test, $match)) { try {
try { $message = $testData['TEST'];
$message = $match['test']; $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
$condition = !empty($match['condition']) ? $match['condition'] : null; $composer = JsonFile::parseJson($testData['COMPOSER']);
$composer = JsonFile::parseJson($match['composer']);
if (isset($composer['repositories'])) { if (isset($composer['repositories'])) {
foreach ($composer['repositories'] as &$repo) { foreach ($composer['repositories'] as &$repo) {
if ($repo['type'] !== 'composer') { if ($repo['type'] !== 'composer') {
continue; continue;
}
// Change paths like file://foobar to file:///path/to/fixtures
if (preg_match('{^file://[^/]}', $repo['url'])) {
$repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
}
unset($repo);
} }
}
if (!empty($match['lock'])) { // Change paths like file://foobar to file:///path/to/fixtures
$lock = JsonFile::parseJson($match['lock']); if (preg_match('{^file://[^/]}', $repo['url'])) {
if (!isset($lock['hash'])) { $repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
$lock['hash'] = md5(json_encode($composer));
} }
unset($repo);
} }
if (!empty($match['installed'])) { }
$installed = JsonFile::parseJson($match['installed']);
} if (!empty($testData['LOCK'])) {
$run = $match['run']; $lock = JsonFile::parseJson($testData['LOCK']);
if (!empty($match['expectLock'])) { if (!isset($lock['hash'])) {
$expectLock = JsonFile::parseJson($match['expectLock']); $lock['hash'] = md5(json_encode($composer));
} }
$expectOutput = $match['expectOutput'];
$expect = $match['expect'];
$expectExitCode = (int) $match['expectExitCode'];
} catch (\Exception $e) {
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
} }
} else { if (!empty($testData['INSTALLED'])) {
die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file))); $installed = JsonFile::parseJson($testData['INSTALLED']);
}
$run = $testData['RUN'];
if (!empty($testData['EXPECT-LOCK'])) {
$expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
}
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
$expect = $testData['EXPECT'];
$expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? (int) $testData['EXPECT-EXIT-CODE'] : 0;
} catch (\Exception $e) {
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
} }
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode); $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
@ -329,4 +311,59 @@ class InstallerTest extends TestCase
return $tests; return $tests;
} }
protected function readTestFile(\SplFileInfo $file, $fixturesDir)
{
$tokens = preg_split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), null, PREG_SPLIT_DELIM_CAPTURE);
$sectionInfo = array(
'TEST' => true,
'CONDITION' => false,
'COMPOSER' => true,
'LOCK' => false,
'INSTALLED' => false,
'RUN' => true,
'EXPECT-LOCK' => false,
'EXPECT-OUTPUT' => false,
'EXPECT-EXIT-CODE' => false,
'EXPECT' => true,
);
$section = null;
foreach ($tokens as $i => $token)
{
if (null === $section && empty($token)) {
continue; // skip leading blank
}
if (null === $section) {
if (!isset($sectionInfo[$token])) {
throw new \RuntimeException(sprintf(
'The test file "%s" must not contain a section named "%s".',
str_replace($fixturesDir.'/', '', $file),
$token
));
}
$section = $token;
continue;
}
$sectionData = $token;
$data[$section] = $sectionData;
$section = $sectionData = null;
}
foreach ($sectionInfo as $section => $required) {
if ($required && !isset($data[$section])) {
throw new \RuntimeException(sprintf(
'The test file "%s" must have a section named "%s".',
str_replace($fixturesDir.'/', '', $file),
$section
));
}
}
return $data;
}
} }

@ -22,9 +22,9 @@ use Composer\IO\IOInterface;
class FactoryMock extends Factory class FactoryMock extends Factory
{ {
public static function createConfig(IOInterface $io = null) public static function createConfig(IOInterface $io = null, $cwd = null)
{ {
$config = new Config(); $config = new Config(true, $cwd);
$config->merge(array( $config->merge(array(
'config' => array('home' => sys_get_temp_dir().'/composer-test'), 'config' => array('home' => sys_get_temp_dir().'/composer-test'),

@ -138,6 +138,50 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Composer\Package\AliasPackage', $package); $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
$this->assertEquals('1.0.x-dev', $package->getPrettyVersion()); $this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
$config = array(
'name' => 'A',
'version' => 'dev-master',
'extra' => array('branch-alias' => array('dev-master' => '1.0-dev')),
);
$package = $this->loader->load($config);
$this->assertInstanceOf('Composer\Package\AliasPackage', $package);
$this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
$config = array(
'name' => 'B',
'version' => '4.x-dev',
'extra' => array('branch-alias' => array('4.x-dev' => '4.0.x-dev')),
);
$package = $this->loader->load($config);
$this->assertInstanceOf('Composer\Package\AliasPackage', $package);
$this->assertEquals('4.0.x-dev', $package->getPrettyVersion());
$config = array(
'name' => 'B',
'version' => '4.x-dev',
'extra' => array('branch-alias' => array('4.x-dev' => '4.0-dev')),
);
$package = $this->loader->load($config);
$this->assertInstanceOf('Composer\Package\AliasPackage', $package);
$this->assertEquals('4.0.x-dev', $package->getPrettyVersion());
$config = array(
'name' => 'C',
'version' => '4.x-dev',
'extra' => array('branch-alias' => array('4.x-dev' => '3.4.x-dev')),
);
$package = $this->loader->load($config);
$this->assertInstanceOf('Composer\Package\CompletePackage', $package);
$this->assertEquals('4.x-dev', $package->getPrettyVersion());
} }
public function testAbandoned() public function testAbandoned()

@ -143,6 +143,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase
'foo/bar' => '~2.1.0-beta2', 'foo/bar' => '~2.1.0-beta2',
'bar/baz' => '1.0.x-dev as 1.2.0', 'bar/baz' => '1.0.x-dev as 1.2.0',
'qux/quux' => '1.0.*@rc', 'qux/quux' => '1.0.*@rc',
'zux/complex' => '~1.0,>=1.0.2@dev'
), ),
'minimum-stability' => 'alpha', 'minimum-stability' => 'alpha',
)); ));
@ -151,6 +152,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array( $this->assertEquals(array(
'bar/baz' => BasePackage::STABILITY_DEV, 'bar/baz' => BasePackage::STABILITY_DEV,
'qux/quux' => BasePackage::STABILITY_RC, 'qux/quux' => BasePackage::STABILITY_RC,
'zux/complex' => BasePackage::STABILITY_DEV,
), $package->getStabilityFlags()); ), $package->getStabilityFlags());
} }
} }

@ -140,6 +140,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
'branch-alias' => array( 'branch-alias' => array(
'dev-master' => '2.0-dev', 'dev-master' => '2.0-dev',
'dev-old' => '1.0.x-dev', 'dev-old' => '1.0.x-dev',
'3.x-dev' => '3.1.x-dev'
), ),
), ),
'bin' => array( 'bin' => array(
@ -324,6 +325,34 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
), ),
false false
), ),
array(
array(
'name' => 'foo/bar',
'extra' => array(
'branch-alias' => array(
'5.x-dev' => '3.1.x-dev'
),
)
),
array(
'extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version'
),
false
),
array(
array(
'name' => 'foo/bar',
'extra' => array(
'branch-alias' => array(
'5.x-dev' => '3.1-dev'
),
)
),
array(
'extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version'
),
false
),
); );
} }
} }

@ -67,6 +67,29 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
return array_map($createPackage, $data); return array_map($createPackage, $data);
} }
/**
* @dataProvider numericAliasVersions
*/
public function testParseNumericAliasPrefix($input, $expected)
{
$parser = new VersionParser;
$this->assertSame($expected, $parser->parseNumericAliasPrefix($input));
}
public function numericAliasVersions()
{
return array(
array('0.x-dev', '0.'),
array('1.0.x-dev', '1.0.'),
array('1.x-dev', '1.'),
array('1.2.x-dev', '1.2.'),
array('1.2-dev', '1.2.'),
array('1-dev', '1.'),
array('dev-develop', false),
array('dev-master', false),
);
}
/** /**
* @dataProvider successfulNormalizedVersions * @dataProvider successfulNormalizedVersions
*/ */

@ -113,8 +113,12 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
array('3.1.2-dev', true, 'dev', '3.1.2-dev'), array('3.1.2-dev', true, 'dev', '3.1.2-dev'),
// dev packages with alias inherit the alias // dev packages with alias inherit the alias
array('dev-master', true, 'dev', '~2.1@dev', '2.1.x-dev'), array('dev-master', true, 'dev', '~2.1@dev', '2.1.x-dev'),
array('dev-master', true, 'dev', '~2.1@dev', '2.1-dev'),
array('dev-master', true, 'dev', '~2.1@dev', '2.1.3.x-dev'), array('dev-master', true, 'dev', '~2.1@dev', '2.1.3.x-dev'),
array('dev-master', true, 'dev', '~2.0@dev', '2.x-dev'), array('dev-master', true, 'dev', '~2.0@dev', '2.x-dev'),
// numeric alias
array('3.x-dev', true, 'dev', '~3.0@dev', '3.0.x-dev'),
array('3.x-dev', true, 'dev', '~3.0@dev', '3.0-dev'),
); );
} }

@ -19,6 +19,14 @@ use Composer\Package\BasePackage;
class ArtifactRepositoryTest extends TestCase class ArtifactRepositoryTest extends TestCase
{ {
public function setUp()
{
parent::setUp();
if (!extension_loaded('zip')) {
$this->markTestSkipped('You need the zip extension to run this test.');
}
}
public function testExtractsConfigsFromZipArchives() public function testExtractsConfigsFromZipArchives()
{ {
$expectedPackages = array( $expectedPackages = array(

@ -121,6 +121,7 @@ class ChannelReaderTest extends TestCase
$expectedPackage->setType('pear-library'); $expectedPackage->setType('pear-library');
$expectedPackage->setDistType('file'); $expectedPackage->setDistType('file');
$expectedPackage->setDescription('description'); $expectedPackage->setDescription('description');
$expectedPackage->setLicense(array('license'));
$expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz"); $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz");
$expectedPackage->setAutoload(array('classmap' => array(''))); $expectedPackage->setAutoload(array('classmap' => array('')));
$expectedPackage->setIncludePaths(array('/')); $expectedPackage->setIncludePaths(array('/'));

@ -84,13 +84,6 @@ class PearRepositoryTest extends TestCase
public function repositoryDataProvider() public function repositoryDataProvider()
{ {
return array( return array(
array(
'pear.phpunit.de',
array(
array('name' => 'pear-pear.phpunit.de/PHPUnit_MockObject', 'version' => '1.1.1'),
array('name' => 'pear-pear.phpunit.de/PHPUnit', 'version' => '3.6.10'),
)
),
array( array(
'pear.php.net', 'pear.php.net',
array( array(

Loading…
Cancel
Save