diff --git a/.travis.yml b/.travis.yml index 56d4b922e..9bc4e43ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,6 @@ php: before_script: - wget -nc http://getcomposer.org/composer.phar - - php composer.phar update + - php composer.phar install script: phpunit diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f21b61b..d56b1f2ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,35 @@ -* 1.0.0-alpha3 - - * Schema: Added 'require-dev' for development-time requirements (tests, etc), install with --dev - * Schema: Removed 'recommend' - * Schema: 'suggest' is now informational and can use any description for a package, not only a constraint - * Added caching of repository metadata (faster startup times & failover if packagist is down) - * Added include_path support for legacy projects that are full of require_once statements - * Added installation notifications API to allow better statistics on Composer repositories - * Improved repository protocol to have large cacheable parts - -* 1.0.0-alpha2 (2012-04-03) - - * Added `create-project` command to install a project from scratch with composer - * Added automated `classmap` autoloading support for non-PSR-0 compliant projects - * Added human readable error reporting when deps can not be solved - * Added support for private GitHub and SVN repositories (use --no-interaction for CI) - * Added "file" downloader type to download plain files - * Added support for authentication with svn repositories - * Added autoload support for PEAR repositories - * Improved clones from GitHub which now automatically select between git/https/http protocols - * Improved `validate` command to give more feedback - * Improved the `search` & `show` commands output - * Removed dependency on filter_var - * Various robustness & error handling improvements, docs fixes and more bug fixes - -* 1.0.0-alpha1 (2012-03-01) - - * Initial release +* 1.0.0-alpha3 + + * Schema: Added 'require-dev' for development-time requirements (tests, etc), install with --dev + * Schema: Removed 'recommend' + * Schema: 'suggest' is now informational and can use any description for a package, not only a constraint + * Break: vendor/.composer/autoload.php has been moved to vendor/autoload.php, other files are now in vendor/composer/ + * Added caching of repository metadata (faster startup times & failover if packagist is down) + * Added removal of packages that are not needed anymore + * Added include_path support for legacy projects that are full of require_once statements + * Added installation notifications API to allow better statistics on Composer repositories + * Added autoloading support for root packages that use target-dir + * Added awareness of the root package presence and support for it's provide/replace/conflict keys + * Added IOInterface::isDecorated to test for colored output support + * Improved repository protocol to have large cacheable parts + * Fixed various bugs relating to package aliasing, proxy configuration, binaries + * Various bug fixes and docs improvements + +* 1.0.0-alpha2 (2012-04-03) + + * Added `create-project` command to install a project from scratch with composer + * Added automated `classmap` autoloading support for non-PSR-0 compliant projects + * Added human readable error reporting when deps can not be solved + * Added support for private GitHub and SVN repositories (use --no-interaction for CI) + * Added "file" downloader type to download plain files + * Added support for authentication with svn repositories + * Added autoload support for PEAR repositories + * Improved clones from GitHub which now automatically select between git/https/http protocols + * Improved `validate` command to give more feedback + * Improved the `search` & `show` commands output + * Removed dependency on filter_var + * Various robustness & error handling improvements, docs fixes and more bug fixes + +* 1.0.0-alpha1 (2012-03-01) + + * Initial release diff --git a/composer.lock b/composer.lock index 78eb49f0b..bb9093141 100644 --- a/composer.lock +++ b/composer.lock @@ -1,5 +1,5 @@ { - "hash": "c4a3809551d45254dd0b45eb282d6285", + "hash": "1350b672ef2fc5723334092cb18c3bca", "packages": [ { "package": "justinrainbow/json-schema", @@ -12,19 +12,19 @@ { "package": "symfony/console", "version": "dev-master", - "source-reference": "8e3c42aa976f18a9bfcb0694553e5f99def3309c", + "source-reference": "eaad4427b10ff39402bce0ae4f8cd1faf2b6532a", "alias": "2.1.9999999.9999999-dev" }, { "package": "symfony/finder", "version": "dev-master", - "source-reference": "57ec7198a70e6c40e450ba66cc2f8ecab98746c8", + "source-reference": "78b2e33951821b6d423718f57788f1894dcb935a", "alias": "2.1.9999999.9999999-dev" }, { "package": "symfony/process", "version": "dev-master", - "source-reference": "2e4da8c8076744bafed97451bb1574c96cda0e68", + "source-reference": "718655f4bc664d693b33f3e6e8a895e454208021", "alias": "2.1.9999999.9999999-dev" } ], diff --git a/doc/00-intro.md b/doc/00-intro.md index 2437b7437..91ee3e66b 100644 --- a/doc/00-intro.md +++ b/doc/00-intro.md @@ -80,7 +80,7 @@ capable of autoloading all of the classes in any of the libraries that it downloads. To use it, just add the following line to your code's bootstrap process: - require 'vendor/.composer/autoload.php'; + require 'vendor/autoload.php'; Woh! Now start using monolog! To keep learning more about Composer, keep reading the "Basic Usage" chapter. diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 27f4d2972..32ce307ec 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -108,7 +108,7 @@ same version of the dependencies. If no `composer.json` lock file exists, it will read the dependencies and versions from `composer.json` and create the lock file. -This means that if any of the dependencies get a new version, you won't get the updates. +This means that if any of the dependencies get a new version, you won't get the updates automatically. To update to the new version, use `update` command. This will fetch the latest matching versions (according to your `composer.json` file) and also update the lock file with the new version. @@ -136,10 +136,10 @@ but it makes life quite a bit simpler. ## Autoloading For libraries that specify autoload information, Composer generates a -`vendor/.composer/autoload.php` file. You can simply include this file and you +`vendor/autoload.php` file. You can simply include this file and you will get autoloading for free. - require 'vendor/.composer/autoload.php'; + require 'vendor/autoload.php'; This makes it really easy to use third party code. For example: If your project depends on monolog, you can just start using classes from it, and they @@ -168,13 +168,13 @@ be in your project root. An example filename would be `src/Acme/Foo.php` containing an `Acme\Foo` class. After adding the `autoload` field, you have to re-run `install` to re-generate -the `vendor/.composer/autoload.php` file. +the `vendor/autoload.php` file. Including that file will also return the autoloader instance, so you can store the return value of the include call in a variable and add more namespaces. This can be useful for autoloading classes in a test suite, for example. - $loader = require 'vendor/.composer/autoload.php'; + $loader = require 'vendor/autoload.php'; $loader->add('Acme\Test', __DIR__); In addition to PSR-0 autoloading, classmap is also supported. This allows @@ -182,7 +182,7 @@ classes to be autoloaded even if they do not conform to PSR-0. See the [autoload reference](04-schema.md#autoload) for more details. > **Note:** Composer provides its own autoloader. If you don't want to use -that one, you can just include `vendor/.composer/autoload_namespaces.php`, +that one, you can just include `vendor/autoload_namespaces.php`, which returns an associative array mapping namespaces to directories. ← [Intro](00-intro.md) | [Libraries](02-libraries.md) → diff --git a/doc/02-libraries.md b/doc/02-libraries.md index 990246537..81d665fda 100644 --- a/doc/02-libraries.md +++ b/doc/02-libraries.md @@ -76,6 +76,14 @@ Here are some examples of version branch names: > **Note:** When you install a dev version, it will install it from source. See [Repositories](05-repositories.md) for more information. +### Aliases + +It is possible alias branch names to versions. For example, you could alias +`dev-master` to `1.0-dev`, which would allow you to require `1.0-dev` in all +the packages. + +See [Aliases](articles/aliases.md) for more information. + ## Lock file For your library you may commit the `composer.lock` file if you want to. This diff --git a/doc/03-cli.md b/doc/03-cli.md index 4648aa42a..94d5b001a 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -199,11 +199,6 @@ directory other than `vendor`. By setting this option you can change the `bin` ([Vendor Bins](articles/vendor-bins.md)) directory to something other than `vendor/bin`. -### COMPOSER_PROCESS_TIMEOUT - -This env var controls the time composer waits for commands (such as git -commands) to finish executing. The default value is 60 seconds. - ### http_proxy or HTTP_PROXY If you are using composer from behind an HTTP proxy, you can use the standard @@ -215,4 +210,19 @@ some tools like git or curl will only use the lower-cased `http_proxy` version. Alternatively you can also define the git proxy using `git config --global http.proxy `. +### COMPOSER_HOME + +The `COMPOSER_HOME` var allows you to change the composer home directory. This +is a hidden, global (per-user on the machine) directory that is shared between +all projects. + +By default it points to `/home//.composer` on *nix, +`/Users//.composer` on OSX and +`C:\Users\\AppData\Roaming\Composer` on Windows. + +### COMPOSER_PROCESS_TIMEOUT + +This env var controls the time composer waits for commands (such as git +commands) to finish executing. The default value is 300 seconds (5 minutes). + ← [Libraries](02-libraries.md) | [Schema](04-schema.md) → diff --git a/doc/04-schema.md b/doc/04-schema.md index 2b7aef879..fe91cf4ed 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -145,6 +145,7 @@ Each author object can have following properties: * **name:** The author's name. Usually his real name. * **email:** The author's email address. * **homepage:** An URL to the author's website. +* **role:** The authors' role in the project (e.g. developer or translator) An example: @@ -153,12 +154,14 @@ An example: { "name": "Nils Adermann", "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" + "homepage": "http://www.naderman.de", + "role": "Developer" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "http://seld.be", + "role": "Developer" } ] } @@ -215,21 +218,26 @@ Example: Autoload mapping for a PHP autoloader. Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) -autoloading and classmap generation are supported. +autoloading and classmap generation are supported. PSR-0 is the recommended way though +since it offers greater flexibility (no need to regenerate the autoloader when you add +classes). Under the `psr-0` key you define a mapping from namespaces to paths, relative to the -package root. +package root. Note that this also supports the PEAR-style convention. Example: { "autoload": { - "psr-0": { "Monolog": "src/" } + "psr-0": { + "Monolog": "src/", + "Vendor\\Namespace": "src/", + "Pear_Style": "src/" + } } } -Optional, but it is highly recommended that you follow PSR-0 and use this. -If you need to search for a same namespace prefix in multiple directories, +If you need to search for a same prefix in multiple directories, you can specify them as an array as such: { @@ -238,15 +246,24 @@ you can specify them as an array as such: } } +If you want to have a fallback directory where any namespace can be, you can +use an empty prefix like: + + { + "autoload": { + "psr-0": { "": "src/" } + } + } + You can use the classmap generation support to define autoloading for all libraries -that do not follow PSR-0. To configure this you specify all directories +that do not follow PSR-0. To configure this you specify all directories or files to search for classes. Example: { "autoload: { - "classmap": ["src/", "lib/"] + "classmap": ["src/", "lib/", "Something.php"] } } @@ -368,6 +385,9 @@ The following options are supported: * **process-timeout:** Defaults to `300`. The duration processes like git clones can run before Composer assumes they died out. You may need to make this higher if you have a slow connection or huge vendors. +* **notify-on-install:** Defaults to `true`. Composer allows repositories to + define a notification URL, so that they get notified whenever a package from + that repository is installed. This option allows you to disable that behaviour. Example: diff --git a/doc/05-repositories.md b/doc/05-repositories.md index ba1272d21..894e35230 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -54,15 +54,24 @@ want to learn why. ### Composer The main repository type is the `composer` repository. It uses a single -`packages.json` file that contains all of the package metadata. The JSON -format is as follows: +`packages.json` file that contains all of the package metadata. + +This is also the repository type that packagist uses. To reference a +`composer` repository, just supply the path before the `packages.json` file. +In case of packagist, that file is located at `/packages.json`, so the URL of +the repository would be `packagist.org`. For `example.org/packages.json` the +repository URL would be `example.org`. + +#### packages + +The only required field is `packages`. The JSON structure is as follows: { - "vendor/packageName": { - "name": "vendor/packageName", - "description": "Package description", - "versions": { + "packages": { + "vendor/packageName": { "master-dev": { @composer.json }, + "1.0.x-dev": { @composer.json }, + "0.0.1": { @composer.json }, "1.0.0": { @composer.json } } } @@ -88,12 +97,54 @@ Here is a minimal package definition: It may include any of the other fields specified in the [schema](04-schema.md). -The `composer` repository is also what packagist uses. To reference a -`composer` repository, just supply the path before the `packages.json` file. -In case of packagist, that file is located at `/packages.json`, so the URL of -the repository would be `http://packagist.org`. For -`http://example.org/packages.json` the repository URL would be -`http://example.org`. +#### notify + +The `notify` field allows you to specify an URL template for a URL that will +be called every time a user installs a package. + +An example value: + + { + "notify": "/downloads/%package%" + } + +For `example.org/packages.json` containing a `monolog/monolog` package, this +would send a `POST` request to `example.org/downloads/monolog/monolog` with +following parameters: + +* **version:** The version of the package. +* **version_normalized:** The normalized internal representation of the + version. + +This field is optional. + +#### includes + +For large repositories it is possible to split the `packages.json` into +multiple files. The `includes` field allows you to reference these additional +files. + +An example: + + { + "includes": { + "packages-2011.json": { + "sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17" + }, + "packages-2012-01.json": { + "sha1": "897cde726f8a3918faf27c803b336da223d400dd" + }, + "packages-2012-02.json": { + "sha1": "26f911ad717da26bbcac3f8f435280d13917efa5" + } + } + } + +The SHA-1 sum of the file allows it to be cached and only re-requested if the +hash changed. + +This field is optional. You probably don't need it for your own custom +repository. ### VCS diff --git a/doc/articles/aliases.md b/doc/articles/aliases.md new file mode 100644 index 000000000..38e0e65ee --- /dev/null +++ b/doc/articles/aliases.md @@ -0,0 +1,90 @@ + +# Aliases + +## Why aliases? + +When you are using a VCS repository, you will only get comparable versions for +branches that look like versions, such as `2.0`. For your `master` branch, you +will get a `dev-master` version. For your `bugfix` branch, you will get a +`dev-bugfix` version. + +If your `master` branch is used to tag releases of the `1.0` development line, +i.e. `1.0.1`, `1.0.2`, `1.0.3`, etc., any package depending on it will +probably require version `1.0.*`. + +If anyone wants to require the latest `dev-master`, they have a problem: Other +packages may require `1.0.*`, so requiring that dev version will lead to +conflicts, since `dev-master` does not match the `1.0.*` constraint. + +Enter aliases. + +## Branch alias + +The `dev-master` branch is one in your main VCS repo. It is rather common that +someone will want the latest master dev version. Thus, Composer allows you to +alias your `dev-master` branch to a `1.0.x-dev` version. It is done by +specifying a `branch-alias` field under `extra` in `composer.json`: + + { + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } + } + +The branch version must begin with `dev-` (non-comparable version), the alias +must be a comparable dev version. 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, you can now require `1.0.*` and it will happily install +`dev-master` for you. + +## Require inline alias + +Branch aliases are great for aliasing main development lines. But in order to +use them you need to have control over the source repository, and you need to +commit changes to version control. + +This is not really fun when you just want to try a bugfix of some library that +is a dependency of your local project. + +For this reason, you can alias packages in your `require` and `require-dev` +fields. Let's say you found a bug in the `monolog/monolog` package. You cloned +Monolog on GitHub and fixed the issue in a branch named `bugfix`. Now you want +to install that version of monolog in your local project. + +You are using `symfony/monolog-bundle` which requires `monolog/monolog` version +`1.*`. So you need your `dev-bugfix` to match that constraint. + +Just add this to your project's root `composer.json`: + + { + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/you/monolog" + } + ], + "require": { + "symfony/monolog-bundle": "2.0", + "monolog/monolog": "dev-bugfix as 1.0.x-dev" + } + } + +That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub +and alias it to `1.0.x-dev`. + +> **Note:** If a package with inline aliases is required, the alias (right of +> the `as`) is used as the version constraint. The part left of the `as` is +> discarded. As a consequence, if A requires B and B requires `monolog/monolog` +> version `dev-bugfix as 1.0.x-dev`, installing A will make B require +> `1.0.x-dev`, which may exist as a branch alias or an actual `1.0` branch. If +> it does not, it must be re-inline-aliased in A's `composer.json`. + +> **Note:** Inline aliasing should be avoided, especially for published +> packages. If you found a bug, try and get your fix merged upstream. This +> helps to avoid issues for users of your package. diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index a91123f90..d615e0178 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -5,7 +5,8 @@ Satis can be used to host the metadata of your company's private packages, or your own. It basically acts as a micro-packagist. You can get it from -[GitHub](http://github.com/composer/satis). +[GitHub](http://github.com/composer/satis) or install via CLI: +`composer.phar create-project composer/satis`. ## Setup @@ -13,12 +14,29 @@ For example let's assume you have a few packages you want to reuse across your company but don't really want to open-source. You would first define a Satis configuration file, which is basically a stripped-down version of a `composer.json` file. It contains a few repositories, and then you use the require -key to say which packages it should dump in the static repository it creates. +key to say which packages it should dump in the static repository it creates, or +use require-all to select all of them. Here is an example configuration, you see that it holds a few VCS repositories, -but those could be any types of [repositories](../05-repositories.md). Then -the require just lists all the packages we need, using a `"*"` constraint to -make sure all versions are selected. +but those could be any types of [repositories](../05-repositories.md). Then it +uses `"require-all": true` which selects all versions of all packages in the +repositories you defined. + + { + "name": "My Repository", + "homepage": "http://packages.example.org", + "repositories": [ + { "type": "vcs", "url": "http://github.com/mycompany/privaterepo" }, + { "type": "vcs", "url": "http://svn.example.org/private/repo" }, + { "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" } + ], + "require-all": true + } + +If you want to cherry pick which packages you want, you can list all the packages +you want to have in your satis repository inside the classic composer `require` key, +using a `"*"` constraint to make sure all versions are selected, or another +constraint if you want really specific versions. { "repositories": [ @@ -29,7 +47,7 @@ make sure all versions are selected. "require": { "company/package": "*", "company/package2": "*", - "company/package3": "*" + "company/package3": "2.0.0" } } diff --git a/res/composer-schema.json b/res/composer-schema.json index c11ed953e..f1f7ed1f9 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -66,6 +66,10 @@ "type": "string", "description": "Homepage URL for the author.", "format": "uri" + }, + "role": { + "type": "string", + "description": "Author's role in the project." } } } diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 422f5ec45..3d8c7b752 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -25,25 +25,26 @@ use Composer\Util\Filesystem; */ class AutoloadGenerator { - public function dump(RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir) + public function dump(RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $bcLinks = false) { $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($installationManager->getVendorPath()); $filesystem->ensureDirectoryExists($targetDir); $vendorPath = strtr(realpath($installationManager->getVendorPath()), '\\', '/'); $relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath, true); - $vendorDirCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); + $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); + $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true); - $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true); - $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir); + $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true); + $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); $namespacesFile = << $paths) { $exportedPaths = array(); foreach ($paths as $path) { - $path = strtr($path, '\\', '/'); - $baseDir = ''; - if (!$filesystem->isAbsolutePath($path)) { - if (strpos($path, $relVendorPath) === 0) { - // path starts with vendor dir - $path = substr($path, strlen($relVendorPath)); - $baseDir = '$vendorDir . '; - } else { - $path = '/'.$path; - $baseDir = '$baseDir . '; - } - } elseif (strpos($path, $vendorPath) === 0) { - $path = substr($path, strlen($vendorPath)); - $baseDir = '$vendorDir . '; - } - $exportedPaths[] = $baseDir.var_export($path, true); + $exportedPaths[] = $this->getPathCode($filesystem, $relVendorPath, $vendorPath, $path); } $exportedPrefix = var_export($namespace, true); $namespacesFile .= " $exportedPrefix => "; @@ -87,13 +73,44 @@ EOF; // autoload_classmap.php generated by Composer -\$vendorDir = $vendorDirCode; -\$baseDir = $appBaseDir; +\$vendorDir = $vendorPathCode; +\$baseDir = $appBaseDirCode; return array( EOF; + // add custom psr-0 autoloading if the root package has a target dir + $targetDirLoader = null; + $mainAutoload = $mainPackage->getAutoload(); + if ($mainPackage->getTargetDir() && $mainAutoload['psr-0']) { + $levels = count(explode('/', trim(strtr($mainPackage->getTargetDir(), '\\', '/'), '/'))); + $prefixes = implode(', ', array_map(function ($prefix) { + return var_export($prefix, true); + }, array_keys($mainAutoload['psr-0']))); + $baseDirFromVendorDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true); + + $targetDirLoader = <<getIncludePathsFile($packageMap)) { + if ($includePathFile = $this->getIncludePathsFile($packageMap, $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { file_put_contents($targetDir.'/include_paths.php', $includePathFile); } - file_put_contents($targetDir.'/autoload.php', $this->getAutoloadFile(true, true, (Boolean) $includePathFile)); + file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, true, true, (Boolean) $includePathFile, $targetDirLoader)); copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); + + // TODO BC feature, add E_DEPRECATED in autoload.php on April 30th, remove after May 30th + if ($bcLinks) { + $filesystem->ensureDirectoryExists($vendorPath.'/.composer'); + file_put_contents($vendorPath.'/.composer/autoload_namespaces.php', "getAutoload() as $type => $mapping) { + // skip misconfigured packages + if (!is_array($mapping)) { + continue; + } foreach ($mapping as $namespace => $paths) { foreach ((array) $paths as $path) { $autoloads[$type][$namespace][] = empty($installPath) ? $path : $installPath.'/'.$path; @@ -182,7 +215,7 @@ EOF; return $loader; } - protected function getIncludePathsFile(array $packageMap) + protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode) { $includePaths = array(); @@ -194,6 +227,7 @@ EOF; } foreach ($package->getIncludePaths() as $includePath) { + $includePath = trim($includePath, '/'); $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath; } } @@ -202,30 +236,65 @@ EOF; return; } - return sprintf( - "getPathCode($filesystem, $relVendorPath, $vendorPath, $path) . ",\n"; + } + + return $includePathsFile . ");\n"; } - protected function getAutoloadFile($usePSR0, $useClassMap, $useIncludePath) + protected function getPathCode(Filesystem $filesystem, $relVendorPath, $vendorPath, $path) { - $file = <<<'HEADER' + $path = strtr($path, '\\', '/'); + $baseDir = ''; + if (!$filesystem->isAbsolutePath($path)) { + if (strpos($path, $relVendorPath) === 0) { + // path starts with vendor dir + $path = substr($path, strlen($relVendorPath)); + $baseDir = '$vendorDir . '; + } else { + $path = '/'.$path; + $baseDir = '$baseDir . '; + } + } elseif (strpos($path, $vendorPath) === 0) { + $path = substr($path, strlen($vendorPath)); + $baseDir = '$vendorDir . '; + } + return $baseDir.var_export($path, true); + } + + protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useClassMap, $useIncludePath, $targetDirLoader) + { + $file = <<
$path) { $loader->add($namespace, $path); } @@ -246,7 +315,7 @@ PSR0; if ($useClassMap) { $file .= <<<'CLASSMAP' - $classMap = require __DIR__.'/autoload_classmap.php'; + $classMap = require $composerDir . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } @@ -255,6 +324,8 @@ PSR0; CLASSMAP; } + $file .= $targetDirLoader; + return $file . <<<'FOOTER' $loader->register(); diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 3dff39eb8..cf45d0ab9 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -18,6 +18,7 @@ use Composer\Installer\ProjectInstaller; use Composer\IO\IOInterface; use Composer\Repository\ComposerRepository; use Composer\Repository\FilesystemRepository; +use Composer\Repository\InstalledFilesystemRepository; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -82,12 +83,13 @@ EOT $dm->setPreferSource(true); } + $config = Factory::createConfig(); if (null === $repositoryUrl) { - $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $io); + $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $io, $config); } elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) { $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io))); } elseif (0 === strpos($repositoryUrl, 'http')) { - $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io); + $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config); } else { throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url."); } @@ -112,7 +114,7 @@ EOT $io->write('Installing ' . $package->getName() . ' as new project.', true); $projectInstaller = new ProjectInstaller($directory, $dm); - $projectInstaller->install($package); + $projectInstaller->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), $package); $io->write('Created project into directory ' . $directory . '', true); chdir($directory); diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index e72f01e73..02902adf7 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Json\JsonFile; +use Composer\Factory; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\ComposerRepository; @@ -34,7 +35,7 @@ class InitCommand extends Command public function parseAuthorString($author) { if (preg_match('/^(?P[- \.,\w\'’]+) <(?P.+?)>$/u', $author, $match)) { - if (!function_exists('filter_var') || $match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) { + if (!function_exists('filter_var') || version_compare(PHP_VERSION, '5.3.3', '<') || $match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) { return array( 'name' => trim($match['name']), 'email' => $match['email'] @@ -229,7 +230,7 @@ EOT if (!$this->repos) { $this->repos = new CompositeRepository(array( new PlatformRepository, - new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()) + new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig()) )); } diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 2a25dc66c..659d70904 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -20,6 +20,7 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\ComposerRepository; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; +use Composer\Factory; /** * @author Robert Schönthal @@ -54,7 +55,7 @@ EOT } else { $output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $installedRepo = $platformRepo; - $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()); + $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig()); $repos = new CompositeRepository(array($installedRepo, $packagist)); } diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index f741beeca..16f92babd 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -53,7 +53,7 @@ EOT $rfs->copy('getcomposer.org', $remoteFilename, $tempFilename); try { - chmod($tempFilename, 0755); + chmod($tempFilename, 0777 & ~umask()); // test the phar validity $phar = new \Phar($tempFilename); // free the variable to unlock the file diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 167d34b30..f9f211e46 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Composer; +use Composer\Factory; use Composer\Package\PackageInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -65,7 +66,7 @@ EOT } else { $output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); $installedRepo = $platformRepo; - $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO()); + $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig()); $repos = new CompositeRepository(array($installedRepo, $packagist)); } @@ -78,7 +79,15 @@ EOT $this->printMeta($input, $output, $package, $installedRepo, $repos); $this->printLinks($input, $output, $package, 'requires'); - $this->printLinks($input, $output, $package, 'recommends'); + $this->printLinks($input, $output, $package, 'devRequires', 'requires (dev)'); + if ($package->getSuggests()) { + $output->writeln("\nsuggests"); + foreach ($package->getSuggests() as $suggested => $reason) { + $output->writeln($suggested . ' ' . $reason . ''); + } + } + $this->printLinks($input, $output, $package, 'provides'); + $this->printLinks($input, $output, $package, 'conflicts'); $this->printLinks($input, $output, $package, 'replaces'); return; } @@ -209,10 +218,11 @@ EOT * * @param string $linkType */ - protected function printLinks(InputInterface $input, OutputInterface $output, PackageInterface $package, $linkType) + protected function printLinks(InputInterface $input, OutputInterface $output, PackageInterface $package, $linkType, $title = null) { + $title = $title ?: $linkType; if ($links = $package->{'get'.ucfirst($linkType)}()) { - $output->writeln("\n" . $linkType . ""); + $output->writeln("\n" . $title . ""); foreach ($links as $link) { $output->writeln($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index 3271b66e9..fabd43450 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -81,10 +81,10 @@ class Compiler $this->addFile($phar, $file); } - $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/ClassLoader.php')); - $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload.php')); - $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_namespaces.php')); - $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_classmap.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php')); + $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php')); $this->addComposerBin($phar); // Stubs diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index bcdd1817d..ae7518618 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -67,6 +67,10 @@ class Application extends BaseApplication $this->registerCommands(); $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); + if (version_compare(PHP_VERSION, '5.3.2', '<')) { + $output->writeln('Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.'); + } + return parent::doRun($input, $output); } diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 3659b0a51..ac3d38995 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -69,6 +69,8 @@ class DefaultPolicy implements PolicyInterface $literals = $this->pruneToBestVersion($literals); $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals); + + $literals = $this->pruneRemoteAliases($literals); } $selected = call_user_func_array('array_merge', $packages); @@ -210,8 +212,8 @@ class DefaultPolicy implements PolicyInterface } /** - * Assumes that installed packages come first and then all highest priority packages - */ + * Assumes that installed packages come first and then all highest priority packages + */ protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals) { $selected = array(); @@ -239,4 +241,38 @@ class DefaultPolicy implements PolicyInterface return $selected; } + + /** + * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones + * + * If no package is a local alias, nothing happens + */ + protected function pruneRemoteAliases(array $literals) + { + $hasLocalAlias = false; + + foreach ($literals as $literal) { + $package = $literal->getPackage(); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $hasLocalAlias = true; + break; + } + } + + if (!$hasLocalAlias) { + return $literals; + } + + $selected = array(); + foreach ($literals as $literal) { + $package = $literal->getPackage(); + + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $selected[] = $literal; + } + } + + return $selected; + } } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 6d939f063..aa014035c 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -1615,7 +1615,7 @@ class Solver $foundDisabled = false; foreach ($problemRules as $problemRule) { - if ($problemRule->disabled()) { + if ($problemRule->isDisabled()) { $foundDisabled = true; break; } diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index c2082350a..87d247568 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -24,6 +24,7 @@ use Composer\Util\Filesystem; class DownloadManager { private $preferSource = false; + private $filesystem; private $downloaders = array(); /** @@ -31,9 +32,10 @@ class DownloadManager * * @param Boolean $preferSource prefer downloading from source */ - public function __construct($preferSource = false) + public function __construct($preferSource = false, Filesystem $filesystem = null) { $this->preferSource = $preferSource; + $this->filesystem = $filesystem ?: new Filesystem(); } /** @@ -135,8 +137,7 @@ class DownloadManager throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); } - $fs = new Filesystem(); - $fs->ensureDirectoryExists($targetDir); + $this->filesystem->ensureDirectoryExists($targetDir); $downloader = $this->getDownloaderForInstalledPackage($package); $downloader->download($package, $targetDir); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 6f2434a27..6af142809 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -26,7 +26,7 @@ class GitDownloader extends VcsDownloader public function doDownload(PackageInterface $package, $path) { $ref = $package->getSourceReference(); - $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s'; + $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s && git remote add composer %1$s'; $this->io->write(" Cloning ".$package->getSourceReference()); $commandCallable = function($url) use ($ref, $path, $command) { @@ -44,14 +44,16 @@ class GitDownloader extends VcsDownloader { $ref = $target->getSourceReference(); $this->io->write(" Checking out ".$target->getSourceReference()); - $command = 'cd %s && git remote set-url origin %s && git fetch origin && git fetch --tags origin && git checkout %3$s && git reset --hard %3$s'; + $command = 'cd %s && git remote set-url composer %s && git fetch composer && git fetch --tags composer && git checkout %3$s && git reset --hard %3$s'; + + // TODO: BC for the composer remote that didn't exist, to be remove after May 18th. + $this->process->execute(sprintf('cd %s && git remote add composer %s', escapeshellarg($path), escapeshellarg($initial->getSourceUrl())), $ignoredOutput); $commandCallable = function($url) use ($ref, $path, $command) { return sprintf($command, escapeshellarg($path), escapeshellarg($url), escapeshellarg($ref)); }; $this->runCommand($commandCallable, $target->getSourceUrl()); - $this->setPushUrl($target, $path); } /** @@ -59,7 +61,7 @@ class GitDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { - $command = sprintf('cd %s && git status --porcelain', escapeshellarg($path)); + $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path)); if (0 !== $this->process->execute($command, $output)) { throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()); } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 9f32ec27c..4cad60d97 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -38,6 +38,14 @@ class Factory } } + // Protect directory against web access + if (!file_exists($home . '/.htaccess')) { + if (!is_dir($home)) { + @mkdir($home, 0777, true); + } + @file_put_contents($home . '/.htaccess', 'Deny from all'); + } + $config = new Config(); $file = new JsonFile($home.'/config.json'); @@ -84,7 +92,7 @@ class Factory } // Configuration defaults - $config = $this->createConfig(); + $config = static::createConfig(); $config->merge($localConfig); $vendorDir = $config->get('vendor-dir'); @@ -151,8 +159,25 @@ class Factory protected function addLocalRepository(RepositoryManager $rm, $vendorDir) { - $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); - $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed_dev.json'))); + // TODO BC feature, remove after May 30th + if (file_exists($vendorDir.'/.composer/installed.json')) { + if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); } + rename($vendorDir.'/.composer/installed.json', $vendorDir.'/composer/installed.json'); + } + if (file_exists($vendorDir.'/.composer/installed_dev.json')) { + if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); } + rename($vendorDir.'/.composer/installed_dev.json', $vendorDir.'/composer/installed_dev.json'); + } + if (file_exists($vendorDir.'/installed.json')) { + if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); } + rename($vendorDir.'/installed.json', $vendorDir.'/composer/installed.json'); + } + if (file_exists($vendorDir.'/installed_dev.json')) { + if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); } + rename($vendorDir.'/installed_dev.json', $vendorDir.'/composer/installed_dev.json'); + } + $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json'))); + $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed_dev.json'))); } protected function addPackagistRepository(array $localConfig) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index d11ed44d1..95aed6a29 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -55,6 +55,14 @@ class ConsoleIO implements IOInterface return $this->input->isInteractive(); } + /** + * {@inheritDoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 5b540892a..583800424 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -33,6 +33,13 @@ interface IOInterface */ function isVerbose(); + /** + * Is this output decorated? + * + * @return Boolean + */ + function isDecorated(); + /** * Writes a message to the output. * diff --git a/src/Composer/IO/NullIO.php b/src/Composer/IO/NullIO.php index b57e42087..78688f6d5 100644 --- a/src/Composer/IO/NullIO.php +++ b/src/Composer/IO/NullIO.php @@ -35,6 +35,14 @@ class NullIO implements IOInterface return false; } + /** + * {@inheritDoc} + */ + public function isDecorated() + { + return false; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 1816e7ee2..f27f687b8 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -27,6 +27,7 @@ use Composer\Package\Link; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\Locker; use Composer\Package\PackageInterface; +use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; @@ -76,6 +77,11 @@ class Installer */ protected $eventDispatcher; + /** + * @var AutoloadGenerator + */ + protected $autoloadGenerator; + protected $preferSource = false; protected $devMode = false; protected $dryRun = false; @@ -102,8 +108,9 @@ class Installer * @param Locker $locker * @param InstallationManager $installationManager * @param EventDispatcher $eventDispatcher + * @param AutoloadGenerator $autoloadGenerator */ - public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher) + public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator) { $this->io = $io; $this->package = $package; @@ -112,6 +119,7 @@ class Installer $this->locker = $locker; $this->installationManager = $installationManager; $this->eventDispatcher = $eventDispatcher; + $this->autoloadGenerator = $autoloadGenerator; } /** @@ -128,7 +136,13 @@ class Installer } // create installed repo, this contains all local packages + platform packages (php & extensions) - $repos = array_merge($this->repositoryManager->getLocalRepositories(), array(new PlatformRepository())); + $repos = array_merge( + $this->repositoryManager->getLocalRepositories(), + array( + new ArrayRepository(array($this->package)), + new PlatformRepository(), + ) + ); $installedRepo = new CompositeRepository($repos); if ($this->additionalInstalledRepository) { $installedRepo->addRepository($this->additionalInstalledRepository); @@ -152,9 +166,11 @@ class Installer } } - // dump suggestions + // output suggestions foreach ($this->suggestedPackages as $suggestion) { - $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')'); + if (!$installedRepo->findPackages($suggestion['target'])) { + $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')'); + } } if (!$this->dryRun) { @@ -172,9 +188,8 @@ class Installer // write autoloader $this->io->write('Generating autoload files'); - $generator = new AutoloadGenerator; $localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories()); - $generator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer'); + $this->autoloadGenerator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath() . '/composer', true); // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; @@ -186,6 +201,11 @@ class Installer protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = false) { + // initialize locker to create aliased packages + if (!$this->update && $this->locker->isLocked($devMode)) { + $lockedPackages = $this->locker->getLockedPackages($devMode); + } + // creating repository pool $pool = new Pool; $pool->addRepository($installedRepo); @@ -196,6 +216,10 @@ class Installer // creating requirements request $installFromLock = false; $request = new Request($pool); + + $constraint = new VersionConstraint('=', $this->package->getVersion()); + $request->install($this->package->getName(), $constraint); + if ($this->update) { $this->io->write('Updating '.($devMode ? 'dev ': '').'dependencies'); @@ -214,7 +238,7 @@ class Installer $this->io->write('Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies'); } - foreach ($this->locker->getLockedPackages($devMode) as $package) { + foreach ($lockedPackages as $package) { $version = $package->getVersion(); foreach ($aliases as $alias) { if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) { @@ -378,14 +402,16 @@ class Installer foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) { $package->setAlias($alias['alias_normalized']); $package->setPrettyAlias($alias['alias']); - $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); + $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); + $aliasPackage->setRootPackageAlias(true); } foreach ($this->repositoryManager->getLocalRepositories() as $repo) { foreach ($repo->findPackages($alias['package'], $alias['version']) as $package) { $package->setAlias($alias['alias_normalized']); $package->setPrettyAlias($alias['alias']); - $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); + $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias'])); $package->getRepository()->removePackage($package); + $aliasPackage->setRootPackageAlias(true); } } } @@ -399,11 +425,13 @@ class Installer * @param IOInterface $io * @param Composer $composer * @param EventDispatcher $eventDispatcher + * @param AutoloadGenerator $autoloadGenerator * @return Installer */ - static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null) + static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null, AutoloadGenerator $autoloadGenerator = null) { $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io); + $autoloadGenerator = $autoloadGenerator ?: new AutoloadGenerator; return new static( $io, @@ -412,7 +440,8 @@ class Installer $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), - $eventDispatcher + $eventDispatcher, + $autoloadGenerator ); } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index c015bd9f9..32587a8db 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -16,6 +16,7 @@ use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Repository\RepositoryInterface; use Composer\Repository\NotifiableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; @@ -96,12 +97,12 @@ class InstallationManager /** * Checks whether provided package is installed in one of the registered installers. * - * @param RepositoryInterface $repo repository in which to check + * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * * @return Boolean */ - public function isPackageInstalled(RepositoryInterface $repo, PackageInterface $package) + public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $this->getInstaller($package->getType())->isInstalled($repo, $package); } @@ -126,11 +127,7 @@ class InstallationManager */ public function install(RepositoryInterface $repo, InstallOperation $operation) { - $package = $operation->getPackage(); - if ($package instanceof AliasPackage) { - $package = $package->getAliasOf(); - $package->setInstalledAsAlias(true); - } + $package = $this->antiAlias($operation->getPackage()); $installer = $this->getInstaller($package->getType()); $installer->install($repo, $package); $this->notifyInstall($package); @@ -144,15 +141,8 @@ class InstallationManager */ public function update(RepositoryInterface $repo, UpdateOperation $operation) { - $initial = $operation->getInitialPackage(); - if ($initial instanceof AliasPackage) { - $initial = $initial->getAliasOf(); - } - $target = $operation->getTargetPackage(); - if ($target instanceof AliasPackage) { - $target = $target->getAliasOf(); - $target->setInstalledAsAlias(true); - } + $initial = $this->antiAlias($operation->getInitialPackage()); + $target = $this->antiAlias($operation->getTargetPackage()); $initialType = $initial->getType(); $targetType = $target->getType(); @@ -175,10 +165,7 @@ class InstallationManager */ public function uninstall(RepositoryInterface $repo, UninstallOperation $operation) { - $package = $operation->getPackage(); - if ($package instanceof AliasPackage) { - $package = $package->getAliasOf(); - } + $package = $this->antiAlias($operation->getPackage()); $installer = $this->getInstaller($package->getType()); $installer->uninstall($repo, $package); } @@ -210,10 +197,23 @@ class InstallationManager return getcwd().DIRECTORY_SEPARATOR.$this->vendorPath; } - protected function notifyInstall(PackageInterface $package) + private function notifyInstall(PackageInterface $package) { if ($package->getRepository() instanceof NotifiableRepositoryInterface) { $package->getRepository()->notifyInstall($package); } } + + private function antiAlias(PackageInterface $package) + { + if ($package instanceof AliasPackage) { + $alias = $package; + $package = $package->getAliasOf(); + $package->setInstalledAsAlias(true); + $package->setAlias($alias->getVersion()); + $package->setPrettyAlias($alias->getPrettyVersion()); + } + + return $package; + } } diff --git a/src/Composer/Installer/InstallerInstaller.php b/src/Composer/Installer/InstallerInstaller.php index 9519d384b..98e941d4c 100644 --- a/src/Composer/Installer/InstallerInstaller.php +++ b/src/Composer/Installer/InstallerInstaller.php @@ -15,7 +15,7 @@ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Autoload\AutoloadGenerator; use Composer\Downloader\DownloadManager; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Package\PackageInterface; @@ -35,7 +35,7 @@ class InstallerInstaller extends LibraryInstaller * @param DownloadManager $dm download manager * @param IOInterface $io io instance * @param InstallationManager $im installation manager - * @param array $localRepositories array of WritableRepositoryInterface + * @param array $localRepositories array of InstalledRepositoryInterface */ public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, InstallationManager $im, array $localRepositories) { @@ -54,7 +54,7 @@ class InstallerInstaller extends LibraryInstaller /** * {@inheritDoc} */ - public function install(WritableRepositoryInterface $repo, PackageInterface $package) + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $extra = $package->getExtra(); if (empty($extra['class'])) { @@ -68,7 +68,7 @@ class InstallerInstaller extends LibraryInstaller /** * {@inheritDoc} */ - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $extra = $target->getExtra(); if (empty($extra['class'])) { diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index 3716ec6df..099b46fdc 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -14,7 +14,7 @@ namespace Composer\Installer; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Package\PackageInterface; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; /** * Interface for the package installation manager. @@ -35,39 +35,39 @@ interface InstallerInterface /** * Checks that provided package is installed. * - * @param WritableRepositoryInterface $repo repository in which to check + * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance * * @return Boolean */ - function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package); + function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Installs specific package. * - * @param WritableRepositoryInterface $repo repository in which to check + * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ - function install(WritableRepositoryInterface $repo, PackageInterface $package); + function install(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Updates specific package. * - * @param WritableRepositoryInterface $repo repository in which to check + * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $initial already installed package version * @param PackageInterface $target updated version * * @throws InvalidArgumentException if $from package is not installed */ - function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); + function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target); /** * Uninstalls specific package. * - * @param WritableRepositoryInterface $repo repository in which to check + * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ - function uninstall(WritableRepositoryInterface $repo, PackageInterface $package); + function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Returns the installation path of a package diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index baff5fdeb..0da923654 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -14,7 +14,7 @@ namespace Composer\Installer; use Composer\IO\IOInterface; use Composer\Downloader\DownloadManager; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; @@ -65,7 +65,7 @@ class LibraryInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package) && is_readable($this->getInstallPath($package)); } @@ -73,7 +73,7 @@ class LibraryInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function install(WritableRepositoryInterface $repo, PackageInterface $package) + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); @@ -93,7 +93,7 @@ class LibraryInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); @@ -114,7 +114,7 @@ class LibraryInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125) @@ -152,7 +152,7 @@ class LibraryInstaller implements InstallerInterface // likely leftover from a previous install, make sure // that the target is still executable in case this // is a fresh install of the vendor. - chmod($link, 0755); + chmod($link, 0777 & ~umask()); } $this->io->write('Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file'); continue; @@ -163,21 +163,24 @@ class LibraryInstaller implements InstallerInterface // add unixy support for cygwin and similar environments if ('.bat' !== substr($bin, -4)) { file_put_contents($link, $this->generateUnixyProxyCode($bin, $link)); - chmod($link, 0755); + chmod($link, 0777 & ~umask()); $link .= '.bat'; } file_put_contents($link, $this->generateWindowsProxyCode($bin, $link)); } else { + $cwd = getcwd(); try { // under linux symlinks are not always supported for example // when using it in smbfs mounted folder - symlink($bin, $link); + $relativeBin = $this->filesystem->findShortestPath($link, $bin); + chdir(dirname($link)); + symlink($relativeBin, $link); } catch (\ErrorException $e) { file_put_contents($link, $this->generateUnixyProxyCode($bin, $link)); } - + chdir($cwd); } - chmod($link, 0755); + chmod($link, 0777 & ~umask()); } } @@ -186,7 +189,7 @@ class LibraryInstaller implements InstallerInterface if (!$package->getBinaries()) { return; } - foreach ($package->getBinaries() as $bin => $os) { + foreach ($package->getBinaries() as $bin) { $link = $this->binDir.'/'.basename($bin); if (!file_exists($link)) { continue; diff --git a/src/Composer/Installer/MetapackageInstaller.php b/src/Composer/Installer/MetapackageInstaller.php index 9dcdbd3d0..e0d19ab6e 100644 --- a/src/Composer/Installer/MetapackageInstaller.php +++ b/src/Composer/Installer/MetapackageInstaller.php @@ -12,7 +12,7 @@ namespace Composer\Installer; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; /** @@ -33,7 +33,7 @@ class MetapackageInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } @@ -41,7 +41,7 @@ class MetapackageInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function install(WritableRepositoryInterface $repo, PackageInterface $package) + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $repo->addPackage(clone $package); } @@ -49,7 +49,7 @@ class MetapackageInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); @@ -62,7 +62,7 @@ class MetapackageInstaller implements InstallerInterface /** * {@inheritDoc} */ - public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125) diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index da9236a97..274d8e5ae 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -15,6 +15,7 @@ namespace Composer\Installer; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Package\PackageInterface; use Composer\Downloader\DownloadManager; +use Composer\Repository\InstalledRepositoryInterface; /** * Project Installer is used to install a single package into a directory as @@ -45,23 +46,17 @@ class ProjectInstaller implements InstallerInterface } /** - * Checks that provided package is installed. - * - * @param PackageInterface $package package instance - * - * @return Boolean + * {@inheritDoc} */ - public function isInstalled(PackageInterface $package) + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return false; } /** - * Installs specific package. - * - * @param PackageInterface $package package instance + * {@inheritDoc} */ - public function install(PackageInterface $package) + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $installPath = $this->installPath; if (file_exists($installPath)) { @@ -75,24 +70,17 @@ class ProjectInstaller implements InstallerInterface } /** - * Updates specific package. - * - * @param PackageInterface $initial already installed package version - * @param PackageInterface $target updated version - * - * @throws InvalidArgumentException if $from package is not installed + * {@inheritDoc} */ - public function update(PackageInterface $initial, PackageInterface $target) + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { throw new \InvalidArgumentException("not supported"); } /** - * Uninstalls specific package. - * - * @param PackageInterface $package package instance + * {@inheritDoc} */ - public function uninstall(PackageInterface $package) + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { throw new \InvalidArgumentException("not supported"); } diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 695679d1d..16d3d3394 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -237,7 +237,7 @@ class JsonFile } } else { // Collapse empty {} and [] - $result = rtrim($result); + $result = rtrim($result)."\n\n".$indentStr; } } diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index af5887c9e..6670b93e5 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -27,6 +27,7 @@ class AliasPackage extends BasePackage protected $prettyVersion; protected $dev; protected $aliasOf; + protected $rootPackageAlias = false; protected $requires; protected $conflicts; @@ -146,6 +147,27 @@ class AliasPackage extends BasePackage return $this->devRequires; } + /** + * Stores whether this is an alias created by an aliasing in the requirements of the root package or not + * + * Use by the policy for sorting manually aliased packages first, see #576 + * + * @param Boolean $value + */ + public function setRootPackageAlias($value) + { + return $this->rootPackageAlias = $value; + } + + /** + * @see setRootPackageAlias + * @return Boolean + */ + public function isRootPackageAlias() + { + return $this->rootPackageAlias; + } + /** * {@inheritDoc} */ @@ -222,6 +244,14 @@ class AliasPackage extends BasePackage { return $this->aliasOf->getScripts(); } + public function setAliases(array $aliases) + { + return $this->aliasOf->setAliases($aliases); + } + public function getAliases() + { + return $this->aliasOf->getAliases(); + } public function getLicense() { return $this->aliasOf->getLicense(); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 0e706bad9..475fd122c 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -58,7 +58,10 @@ class ArrayLoader $package->setExtra($config['extra']); } - if (isset($config['bin']) && is_array($config['bin'])) { + if (isset($config['bin'])) { + if (!is_array($config['bin'])) { + throw new \UnexpectedValueException('Package '.$config['name'].'\'s bin key should be an array, '.gettype($config['bin']).' given.'); + } foreach ($config['bin'] as $key => $bin) { $config['bin'][$key]= ltrim($bin, '/'); } @@ -166,6 +169,11 @@ class ArrayLoader } if (isset($config['suggest']) && is_array($config['suggest'])) { + foreach ($config['suggest'] as $target => $reason) { + if ('self.version' === trim($reason)) { + $config['suggest'][$target] = $package->getPrettyVersion(); + } + } $package->setSuggests($config['suggest']); } diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 734e28c1a..c38a205e9 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -14,6 +14,8 @@ namespace Composer\Package\Loader; use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryManager; +use Composer\Util\ProcessExecutor; +use Composer\Package\AliasPackage; /** * ArrayLoader built for the sole purpose of loading the root package @@ -25,10 +27,12 @@ use Composer\Repository\RepositoryManager; class RootPackageLoader extends ArrayLoader { private $manager; + private $process; - public function __construct(RepositoryManager $manager, VersionParser $parser = null) + public function __construct(RepositoryManager $manager, VersionParser $parser = null, ProcessExecutor $process = null) { $this->manager = $manager; + $this->process = $process ?: new ProcessExecutor(); parent::__construct($parser); } @@ -38,7 +42,20 @@ class RootPackageLoader extends ArrayLoader $config['name'] = '__root__'; } if (!isset($config['version'])) { - $config['version'] = '1.0.0'; + $version = '1.0.0'; + + // try to fetch current version from git branch + if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output)) { + foreach ($this->process->splitLines($output) as $branch) { + if ($branch && preg_match('{^(?:\* ) *(?:[^/ ]+?/)?(\S+) *[a-f0-9]+ .*$}', $branch, $match)) { + $version = 'dev-'.$match[1]; + } + } + } + + $config['version'] = $version; + } else { + $version = $config['version']; } $package = parent::load($config); @@ -76,6 +93,16 @@ class RootPackageLoader extends ArrayLoader $package->setRepositories($config['repositories']); } + if (isset($config['extra']['branch-alias'][$version]) + && substr($config['extra']['branch-alias'][$version], -4) === '-dev' + ) { + $targetBranch = $config['extra']['branch-alias'][$version]; + $normalized = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); + $version = preg_replace('{(\.9{7})+}', '.x', $normalized); + + return new AliasPackage($package, $normalized, $version); + } + return $package; } } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index fc448c360..a7b5bdcd1 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -90,12 +90,22 @@ class Locker foreach ($lockedPackages as $info) { $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version']; + + // try to find the package in the local repo (best match) $package = $repo->findPackage($info['package'], $resolvedVersion); + // try to find the package in any repo if (!$package) { + $package = $this->repositoryManager->findPackage($info['package'], $resolvedVersion); + } + + // try to find the package in any repo (second pass without alias + rebuild alias since it disappeared) + if (!$package && !empty($info['alias'])) { $package = $this->repositoryManager->findPackage($info['package'], $info['version']); - if ($package && !empty($info['alias'])) { - $package = new AliasPackage($package, $info['alias'], $info['alias']); + if ($package) { + $alias = new AliasPackage($package, $info['alias'], $info['alias']); + $package->getRepository()->addPackage($alias); + $package = $alias; } } diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 1af73d311..7a6336393 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -25,6 +25,13 @@ class ArrayRepository implements RepositoryInterface { protected $packages; + public function __construct(array $packages = array()) + { + foreach ($packages as $package) { + $this->addPackage($package); + } + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 9bdd9f05d..938e7b566 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -40,7 +40,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository $repoConfig['url'] = 'http://'.$repoConfig['url']; } $repoConfig['url'] = rtrim($repoConfig['url'], '/'); - if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { + if (function_exists('filter_var') && version_compare(PHP_VERSION, '5.3.3', '>=') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); } @@ -70,7 +70,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => http_build_query($params), + 'content' => http_build_query($params, '', '&'), 'timeout' => 3, ) ); diff --git a/src/Composer/Repository/InstalledRepositoryInterface.php b/src/Composer/Repository/InstalledRepositoryInterface.php index de71d23c6..7b9ddaeae 100644 --- a/src/Composer/Repository/InstalledRepositoryInterface.php +++ b/src/Composer/Repository/InstalledRepositoryInterface.php @@ -21,6 +21,6 @@ use Composer\Package\PackageInterface; * * @author Jordi Boggiano */ -interface InstalledRepositoryInterface +interface InstalledRepositoryInterface extends WritableRepositoryInterface { } diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 9c4bf7ba7..59a0707ce 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -28,6 +28,7 @@ class PearRepository extends ArrayRepository private static $channelNames = array(); private $url; + private $baseUrl; private $channel; private $io; private $rfs; @@ -38,7 +39,7 @@ class PearRepository extends ArrayRepository $repoConfig['url'] = 'http://'.$repoConfig['url']; } - if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { + if (function_exists('filter_var') && version_compare(PHP_VERSION, '5.3.3', '>=') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']); } @@ -68,8 +69,13 @@ class PearRepository extends ArrayRepository $loader = new ArrayLoader(); foreach ($packages as $data) { foreach ($data['versions'] as $rev) { - $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name']; + if (strpos($rev['name'], 'pear-'.$this->channel) !== 0) { + $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name']; + } $this->addPackage($loader->load($rev)); + if ($this->io->isVerbose()) { + $this->io->write('Loaded '.$rev['name'].' '.$rev['version']); + } } } @@ -87,26 +93,31 @@ class PearRepository extends ArrayRepository $this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue; } + if (!$this->baseUrl) { + $this->baseUrl = $channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue + ? trim($channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue, '/') + : $this->url . '/rest'; + } self::$channelNames[$channelXML->getElementsByTagName("name")->item(0)->nodeValue] = $this->channel; } protected function fetchFromServer() { - $categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml"); + $categoryXML = $this->requestXml($this->baseUrl . "/c/categories.xml"); $categories = $categoryXML->getElementsByTagName("c"); foreach ($categories as $category) { - $link = '/' . ltrim($category->getAttribute("xlink:href"), '/'); + $link = $this->baseUrl . '/c/' . str_replace(' ', '+', $category->nodeValue); try { - $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link); - $this->fetchPear2Packages($this->url . $packagesLink); + $packagesLink = $link . "/packagesinfo.xml"; + $this->fetchPear2Packages($packagesLink); } catch (TransportException $e) { if (false === strpos($e->getMessage(), '404')) { throw $e; } - $categoryLink = str_replace("info.xml", "packages.xml", $link); - $this->fetchPearPackages($this->url . $categoryLink); + $categoryLink = $link . "/packages.xml"; + $this->fetchPearPackages($categoryLink); } } @@ -126,8 +137,7 @@ class PearRepository extends ArrayRepository $packageName = $package->nodeValue; $fullName = 'pear-'.$this->channel.'/'.$packageName; - $packageLink = $package->getAttribute('xlink:href'); - $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink); + $releaseLink = $this->baseUrl . "/r/" . $packageName; $allReleasesLink = $releaseLink . "/allreleases2.xml"; try { @@ -357,7 +367,7 @@ class PearRepository extends ArrayRepository throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); } $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->loadXML($content); + $dom->loadXML($content, LIBXML_NOERROR); return $dom; } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index c0132cd10..b5cb8960d 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -27,20 +27,14 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface protected $rootIdentifier; protected $infoCache = array(); - public function __construct($url, IOInterface $io) - { - preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url, $match); - $this->owner = $match[1]; - $this->repository = $match[2]; - - parent::__construct($url, $io); - } - /** * {@inheritDoc} */ public function initialize() { + preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match); + $this->owner = $match[1]; + $this->repository = $match[2]; } /** diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 73f9e1aed..b33d75d85 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -28,11 +28,6 @@ class GitDriver extends VcsDriver protected $repoDir; protected $infoCache = array(); - public function __construct($url, IOInterface $io, ProcessExecutor $process = null) - { - parent::__construct($url, $io, $process); - } - /** * {@inheritDoc} */ @@ -41,11 +36,13 @@ class GitDriver extends VcsDriver if (static::isLocalUrl($this->url)) { $this->repoDir = str_replace('file://', '', $this->url); } else { - $this->repoDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; + $this->repoDir = $this->config->get('home') . '/cache.git/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; // update the repo if it is a valid git repository if (is_dir($this->repoDir) && 0 === $this->process->execute('git remote', $output, $this->repoDir)) { - $this->process->execute('git remote update --prune origin', $output, $this->repoDir); + if (0 !== $this->process->execute('git remote update --prune origin', $output, $this->repoDir)) { + $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + } } else { // clean up directory and do a fresh clone into it $fs = new Filesystem(); diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 42633aafe..9091d0c6f 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -39,27 +39,14 @@ class GitHubDriver extends VcsDriver protected $gitDriver; /** - * Constructor - * - * @param string $url - * @param IOInterface $io - * @param ProcessExecutor $process - * @param RemoteFilesystem $remoteFilesystem + * {@inheritDoc} */ - public function __construct($url, IOInterface $io, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) + public function initialize() { - preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url, $match); + preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match); $this->owner = $match[1]; $this->repository = $match[2]; - parent::__construct($url, $io, $process, $remoteFilesystem); - } - - /** - * {@inheritDoc} - */ - public function initialize() - { $this->fetchRootIdentifier(); } @@ -248,6 +235,7 @@ class GitHubDriver extends VcsDriver $this->gitDriver = new GitDriver( $this->generateSshUrl(), $this->io, + $this->config, $this->process, $this->remoteFilesystem ); diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 993ece82d..43fff9aba 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -27,20 +27,14 @@ class HgBitbucketDriver extends VcsDriver protected $rootIdentifier; protected $infoCache = array(); - public function __construct($url, IOInterface $io) - { - preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url, $match); - $this->owner = $match[1]; - $this->repository = $match[2]; - - parent::__construct($url, $io); - } - /** * {@inheritDoc} */ public function initialize() { + preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match); + $this->owner = $match[1]; + $this->repository = $match[2]; } /** diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 833669cf9..45186da7c 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -26,24 +26,21 @@ class HgDriver extends VcsDriver protected $rootIdentifier; protected $infoCache = array(); - public function __construct($url, IOInterface $io, ProcessExecutor $process = null) - { - $this->tmpDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/'; - - parent::__construct($url, $io, $process); - } - /** * {@inheritDoc} */ public function initialize() { - $url = escapeshellarg($this->url); - $tmpDir = escapeshellarg($this->tmpDir); + $this->tmpDir = $this->config->get('home') . '/cache.hg/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; + if (is_dir($this->tmpDir)) { - $this->process->execute(sprintf('cd %s && hg pull -u', $tmpDir), $output); + $this->process->execute(sprintf('cd %s && hg pull -u', escapeshellarg($this->tmpDir)), $output); } else { - $this->process->execute(sprintf('cd %s && hg clone %s %s', escapeshellarg(sys_get_temp_dir()), $url, $tmpDir), $output); + $dir = dirname($this->tmpDir); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + $this->process->execute(sprintf('cd %s && hg clone %s %s', escapeshellarg($dir), escapeshellarg($this->url), escapeshellarg($this->tmpDir)), $output); } $this->getTags(); diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 6f13296ce..7b2b5cea7 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -33,25 +33,7 @@ class SvnDriver extends VcsDriver /** * @var \Composer\Util\Svn */ - protected $util; - - /** - * @param string $url - * @param IOInterface $io - * @param ProcessExecutor $process - * - * @return $this - */ - public function __construct($url, IOInterface $io, ProcessExecutor $process = null) - { - $url = self::normalizeUrl($url); - parent::__construct($this->baseUrl = rtrim($url, '/'), $io, $process); - - if (false !== ($pos = strrpos($url, '/trunk'))) { - $this->baseUrl = substr($url, 0, $pos); - } - $this->util = new SvnUtil($this->baseUrl, $io, $this->process); - } + private $util; /** * Execute an SVN command and try to fix up the process with credentials @@ -64,6 +46,10 @@ class SvnDriver extends VcsDriver */ protected function execute($command, $url) { + if (null === $this->util) { + $this->util = new SvnUtil($this->baseUrl, $this->io, $this->process); + } + try { return $this->util->execute($command, $url); } catch (\RuntimeException $e) { @@ -78,6 +64,12 @@ class SvnDriver extends VcsDriver */ public function initialize() { + $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/'); + + if (false !== ($pos = strrpos($this->url, '/trunk'))) { + $this->baseUrl = substr($this->url, 0, $pos); + } + $this->getBranches(); $this->getTags(); } diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 99c705c50..d13b4b4a1 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Downloader\TransportException; +use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; @@ -26,6 +27,7 @@ abstract class VcsDriver implements VcsDriverInterface { protected $url; protected $io; + protected $config; protected $process; protected $remoteFilesystem; @@ -34,13 +36,15 @@ abstract class VcsDriver implements VcsDriverInterface * * @param string $url The URL * @param IOInterface $io The IO instance + * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param callable $remoteFilesystem Remote Filesystem, injectable for mocking */ - public function __construct($url, IOInterface $io, ProcessExecutor $process = null, $remoteFilesystem = null) + final public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null, $remoteFilesystem = null) { $this->url = $url; $this->io = $io; + $this->config = $config; $this->process = $process ?: new ProcessExecutor; $this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io); } @@ -58,7 +62,6 @@ abstract class VcsDriver implements VcsDriverInterface return false; } - /** * Get the https or http protocol depending on SSL support. * diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index b70e7c09d..ecc435582 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -30,6 +30,7 @@ class VcsRepository extends ArrayRepository protected $packageName; protected $verbose; protected $io; + protected $config; protected $versionParser; protected $type; @@ -48,20 +49,21 @@ class VcsRepository extends ArrayRepository $this->io = $io; $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; $this->verbose = $io->isVerbose(); + $this->config = $config; } public function getDriver() { if (isset($this->drivers[$this->type])) { $class = $this->drivers[$this->type]; - $driver = new $class($this->url, $this->io); + $driver = new $class($this->url, $this->io, $this->config); $driver->initialize(); return $driver; } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->url)) { - $driver = new $driver($this->url, $this->io); + $driver = new $driver($this->url, $this->io, $this->config); $driver->initialize(); return $driver; } @@ -69,7 +71,7 @@ class VcsRepository extends ArrayRepository foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->url, true)) { - $driver = new $driver($this->url, $this->io); + $driver = new $driver($this->url, $this->io, $this->config); $driver->initialize(); return $driver; } diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 9826628e3..384ff79dd 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -46,7 +46,11 @@ final class StreamContextFactory } // http(s):// is not supported in proxy - $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); + if ('http://' == substr($proxy, 0, 7)) { + $proxy = 'tcp://' . rtrim(substr($proxy, 7), '/') . (parse_url($proxy, PHP_URL_PORT) ? '' : ':80'); + } else if ('https://' == substr($proxy, 0, 8)) { + $proxy = 'ssl://' . rtrim(substr($proxy, 8), '/') . (parse_url($proxy, PHP_URL_PORT) ? '' : ':443'); + } if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) { throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); diff --git a/src/bootstrap.php b/src/bootstrap.php index c7f2fd0d6..3ce95752c 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -1,25 +1,25 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -function includeIfExists($file) { - if (file_exists($file)) { - return include $file; - } -} - -if ((!$loader = includeIfExists(__DIR__.'/../vendor/.composer/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../.composer/autoload.php'))) { - die('You must set up the project dependencies, run the following commands:'.PHP_EOL. - 'curl -s http://getcomposer.org/installer | php'.PHP_EOL. - 'php composer.phar install'.PHP_EOL); -} - -return $loader; \ No newline at end of file + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +function includeIfExists($file) { + if (file_exists($file)) { + return include $file; + } +} + +if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) { + die('You must set up the project dependencies, run the following commands:'.PHP_EOL. + 'curl -s http://getcomposer.org/installer | php'.PHP_EOL. + 'php composer.phar install'.PHP_EOL); +} + +return $loader; diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index dced4ef7f..b9974fd9b 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -60,14 +60,14 @@ class AutoloadGeneratorTest extends TestCase protected function tearDown() { if ($this->vendorDir === $this->workingDir) { - if (is_dir($this->workingDir.'/.composer')) { - $this->fs->removeDirectory($this->workingDir.'/.composer'); + if (is_dir($this->workingDir.'/composer')) { + $this->fs->removeDirectory($this->workingDir.'/composer'); } } elseif (is_dir($this->vendorDir)) { $this->fs->removeDirectory($this->vendorDir); } - if (is_dir($this->workingDir.'/.composersrc')) { - $this->fs->removeDirectory($this->workingDir.'/.composersrc'); + if (is_dir($this->workingDir.'/composersrc')) { + $this->fs->removeDirectory($this->workingDir.'/composersrc'); } chdir($this->dir); @@ -78,22 +78,22 @@ class AutoloadGeneratorTest extends TestCase $package = new MemoryPackage('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')), - 'classmap' => array('.composersrc/'), + 'classmap' => array('composersrc/'), )); $this->repository->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - if (!is_dir($this->vendorDir.'/.composer')) { - mkdir($this->vendorDir.'/.composer'); + if (!is_dir($this->vendorDir.'/composer')) { + mkdir($this->vendorDir.'/composer'); } $this->createClassFile($this->workingDir); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('main', $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('classmap', $this->vendorDir.'/.composer', 'classmap'); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('main', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); } public function testVendorDirSameAsWorkingDir() @@ -103,22 +103,22 @@ class AutoloadGeneratorTest extends TestCase $package = new MemoryPackage('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'), - 'classmap' => array('.composersrc/'), + 'classmap' => array('composersrc/'), )); $this->repository->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - if (!is_dir($this->vendorDir.'/.composer')) { - mkdir($this->vendorDir.'/.composer', 0777, true); + if (!is_dir($this->vendorDir.'/composer')) { + mkdir($this->vendorDir.'/composer', 0777, true); } $this->createClassFile($this->vendorDir); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('main3', $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/.composer', 'classmap'); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('main3', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap'); } public function testMainPackageAutoloadingAlternativeVendorDir() @@ -126,7 +126,7 @@ class AutoloadGeneratorTest extends TestCase $package = new MemoryPackage('a', '1.0', '1.0'); $package->setAutoload(array( 'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'), - 'classmap' => array('.composersrc/'), + 'classmap' => array('composersrc/'), )); $this->repository->expects($this->once()) @@ -134,11 +134,27 @@ class AutoloadGeneratorTest extends TestCase ->will($this->returnValue(array())); $this->vendorDir .= '/subdir'; - mkdir($this->vendorDir.'/.composer', 0777, true); + mkdir($this->vendorDir.'/composer', 0777, true); $this->createClassFile($this->workingDir); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('main2', $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/.composer', 'classmap'); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('main2', $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap'); + } + + public function testMainPackageAutoloadingWithTargetDir() + { + $package = new MemoryPackage('a', '1.0', '1.0'); + $package->setAutoload(array( + 'psr-0' => array('Main\\Foo' => '', 'Main\\Bar' => ''), + )); + $package->setTargetDir('Main/Foo/'); + + $this->repository->expects($this->once()) + ->method('getPackages') + ->will($this->returnValue(array())); + + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertFileEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php'); } public function testVendorsAutoloading() @@ -155,10 +171,10 @@ class AutoloadGeneratorTest extends TestCase ->method('getPackages') ->will($this->returnValue($packages)); - mkdir($this->vendorDir.'/.composer', 0777, true); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer'); - $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty."); + mkdir($this->vendorDir.'/composer', 0777, true); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty."); } public function testVendorsClassMapAutoloading() @@ -175,7 +191,7 @@ class AutoloadGeneratorTest extends TestCase ->method('getPackages') ->will($this->returnValue($packages)); - @mkdir($this->vendorDir.'/.composer', 0777, true); + @mkdir($this->vendorDir.'/composer', 0777, true); mkdir($this->vendorDir.'/a/a/src', 0777, true); mkdir($this->vendorDir.'/b/b/src', 0777, true); mkdir($this->vendorDir.'/b/b/lib', 0777, true); @@ -183,17 +199,17 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->vendorDir.'/b/b/src/b.php', 'vendorDir.'/b/b/lib/c.php', 'generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated."); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated."); $this->assertEquals( array( 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php', 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.php', 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/b/b/lib/c.php', ), - include ($this->vendorDir.'/.composer/autoload_classmap.php') + include ($this->vendorDir.'/composer/autoload_classmap.php') ); - $this->assertAutoloadFiles('classmap4', $this->vendorDir.'/.composer', 'classmap'); + $this->assertAutoloadFiles('classmap4', $this->vendorDir.'/composer', 'classmap'); } public function testClassMapAutoloadingEmptyDirAndExactFile() @@ -212,7 +228,7 @@ class AutoloadGeneratorTest extends TestCase ->method('getPackages') ->will($this->returnValue($packages)); - @mkdir($this->vendorDir.'/.composer', 0777, true); + @mkdir($this->vendorDir.'/composer', 0777, true); mkdir($this->vendorDir.'/a/a/src', 0777, true); mkdir($this->vendorDir.'/b/b', 0777, true); mkdir($this->vendorDir.'/c/c/foo', 0777, true); @@ -220,17 +236,17 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->vendorDir.'/b/b/test.php', 'vendorDir.'/c/c/foo/test.php', 'generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated."); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated."); $this->assertEquals( array( 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php', 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/test.php', 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/c/c/foo/test.php', ), - include ($this->vendorDir.'/.composer/autoload_classmap.php') + include ($this->vendorDir.'/composer/autoload_classmap.php') ); - $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/.composer', 'classmap'); + $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap'); } public function testOverrideVendorsAutoloading() @@ -248,9 +264,9 @@ class AutoloadGeneratorTest extends TestCase ->method('getPackages') ->will($this->returnValue($packages)); - mkdir($this->vendorDir.'/.composer', 0777, true); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer'); - $this->assertAutoloadFiles('override_vendors', $this->vendorDir.'/.composer'); + mkdir($this->vendorDir.'/composer', 0777, true); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer'); + $this->assertAutoloadFiles('override_vendors', $this->vendorDir.'/composer'); } public function testIncludePathFileGeneration() @@ -264,23 +280,29 @@ class AutoloadGeneratorTest extends TestCase $b = new MemoryPackage("b/b", "1.0", "1.0"); $b->setIncludePaths(array("library")); + $c = new MemoryPackage("c", "1.0", "1.0"); + $c->setIncludePaths(array("library")); + $packages[] = $a; $packages[] = $b; + $packages[] = $c; $this->repository->expects($this->once()) ->method("getPackages") ->will($this->returnValue($packages)); - mkdir($this->vendorDir."/.composer", 0777, true); + mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer"); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer"); + $this->assertFileEquals(__DIR__.'/Fixtures/include_paths.php', $this->vendorDir.'/composer/include_paths.php'); $this->assertEquals( array( - $this->vendorDir."/a/a/lib/", - $this->vendorDir."/b/b/library" + $this->vendorDir."/a/a/lib", + $this->vendorDir."/b/b/library", + $this->vendorDir."/c/library", ), - require($this->vendorDir."/.composer/include_paths.php") + require($this->vendorDir."/composer/include_paths.php") ); } @@ -298,16 +320,16 @@ class AutoloadGeneratorTest extends TestCase ->method("getPackages") ->will($this->returnValue($packages)); - mkdir($this->vendorDir."/.composer", 0777, true); + mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer"); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer"); $oldIncludePath = get_include_path(); - require($this->vendorDir."/.composer/autoload.php"); + require($this->vendorDir."/autoload.php"); $this->assertEquals( - $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib/", + $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib", get_include_path() ); @@ -326,20 +348,20 @@ class AutoloadGeneratorTest extends TestCase ->method("getPackages") ->will($this->returnValue($packages)); - mkdir($this->vendorDir."/.composer", 0777, true); + mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer"); + $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer"); - $this->assertFalse(file_exists($this->vendorDir."/.composer/include_paths.php")); + $this->assertFalse(file_exists($this->vendorDir."/composer/include_paths.php")); } private function createClassFile($basedir) { - if (!is_dir($basedir.'/.composersrc')) { - mkdir($basedir.'/.composersrc', 0777, true); + if (!is_dir($basedir.'/composersrc')) { + mkdir($basedir.'/composersrc', 0777, true); } - file_put_contents($basedir.'/.composersrc/foo.php', ' $baseDir . '/.composersrc/foo.php', + 'ClassMapFoo' => $baseDir . '/composersrc/foo.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php index 2931e0f09..6016af10e 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php @@ -6,5 +6,5 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); return array( - 'ClassMapFoo' => $baseDir . '/.composersrc/foo.php', + 'ClassMapFoo' => $baseDir . '/composersrc/foo.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php index 15917ec1a..35f1159c1 100644 --- a/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php @@ -6,5 +6,5 @@ $vendorDir = dirname(__DIR__); $baseDir = $vendorDir; return array( - 'ClassMapFoo' => $baseDir . '/.composersrc/foo.php', + 'ClassMapFoo' => $baseDir . '/composersrc/foo.php', ); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php b/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php new file mode 100644 index 000000000..16d187d27 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php @@ -0,0 +1,41 @@ + $path) { + $loader->add($namespace, $path); + } + + $classMap = require $composerDir . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + spl_autoload_register(function($class) { + $dir = dirname(__DIR__) . '/'; + $prefixes = array('Main\\Foo', 'Main\\Bar'); + foreach ($prefixes as $prefix) { + if (0 !== strpos($class, $prefix)) { + continue; + } + $path = $dir . implode('/', array_slice(explode('\\', $class), 2)).'.php'; + if (!stream_resolve_include_path($path)) { + return false; + } + require_once $path; + return true; + } + }); + + $loader->register(); + + return $loader; +}); diff --git a/tests/Composer/Test/Autoload/Fixtures/include_paths.php b/tests/Composer/Test/Autoload/Fixtures/include_paths.php new file mode 100644 index 000000000..37ac66c87 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/include_paths.php @@ -0,0 +1,12 @@ +assertEquals($expected, $selected); } + public function testSelectLocalReposFirst() + { + $this->repoImportant = new ArrayRepository; + + $this->repo->addPackage($packageA = $this->getPackage('A', 'dev-master')); + $this->repo->addPackage($packageAAlias = new AliasPackage($packageA, '2.1.9999999.9999999-dev', '2.1.x-dev')); + $this->repoImportant->addPackage($packageAImportant = $this->getPackage('A', 'dev-feature-a')); + $this->repoImportant->addPackage($packageAAliasImportant = new AliasPackage($packageAImportant, '2.1.9999999.9999999-dev', '2.1.x-dev')); + $this->repoImportant->addPackage($packageA2Important = $this->getPackage('A', 'dev-master')); + $this->repoImportant->addPackage($packageA2AliasImportant = new AliasPackage($packageA2Important, '2.1.9999999.9999999-dev', '2.1.x-dev')); + $packageAAliasImportant->setRootPackageAlias(true); + + $this->pool->addRepository($this->repoInstalled); + $this->pool->addRepository($this->repoImportant); + $this->pool->addRepository($this->repo); + + $packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev')); + $literals = array(); + foreach ($packages as $package) { + $literals[] = new Literal($package, true); + } + + $expected = array(new Literal($packageAAliasImportant, true)); + + $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals); + + $this->assertEquals($expected, $selected); + } + public function testSelectAllProviders() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php index 9c1e2894c..29b2edf90 100644 --- a/tests/Composer/Test/Downloader/DownloadManagerTest.php +++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php @@ -16,10 +16,17 @@ use Composer\Downloader\DownloadManager; class DownloadManagerTest extends \PHPUnit_Framework_TestCase { + protected $filesystem; + + public function setUp() + { + $this->filesystem = $this->getMock('Composer\Util\Filesystem'); + } + public function testSetGetDownloader() { $downloader = $this->createDownloaderMock(); - $manager = new DownloadManager(); + $manager = new DownloadManager(false, $this->filesystem); $manager->setDownloader('test', $downloader); $this->assertSame($downloader, $manager->getDownloader('test')); @@ -36,7 +43,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->method('getInstallationSource') ->will($this->returnValue(null)); - $manager = new DownloadManager(); + $manager = new DownloadManager(false, $this->filesystem); $this->setExpectedException('InvalidArgumentException'); @@ -62,6 +69,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('dist')); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloader')) ->getMock(); @@ -93,6 +101,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('source')); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloader')) ->getMock(); @@ -126,6 +135,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('source')); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloader')) ->getMock(); @@ -157,6 +167,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('dist')); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloader')) ->getMock(); @@ -195,6 +206,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -218,7 +230,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->method('getDistType') ->will($this->returnValue(null)); - $manager = new DownloadManager(); + $manager = new DownloadManager(false, $this->filesystem); $this->setExpectedException('InvalidArgumentException'); $manager->download($package, 'target_dir'); @@ -248,6 +260,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -283,6 +296,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -318,6 +332,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -354,6 +369,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -390,6 +406,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'target_dir'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -414,7 +431,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->method('getDistType') ->will($this->returnValue(null)); - $manager = new DownloadManager(); + $manager = new DownloadManager(false, $this->filesystem); $manager->setPreferSource(true); $this->setExpectedException('InvalidArgumentException'); @@ -450,6 +467,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($initial, $target, 'vendor/bundles/FOS/UserBundle'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager @@ -486,6 +504,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($initial, 'vendor/bundles/FOS/UserBundle'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage', 'download')) ->getMock(); $manager @@ -526,6 +545,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($initial, $target, 'vendor/pkg'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage', 'download')) ->getMock(); $manager @@ -562,6 +582,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($initial, 'vendor/pkg'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage', 'download')) ->getMock(); $manager @@ -588,6 +609,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase ->with($package, 'vendor/bundles/FOS/UserBundle'); $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') + ->setConstructorArgs(array(false, $this->filesystem)) ->setMethods(array('getDownloaderForInstalledPackage')) ->getMock(); $manager diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 8efb1e027..c23b95a7a 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -50,7 +50,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('https://example.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); - $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://example.com/composer/composer'"); $processExecutor->expects($this->once()) ->method('execute') ->with($this->equalTo($expectedGitCommand)) @@ -71,19 +71,19 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('https://github.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); - $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'git://github.com/composer/composer'"); $processExecutor->expects($this->at(0)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(1)); - $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://github.com/composer/composer'"); $processExecutor->expects($this->at(1)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(1)); - $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'http://github.com/composer/composer'"); $processExecutor->expects($this->at(2)) ->method('execute') ->with($this->equalTo($expectedGitCommand)) @@ -104,7 +104,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase */ public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() { - $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://example.com/composer/composer'"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -139,8 +139,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch origin && git fetch --tags origin && git checkout 'ref' && git reset --hard 'ref'"); - $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain"); + $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -155,6 +155,10 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($expectedGitResetCommand)) ->will($this->returnValue(0)); $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($this->getCmd("cd 'composerPath' && git remote add composer 'https://github.com/composer/composer'"))) + ->will($this->returnValue(0)); + $processExecutor->expects($this->at(2)) ->method('execute') ->with($this->equalTo($expectedGitUpdateCommand)) ->will($this->returnValue(0)); @@ -168,8 +172,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase */ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails() { - $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch origin && git fetch --tags origin && git checkout 'ref' && git reset --hard 'ref'"); - $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain"); + $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'"); + $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -184,6 +188,10 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($expectedGitResetCommand)) ->will($this->returnValue(0)); $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($this->getCmd("cd 'composerPath' && git remote add composer 'https://github.com/composer/composer'"))) + ->will($this->returnValue(0)); + $processExecutor->expects($this->at(2)) ->method('execute') ->with($this->equalTo($expectedGitUpdateCommand)) ->will($this->returnValue(1)); @@ -194,7 +202,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testRemove() { - $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain"); + $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php index cfda3c3d3..bfad4a88a 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php @@ -4,16 +4,16 @@ namespace Installer; use Composer\Installer\InstallerInterface; use Composer\Package\PackageInterface; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; class Custom implements InstallerInterface { public $version = 'installer-v1'; public function supports($packageType) {} - public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function install(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} - public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {} + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php index 52f46371b..f7f5b9108 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php @@ -4,16 +4,16 @@ namespace Installer; use Composer\Installer\InstallerInterface; use Composer\Package\PackageInterface; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; class Custom2 implements InstallerInterface { public $version = 'installer-v2'; public function supports($packageType) {} - public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function install(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} - public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {} + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php index ca8ad7897..6cc952765 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php @@ -4,16 +4,16 @@ namespace Installer; use Composer\Installer\InstallerInterface; use Composer\Package\PackageInterface; -use Composer\Repository\WritableRepositoryInterface; +use Composer\Repository\InstalledRepositoryInterface; class Custom2 implements InstallerInterface { public $version = 'installer-v3'; public function supports($packageType) {} - public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function install(WritableRepositoryInterface $repo, PackageInterface $package) {} - public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} - public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {} + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {} + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {} + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index a657ba948..5b404939e 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -21,7 +21,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase { public function setUp() { - $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface'); + $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); } public function testVendorDirOutsideTheWorkingDir() diff --git a/tests/Composer/Test/Installer/InstallerInstallerTest.php b/tests/Composer/Test/Installer/InstallerInstallerTest.php index 2c107c32c..8fd2a7ad2 100644 --- a/tests/Composer/Test/Installer/InstallerInstallerTest.php +++ b/tests/Composer/Test/Installer/InstallerInstallerTest.php @@ -34,7 +34,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface'); + $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $this->io = $this->getMock('Composer\IO\IOInterface'); } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 7983cf2a2..3edc8a477 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -40,7 +40,7 @@ class LibraryInstallerTest extends TestCase ->disableOriginalConstructor() ->getMock(); - $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface'); + $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $this->io = $this->getMock('Composer\IO\IOInterface'); } diff --git a/tests/Composer/Test/Installer/MetapackageInstallerTest.php b/tests/Composer/Test/Installer/MetapackageInstallerTest.php index 45d1ff605..1aa3d3e66 100644 --- a/tests/Composer/Test/Installer/MetapackageInstallerTest.php +++ b/tests/Composer/Test/Installer/MetapackageInstallerTest.php @@ -22,7 +22,7 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface'); + $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface'); $this->io = $this->getMock('Composer\IO\IOInterface'); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php new file mode 100644 index 000000000..478815851 --- /dev/null +++ b/tests/Composer/Test/InstallerTest.php @@ -0,0 +1,115 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Installer; +use Composer\Repository\ArrayRepository; +use Composer\Repository\RepositoryManager; +use Composer\Repository\RepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Package\Link; +use Composer\Test\Mock\WritableRepositoryMock; +use Composer\Test\Mock\InstallationManagerMock; + +class InstallerTest extends TestCase +{ + /** + * @dataProvider provideInstaller + */ + public function testInstaller(PackageInterface $rootPackage, $repositories, array $options) + { + $io = $this->getMock('Composer\IO\IOInterface'); + + $downloadManager = $this->getMock('Composer\Downloader\DownloadManager'); + $config = $this->getMock('Composer\Config'); + + $repositoryManager = new RepositoryManager($io, $config); + $repositoryManager->setLocalRepository(new WritableRepositoryMock()); + $repositoryManager->setLocalDevRepository(new WritableRepositoryMock()); + + if (!is_array($repositories)) { + $repositories = array($repositories); + } + foreach ($repositories as $repository) { + $repositoryManager->addRepository($repository); + } + + $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); + $installationManager = new InstallationManagerMock(); + $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock(); + $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator'); + + $installer = new Installer($io, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator); + $result = $installer->run(); + $this->assertTrue($result); + + $expectedInstalled = isset($options['install']) ? $options['install'] : array(); + $expectedUpdated = isset($options['update']) ? $options['update'] : array(); + $expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array(); + + $installed = $installationManager->getInstalledPackages(); + $this->assertSame($expectedInstalled, $installed); + + $updated = $installationManager->getUpdatedPackages(); + $this->assertSame($expectedUpdated, $updated); + + $uninstalled = $installationManager->getUninstalledPackages(); + $this->assertSame($expectedUninstalled, $uninstalled); + } + + public function provideInstaller() + { + $cases = array(); + + // when A requires B and B requires A, and A is a non-published root package + // the install of B should succeed + + $a = $this->getPackage('A', '1.0.0'); + $a->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')), + )); + $b = $this->getPackage('B', '1.0.0'); + $b->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')), + )); + + $cases[] = array( + $a, + new ArrayRepository(array($b)), + array( + 'install' => array($b) + ), + ); + + // #480: when A requires B and B requires A, and A is a published root package + // only B should be installed, as A is the root + + $a = $this->getPackage('A', '1.0.0'); + $a->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')), + )); + $b = $this->getPackage('B', '1.0.0'); + $b->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')), + )); + + $cases[] = array( + $a, + new ArrayRepository(array($a, $b)), + array( + 'install' => array($b) + ), + ); + + return $cases; + } +} diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 4e30928cb..159017aa6 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -128,6 +128,20 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertJsonFormat($json, $data); } + public function testFormatEmptyArray() + { + $data = array('test' => array(), 'test2' => new \stdClass); + $json = '{ + "test": [ + + ], + "test2": { + + } +}'; + $this->assertJsonFormat($json, $data); + } + public function testEscape() { $data = array("Metadata\\\"" => 'src/'); diff --git a/tests/Composer/Test/Mock/InstallationManagerMock.php b/tests/Composer/Test/Mock/InstallationManagerMock.php new file mode 100644 index 000000000..9a7af786b --- /dev/null +++ b/tests/Composer/Test/Mock/InstallationManagerMock.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\Installer\InstallationManager; +use Composer\Repository\RepositoryInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +class InstallationManagerMock extends InstallationManager +{ + private $installed = array(); + private $updated = array(); + private $uninstalled = array(); + + public function install(RepositoryInterface $repo, InstallOperation $operation) + { + $this->installed[] = $operation->getPackage(); + } + + public function update(RepositoryInterface $repo, UpdateOperation $operation) + { + $this->updated[] = array($operation->getInitialPackage(), $operation->getTargetPackage()); + } + + public function uninstall(RepositoryInterface $repo, UninstallOperation $operation) + { + $this->uninstalled[] = $operation->getPackage(); + } + + public function getInstalledPackages() + { + return $this->installed; + } + + public function getUpdatedPackages() + { + return $this->updated; + } + + public function getUninstalledPackages() + { + return $this->uninstalled; + } +} diff --git a/tests/Composer/Test/Mock/WritableRepositoryMock.php b/tests/Composer/Test/Mock/WritableRepositoryMock.php new file mode 100644 index 000000000..59bb19f88 --- /dev/null +++ b/tests/Composer/Test/Mock/WritableRepositoryMock.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\Repository\ArrayRepository; +use Composer\Repository\WritableRepositoryInterface; + +class WritableRepositoryMock extends ArrayRepository implements WritableRepositoryInterface +{ + public function reload() + { + } + + public function write() + { + } +} diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 0bee2bf1a..508936d2d 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -89,7 +89,7 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase 'homepage' => 'http://example.com', 'license' => array('MIT', 'GPLv3'), 'authors' => array( - array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org'), + array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'), ), 'require' => array( 'foo/bar' => '1.0', diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index 845fb990f..88483bff8 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -15,6 +15,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\GitHubDriver; use Composer\Util\Filesystem; +use Composer\Config; /** * @author Beau Simensen @@ -64,7 +65,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) ->will($this->returnValue('{"master_branch": "test_master"}')); - $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoUrl, $io, new Config(), null, $remoteFilesystem); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -114,7 +115,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) ->will($this->returnValue('{"master_branch": "test_master"}')); - $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoUrl, $io, new Config(), null, $remoteFilesystem); $gitHubDriver->initialize(); $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); @@ -171,7 +172,14 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase // clean local clone if present $fs = new Filesystem(); - $fs->removeDirectory(sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9.]}i', '-', $repoSshUrl) . '/'); + $fs->removeDirectory(sys_get_temp_dir() . '/composer-test'); + + $config = new Config(); + $config->merge(array( + 'config' => array( + 'home' => sys_get_temp_dir() . '/composer-test', + ), + )); $process->expects($this->at(0)) ->method('execute') @@ -202,7 +210,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase ->method('splitLines') ->will($this->returnValue(array('* test_master'))); - $gitHubDriver = new GitHubDriver($repoUrl, $io, $process, $remoteFilesystem); + $gitHubDriver = new GitHubDriver($repoUrl, $io, $config, $process, $remoteFilesystem); $gitHubDriver->initialize(); $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 5399c394e..569fab69f 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\SvnDriver; use Composer\IO\NullIO; +use Composer\Config; class SvnDriverTest extends \PHPUnit_Framework_TestCase { @@ -39,8 +40,8 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase ->method('getErrorOutput') ->will($this->returnValue($output)); - $svn = new SvnDriver('http://till:secret@corp.svn.local/repo', $console, $process); - $svn->getTags(); + $svn = new SvnDriver('http://till:secret@corp.svn.local/repo', $console, new Config(), $process); + $svn->initialize(); } private function getCmd($cmd) diff --git a/tests/Composer/Test/Repository/VcsRepositoryTest.php b/tests/Composer/Test/Repository/VcsRepositoryTest.php index 9c09db0a2..c2060d5b7 100644 --- a/tests/Composer/Test/Repository/VcsRepositoryTest.php +++ b/tests/Composer/Test/Repository/VcsRepositoryTest.php @@ -19,6 +19,7 @@ use Composer\Repository\Vcs\GitDriver; use Composer\Util\Filesystem; use Composer\Util\ProcessExecutor; use Composer\IO\NullIO; +use Composer\Config; class VcsRepositoryTest extends \PHPUnit_Framework_TestCase { @@ -123,7 +124,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase 'dev-master' => true, ); - $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO); + $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, new Config()); $packages = $repo->getPackages(); $dumper = new ArrayDumper(); diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index f0c8ab8fd..3756e9fa3 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -19,26 +19,29 @@ use Composer\Util\Filesystem; abstract class TestCase extends \PHPUnit_Framework_TestCase { - private static $versionParser; + private static $parser; - public static function setUpBeforeClass() + protected static function getVersionParser() { - if (!self::$versionParser) { - self::$versionParser = new VersionParser(); + if (!self::$parser) { + self::$parser = new VersionParser(); } + + return self::$parser; } protected function getVersionConstraint($operator, $version) { return new VersionConstraint( $operator, - self::$versionParser->normalize($version) + self::getVersionParser()->normalize($version) ); } protected function getPackage($name, $version) { - $normVersion = self::$versionParser->normalize($version); + $normVersion = self::getVersionParser()->normalize($version); + return new MemoryPackage($name, $normVersion, $version); } diff --git a/tests/Composer/Test/Util/StreamContextFactoryTest.php b/tests/Composer/Test/Util/StreamContextFactoryTest.php index 0e9928ede..b50c0cef8 100644 --- a/tests/Composer/Test/Util/StreamContextFactoryTest.php +++ b/tests/Composer/Test/Util/StreamContextFactoryTest.php @@ -57,30 +57,48 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase public function testHttpProxy() { - $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:1234'; - $_SERVER['HTTP_PROXY'] = 'http://proxyserver'; + $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:3128/'; + $_SERVER['HTTP_PROXY'] = 'http://proxyserver/'; $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET'))); $options = stream_context_get_options($context); $this->assertEquals(array('http' => array( - 'proxy' => 'tcp://proxyserver.net:1234', + 'proxy' => 'tcp://proxyserver.net:3128', 'request_fulluri' => true, 'method' => 'GET', 'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n" )), $options); } - public function testSSLProxy() + public function testHttpProxyWithoutPort() { - $_SERVER['http_proxy'] = 'https://proxyserver'; + $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net'; + + $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET'))); + $options = stream_context_get_options($context); + + $this->assertEquals(array('http' => array( + 'proxy' => 'tcp://proxyserver.net:80', + 'request_fulluri' => true, + 'method' => 'GET', + 'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n" + )), $options); + } + + /** + * @dataProvider dataSSLProxy + */ + public function testSSLProxy($expected, $proxy) + { + $_SERVER['http_proxy'] = $proxy; if (extension_loaded('openssl')) { $context = StreamContextFactory::getContext(); $options = stream_context_get_options($context); - $this->assertSame(array('http' => array( - 'proxy' => 'ssl://proxyserver', + $this->assertEquals(array('http' => array( + 'proxy' => $expected, 'request_fulluri' => true, )), $options); } else { @@ -92,4 +110,12 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase } } } + + public function dataSSLProxy() + { + return array( + array('ssl://proxyserver:443', 'https://proxyserver/'), + array('ssl://proxyserver:8443', 'https://proxyserver:8443'), + ); + } }