diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..820a4002e --- /dev/null +++ b/CONTRIBUTING.md @@ -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). diff --git a/README.md b/README.md index a86bc83f6..728e11f2e 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,6 @@ themselves. To create libraries/packages please read the 3. Run Composer: `php composer.phar install` 4. Browse for more packages on [Packagist](https://packagist.org). -Installation from Source ------------------------- - -To run tests, or develop Composer itself, you must use the sources and not the phar -file as described above. - -1. Run `git clone https://github.com/composer/composer.git` -2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable -3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install` - -You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer` - Global installation of Composer (manual) ---------------------------------------- @@ -55,20 +43,6 @@ Updating Composer Running `php composer.phar self-update` or equivalent will update a phar install with the latest version. -Contributing ------------- - -All code contributions - including those of people having commit access - -must go through a pull request and approved by a core developer before being -merged. This is to ensure proper review of all the code. - -Fork the project, create a feature branch, and send us a pull request. - -To ensure a consistent code base, you should make sure the code follows -the [Coding Standards](http://symfony.com/doc/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 --------- diff --git a/composer.json b/composer.json index 6d239970d..9fca7d74c 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require": { "php": ">=5.3.2", - "justinrainbow/json-schema": "~1.1", + "justinrainbow/json-schema": "~1.3", "seld/jsonlint": "~1.0", "symfony/console": "~2.3", "symfony/finder": "~2.2", diff --git a/composer.lock b/composer.lock index 0c7ff85ba..7d28fcb09 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "0430383b5ba00e406ce4a44253f49fe7", + "hash": "2bc9cc8aa706b68d611d7058e4eb8de7", "packages": [ { "name": "justinrainbow/json-schema", @@ -327,16 +327,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.0.13", + "version": "2.0.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5" + "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", - "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca158276c1200cc27f5409a5e338486bc0b4fc94", + "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94", "shasum": "" }, "require": { @@ -388,7 +388,7 @@ "testing", "xunit" ], - "time": "2014-12-03 06:41:44" + "time": "2014-12-26 13:28:33" }, { "name": "phpunit/php-file-iterator", @@ -574,16 +574,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.4.0", + "version": "4.4.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0" + "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", - "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a5e49a86ce5e33b8d0657abe145057fc513543a", + "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a", "shasum": "" }, "require": { @@ -641,7 +641,7 @@ "testing", "xunit" ], - "time": "2014-12-05 06:49:03" + "time": "2014-12-28 07:57:05" }, { "name": "phpunit/phpunit-mock-objects", @@ -982,16 +982,16 @@ }, { "name": "sebastian/version", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" + "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", - "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/a77d9123f8e809db3fbdea15038c27a95da4058b", + "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b", "shasum": "" }, "type": "library", @@ -1013,7 +1013,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2014-03-07 15:35:33" + "time": "2014-12-15 14:25:24" }, { "name": "symfony/yaml", @@ -1067,6 +1067,7 @@ "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, + "prefer-lowest": false, "platform": { "php": ">=5.3.2" }, diff --git a/doc/00-intro.md b/doc/00-intro.md index c1bafa3cd..714b55be0 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -107,6 +107,8 @@ mv composer.phar /usr/local/bin/composer > **Note:** If the above fails due to permissions, run the `mv` line > again with sudo. +> **Note:** In OSX Yosemite the `/usr` directory does not exist by default. If you receive the error "/usr/local/bin/composer: No such file or directory" then you must create `/usr/local/bin/` manually before proceeding. + Then, just run `composer` in order to run Composer instead of `php composer.phar`. ## Installation - Windows diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 1ab4acbdb..ef72f556c 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -2,7 +2,7 @@ ## 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 @@ -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 filename would be `src/Foo.php` containing an `Acme\Foo` class. -After adding the `autoload` field, you have to re-run `install` to re-generate +After adding the `autoload` field, you have to re-run `dump-autoload` to re-generate the `vendor/autoload.php` file. Including that file will also return the autoloader instance, so you can store diff --git a/doc/02-libraries.md b/doc/02-libraries.md index 561f3aa79..913f996b7 100644 --- a/doc/02-libraries.md +++ b/doc/02-libraries.md @@ -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 created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix -of `-patch`, `-alpha`, `-beta` or `-RC`. The suffixes can also be followed by -a number. +of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes +can also be followed by a number. Here are a few examples of valid tag names: diff --git a/doc/03-cli.md b/doc/03-cli.md index 3c9dd82a4..5eaed1d44 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -87,7 +87,8 @@ resolution. installing a package, you can use `--dry-run`. This will simulate the installation and show you what would happen. * **--dev:** Install packages listed in `require-dev` (this is the default behavior). -* **--no-dev:** Skip installing packages listed in `require-dev`. +* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules. +* **--no-autoloader:** Skips autoloader generation. * **--no-scripts:** Skips execution of scripts defined in `composer.json`. * **--no-plugins:** Disables plugins. * **--no-progress:** Removes the progress display that can mess with some @@ -129,7 +130,8 @@ php composer.phar update vendor/* fulfill these. * **--dry-run:** Simulate the command without actually doing anything. * **--dev:** Install packages listed in `require-dev` (this is the default behavior). -* **--no-dev:** Skip installing packages listed in `require-dev`. +* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules. +* **--no-autoloader:** Skips autoloader generation. * **--no-scripts:** Skips execution of scripts defined in `composer.json`. * **--no-plugins:** Disables plugins. * **--no-progress:** Removes the progress display that can mess with some @@ -397,16 +399,18 @@ options. ### Options * **--global (-g):** Operate on the global config file located at -`$COMPOSER_HOME/config.json` by default. Without this option, this command -affects the local composer.json file or a file specified by `--file`. + `$COMPOSER_HOME/config.json` by default. Without this option, this command + affects the local composer.json file or a file specified by `--file`. * **--editor (-e):** Open the local composer.json file using in a text editor as -defined by the `EDITOR` env variable. With the `--global` option, this opens -the global config file. + defined by the `EDITOR` env variable. With the `--global` option, this opens + the global config file. * **--unset:** Remove the configuration element named by `setting-key`. * **--list (-l):** Show the list of current config variables. With the `--global` - option this lists the global configuration only. + option this lists the global configuration only. * **--file="..." (-f):** Operate on a specific file instead of composer.json. Note - that this cannot be used in conjunction with the `--global` option. + that this cannot be used in conjunction with the `--global` option. +* **--absolute:** Returns absolute paths when fetching *-dir config values + instead of relative. ### Modifying Repositories @@ -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 project. This is mostly useful if you run the command in non-interactive mode. +* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*` + requirements and force the installation even if the local machine does not + fulfill these. ## dump-autoload diff --git a/doc/04-schema.md b/doc/04-schema.md index 95b72d6c2..6e02fb21a 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -54,8 +54,8 @@ The version of the package. In most cases this is not required and should be omitted (see below). This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix -of `-dev`, `-patch`, `-alpha`, `-beta` or `-RC`. The patch, alpha, beta and -RC suffixes can also be followed by a number. +of `-dev`, `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. +The patch, alpha, beta and RC suffixes can also be followed by a number. Examples: @@ -67,6 +67,7 @@ Examples: - 1.0.0-alpha3 - 1.0.0-beta2 - 1.0.0-RC5 +- v2.0.4-p1 Optional if the package repository can infer the version from somewhere, such as the VCS tag name in the VCS repository. In that case it is also recommended @@ -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 simply list it in `provide`. -### suggest +#### suggest Suggested packages that can enhance or work well with this package. These are just informational and are displayed after the package is installed, to give @@ -790,6 +791,9 @@ The following options are supported: the generated Composer autoloader. When null a random one will be generated. * **optimize-autoloader** Defaults to `false`. Always optimize when dumping the autoloader. +* **classmap-authoritative:** Defaults to `false`. If true, the composer + autoloader will not scan the filesystem for classes that are not found in + the class map. Implies 'optimize-autoloader'. * **github-domains:** Defaults to `["github.com"]`. A list of domains to use in github mode. This is used for GitHub Enterprise setups. * **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 975b473e5..80d15c561 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -122,7 +122,7 @@ JSON request body: ```json { "downloads": [ - {"name": "monolog/monolog", "version": "1.2.1.0"}, + {"name": "monolog/monolog", "version": "1.2.1.0"} ] } ``` diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md index 2b436322f..79c573d3d 100644 --- a/doc/articles/aliases.md +++ b/doc/articles/aliases.md @@ -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 -must be a comparable dev version (i.e. start with numbers, and end with -`.x-dev`). The `branch-alias` must be present on the branch that it references. -For `dev-master`, you need to commit it on the `master` branch. +If you alias a non-comparible version (such as dev-develop) `dev-` must prefix the +branch name. You may also alias a comparible version (i.e. start with numbers, +and end with `.x-dev`), but only as a more specific version. +For example, 1.x-dev could be aliased as 1.2.x-dev. + +The alias must be a comparable dev version, and the `branch-alias` must be present on +the branch that it references. For `dev-master`, you need to commit it on the +`master` branch. As a result, anyone can now require `1.0.*` and it will happily install `dev-master`. diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index 0ee0adbca..5fce5377b 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -66,7 +66,7 @@ constraint if you want really specific versions. } ``` -Once you did this, you just run `php bin/satis build `. +Once you've done this, you just run `php bin/satis build `. For example `php bin/satis build config.json web/` would read the `config.json` file and build a static repository inside the `web/` directory. diff --git a/doc/articles/http-basic-authentication.md b/doc/articles/http-basic-authentication.md index af62fe2df..1add2d7a6 100644 --- a/doc/articles/http-basic-authentication.md +++ b/doc/articles/http-basic-authentication.md @@ -40,7 +40,7 @@ username/password pairs, for example: ```json { - "basic-auth": [ + "basic-auth": { "repo.example1.org": { "username": "my-username1", "password": "my-secret-password1" @@ -49,7 +49,7 @@ username/password pairs, for example: "username": "my-username2", "password": "my-secret-password2" } - ] + } } ``` diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 922526a52..4de3ad19e 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -114,8 +114,9 @@ php -d memory_limit=-1 composer.phar <...> ## "The system cannot find the path specified" (Windows) 1. Open regedit. -2. Search for an ```AutoRun``` key inside ```HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor``` - or ```HKEY_CURRENT_USER\Software\Microsoft\Command Processor```. +2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`, + `HKEY_CURRENT_USER\Software\Microsoft\Command Processor` + or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`. 3. Check if it contains any path to non-existent file, if it's the case, just remove them. ## API rate limit and OAuth tokens diff --git a/res/composer-schema.json b/res/composer-schema.json index 95cd199e3..4c40bdfb2 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -1,12 +1,13 @@ { + "$schema": "http://json-schema.org/draft-04/schema#", "name": "Package", "type": "object", "additionalProperties": false, + "required": [ "name", "description" ], "properties": { "name": { "type": "string", - "description": "Package name, including 'vendor-name/' prefix.", - "required": true + "description": "Package name, including 'vendor-name/' prefix." }, "type": { "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", @@ -18,8 +19,7 @@ }, "description": { "type": "string", - "description": "Short package description.", - "required": true + "description": "Short package description." }, "keywords": { "type": "array", @@ -51,11 +51,11 @@ "items": { "type": "object", "additionalProperties": false, + "required": [ "name"], "properties": { "name": { "type": "string", - "description": "Full name of the author.", - "required": true + "description": "Full name of the author." }, "email": { "type": "string", @@ -197,6 +197,10 @@ "type": "boolean", "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." }, + "classmap-authoritative": { + "type": "boolean", + "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." + }, "github-domains": { "type": "array", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 19f194fa1..c88c34b41 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -63,6 +63,7 @@ class AutoloadGenerator $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir'))); $useGlobalIncludePath = (bool) $config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; + $classMapAuthoritative = $config->get('classmap-authoritative'); $targetDir = $vendorPath.'/'.$targetDir; $filesystem->ensureDirectoryExists($targetDir); @@ -226,7 +227,7 @@ EOF; file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile); } file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); - file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)); + file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)); // use stream_copy_to_stream instead of copy // to work around https://bugs.php.net/bug.php?id=64634 @@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader(); AUTOLOAD; } - protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader) + protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative) { // TODO the class ComposerAutoloaderInit should be revert to a closure // when APC has been fixed: @@ -520,6 +521,13 @@ PSR4; CLASSMAP; } + if ($classMapAuthoritative) { + $file .= <<<'CLASSMAPAUTHORITATIVE' + $loader->setClassMapAuthoritative(true); + +CLASSMAPAUTHORITATIVE; + } + if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); diff --git a/src/Composer/Autoload/ClassLoader.php b/src/Composer/Autoload/ClassLoader.php index 70d78bc3f..5e1469e83 100644 --- a/src/Composer/Autoload/ClassLoader.php +++ b/src/Composer/Autoload/ClassLoader.php @@ -54,6 +54,8 @@ class ClassLoader private $useIncludePath = false; private $classMap = array(); + private $classMapAuthoritative = false; + public function getPrefixes() { if (!empty($this->prefixesPsr0)) { @@ -248,6 +250,27 @@ class ClassLoader return $this->useIncludePath; } + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + /** * Registers this instance as an autoloader. * @@ -299,6 +322,9 @@ class ClassLoader if (isset($this->classMap[$class])) { return $this->classMap[$class]; } + if ($this->classMapAuthoritative) { + return false; + } $file = $this->findFileWithExtension($class, '.php'); diff --git a/src/Composer/Command/Command.php b/src/Composer/Command/Command.php index b99686219..6c5226c6a 100644 --- a/src/Composer/Command/Command.php +++ b/src/Composer/Command/Command.php @@ -16,6 +16,8 @@ use Composer\Composer; use Composer\Console\Application; use Composer\IO\IOInterface; use Composer\IO\NullIO; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command as BaseCommand; /** @@ -102,4 +104,16 @@ abstract class Command extends BaseCommand { $this->io = $io; } + + /** + * {@inheritDoc} + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) { + $input->setOption('no-progress', true); + } + + parent::initialize($input, $output); + } } diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index a08a6f4a6..e21c99c8a 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -57,6 +57,7 @@ class ConfigCommand extends Command new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'), + new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), new InputArgument('setting-key', null, 'Setting key'), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), )) @@ -99,6 +100,8 @@ EOT */ protected function initialize(InputInterface $input, OutputInterface $output) { + parent::initialize($input, $output); + if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) { throw new \RuntimeException('--file and --global can not be combined'); } @@ -134,7 +137,7 @@ EOT } 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; } elseif (isset($data['config'][$settingKey])) { - $value = $data['config'][$settingKey]; + $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); } else { throw new \RuntimeException($settingKey.' is not defined'); } @@ -322,6 +325,7 @@ EOT ), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), + 'classmap-authoritative' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer), ); diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 4a3d33831..0242afcd3 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -69,6 +69,7 @@ class CreateProjectCommand extends Command new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), + new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), )) ->setHelp(<<create-project command creates a new project from a given @@ -125,11 +126,12 @@ EOT $input->getOption('keep-vcs'), $input->getOption('no-progress'), $input->getOption('no-install'), + $input->getOption('ignore-platform-reqs'), $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(); @@ -159,7 +161,8 @@ EOT $installer->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode($installDevPackages) - ->setRunScripts( ! $noScripts); + ->setRunScripts(!$noScripts) + ->setIgnorePlatformRequirements($ignorePlatformReqs); if ($disablePlugins) { $installer->disablePlugins(); diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index b30fbd140..adcc7adfd 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -52,7 +52,7 @@ EOT $package = $composer->getPackage(); $config = $composer->getConfig(); - $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); + $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); if ($optimize) { $output->writeln('Generating optimized autoload files'); diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index 5b0e32244..7dd8113cb 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -40,12 +40,14 @@ class HomeCommand extends Command ->setDefinition(array( new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), + new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), )) ->setHelp(<<initializeRepo($input, $output); + $repo = $this->initializeRepo(); $return = 0; foreach ($input->getArgument('packages') as $packageName) { @@ -81,7 +83,11 @@ EOT continue; } - $this->openBrowser($url); + if ($input->getOption('show')) { + $output->writeln(sprintf('%s', $url)); + } else { + $this->openBrowser($url); + } } return $return; @@ -138,13 +144,11 @@ EOT } /** - * initializes the repo + * Initializes the repo * - * @param InputInterface $input - * @param OutputInterface $output * @return CompositeRepository */ - private function initializeRepo(InputInterface $input, OutputInterface $output) + private function initializeRepo() { $composer = $this->getComposer(false); diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 669c03582..115e1d6af 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -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-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), @@ -74,6 +75,10 @@ EOT $input->setOption('no-plugins', true); } + if ($input->getOption('dev')) { + $output->writeln('You are using the deprecated option "dev". Dev packages are installed by default now.'); + } + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); @@ -105,7 +110,7 @@ EOT $preferDist = $input->getOption('prefer-dist'); } - $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); $install ->setDryRun($input->getOption('dry-run')) @@ -113,6 +118,7 @@ EOT ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setOptimizeAutoloader($optimize) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 91043ad71..ea972aaf0 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -38,7 +38,7 @@ class RequireCommand extends InitCommand ->setName('require') ->setDescription('Adds required packages to your composer.json and installs them') ->setDefinition(array( - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package name optionally including a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), @@ -50,7 +50,9 @@ class RequireCommand extends InitCommand new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), )) ->setHelp(<<setName('show') + ->setAliases(array('info')) ->setDescription('Show information about packages') ->setDefinition(array( new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'), diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 5e83658c9..460075f0f 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -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('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), + new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'), @@ -78,6 +79,10 @@ EOT $input->setOption('no-plugins', true); } + if ($input->getOption('dev')) { + $output->writeln('You are using the deprecated option "dev". Dev packages are installed by default now.'); + } + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); @@ -109,7 +114,7 @@ EOT $preferDist = $input->getOption('prefer-dist'); } - $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); + $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative'); $install ->setDryRun($input->getOption('dry-run')) @@ -117,6 +122,7 @@ EOT ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) + ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setRunScripts(!$input->getOption('no-scripts')) ->setOptimizeAutoloader($optimize) ->setUpdate(true) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index ca222b12b..80c61820e 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -19,6 +19,8 @@ use Composer\Config\ConfigSourceInterface; */ class Config { + const RELATIVE_PATHS = 1; + public static $defaultConfig = array( 'process-timeout' => 300, 'use-include-path' => false, @@ -38,6 +40,7 @@ class Config 'discard-changes' => false, 'autoloader-suffix' => null, 'optimize-autoloader' => false, + 'classmap-authoritative' => false, 'prepend-autoloader' => true, 'github-domains' => array('github.com'), 'github-expose-hostname' => true, @@ -56,6 +59,7 @@ class Config ); private $config; + private $baseDir; private $repositories; private $configSource; private $authConfigSource; @@ -64,12 +68,13 @@ class Config /** * @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 $this->config = static::$defaultConfig; $this->repositories = static::$defaultRepositories; $this->useEnvironment = (bool) $useEnvironment; + $this->baseDir = $baseDir; } public function setConfigSource(ConfigSourceInterface $source) @@ -121,7 +126,7 @@ class Config } // disable a repository with an anonymous {"name": false} repo - if (1 === count($repository) && false === current($repository)) { + if (is_array($repository) && 1 === count($repository) && false === current($repository)) { unset($this->repositories[key($repository)]); continue; } @@ -149,10 +154,11 @@ class Config * Returns a setting * * @param string $key + * @param int $flags Options (see class constants) * @throws \RuntimeException * @return mixed */ - public function get($key) + public function get($key, $flags = 0) { switch ($key) { case 'vendor-dir': @@ -166,10 +172,14 @@ class Config // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); - $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); - return $val; + if (substr($key, -4) !== '-dir') { + return $val; + } + + return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val); case 'cache-ttl': return (int) $this->config[$key]; @@ -205,7 +215,7 @@ class Config return (int) $this->config['cache-ttl']; case 'home': - return rtrim($this->process($this->config[$key]), '/\\'); + return rtrim($this->process($this->config[$key], $flags), '/\\'); case 'discard-changes': if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) { @@ -242,17 +252,17 @@ class Config return null; } - return $this->process($this->config[$key]); + return $this->process($this->config[$key], $flags); } } - public function all() + public function all($flags = 0) { $all = array( 'repositories' => $this->getRepositories(), ); foreach (array_keys($this->config) as $key) { - $all['config'][$key] = $this->get($key); + $all['config'][$key] = $this->get($key, $flags); } return $all; @@ -281,9 +291,10 @@ class Config * Replaces {$refs} inside a config string * * @param string $value a config string that can contain {$refs-to-other-config} + * @param int $flags Options (see class constants) * @return string */ - private function process($value) + private function process($value, $flags) { $config = $this; @@ -291,11 +302,28 @@ class Config return $value; } - return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) { - return $config->get($match[1]); + return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) { + return $config->get($match[1], $flags); }, $value); } + /** + * Turns relative paths in absolute paths without realpath() + * + * Since the dirs might not exist yet we can not call realpath or it will fail. + * + * @param string $path + * @return string + */ + private function realpath($path) + { + if (substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':') { + return $path; + } + + return $this->baseDir . '/' . $path; + } + /** * Reads the value of a Composer environment variable * diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 8e49eedb5..1c8246f88 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -184,6 +184,7 @@ class Application extends BaseApplication $minSpaceFree = 1024*1024; if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) + || (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { $output->writeln('The disk hosting '.$dir.' is full, this may be the cause of the following exception'); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 0c03f18ab..96bd57c06 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -90,10 +90,10 @@ class FileDownloader implements DownloaderInterface } catch (\Exception $e) { if ($this->io->isDebug()) { $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)) { $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)) { diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index c5033124e..dff5456e1 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -44,6 +44,7 @@ class EventDispatcher protected $io; protected $loader; protected $process; + protected $listeners; /** * Constructor. diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 54e8ee589..d28a0cbe1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -17,10 +17,9 @@ use Composer\Json\JsonFile; use Composer\IO\IOInterface; use Composer\Package\Archiver; use Composer\Repository\RepositoryManager; -use Composer\Repository\RepositoryInterface; +use Composer\Repository\WritableRepositoryInterface; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; -use Composer\Util\Filesystem; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; @@ -36,7 +35,6 @@ use Composer\Package\Version\VersionParser; */ class Factory { - /** * @return string * @throws \RuntimeException @@ -73,9 +71,9 @@ class Factory if (!$xdgConfig) { $xdgConfig = $userDir . '/.config'; } - $home = $xdgConfig . '/composer'; - } else { - $home = $userDir . '/.composer'; + $home = $xdgConfig . '/composer'; + } else { + $home = $userDir . '/.composer'; } } } @@ -144,8 +142,10 @@ class Factory * @param IOInterface|null $io * @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 $home = self::getHomeDir(); $cacheDir = self::getCacheDir($home); @@ -188,7 +188,7 @@ class Factory } } - $config = new Config(); + $config = new Config(true, $cwd); // add dirs to the config $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir))); @@ -218,14 +218,14 @@ class Factory public static function getComposerFile() { - return trim(getenv('COMPOSER')) ? : './composer.json'; + return trim(getenv('COMPOSER')) ?: './composer.json'; } public static function createAdditionalStyles() { return array( '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'); } $factory = new static; - $rm = $factory->createRepositoryManager($io, $config); + $rm = $factory->createRepositoryManager($io, $config); } foreach ($config->getRepositories() as $index => $repo) { + if (is_string($repo)) { + throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); + } if (!is_array($repo)) { - throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') should be an array, ' . gettype($repo) . ' given'); + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given'); } if (!isset($repo['type'])) { - throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') must have a type defined'); + throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined'); } $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index; while (isset($repos[$name])) { @@ -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 * read from the default filename * @param bool $disablePlugins Whether plugins should not be loaded + * @param bool $fullLoad Whether to initialize everything or only main project stuff (used when loading the global composer) * @throws \InvalidArgumentException * @throws \UnexpectedValueException * @return Composer */ - public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false) + public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true) { + $cwd = $cwd ?: getcwd(); + // load Composer configuration if (null === $localConfig) { $localConfig = static::getComposerFile(); @@ -281,16 +287,16 @@ class Factory if (is_string($localConfig)) { $composerFile = $localConfig; - $file = new JsonFile($localConfig, new RemoteFilesystem($io)); + $file = new JsonFile($localConfig, new RemoteFilesystem($io)); if (!$file->exists()) { if ($localConfig === './composer.json' || $localConfig === 'composer.json') { - $message = 'Composer could not find a composer.json file in ' . getcwd(); + $message = 'Composer could not find a composer.json file in '.$cwd; } else { - $message = 'Composer could not find the config file: ' . $localConfig; + $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'; - throw new \InvalidArgumentException($message . PHP_EOL . $instructions); + throw new \InvalidArgumentException($message.PHP_EOL.$instructions); } $file->validateSchema(JsonFile::LAX_SCHEMA); @@ -298,7 +304,7 @@ class Factory } // Load config and override with local config/auth config - $config = static::createConfig($io); + $config = static::createConfig($io, $cwd); $config->merge($localConfig); if (isset($composerFile)) { 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'); - $binDir = $config->get('bin-dir'); - - // setup process timeout - ProcessExecutor::setTimeout((int) $config->get('process-timeout')); + $binDir = $config->get('bin-dir'); // initialize composer $composer = new Composer(); $composer->setConfig($config); + if ($fullLoad) { + // load auth configs into the IO instance + $io->loadConfiguration($config); + + // setup process timeout + ProcessExecutor::setTimeout((int) $config->get('process-timeout')); + } + // initialize event dispatcher $dispatcher = new EventDispatcher($composer, $io); + $composer->setEventDispatcher($dispatcher); // initialize repository manager $rm = $this->createRepositoryManager($io, $config, $dispatcher); + $composer->setRepositoryManager($rm); // load local repository $this->addLocalRepository($rm, $vendorDir); // load package - $parser = new VersionParser; + $parser = new VersionParser; $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io)); $package = $loader->load($localConfig); + $composer->setPackage($package); // initialize installation manager $im = $this->createInstallationManager(); - - // Composer composition - $composer->setPackage($package); - $composer->setRepositoryManager($rm); $composer->setInstallationManager($im); - // initialize download manager - $dm = $this->createDownloadManager($io, $config, $dispatcher); - - $composer->setDownloadManager($dm); - $composer->setEventDispatcher($dispatcher); + if ($fullLoad) { + // initialize download manager + $dm = $this->createDownloadManager($io, $config, $dispatcher); + $composer->setDownloadManager($dm); - // initialize autoload generator - $generator = new AutoloadGenerator($dispatcher, $io); - $composer->setAutoloadGenerator($generator); + // initialize autoload generator + $generator = new AutoloadGenerator($dispatcher, $io); + $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); - $globalRepository = $this->createGlobalRepository($config, $vendorDir); - $pm = $this->createPluginManager($composer, $io, $globalRepository); - $composer->setPluginManager($pm); + if ($fullLoad) { + $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins); + $pm = $this->createPluginManager($io, $composer, $globalComposer); + $composer->setPluginManager($pm); - if (!$disablePlugins) { - $pm->loadInstalledPlugins(); - } + if (!$disablePlugins) { + $pm->loadInstalledPlugins(); + } - // purge packages if they have been deleted on the filesystem - $this->purgePackages($rm, $im); + // once we have plugins and custom installers we can + // purge packages from local repos if they have been deleted on the filesystem + if ($rm->getLocalRepository()) { + $this->purgePackages($rm->getLocalRepository(), $im); + } + } // init locker if possible - if (isset($composerFile)) { - $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4) . 'lock' : $composerFile . '.lock'; - $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile)); + if ($fullLoad && isset($composerFile)) { + $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) + ? substr($composerFile, 0, -4).'lock' + : $composerFile . '.lock'; + $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile)); $composer->setLocker($locker); } @@ -411,26 +425,29 @@ class Factory */ 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 string $vendorDir - * @return Repository\InstalledFilesystemRepository|null + * @param Config $config + * @return Composer|null */ - protected function createGlobalRepository(Config $config, $vendorDir) + protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins) { - if ($config->get('home') == $vendorDir) { - return null; + if (realpath($config->get('home')) === getcwd()) { + return; } - $path = $config->get('home') . '/vendor/composer/installed.json'; - if (!file_exists($path)) { - return null; + $composer = null; + try { + $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false); + } catch (\Exception $e) { + if ($io->isDebug()) { + $io->write('Failed to initialize global composer: '.$e->getMessage()); + } } - return new Repository\InstalledFilesystemRepository(new JsonFile($path)); + return $composer; } /** @@ -495,14 +512,14 @@ class Factory } /** - * @param Composer $composer * @param IOInterface $io - * @param RepositoryInterface $globalRepository + * @param Composer $composer + * @param Composer $globalComposer * @return Plugin\PluginManager */ - protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null) + protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null) { - return new Plugin\PluginManager($io, $composer, $globalRepository); + return new Plugin\PluginManager($io, $composer, $globalComposer); } /** @@ -527,12 +544,11 @@ class Factory } /** - * @param Repository\RepositoryManager $rm - * @param Installer\InstallationManager $im + * @param WritableRepositoryInterface $repo repository to purge packages from + * @param Installer\InstallationManager $im manager to check whether packages are still installed */ - protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im) + protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im) { - $repo = $rm->getLocalRepository(); foreach ($repo->getPackages() as $package) { if (!$im->isPackageInstalled($repo, $package)) { $repo->removePackage($package); @@ -553,5 +569,4 @@ class Factory return $factory->createComposer($io, $config, $disablePlugins); } - } diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 07e529666..7df26eabf 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -96,13 +96,11 @@ class ConsoleIO extends BaseIO public function write($messages, $newline = true) { if (null !== $this->startTime) { - $messages = (array) $messages; - $messages[0] = sprintf( - '[%.1fMB/%.2fs] %s', - memory_get_usage() / 1024 / 1024, - microtime(true) - $this->startTime, - $messages[0] - ); + $memoryUsage = memory_get_usage() / 1024 / 1024; + $timeSpent = microtime(true) - $this->startTime; + $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) { + return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + }, (array) $messages); } $this->output->write($messages, $newline); $this->lastMessage = join($newline ? "\n" : '', (array) $messages); @@ -113,6 +111,14 @@ class ConsoleIO extends BaseIO */ public function overwrite($messages, $newline = true, $size = null) { + if (!$this->output->isDecorated()) { + if (!$messages) { + return; + } + + return $this->write($messages, count($messages) === 1 || $newline); + } + // messages can be an array, let's convert it to string anyway $messages = join($newline ? "\n" : '', (array) $messages); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 35df7e61b..3594336e3 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -105,6 +105,7 @@ class Installer protected $dryRun = false; protected $verbose = false; protected $update = false; + protected $dumpAutoloader = true; protected $runScripts = true; protected $ignorePlatformReqs = false; protected $preferStable = false; @@ -317,15 +318,17 @@ class Installer } } - // write autoloader - if ($this->optimizeAutoloader) { - $this->io->write('Generating optimized autoload files'); - } else { - $this->io->write('Generating autoload files'); - } + if ($this->dumpAutoloader) { + // write autoloader + if ($this->optimizeAutoloader) { + $this->io->write('Generating optimized autoload files'); + } else { + $this->io->write('Generating autoload files'); + } - $this->autoloadGenerator->setDevMode($this->devMode); - $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); + $this->autoloadGenerator->setDevMode($this->devMode); + $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); + } if ($this->runScripts) { // dispatch post event @@ -1163,6 +1166,21 @@ class Installer return $this; } + + /** + * set whether to run autoloader or not + * + * @param boolean $dumpAutoloader + * @return Installer + */ + public function setDumpAutoloader($dumpAutoloader = true) + { + $this->dumpAutoloader = (boolean) $dumpAutoloader; + + return $this; + } + + /** * set whether to run scripts or not * diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index b5469ccc6..ddb8e5bdd 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -61,7 +61,7 @@ class PluginInstaller extends LibraryInstaller } 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); - $this->composer->getPluginManager()->registerPackage($target); + $this->composer->getPluginManager()->registerPackage($target, true); } } diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index ceaffaa25..3375329c7 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -154,8 +154,7 @@ class JsonFile if ($schema === self::LAX_SCHEMA) { $schemaData->additionalProperties = true; - $schemaData->properties->name->required = false; - $schemaData->properties->description->required = false; + $schemaData->required = array(); } $validator = new Validator(); diff --git a/src/Composer/Json/JsonValidationException.php b/src/Composer/Json/JsonValidationException.php index 0b2b2ba70..5d21f533c 100644 --- a/src/Composer/Json/JsonValidationException.php +++ b/src/Composer/Json/JsonValidationException.php @@ -21,10 +21,10 @@ class JsonValidationException extends Exception { protected $errors; - public function __construct($message, $errors = array()) + public function __construct($message, $errors = array(), \Exception $previous = null) { $this->errors = $errors; - parent::__construct($message); + parent::__construct($message, 0, $previous); } public function getErrors() diff --git a/src/Composer/Package/LinkConstraint/MultiConstraint.php b/src/Composer/Package/LinkConstraint/MultiConstraint.php index f2eff93ec..3871aeb2f 100644 --- a/src/Composer/Package/LinkConstraint/MultiConstraint.php +++ b/src/Composer/Package/LinkConstraint/MultiConstraint.php @@ -78,6 +78,6 @@ class MultiConstraint implements LinkConstraintInterface $constraints[] = $constraint->__toString(); } - return '['.implode($this->conjunctive ? ', ' : ' | ', $constraints).']'; + return '['.implode($this->conjunctive ? ' ' : ' || ', $constraints).']'; } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 243b7b574..558a24e35 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -224,7 +224,7 @@ class ArrayLoader implements LoaderInterface */ 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']) || !is_array($config['extra']['branch-alias']) ) { @@ -248,6 +248,14 @@ class ArrayLoader implements LoaderInterface 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; } } diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 64880d727..250f2952f 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -131,7 +131,7 @@ class RootPackageLoader extends ArrayLoader $minimumStability = $stabilities[$minimumStability]; foreach ($requires as $reqName => $reqVersion) { // 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); $stability = $stabilities[VersionParser::normalizeStability($match[1])]; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 3493d3d5b..9a6f4dd32 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -251,6 +251,17 @@ class ValidatingArrayLoader implements LoaderInterface if ('-dev' !== substr($validatedTargetBranch, -4)) { $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]); + + 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]); } } } diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 2a2c38371..9707ac017 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -169,6 +169,22 @@ class VersionParser 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(\d+\\.)*\d+)(?:\.x)?-dev$/i', $branch, $matches)) { + return $matches['version']."."; + } + + return false; + } + /** * Normalizes a branch name to be able to perform comparisons on it * diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index d7c2d8657..9e4c12391 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -190,10 +190,11 @@ class PluginManager * instead for BC * * @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 */ - public function registerPackage(PackageInterface $package) + public function registerPackage(PackageInterface $package, $failOnMissingClasses = false) { $oldInstallerPlugin = ($package->getType() === 'composer-installer'); @@ -242,10 +243,12 @@ class PluginManager if ($oldInstallerPlugin) { $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); - } else { + } elseif (class_exists($class)) { $plugin = new $class(); $this->addPlugin($plugin); $this->registeredPlugins[] = $package->getName(); + } elseif ($failOnMissingClasses) { + throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); } } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index d5d8c11ef..ad3c9996b 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -53,8 +53,6 @@ class ComposerRepository extends ArrayRepository protected $eventDispatcher; protected $sourceMirrors; protected $distMirrors; - private $rawData; - private $minimalPackages; private $degradedMode = false; private $rootData; @@ -206,6 +204,11 @@ class ComposerRepository extends ArrayRepository $this->loadProviderListings($this->loadRootServerFile()); } + if ($this->lazyProvidersUrl) { + // Can not determine list of provided packages for lazy repositories + return array(); + } + if ($this->providersUrl) { return array_keys($this->providerListing); } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 5006c4927..6086df1ed 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -160,6 +160,7 @@ class PearRepository extends ArrayRepository $package = new CompletePackage($composerPackageName, $normalizedVersion, $version); $package->setType('pear-library'); $package->setDescription($packageDefinition->getDescription()); + $package->setLicense(array($packageDefinition->getLicense())); $package->setDistType('file'); $package->setDistUrl($distUrl); $package->setAutoload(array('classmap' => array(''))); diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 88a6d5454..68389dc33 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -33,7 +33,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ 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->repository = $match[2]; $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) { - if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) { + if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) { return false; } diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index daa3d81d3..807ab7294 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -202,7 +202,7 @@ class GitDriver extends VcsDriver $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $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]; } } @@ -241,7 +241,11 @@ class GitDriver extends VcsDriver 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; } } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 5e6f71241..cc2b386eb 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -33,7 +33,7 @@ class HgBitbucketDriver extends VcsDriver */ public function initialize() { - preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match); + preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match); $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; @@ -153,7 +153,7 @@ class HgBitbucketDriver extends VcsDriver */ 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; } diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 4a0c3933f..c3c5eb02c 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -169,6 +169,9 @@ class Git if (getenv('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) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index cfb4a946e..455bda92e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -26,7 +26,6 @@ class RemoteFilesystem { private $io; private $config; - private $firstCall; private $bytesMax; private $originUrl; private $fileUrl; @@ -344,7 +343,7 @@ class RemoteFilesystem { 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'); - $gitHubUtil = new GitHub($this->io, $this->config, null, $this); + $gitHubUtil = new GitHub($this->io, $this->config, null); if (!$gitHubUtil->authorizeOAuth($this->originUrl) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message)) ) { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 7c4ddccd9..2ad1e4729 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -67,6 +67,17 @@ class AutoloadGeneratorTest extends TestCase */ 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() { $this->fs = new Filesystem; @@ -79,18 +90,23 @@ class AutoloadGeneratorTest extends TestCase $this->config = $this->getMock('Composer\Config'); - $this->config->expects($this->at(0)) - ->method('get') - ->with($this->equalTo('vendor-dir')) - ->will($this->returnCallback(function () use ($that) { + $this->configValueMap = array( + 'vendor-dir' => function () use ($that) { return $that->vendorDir; - })); + }, + ); - $this->config->expects($this->at(1)) + $this->config->expects($this->atLeastOnce()) ->method('get') - ->with($this->equalTo('vendor-dir')) - ->will($this->returnCallback(function () use ($that) { - return $that->vendorDir; + ->will($this->returnCallback(function ($arg) use ($that) { + $ret = null; + if (isset($that->configValueMap[$arg])) { + $ret = $that->configValueMap[$arg]; + if (is_callable($ret)) { + $ret = $ret(); + } + } + return $ret; })); $this->origDir = getcwd(); @@ -483,6 +499,48 @@ class AutoloadGeneratorTest extends TestCase include $this->vendorDir.'/composer/autoload_classmap.php' ); $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', 'vendorDir.'/b/b/test.php', 'vendorDir.'/c/c/foo/test.php', '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() @@ -829,10 +887,7 @@ EOF; ->method('getCanonicalPackages') ->will($this->returnValue(array())); - $this->config->expects($this->at(2)) - ->method('get') - ->with($this->equalTo('use-include-path')) - ->will($this->returnValue(true)); + $this->configValueMap['use-include-path'] = true; $this->fs->ensureDirectoryExists($this->vendorDir.'/a'); diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 521fb634b..e50af5e87 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -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; } @@ -121,6 +133,35 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $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() { $config = new Config(false); diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php index 3a4313f69..c83ec6296 100644 --- a/tests/Composer/Test/IO/ConsoleIOTest.php +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -49,6 +49,30 @@ class ConsoleIOTest extends TestCase $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() { $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); @@ -58,21 +82,27 @@ class ConsoleIOTest extends TestCase ->method('write') ->with($this->equalTo('something (strlen = 23)')); $outputMock->expects($this->at(1)) + ->method('isDecorated') + ->willReturn(true); + $outputMock->expects($this->at(2)) ->method('write') ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false)); - $outputMock->expects($this->at(2)) + $outputMock->expects($this->at(3)) ->method('write') ->with($this->equalTo('shorter (12)'), $this->equalTo(false)); - $outputMock->expects($this->at(3)) + $outputMock->expects($this->at(4)) ->method('write') ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false)); - $outputMock->expects($this->at(4)) + $outputMock->expects($this->at(5)) ->method('write') ->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') ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false)); - $outputMock->expects($this->at(6)) + $outputMock->expects($this->at(8)) ->method('write') ->with($this->equalTo('something longer than initial (34)')); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 9bf2c01a7..2c77f9bc3 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -241,10 +241,10 @@ class InstallerTest extends TestCase } $installationManager = $composer->getInstallationManager(); - $this->assertSame($expect, implode("\n", $installationManager->getTrace())); + $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace())); if ($expectOutput) { - $this->assertEquals($expectOutput, $output); + $this->assertEquals(rtrim($expectOutput), rtrim($output)); } } @@ -258,21 +258,7 @@ class InstallerTest extends TestCase continue; } - $test = file_get_contents($file->getRealpath()); - - $content = '(?:.(?!--[A-Z]))+'; - $pattern = '{^ - --TEST--\s*(?P.*?)\s* - (?:--CONDITION--\s*(?P'.$content.'))?\s* - --COMPOSER--\s*(?P'.$content.')\s* - (?:--LOCK--\s*(?P'.$content.'))?\s* - (?:--INSTALLED--\s*(?P'.$content.'))?\s* - --RUN--\s*(?P.*?)\s* - (?:--EXPECT-LOCK--\s*(?P'.$content.'))?\s* - (?:--EXPECT-OUTPUT--\s*(?P'.$content.'))?\s* - (?:--EXPECT-EXIT-CODE--\s*(?P\d+))?\s* - --EXPECT--\s*(?P.*?)\s* - $}xs'; + $testData = $this->readTestFile($file, $fixturesDir); $installed = array(); $installedDev = array(); @@ -280,48 +266,44 @@ class InstallerTest extends TestCase $expectLock = array(); $expectExitCode = 0; - if (preg_match($pattern, $test, $match)) { - try { - $message = $match['test']; - $condition = !empty($match['condition']) ? $match['condition'] : null; - $composer = JsonFile::parseJson($match['composer']); + try { + $message = $testData['TEST']; + $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null; + $composer = JsonFile::parseJson($testData['COMPOSER']); - if (isset($composer['repositories'])) { - foreach ($composer['repositories'] as &$repo) { - if ($repo['type'] !== 'composer') { - 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 (isset($composer['repositories'])) { + foreach ($composer['repositories'] as &$repo) { + if ($repo['type'] !== 'composer') { + continue; } - } - if (!empty($match['lock'])) { - $lock = JsonFile::parseJson($match['lock']); - if (!isset($lock['hash'])) { - $lock['hash'] = md5(json_encode($composer)); + // 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['installed'])) { - $installed = JsonFile::parseJson($match['installed']); - } - $run = $match['run']; - if (!empty($match['expectLock'])) { - $expectLock = JsonFile::parseJson($match['expectLock']); + } + + if (!empty($testData['LOCK'])) { + $lock = JsonFile::parseJson($testData['LOCK']); + if (!isset($lock['hash'])) { + $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 { - die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file))); + if (!empty($testData['INSTALLED'])) { + $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); @@ -329,4 +311,59 @@ class InstallerTest extends TestCase 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; + } } diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index ccbda8040..52c3fbf2e 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -22,9 +22,9 @@ use Composer\IO\IOInterface; 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' => array('home' => sys_get_temp_dir().'/composer-test'), diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 6e4e2f5ee..1491571a1 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -138,6 +138,50 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('Composer\Package\AliasPackage', $package); $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() diff --git a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php index 51799d053..d1f3be1f8 100644 --- a/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php @@ -143,6 +143,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase 'foo/bar' => '~2.1.0-beta2', 'bar/baz' => '1.0.x-dev as 1.2.0', 'qux/quux' => '1.0.*@rc', + 'zux/complex' => '~1.0,>=1.0.2@dev' ), 'minimum-stability' => 'alpha', )); @@ -151,6 +152,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array( 'bar/baz' => BasePackage::STABILITY_DEV, 'qux/quux' => BasePackage::STABILITY_RC, + 'zux/complex' => BasePackage::STABILITY_DEV, ), $package->getStabilityFlags()); } } diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 23c47c3c7..32c55bd40 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -140,6 +140,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase 'branch-alias' => array( 'dev-master' => '2.0-dev', 'dev-old' => '1.0.x-dev', + '3.x-dev' => '3.1.x-dev' ), ), 'bin' => array( @@ -324,6 +325,34 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase ), 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 + ), ); } } diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 88058f6aa..13920e2f0 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -67,6 +67,29 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase 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 */ diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index 90f820e8f..6feb207a7 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -113,8 +113,12 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase array('3.1.2-dev', true, 'dev', '3.1.2-dev'), // 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-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'), + // 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'), ); } diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php index 7d64a0141..66c02acdc 100644 --- a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php @@ -19,6 +19,14 @@ use Composer\Package\BasePackage; 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() { $expectedPackages = array( diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php index 214d7b702..c22250a04 100644 --- a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -121,6 +121,7 @@ class ChannelReaderTest extends TestCase $expectedPackage->setType('pear-library'); $expectedPackage->setDistType('file'); $expectedPackage->setDescription('description'); + $expectedPackage->setLicense(array('license')); $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz"); $expectedPackage->setAutoload(array('classmap' => array(''))); $expectedPackage->setIncludePaths(array('/')); diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index a42c8e0b3..7eaad13e7 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -84,13 +84,6 @@ class PearRepositoryTest extends TestCase public function repositoryDataProvider() { 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( 'pear.php.net', array(