Merge remote-tracking branch 'upstream/master' into repro-4795
* upstream/master: (98 commits) Fallback to zlib extension to unpack gzip on non Windows systems Zip extension does not provide zlib support Unified all Windows tests throughout the code. Added Platform utility and unit test for it. Remove warnings for non-writable dirs, refs #3588 [doc] add -H flag to sudo commands use full json content to determine reference, closes #4859 typos Make sure COMPOSER_AUTH is also loaded in Config, refs #4546 Use proper defaults for IO authentications Add verbosity input support to IOInterface Update SolverTest.php Update broken-deps-do-not-replace.test Update SolverProblemsException.php Cleaned up check+conversion that was no longer required. Cleaner notation for expected exceptions in fixtures. Introduced more generic, less invasive way to test for exceptions in fixtures, more in line with how phpunit works. Included unit test for circular root dependencies. Expanded InstallerTest to support expecting Exceptions by supplying "EXCEPTION" as "--EXPECT--" Clarified error message and added braces. ...pull/4817/head
commit
be5719eb53
32
.php_cs
32
.php_cs
|
@ -10,7 +10,7 @@ For the full copyright and license information, please view the LICENSE
|
||||||
file that was distributed with this source code.
|
file that was distributed with this source code.
|
||||||
EOF;
|
EOF;
|
||||||
|
|
||||||
$finder = Symfony\CS\Finder\DefaultFinder::create()
|
$finder = Symfony\CS\Finder::create()
|
||||||
->files()
|
->files()
|
||||||
->name('*.php')
|
->name('*.php')
|
||||||
->exclude('Fixtures')
|
->exclude('Fixtures')
|
||||||
|
@ -18,23 +18,27 @@ $finder = Symfony\CS\Finder\DefaultFinder::create()
|
||||||
->in(__DIR__.'/tests')
|
->in(__DIR__.'/tests')
|
||||||
;
|
;
|
||||||
|
|
||||||
return Symfony\CS\Config\Config::create()
|
return Symfony\CS\Config::create()
|
||||||
->setUsingCache(true)
|
->setUsingCache(true)
|
||||||
->setRiskyAllowed(true)
|
->setRiskyAllowed(true)
|
||||||
->setRules(array(
|
->setRules(array(
|
||||||
'@PSR2' => true,
|
'@PSR2' => true,
|
||||||
'duplicate_semicolon' => true,
|
'binary_operator_spaces' => true,
|
||||||
'extra_empty_lines' => true,
|
'blank_line_before_return' => true,
|
||||||
'header_comment' => array('header' => $header),
|
'header_comment' => array('header' => $header),
|
||||||
'include' => true,
|
'include' => true,
|
||||||
'long_array_syntax' => true,
|
'long_array_syntax' => true,
|
||||||
'method_separation' => true,
|
'method_separation' => true,
|
||||||
'multiline_array_trailing_comma' => true,
|
|
||||||
'namespace_no_leading_whitespace' => true,
|
|
||||||
'no_blank_lines_after_class_opening' => true,
|
'no_blank_lines_after_class_opening' => true,
|
||||||
'no_empty_lines_after_phpdocs' => true,
|
'no_blank_lines_after_phpdoc' => true,
|
||||||
'object_operator' => true,
|
'no_blank_lines_between_uses' => true,
|
||||||
'operators_spaces' => true,
|
'no_duplicate_semicolons' => true,
|
||||||
|
'no_extra_consecutive_blank_lines' => true,
|
||||||
|
'no_leading_import_slash' => true,
|
||||||
|
'no_leading_namespace_whitespace' => true,
|
||||||
|
'no_trailing_comma_in_singleline_array' => true,
|
||||||
|
'no_unused_imports' => true,
|
||||||
|
'object_operator_without_whitespace' => true,
|
||||||
'phpdoc_align' => true,
|
'phpdoc_align' => true,
|
||||||
'phpdoc_indent' => true,
|
'phpdoc_indent' => true,
|
||||||
'phpdoc_no_access' => true,
|
'phpdoc_no_access' => true,
|
||||||
|
@ -44,15 +48,11 @@ return Symfony\CS\Config\Config::create()
|
||||||
'phpdoc_trim' => true,
|
'phpdoc_trim' => true,
|
||||||
'phpdoc_type_to_var' => true,
|
'phpdoc_type_to_var' => true,
|
||||||
'psr0' => true,
|
'psr0' => true,
|
||||||
'return' => true,
|
|
||||||
'remove_leading_slash_use' => true,
|
|
||||||
'remove_lines_between_uses' => true,
|
|
||||||
'single_array_no_trailing_comma' => true,
|
|
||||||
'single_blank_line_before_namespace' => true,
|
'single_blank_line_before_namespace' => true,
|
||||||
'spaces_cast' => true,
|
'spaces_cast' => true,
|
||||||
'standardize_not_equal' => true,
|
'standardize_not_equals' => true,
|
||||||
'ternary_spaces' => true,
|
'ternary_operator_spaces' => true,
|
||||||
'unused_use' => true,
|
'trailing_comma_in_multiline_array' => true,
|
||||||
'whitespacy_lines' => true,
|
'whitespacy_lines' => true,
|
||||||
))
|
))
|
||||||
->finder($finder)
|
->finder($finder)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
* Added --strict to the `validate` command to treat any warning as an error that then returns a non-zero exit code
|
* Added --strict to the `validate` command to treat any warning as an error that then returns a non-zero exit code
|
||||||
* Added a dependency on composer/semver, which is the externalized lib for all the version constraints parsing and handling
|
* Added a dependency on composer/semver, which is the externalized lib for all the version constraints parsing and handling
|
||||||
* Added support for classmap autoloading to load plugin classes and script handlers
|
* Added support for classmap autoloading to load plugin classes and script handlers
|
||||||
* Added `bin-compat` config option that if set to `full` will create .bat proxy for binaries even if Compoesr runs in a linux VM
|
* Added `bin-compat` config option that if set to `full` will create .bat proxy for binaries even if Composer runs in a linux VM
|
||||||
* Added SPDX 2.0 support, and externalized that in a composer/spdx-licenses lib
|
* Added SPDX 2.0 support, and externalized that in a composer/spdx-licenses lib
|
||||||
* Added warnings when the classmap autoloader finds duplicate classes
|
* Added warnings when the classmap autoloader finds duplicate classes
|
||||||
* Added --file to the `archive` command to choose the filename
|
* Added --file to the `archive` command to choose the filename
|
||||||
|
|
|
@ -45,7 +45,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic",
|
"ext-zip": "Enabling the zip extension allows you to unzip archives",
|
||||||
|
"ext-zlib": "Allow gzip compression of HTTP requests",
|
||||||
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"
|
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"hash": "fdf4b487fa59607376721ebec4ff4783",
|
"hash": "31b3c13c89f8d6c810637ca1fe8fc6ae",
|
||||||
"content-hash": "454148e20b837d9755dee7862f9c7a5d",
|
"content-hash": "454148e20b837d9755dee7862f9c7a5d",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -109,7 +109,7 @@ mv composer.phar /usr/local/bin/composer
|
||||||
A quick copy-paste version including sudo:
|
A quick copy-paste version including sudo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer
|
curl -sS https://getcomposer.org/installer | sudo -H php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** On some versions of OSX the `/usr` directory does not exist by
|
> **Note:** On some versions of OSX the `/usr` directory does not exist by
|
||||||
|
|
|
@ -413,7 +413,7 @@ If you have installed Composer for your entire system (see [global installation]
|
||||||
you may have to run the command with `root` privileges
|
you may have to run the command with `root` privileges
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo composer self-update
|
sudo -H composer self-update
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
@ -466,6 +466,12 @@ changes to the repositories section by using it the following way:
|
||||||
php composer.phar config repositories.foo vcs https://github.com/foo/bar
|
php composer.phar config repositories.foo vcs https://github.com/foo/bar
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If your repository requires more configuration options, you can instead pass its JSON representation :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
php composer.phar config repositories.foo '{"type": "vcs", "url": "http://svn.example.org/my-project/", "trunk-path": "master"}'
|
||||||
|
```
|
||||||
|
|
||||||
## create-project
|
## create-project
|
||||||
|
|
||||||
You can use Composer to create new projects from an existing package. This is
|
You can use Composer to create new projects from an existing package. This is
|
||||||
|
@ -711,6 +717,13 @@ commands) to finish executing. The default value is 300 seconds (5 minutes).
|
||||||
By setting this environmental value, you can set a path to a certificate bundle
|
By setting this environmental value, you can set a path to a certificate bundle
|
||||||
file to be used during SSL/TLS peer verification.
|
file to be used during SSL/TLS peer verification.
|
||||||
|
|
||||||
|
### COMPOSER_AUTH
|
||||||
|
|
||||||
|
The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable.
|
||||||
|
The contents of the variable should be a JSON formatted object containing http-basic,
|
||||||
|
github-oauth, ... objects as needed, and following the
|
||||||
|
[spec from the config](06-config.md#gitlab-oauth).
|
||||||
|
|
||||||
### COMPOSER_DISCARD_CHANGES
|
### COMPOSER_DISCARD_CHANGES
|
||||||
|
|
||||||
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
|
This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
|
||||||
|
|
|
@ -639,6 +639,11 @@ file, you can use the following configuration:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the package is a local VCS repository, the version may be inferred by
|
||||||
|
the branch or tag that is currently checked out. Otherwise, the version should
|
||||||
|
be explicitly defined in the package's `composer.json` file. If the version
|
||||||
|
cannot be resolved by these means, it is assumed to be `dev-master`.
|
||||||
|
|
||||||
The local package will be symlinked if possible, in which case the output in
|
The local package will be symlinked if possible, in which case the output in
|
||||||
the console will read `Symlinked from ../../packages/my-package`. If symlinking
|
the console will read `Symlinked from ../../packages/my-package`. If symlinking
|
||||||
is _not_ possible the package will be copied. In that case, the console will
|
is _not_ possible the package will be copied. In that case, the console will
|
||||||
|
|
|
@ -55,9 +55,15 @@ php_openssl extension in php.ini.
|
||||||
|
|
||||||
## cafile
|
## cafile
|
||||||
|
|
||||||
A way to set the path to the openssl CA file. In PHP 5.6+ you should rather
|
Location of Certificate Authority file on local filesystem. In PHP 5.6+ you
|
||||||
set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to
|
should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should
|
||||||
detect your system CA file automatically.
|
be able to detect your system CA file automatically.
|
||||||
|
|
||||||
|
## capath
|
||||||
|
|
||||||
|
If cafile is not specified or if the certificate is not found there, the
|
||||||
|
directory pointed to by capath is searched for a suitable certificate.
|
||||||
|
capath must be a correctly hashed certificate directory.
|
||||||
|
|
||||||
## http-basic
|
## http-basic
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ Example:
|
||||||
"class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
|
"class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer-plugin-api": "1.0.0"
|
"composer-plugin-api": "^1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -89,9 +89,54 @@ Furthermore plugins may implement the
|
||||||
event handlers automatically registered with the `EventDispatcher` when the
|
event handlers automatically registered with the `EventDispatcher` when the
|
||||||
plugin is loaded.
|
plugin is loaded.
|
||||||
|
|
||||||
Plugin can subscribe to any of the available [script events](scripts.md#event-names).
|
To register a method to an event, implement the method `getSubscribedEvents()`
|
||||||
|
and have it return an array. The array key must be the
|
||||||
|
[event name](https://getcomposer.org/doc/articles/scripts.md#event-names)
|
||||||
|
and the value is the name of the method in this class to be called.
|
||||||
|
|
||||||
Example:
|
```php
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'post-autoload-dump' => 'methodToBeCalled',
|
||||||
|
// ^ event name ^ ^ method name ^
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the priority of an event handler is set to 0. The priorty can be
|
||||||
|
changed by attaching a tuple where the first value is the method name, as
|
||||||
|
before, and the second value is an integer representing the priority.
|
||||||
|
Higher integers represent higher priorities. Priortity 2 is called before
|
||||||
|
priority 1, etc.
|
||||||
|
|
||||||
|
```php
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// Will be called before events with priority 0
|
||||||
|
'post-autoload-dump' => array('methodToBeCalled', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If multiple methods should be called, then an array of tuples can be attached
|
||||||
|
to each event. The tuples do not need to include the priority. If it is
|
||||||
|
omitted, it will default to 0.
|
||||||
|
|
||||||
|
```php
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'post-autoload-dump' => array(
|
||||||
|
array('methodToBeCalled' ), // Priority defaults to 0
|
||||||
|
array('someOtherMethodName', 1), // This fires first
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's a complete example:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -76,16 +76,16 @@ This is a list of common pitfalls on using Composer, and how to avoid them.
|
||||||
|
|
||||||
## I have a dependency which contains a "repositories" definition in its composer.json, but it seems to be ignored.
|
## I have a dependency which contains a "repositories" definition in its composer.json, but it seems to be ignored.
|
||||||
|
|
||||||
The [`repositories`](04-schema.md#repositories) configuration property is defined as [root-only]
|
The [`repositories`](../04-schema.md#repositories) configuration property is defined as [root-only]
|
||||||
(04-schema.md#root-package). It is not inherited. You can read more about the reasons behind this in the "[why can't
|
(../04-schema.md#root-package). It is not inherited. You can read more about the reasons behind this in the "[why can't
|
||||||
composer load repositories recursively?](articles/why-can't-composer-load-repositories-recursively.md)" article.
|
composer load repositories recursively?](../faqs/why-can't-composer-load-repositories-recursively.md)" article.
|
||||||
The simplest work-around to this limitation, is moving or duplicating the `repositories` definition into your root
|
The simplest work-around to this limitation, is moving or duplicating the `repositories` definition into your root
|
||||||
composer.json.
|
composer.json.
|
||||||
|
|
||||||
## I have locked a dependency to a specific commit but get unexpected results.
|
## I have locked a dependency to a specific commit but get unexpected results.
|
||||||
|
|
||||||
While Composer supports locking dependencies to a specific commit using the `#commit-ref` syntax, there are certain
|
While Composer supports locking dependencies to a specific commit using the `#commit-ref` syntax, there are certain
|
||||||
caveats that one should take into account. The most important one is [documented](04-schema.md#package-links), but
|
caveats that one should take into account. The most important one is [documented](../04-schema.md#package-links), but
|
||||||
frequently overlooked:
|
frequently overlooked:
|
||||||
|
|
||||||
> **Note:** While this is convenient at times, it should not be how you use
|
> **Note:** While this is convenient at times, it should not be how you use
|
||||||
|
|
|
@ -149,6 +149,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."
|
"description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."
|
||||||
},
|
},
|
||||||
|
"capath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory."
|
||||||
|
},
|
||||||
"http-basic": {
|
"http-basic": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
|
"description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
namespace Composer\Autoload;
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ class ClassMapGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$contents = @php_strip_whitespace($path);
|
$contents = Silencer::call('php_strip_whitespace', $path);
|
||||||
if (!$contents) {
|
if (!$contents) {
|
||||||
if (!file_exists($path)) {
|
if (!file_exists($path)) {
|
||||||
throw new \Exception('File does not exist');
|
throw new \Exception('File does not exist');
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer;
|
||||||
|
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,7 +45,7 @@ class Cache
|
||||||
$this->filesystem = $filesystem ?: new Filesystem();
|
$this->filesystem = $filesystem ?: new Filesystem();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!is_dir($this->root) && !@mkdir($this->root, 0777, true))
|
(!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true))
|
||||||
|| !is_writable($this->root)
|
|| !is_writable($this->root)
|
||||||
) {
|
) {
|
||||||
$this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache</warning>');
|
$this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache</warning>');
|
||||||
|
@ -66,9 +67,7 @@ class Cache
|
||||||
{
|
{
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||||
if ($this->enabled && file_exists($this->root . $file)) {
|
if ($this->enabled && file_exists($this->root . $file)) {
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('Reading '.$this->root . $file.' from cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
return file_get_contents($this->root . $file);
|
return file_get_contents($this->root . $file);
|
||||||
}
|
}
|
||||||
|
@ -81,16 +80,12 @@ class Cache
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
$file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
|
||||||
|
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('Writing '.$this->root . $file.' into cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return file_put_contents($this->root . $file, $contents);
|
return file_put_contents($this->root . $file, $contents);
|
||||||
} catch (\ErrorException $e) {
|
} catch (\ErrorException $e) {
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>');
|
|
||||||
}
|
|
||||||
if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
|
if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
|
||||||
// Remove partial file.
|
// Remove partial file.
|
||||||
unlink($this->root . $file);
|
unlink($this->root . $file);
|
||||||
|
@ -151,9 +146,7 @@ class Cache
|
||||||
touch($this->root . $file);
|
touch($this->root . $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('Reading '.$this->root . $file.' from cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy($this->root . $file, $target);
|
return copy($this->root . $file, $target);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace Composer\Command;
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\Util\Platform;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -142,7 +144,7 @@ EOT
|
||||||
? ($this->config->get('home') . '/config.json')
|
? ($this->config->get('home') . '/config.json')
|
||||||
: ($input->getOption('file') ?: trim(getenv('COMPOSER')) ?: 'composer.json');
|
: ($input->getOption('file') ?: trim(getenv('COMPOSER')) ?: 'composer.json');
|
||||||
|
|
||||||
// create global composer.json if this was invoked using `composer global config`
|
// Create global composer.json if this was invoked using `composer global config`
|
||||||
if ($configFile === 'composer.json' && !file_exists($configFile) && realpath(getcwd()) === realpath($this->config->get('home'))) {
|
if ($configFile === 'composer.json' && !file_exists($configFile) && realpath(getcwd()) === realpath($this->config->get('home'))) {
|
||||||
file_put_contents($configFile, "{\n}\n");
|
file_put_contents($configFile, "{\n}\n");
|
||||||
}
|
}
|
||||||
|
@ -157,16 +159,16 @@ EOT
|
||||||
$this->authConfigFile = new JsonFile($authConfigFile, null, $io);
|
$this->authConfigFile = new JsonFile($authConfigFile, null, $io);
|
||||||
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
$this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);
|
||||||
|
|
||||||
// initialize the global file if it's not there
|
// Initialize the global file if it's not there, ignoring any warnings or notices
|
||||||
if ($input->getOption('global') && !$this->configFile->exists()) {
|
if ($input->getOption('global') && !$this->configFile->exists()) {
|
||||||
touch($this->configFile->getPath());
|
touch($this->configFile->getPath());
|
||||||
$this->configFile->write(array('config' => new \ArrayObject));
|
$this->configFile->write(array('config' => new \ArrayObject));
|
||||||
@chmod($this->configFile->getPath(), 0600);
|
Silencer::call('chmod', $this->configFile->getPath(), 0600);
|
||||||
}
|
}
|
||||||
if ($input->getOption('global') && !$this->authConfigFile->exists()) {
|
if ($input->getOption('global') && !$this->authConfigFile->exists()) {
|
||||||
touch($this->authConfigFile->getPath());
|
touch($this->authConfigFile->getPath());
|
||||||
$this->authConfigFile->write(array('http-basic' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject));
|
$this->authConfigFile->write(array('http-basic' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject));
|
||||||
@chmod($this->authConfigFile->getPath(), 0600);
|
Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->configFile->exists()) {
|
if (!$this->configFile->exists()) {
|
||||||
|
@ -183,7 +185,7 @@ EOT
|
||||||
if ($input->getOption('editor')) {
|
if ($input->getOption('editor')) {
|
||||||
$editor = escapeshellcmd(getenv('EDITOR'));
|
$editor = escapeshellcmd(getenv('EDITOR'));
|
||||||
if (!$editor) {
|
if (!$editor) {
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$editor = 'notepad';
|
$editor = 'notepad';
|
||||||
} else {
|
} else {
|
||||||
foreach (array('vim', 'vi', 'nano', 'pico', 'ed') as $candidate) {
|
foreach (array('vim', 'vi', 'nano', 'pico', 'ed') as $candidate) {
|
||||||
|
@ -196,7 +198,7 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
|
$file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
|
||||||
system($editor . ' ' . $file . (defined('PHP_WINDOWS_VERSION_BUILD') ? '' : ' > `tty`'));
|
system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`'));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -331,7 +333,11 @@ EOT
|
||||||
'disable-tls' => array($booleanValidator, $booleanNormalizer),
|
'disable-tls' => array($booleanValidator, $booleanNormalizer),
|
||||||
'cafile' => array(
|
'cafile' => array(
|
||||||
function ($val) { return file_exists($val) && is_readable($val); },
|
function ($val) { return file_exists($val) && is_readable($val); },
|
||||||
function ($val) { return $val === 'null' ? null : $val; }
|
function ($val) { return $val === 'null' ? null : $val; },
|
||||||
|
),
|
||||||
|
'capath' => array(
|
||||||
|
function ($val) { return is_dir($val) && is_readable($val); },
|
||||||
|
function ($val) { return $val === 'null' ? null : $val; },
|
||||||
),
|
),
|
||||||
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
|
||||||
);
|
);
|
||||||
|
@ -434,10 +440,19 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
if (1 === count($values)) {
|
if (1 === count($values)) {
|
||||||
$bool = strtolower($values[0]);
|
$value = strtolower($values[0]);
|
||||||
if (true === $booleanValidator($bool) && false === $booleanNormalizer($bool)) {
|
if (true === $booleanValidator($value)) {
|
||||||
|
if (false === $booleanNormalizer($value)) {
|
||||||
return $this->configSource->addRepository($matches[1], false);
|
return $this->configSource->addRepository($matches[1], false);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$value = json_decode($values[0], true);
|
||||||
|
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('%s is not valid JSON.', $values[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->configSource->addRepository($matches[1], $value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
|
throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
|
||||||
|
|
|
@ -27,6 +27,7 @@ use Composer\Repository\CompositeRepository;
|
||||||
use Composer\Repository\FilesystemRepository;
|
use Composer\Repository\FilesystemRepository;
|
||||||
use Composer\Repository\InstalledFilesystemRepository;
|
use Composer\Repository\InstalledFilesystemRepository;
|
||||||
use Composer\Script\ScriptEvents;
|
use Composer\Script\ScriptEvents;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -35,7 +36,6 @@ use Symfony\Component\Finder\Finder;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\Config\JsonConfigSource;
|
use Composer\Config\JsonConfigSource;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Composer\Util\RemoteFilesystem;
|
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,10 +224,10 @@ EOT
|
||||||
chdir($oldCwd);
|
chdir($oldCwd);
|
||||||
$vendorComposerDir = $composer->getConfig()->get('vendor-dir').'/composer';
|
$vendorComposerDir = $composer->getConfig()->get('vendor-dir').'/composer';
|
||||||
if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) {
|
if (is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) {
|
||||||
@rmdir($vendorComposerDir);
|
Silencer::call('rmdir', $vendorComposerDir);
|
||||||
$vendorDir = $composer->getConfig()->get('vendor-dir');
|
$vendorDir = $composer->getConfig()->get('vendor-dir');
|
||||||
if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) {
|
if (is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) {
|
||||||
@rmdir($vendorDir);
|
Silencer::call('rmdir', $vendorDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ use Composer\Util\ConfigValidator;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
use Composer\Util\StreamContextFactory;
|
use Composer\Util\StreamContextFactory;
|
||||||
|
use Composer\Util\Keys;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
@ -133,6 +134,9 @@ EOT
|
||||||
$io->write('Checking disk free space: ', false);
|
$io->write('Checking disk free space: ', false);
|
||||||
$this->outputResult($this->checkDiskSpace($config));
|
$this->outputResult($this->checkDiskSpace($config));
|
||||||
|
|
||||||
|
$io->write('Checking pubkeys: ', false);
|
||||||
|
$this->outputResult($this->checkPubKeys($config));
|
||||||
|
|
||||||
$io->write('Checking composer version: ', false);
|
$io->write('Checking composer version: ', false);
|
||||||
$this->outputResult($this->checkVersion());
|
$this->outputResult($this->checkVersion());
|
||||||
|
|
||||||
|
@ -327,6 +331,35 @@ EOT
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkPubKeys($config)
|
||||||
|
{
|
||||||
|
$home = $config->get('home');
|
||||||
|
$errors = array();
|
||||||
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) {
|
||||||
|
$io->write('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($home.'/keys.tags.pub')) {
|
||||||
|
$io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub'));
|
||||||
|
} else {
|
||||||
|
$errors[] = '<error>Missing pubkey for tags verification</error>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($home.'/keys.dev.pub')) {
|
||||||
|
$io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub'));
|
||||||
|
} else {
|
||||||
|
$errors[] = '<error>Missing pubkey for dev verification</error>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
$errors[] = '<error>Run composer self-update --update-keys to set them up</error>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors ?: true;
|
||||||
|
}
|
||||||
|
|
||||||
private function checkVersion()
|
private function checkVersion()
|
||||||
{
|
{
|
||||||
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
$protocol = extension_loaded('openssl') ? 'https' : 'http';
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Factory;
|
||||||
use Composer\Package\CompletePackageInterface;
|
use Composer\Package\CompletePackageInterface;
|
||||||
use Composer\Repository\RepositoryInterface;
|
use Composer\Repository\RepositoryInterface;
|
||||||
use Composer\Repository\ArrayRepository;
|
use Composer\Repository\ArrayRepository;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -117,7 +118,7 @@ EOT
|
||||||
{
|
{
|
||||||
$url = ProcessExecutor::escape($url);
|
$url = ProcessExecutor::escape($url);
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
if (Platform::isWindows()) {
|
||||||
return passthru('start "web" explorer "' . $url . '"');
|
return passthru('start "web" explorer "' . $url . '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ class RemoveCommand extends Command
|
||||||
->setDefinition(array(
|
->setDefinition(array(
|
||||||
new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.'),
|
new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.'),
|
||||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
|
new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
|
||||||
|
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
|
||||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||||
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
||||||
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
||||||
|
@ -92,7 +93,7 @@ EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update packages
|
// Update packages
|
||||||
$composer = $this->getComposer();
|
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||||
|
|
||||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
|
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
|
||||||
|
|
|
@ -42,6 +42,7 @@ class RequireCommand extends InitCommand
|
||||||
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
|
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
|
||||||
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
|
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
|
||||||
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
|
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
|
||||||
|
new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
|
||||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||||
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
|
||||||
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
|
||||||
|
@ -93,7 +94,7 @@ EOT
|
||||||
$composerDefinition = $json->read();
|
$composerDefinition = $json->read();
|
||||||
$composerBackup = file_get_contents($json->getPath());
|
$composerBackup = file_get_contents($json->getPath());
|
||||||
|
|
||||||
$composer = $this->getComposer();
|
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||||
$repos = $composer->getRepositoryManager()->getRepositories();
|
$repos = $composer->getRepositoryManager()->getRepositories();
|
||||||
|
|
||||||
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
||||||
|
@ -143,7 +144,7 @@ EOT
|
||||||
|
|
||||||
// Update packages
|
// Update packages
|
||||||
$this->resetComposer();
|
$this->resetComposer();
|
||||||
$composer = $this->getComposer();
|
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
|
||||||
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
|
||||||
|
|
||||||
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
|
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
|
||||||
|
|
|
@ -14,8 +14,10 @@ namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\Composer;
|
use Composer\Composer;
|
||||||
use Composer\Factory;
|
use Composer\Factory;
|
||||||
|
use Composer\Config;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\Keys;
|
||||||
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Downloader\FilesystemException;
|
use Composer\Downloader\FilesystemException;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
@ -44,6 +46,7 @@ class SelfUpdateCommand extends Command
|
||||||
new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
|
new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
|
||||||
new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
|
new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
|
||||||
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
|
||||||
|
new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'),
|
||||||
))
|
))
|
||||||
->setHelp(<<<EOT
|
->setHelp(<<<EOT
|
||||||
The <info>self-update</info> command checks getcomposer.org for newer
|
The <info>self-update</info> command checks getcomposer.org for newer
|
||||||
|
@ -71,8 +74,13 @@ EOT
|
||||||
|
|
||||||
$cacheDir = $config->get('cache-dir');
|
$cacheDir = $config->get('cache-dir');
|
||||||
$rollbackDir = $config->get('data-dir');
|
$rollbackDir = $config->get('data-dir');
|
||||||
|
$home = $config->get('home');
|
||||||
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
|
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
|
||||||
|
|
||||||
|
if ($input->getOption('update-keys')) {
|
||||||
|
return $this->fetchKeys($io, $config);
|
||||||
|
}
|
||||||
|
|
||||||
// check if current dir is writable and if not try the cache dir from settings
|
// check if current dir is writable and if not try the cache dir from settings
|
||||||
$tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;
|
$tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;
|
||||||
|
|
||||||
|
@ -80,9 +88,6 @@ EOT
|
||||||
if (!is_writable($tmpDir)) {
|
if (!is_writable($tmpDir)) {
|
||||||
throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
|
throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
|
||||||
}
|
}
|
||||||
if (!is_writable($localFilename)) {
|
|
||||||
throw new FilesystemException('Composer update failed: the "'.$localFilename.'" file could not be written');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input->getOption('rollback')) {
|
if ($input->getOption('rollback')) {
|
||||||
return $this->rollback($output, $rollbackDir, $localFilename);
|
return $this->rollback($output, $rollbackDir, $localFilename);
|
||||||
|
@ -112,15 +117,79 @@ EOT
|
||||||
self::OLD_INSTALL_EXT
|
self::OLD_INSTALL_EXT
|
||||||
);
|
);
|
||||||
|
|
||||||
$io->writeError(sprintf("Updating to version <info>%s</info>.", $updateVersion));
|
$updatingToTag = !preg_match('{^[0-9a-f]{40}$}', $updateVersion);
|
||||||
$remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar");
|
|
||||||
|
$io->write(sprintf("Updating to version <info>%s</info>.", $updateVersion));
|
||||||
|
$remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
|
||||||
|
$signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false);
|
||||||
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
|
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
|
||||||
if (!file_exists($tempFilename)) {
|
if (!file_exists($tempFilename) || !$signature) {
|
||||||
$io->writeError('<error>The download of the new composer version failed for an unexpected reason</error>');
|
$io->writeError('<error>The download of the new composer version failed for an unexpected reason</error>');
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify phar signature
|
||||||
|
if (!extension_loaded('openssl') && $config->get('disable-tls')) {
|
||||||
|
$io->writeError('<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>');
|
||||||
|
} else {
|
||||||
|
if (!extension_loaded('openssl')) {
|
||||||
|
throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. '
|
||||||
|
. 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub');
|
||||||
|
if (!file_exists($sigFile)) {
|
||||||
|
file_put_contents($home.'/keys.dev.pub', <<<DEVPUBKEY
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
|
||||||
|
FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
|
||||||
|
i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
|
||||||
|
hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
|
||||||
|
o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
|
||||||
|
8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
|
||||||
|
8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
|
||||||
|
TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
|
||||||
|
pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
|
||||||
|
8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
|
||||||
|
r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
|
||||||
|
wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
DEVPUBKEY
|
||||||
|
);
|
||||||
|
file_put_contents($home.'/keys.tags.pub', <<<TAGSPUBKEY
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
|
||||||
|
MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
|
||||||
|
vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
|
||||||
|
bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
|
||||||
|
mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
|
||||||
|
noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
|
||||||
|
nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
|
||||||
|
rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
|
||||||
|
RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
|
||||||
|
tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
|
||||||
|
TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
|
||||||
|
RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
TAGSPUBKEY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pubkeyid = openssl_pkey_get_public($sigFile);
|
||||||
|
$algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
|
||||||
|
if (!in_array('SHA384', openssl_get_md_methods())) {
|
||||||
|
throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
|
||||||
|
}
|
||||||
|
$signature = json_decode($signature, true);
|
||||||
|
$signature = base64_decode($signature['sha384']);
|
||||||
|
$verified = 1 === openssl_verify(file_get_contents($tempFilename), $signature, $pubkeyid, $algo);
|
||||||
|
openssl_free_key($pubkeyid);
|
||||||
|
if (!$verified) {
|
||||||
|
throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// remove saved installations of composer
|
// remove saved installations of composer
|
||||||
if ($input->getOption('clean-backups')) {
|
if ($input->getOption('clean-backups')) {
|
||||||
$finder = $this->getOldInstallationFinder($rollbackDir);
|
$finder = $this->getOldInstallationFinder($rollbackDir);
|
||||||
|
@ -147,6 +216,51 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function fetchKeys(IOInterface $io, Config $config)
|
||||||
|
{
|
||||||
|
if (!$io->isInteractive()) {
|
||||||
|
throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively');
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->write('Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys');
|
||||||
|
|
||||||
|
$validator = function ($value) {
|
||||||
|
if (!preg_match('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) {
|
||||||
|
throw new \UnexpectedValueException('Invalid input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($value)."\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
$devKey = '';
|
||||||
|
while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) {
|
||||||
|
$devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator);
|
||||||
|
while ($line = $io->ask('')) {
|
||||||
|
$devKey .= trim($line)."\n";
|
||||||
|
if (trim($line) === '-----END PUBLIC KEY-----') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]);
|
||||||
|
$io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));
|
||||||
|
|
||||||
|
$tagsKey = '';
|
||||||
|
while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) {
|
||||||
|
$tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator);
|
||||||
|
while ($line = $io->ask('')) {
|
||||||
|
$tagsKey .= trim($line)."\n";
|
||||||
|
if (trim($line) === '-----END PUBLIC KEY-----') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]);
|
||||||
|
$io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));
|
||||||
|
|
||||||
|
$io->write('Public keys stored in '.$config->get('home'));
|
||||||
|
}
|
||||||
|
|
||||||
protected function rollback(OutputInterface $output, $rollbackDir, $localFilename)
|
protected function rollback(OutputInterface $output, $rollbackDir, $localFilename)
|
||||||
{
|
{
|
||||||
$rollbackVersion = $this->getLastBackupVersion($rollbackDir);
|
$rollbackVersion = $this->getLastBackupVersion($rollbackDir);
|
||||||
|
@ -154,10 +268,6 @@ EOT
|
||||||
throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
|
throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_writable($rollbackDir)) {
|
|
||||||
throw new FilesystemException('Composer rollback failed: the "'.$rollbackDir.'" dir could not be written to');
|
|
||||||
}
|
|
||||||
|
|
||||||
$old = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;
|
$old = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;
|
||||||
|
|
||||||
if (!is_file($old)) {
|
if (!is_file($old)) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Semver\VersionParser;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
@ -232,7 +233,7 @@ EOT
|
||||||
// outside of a real terminal, use space without a limit
|
// outside of a real terminal, use space without a limit
|
||||||
$width = PHP_INT_MAX;
|
$width = PHP_INT_MAX;
|
||||||
}
|
}
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$width--;
|
$width--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,10 +247,10 @@ EOT
|
||||||
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width);
|
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width);
|
||||||
foreach ($packages[$type] as $package) {
|
foreach ($packages[$type] as $package) {
|
||||||
if (is_object($package)) {
|
if (is_object($package)) {
|
||||||
$output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
|
$io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
|
||||||
|
|
||||||
if ($writeVersion) {
|
if ($writeVersion) {
|
||||||
$output->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false);
|
$io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($writeDescription) {
|
if ($writeDescription) {
|
||||||
|
@ -258,15 +259,15 @@ EOT
|
||||||
if (strlen($description) > $remaining) {
|
if (strlen($description) > $remaining) {
|
||||||
$description = substr($description, 0, $remaining - 3) . '...';
|
$description = substr($description, 0, $remaining - 3) . '...';
|
||||||
}
|
}
|
||||||
$output->write(' ' . $description);
|
$io->write(' ' . $description, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($writePath) {
|
if ($writePath) {
|
||||||
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
|
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
|
||||||
$output->write(' ' . $path);
|
$io->write(' ' . $path, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$output->write($indent . $package);
|
$io->write($indent . $package, false);
|
||||||
}
|
}
|
||||||
$io->write('');
|
$io->write('');
|
||||||
}
|
}
|
||||||
|
@ -489,10 +490,10 @@ EOT
|
||||||
$packagesInTree = array();
|
$packagesInTree = array();
|
||||||
$packagesInTree[] = $package;
|
$packagesInTree[] = $package;
|
||||||
|
|
||||||
$output->write(sprintf('<info>%s</info>', $package->getPrettyName()));
|
$io = $this->getIO();
|
||||||
$output->write(' ' . $package->getPrettyVersion());
|
$io->write(sprintf('<info>%s</info>', $package->getPrettyName()), false);
|
||||||
$output->write(' ' . strtok($package->getDescription(), "\r\n"));
|
$io->write(' ' . $package->getPrettyVersion(), false);
|
||||||
$output->writeln('');
|
$io->write(' ' . strtok($package->getDescription(), "\r\n"));
|
||||||
|
|
||||||
if (is_object($package)) {
|
if (is_object($package)) {
|
||||||
$requires = $package->getRequires();
|
$requires = $package->getRequires();
|
||||||
|
@ -531,7 +532,7 @@ EOT
|
||||||
* @param array $packagesInTree
|
* @param array $packagesInTree
|
||||||
* @param OutputInterface $output
|
* @param OutputInterface $output
|
||||||
* @param string $previousTreeBar
|
* @param string $previousTreeBar
|
||||||
* @param integer $level
|
* @param int $level
|
||||||
*/
|
*/
|
||||||
protected function displayTree($name, $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos, array $packagesInTree, OutputInterface $output, $previousTreeBar = '├', $level = 1)
|
protected function displayTree($name, $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos, array $packagesInTree, OutputInterface $output, $previousTreeBar = '├', $level = 1)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,6 +47,7 @@ class Config
|
||||||
'github-domains' => array('github.com'),
|
'github-domains' => array('github.com'),
|
||||||
'disable-tls' => false,
|
'disable-tls' => false,
|
||||||
'cafile' => null,
|
'cafile' => null,
|
||||||
|
'capath' => null,
|
||||||
'github-expose-hostname' => true,
|
'github-expose-hostname' => true,
|
||||||
'gitlab-domains' => array('gitlab.com'),
|
'gitlab-domains' => array('gitlab.com'),
|
||||||
'store-auths' => 'prompt',
|
'store-auths' => 'prompt',
|
||||||
|
@ -179,6 +180,7 @@ class Config
|
||||||
case 'cache-repo-dir':
|
case 'cache-repo-dir':
|
||||||
case 'cache-vcs-dir':
|
case 'cache-vcs-dir':
|
||||||
case 'cafile':
|
case 'cafile':
|
||||||
|
case 'capath':
|
||||||
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
|
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
|
||||||
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
|
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
|
||||||
|
|
||||||
|
@ -189,7 +191,7 @@ class Config
|
||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($flags & self::RELATIVE_PATHS == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
|
return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
|
||||||
|
|
||||||
case 'cache-ttl':
|
case 'cache-ttl':
|
||||||
return (int) $this->config[$key];
|
return (int) $this->config[$key];
|
||||||
|
@ -343,7 +345,7 @@ class Config
|
||||||
*/
|
*/
|
||||||
private function realpath($path)
|
private function realpath($path)
|
||||||
{
|
{
|
||||||
if (substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':') {
|
if (preg_match('{^(?:/|[a-z]:|[a-z0-9.]+://)}i', $path)) {
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Config;
|
||||||
|
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\Json\JsonManipulator;
|
use Composer\Json\JsonManipulator;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON Configuration Source
|
* JSON Configuration Source
|
||||||
|
@ -173,7 +174,7 @@ class JsonConfigSource implements ConfigSourceInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($newFile) {
|
if ($newFile) {
|
||||||
@chmod($this->file->getPath(), 0600);
|
Silencer::call('chmod', $this->file->getPath(), 0600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
|
|
||||||
namespace Composer\Console;
|
namespace Composer\Console;
|
||||||
|
|
||||||
|
use Composer\Util\Platform;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Console\Application as BaseApplication;
|
use Symfony\Component\Console\Application as BaseApplication;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
|
@ -64,7 +65,7 @@ class Application extends BaseApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
|
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
|
||||||
date_default_timezone_set(@date_default_timezone_get());
|
date_default_timezone_set(Silencer::call('date_default_timezone_get'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$shutdownRegistered) {
|
if (!$shutdownRegistered) {
|
||||||
|
@ -136,9 +137,7 @@ class Application extends BaseApplication
|
||||||
if ($newWorkDir = $this->getNewWorkingDir($input)) {
|
if ($newWorkDir = $this->getNewWorkingDir($input)) {
|
||||||
$oldWorkingDir = getcwd();
|
$oldWorkingDir = getcwd();
|
||||||
chdir($newWorkDir);
|
chdir($newWorkDir);
|
||||||
if ($io->isDebug() >= 4) {
|
$io->writeError('Changed CWD to ' . getcwd(), true, IOInterface::DEBUG);
|
||||||
$io->writeError('Changed CWD to ' . getcwd());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add non-standard scripts as own commands
|
// add non-standard scripts as own commands
|
||||||
|
@ -203,30 +202,32 @@ class Application extends BaseApplication
|
||||||
{
|
{
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
Silencer::suppress();
|
||||||
try {
|
try {
|
||||||
$composer = $this->getComposer(false, true);
|
$composer = $this->getComposer(false, true);
|
||||||
if ($composer) {
|
if ($composer) {
|
||||||
$config = $composer->getConfig();
|
$config = $composer->getConfig();
|
||||||
|
|
||||||
$minSpaceFree = 1024 * 1024;
|
$minSpaceFree = 1024 * 1024;
|
||||||
if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
|
if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
|
||||||
|| (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
|
|| (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
|
||||||
|| (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
|
|| (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
|
||||||
) {
|
) {
|
||||||
$io->writeError('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>');
|
$io->writeError('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>', true, IOInterface::QUIET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
|
Silencer::restore();
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
|
if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
|
||||||
$io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>');
|
$io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>', true, IOInterface::QUIET);
|
||||||
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>');
|
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
|
if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
|
||||||
$io->writeError('<error>The following exception is caused by a lack of memory and not having swap configured</error>');
|
$io->writeError('<error>The following exception is caused by a lack of memory and not having swap configured</error>', true, IOInterface::QUIET);
|
||||||
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>');
|
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>', true, IOInterface::QUIET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,12 +31,21 @@ class SolverProblemsException extends \RuntimeException
|
||||||
protected function createMessage()
|
protected function createMessage()
|
||||||
{
|
{
|
||||||
$text = "\n";
|
$text = "\n";
|
||||||
|
$hasExtensionProblems = false;
|
||||||
foreach ($this->problems as $i => $problem) {
|
foreach ($this->problems as $i => $problem) {
|
||||||
$text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n";
|
$text .= " Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n";
|
||||||
|
|
||||||
|
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
|
||||||
|
$hasExtensionProblems = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
|
if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
|
||||||
$text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
$text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasExtensionProblems) {
|
||||||
|
$text .= $this->createExtensionHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $text;
|
return $text;
|
||||||
|
@ -46,4 +55,40 @@ class SolverProblemsException extends \RuntimeException
|
||||||
{
|
{
|
||||||
return $this->problems;
|
return $this->problems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createExtensionHint()
|
||||||
|
{
|
||||||
|
$paths = array();
|
||||||
|
|
||||||
|
if (($iniPath = php_ini_loaded_file()) !== false) {
|
||||||
|
$paths[] = $iniPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('HHVM_VERSION') && $additionalIniPaths = php_ini_scanned_files()) {
|
||||||
|
$paths = array_merge($paths, array_map("trim", explode(",", $additionalIniPaths)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($paths) === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = "\n To enable extensions, verify that they are enabled in those .ini files:\n - ";
|
||||||
|
$text .= implode("\n - ", $paths);
|
||||||
|
$text .= "\n You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.";
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasExtensionProblems(array $reasonSets)
|
||||||
|
{
|
||||||
|
foreach ($reasonSets as $reasonSet) {
|
||||||
|
foreach ($reasonSet as $reason) {
|
||||||
|
if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Downloader;
|
||||||
|
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Composer\IO\IOInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base downloader for archives
|
* Base downloader for archives
|
||||||
|
@ -34,9 +35,7 @@ abstract class ArchiveDownloader extends FileDownloader
|
||||||
while ($retries--) {
|
while ($retries--) {
|
||||||
$fileName = parent::download($package, $path);
|
$fileName = parent::download($package, $path);
|
||||||
|
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError(' Extracting archive');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
$this->filesystem->ensureDirectoryExists($temporaryDir);
|
||||||
|
|
|
@ -141,9 +141,7 @@ class FileDownloader implements DownloaderInterface
|
||||||
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
|
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError(' Download failed, retrying...');
|
|
||||||
}
|
|
||||||
usleep(500000);
|
usleep(500000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Downloader;
|
||||||
|
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Util\Git as GitUtil;
|
use Composer\Util\Git as GitUtil;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
@ -43,7 +44,7 @@ class GitDownloader extends VcsDownloader
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
|
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
$flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : '';
|
$flag = Platform::isWindows() ? '/D ' : '';
|
||||||
$command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer';
|
$command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer';
|
||||||
$this->io->writeError(" Cloning ".$ref);
|
$this->io->writeError(" Cloning ".$ref);
|
||||||
|
|
||||||
|
@ -353,7 +354,7 @@ class GitDownloader extends VcsDownloader
|
||||||
|
|
||||||
protected function normalizePath($path)
|
protected function normalizePath($path)
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) {
|
if (Platform::isWindows() && strlen($path) > 0) {
|
||||||
$basePath = $path;
|
$basePath = $path;
|
||||||
$removed = array();
|
$removed = array();
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Composer\Config;
|
||||||
use Composer\Cache;
|
use Composer\Cache;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
@ -40,25 +41,26 @@ class GzipDownloader extends ArchiveDownloader
|
||||||
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));
|
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));
|
||||||
|
|
||||||
// Try to use gunzip on *nix
|
// Try to use gunzip on *nix
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (!Platform::isWindows()) {
|
||||||
$command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);
|
$command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);
|
||||||
|
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension_loaded('zlib')) {
|
||||||
|
// Fallback to using the PHP extension.
|
||||||
|
$this->extractUsingExt($file, $targetFilepath);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
||||||
throw new \RuntimeException($processError);
|
throw new \RuntimeException($processError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows version of PHP has built-in support of gzip functions
|
// Windows version of PHP has built-in support of gzip functions
|
||||||
$archiveFile = gzopen($file, 'rb');
|
$this->extractUsingExt($file, $targetFilepath);
|
||||||
$targetFile = fopen($targetFilepath, 'wb');
|
|
||||||
while ($string = gzread($archiveFile, 4096)) {
|
|
||||||
fwrite($targetFile, $string, strlen($string));
|
|
||||||
}
|
|
||||||
gzclose($archiveFile);
|
|
||||||
fclose($targetFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,4 +70,15 @@ class GzipDownloader extends ArchiveDownloader
|
||||||
{
|
{
|
||||||
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function extractUsingExt($file, $targetFilepath)
|
||||||
|
{
|
||||||
|
$archiveFile = gzopen($file, 'rb');
|
||||||
|
$targetFile = fopen($targetFilepath, 'wb');
|
||||||
|
while ($string = gzread($archiveFile, 4096)) {
|
||||||
|
fwrite($targetFile, $string, strlen($string));
|
||||||
|
}
|
||||||
|
gzclose($archiveFile);
|
||||||
|
fclose($targetFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Downloader;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Cache;
|
use Composer\Cache;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
@ -42,7 +43,7 @@ class RarDownloader extends ArchiveDownloader
|
||||||
$processError = null;
|
$processError = null;
|
||||||
|
|
||||||
// Try to use unrar on *nix
|
// Try to use unrar on *nix
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (!Platform::isWindows()) {
|
||||||
$command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
$command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
||||||
|
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
|
@ -65,7 +66,7 @@ class RarDownloader extends ArchiveDownloader
|
||||||
$error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
|
$error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
|
||||||
. $iniMessage . "\n" . $processError;
|
. $iniMessage . "\n" . $processError;
|
||||||
|
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (!Platform::isWindows()) {
|
||||||
$error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
|
$error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer\Downloader;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Cache;
|
use Composer\Cache;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
@ -38,7 +39,7 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
$processError = null;
|
$processError = null;
|
||||||
|
|
||||||
// try to use unzip on *nix
|
// try to use unzip on *nix
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (!Platform::isWindows()) {
|
||||||
$command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
$command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path);
|
||||||
try {
|
try {
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
|
@ -64,7 +65,7 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
$error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n"
|
$error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n"
|
||||||
. $iniMessage . "\n" . $processError;
|
. $iniMessage . "\n" . $processError;
|
||||||
|
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (!Platform::isWindows()) {
|
||||||
$error = "Could not decompress the archive, enable the PHP zip extension.\n" . $iniMessage;
|
$error = "Could not decompress the archive, enable the PHP zip extension.\n" . $iniMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,9 +155,7 @@ class EventDispatcher
|
||||||
$event = $this->checkListenerExpectedEvent($callable, $event);
|
$event = $this->checkListenerExpectedEvent($callable, $event);
|
||||||
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
$return = false === call_user_func($callable, $event) ? 1 : 0;
|
||||||
} elseif ($this->isComposerScript($callable)) {
|
} elseif ($this->isComposerScript($callable)) {
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable));
|
|
||||||
}
|
|
||||||
$scriptName = substr($callable, 1);
|
$scriptName = substr($callable, 1);
|
||||||
$args = $event->getArguments();
|
$args = $event->getArguments();
|
||||||
$flags = $event->getFlags();
|
$flags = $event->getFlags();
|
||||||
|
|
|
@ -20,8 +20,10 @@ use Composer\Package\Version\VersionGuesser;
|
||||||
use Composer\Repository\RepositoryManager;
|
use Composer\Repository\RepositoryManager;
|
||||||
use Composer\Repository\WritableRepositoryInterface;
|
use Composer\Repository\WritableRepositoryInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
use Composer\EventDispatcher\EventDispatcher;
|
use Composer\EventDispatcher\EventDispatcher;
|
||||||
use Composer\Autoload\AutoloadGenerator;
|
use Composer\Autoload\AutoloadGenerator;
|
||||||
|
@ -40,8 +42,8 @@ use Seld\JsonLint\JsonParser;
|
||||||
class Factory
|
class Factory
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return string
|
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected static function getHomeDir()
|
protected static function getHomeDir()
|
||||||
{
|
{
|
||||||
|
@ -50,7 +52,7 @@ class Factory
|
||||||
return $home;
|
return $home;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
if (Platform::isWindows()) {
|
||||||
if (!getenv('APPDATA')) {
|
if (!getenv('APPDATA')) {
|
||||||
throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
|
throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,7 @@ class Factory
|
||||||
return $homeEnv . '/cache';
|
return $homeEnv . '/cache';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
if (Platform::isWindows()) {
|
||||||
if ($cacheDir = getenv('LOCALAPPDATA')) {
|
if ($cacheDir = getenv('LOCALAPPDATA')) {
|
||||||
$cacheDir .= '/Composer';
|
$cacheDir .= '/Composer';
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,7 +126,7 @@ class Factory
|
||||||
return $homeEnv;
|
return $homeEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
|
if (Platform::isWindows()) {
|
||||||
return strtr($home, '\\', '/');
|
return strtr($home, '\\', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +165,9 @@ class Factory
|
||||||
foreach ($dirs as $dir) {
|
foreach ($dirs as $dir) {
|
||||||
if (!file_exists($dir . '/.htaccess')) {
|
if (!file_exists($dir . '/.htaccess')) {
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
@mkdir($dir, 0777, true);
|
Silencer::call('mkdir', $dir, 0777, true);
|
||||||
}
|
}
|
||||||
@file_put_contents($dir . '/.htaccess', 'Deny from all');
|
Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +191,20 @@ class Factory
|
||||||
}
|
}
|
||||||
$config->setAuthConfigSource(new JsonConfigSource($file, true));
|
$config->setAuthConfigSource(new JsonConfigSource($file, true));
|
||||||
|
|
||||||
|
// load COMPOSER_AUTH environment variable if set
|
||||||
|
if ($composerAuthEnv = getenv('COMPOSER_AUTH')) {
|
||||||
|
$authData = json_decode($composerAuthEnv, true);
|
||||||
|
|
||||||
|
if (is_null($authData)) {
|
||||||
|
throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($io && $io->isDebug()) {
|
||||||
|
$io->writeError('Loading auth config from COMPOSER_AUTH');
|
||||||
|
}
|
||||||
|
$config->merge(array('config' => $authData));
|
||||||
|
}
|
||||||
|
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,14 +308,10 @@ class Factory
|
||||||
$config = static::createConfig($io, $cwd);
|
$config = static::createConfig($io, $cwd);
|
||||||
$config->merge($localConfig);
|
$config->merge($localConfig);
|
||||||
if (isset($composerFile)) {
|
if (isset($composerFile)) {
|
||||||
if ($io && $io->isDebug()) {
|
$io->writeError('Loading config file ' . $composerFile, true, IOInterface::DEBUG);
|
||||||
$io->writeError('Loading config file ' . $composerFile);
|
|
||||||
}
|
|
||||||
$localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json');
|
$localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json');
|
||||||
if ($localAuthFile->exists()) {
|
if ($localAuthFile->exists()) {
|
||||||
if ($io && $io->isDebug()) {
|
$io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG);
|
||||||
$io->writeError('Loading config file ' . $localAuthFile->getPath());
|
|
||||||
}
|
|
||||||
$config->merge(array('config' => $localAuthFile->read()));
|
$config->merge(array('config' => $localAuthFile->read()));
|
||||||
$config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
|
$config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
|
||||||
}
|
}
|
||||||
|
@ -434,9 +446,7 @@ class Factory
|
||||||
try {
|
try {
|
||||||
$composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false);
|
$composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
if ($io->isDebug()) {
|
$io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG);
|
||||||
$io->writeError('Failed to initialize global composer: '.$e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $composer;
|
return $composer;
|
||||||
|
@ -590,9 +600,12 @@ class Factory
|
||||||
$remoteFilesystemOptions = array();
|
$remoteFilesystemOptions = array();
|
||||||
if ($disableTls === false) {
|
if ($disableTls === false) {
|
||||||
if ($config && $config->get('cafile')) {
|
if ($config && $config->get('cafile')) {
|
||||||
$remoteFilesystemOptions = array('ssl' => array('cafile' => $config->get('cafile')));
|
$remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
|
||||||
}
|
}
|
||||||
$remoteFilesystemOptions = array_merge_recursive($remoteFilesystemOptions, $options);
|
if ($config && $config->get('capath')) {
|
||||||
|
$remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
|
||||||
|
}
|
||||||
|
$remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
|
$remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $disableTls);
|
||||||
|
@ -612,7 +625,7 @@ class Factory
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return boolean
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private static function useXdg()
|
private static function useXdg()
|
||||||
{
|
{
|
||||||
|
@ -626,8 +639,8 @@ class Factory
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
private static function getUserDir()
|
private static function getUserDir()
|
||||||
{
|
{
|
||||||
|
|
|
@ -60,28 +60,26 @@ abstract class BaseIO implements IOInterface
|
||||||
*/
|
*/
|
||||||
public function loadConfiguration(Config $config)
|
public function loadConfiguration(Config $config)
|
||||||
{
|
{
|
||||||
|
$githubOauth = $config->get('github-oauth') ?: array();
|
||||||
|
$gitlabOauth = $config->get('gitlab-oauth') ?: array();
|
||||||
|
$httpBasic = $config->get('http-basic') ?: array();
|
||||||
|
|
||||||
// reload oauth token from config if available
|
// reload oauth token from config if available
|
||||||
if ($tokens = $config->get('github-oauth')) {
|
foreach ($githubOauth as $domain => $token) {
|
||||||
foreach ($tokens as $domain => $token) {
|
|
||||||
if (!preg_match('{^[a-z0-9]+$}', $token)) {
|
if (!preg_match('{^[a-z0-9]+$}', $token)) {
|
||||||
throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
|
throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
|
||||||
}
|
}
|
||||||
$this->setAuthentication($domain, $token, 'x-oauth-basic');
|
$this->setAuthentication($domain, $token, 'x-oauth-basic');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($tokens = $config->get('gitlab-oauth')) {
|
foreach ($gitlabOauth as $domain => $token) {
|
||||||
foreach ($tokens as $domain => $token) {
|
|
||||||
$this->setAuthentication($domain, $token, 'oauth2');
|
$this->setAuthentication($domain, $token, 'oauth2');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// reload http basic credentials from config if available
|
// reload http basic credentials from config if available
|
||||||
if ($creds = $config->get('http-basic')) {
|
foreach ($httpBasic as $domain => $cred) {
|
||||||
foreach ($creds as $domain => $cred) {
|
|
||||||
$this->setAuthentication($domain, $cred['username'], $cred['password']);
|
$this->setAuthentication($domain, $cred['username'], $cred['password']);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// setup process timeout
|
// setup process timeout
|
||||||
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
|
ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
|
||||||
|
|
|
@ -35,7 +35,7 @@ class BufferIO extends ConsoleIO
|
||||||
$input = new StringInput($input);
|
$input = new StringInput($input);
|
||||||
$input->setInteractive(false);
|
$input->setInteractive(false);
|
||||||
|
|
||||||
$output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, !empty($formatter), $formatter);
|
$output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter);
|
||||||
|
|
||||||
parent::__construct($input, $output, new HelperSet(array()));
|
parent::__construct($input, $output, new HelperSet(array()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ConsoleIO extends BaseIO
|
||||||
protected $lastMessage;
|
protected $lastMessage;
|
||||||
protected $lastMessageErr;
|
protected $lastMessageErr;
|
||||||
private $startTime;
|
private $startTime;
|
||||||
|
private $verbosityMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -46,6 +47,13 @@ class ConsoleIO extends BaseIO
|
||||||
$this->input = $input;
|
$this->input = $input;
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
$this->helperSet = $helperSet;
|
$this->helperSet = $helperSet;
|
||||||
|
$this->verbosityMap = array(
|
||||||
|
self::QUIET => OutputInterface::VERBOSITY_QUIET,
|
||||||
|
self::NORMAL => OutputInterface::VERBOSITY_NORMAL,
|
||||||
|
self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
|
||||||
|
self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
|
||||||
|
self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function enableDebugging($startTime)
|
public function enableDebugging($startTime)
|
||||||
|
@ -96,26 +104,32 @@ class ConsoleIO extends BaseIO
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function write($messages, $newline = true)
|
public function write($messages, $newline = true, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
$this->doWrite($messages, $newline, false);
|
$this->doWrite($messages, $newline, false, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function writeError($messages, $newline = true)
|
public function writeError($messages, $newline = true, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
$this->doWrite($messages, $newline, true);
|
$this->doWrite($messages, $newline, true, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|string $messages
|
* @param array|string $messages
|
||||||
* @param bool $newline
|
* @param bool $newline
|
||||||
* @param bool $stderr
|
* @param bool $stderr
|
||||||
|
* @param int $verbosity
|
||||||
*/
|
*/
|
||||||
private function doWrite($messages, $newline, $stderr)
|
private function doWrite($messages, $newline, $stderr, $verbosity)
|
||||||
{
|
{
|
||||||
|
$sfVerbosity = $this->verbosityMap[$verbosity];
|
||||||
|
if ($sfVerbosity > $this->output->getVerbosity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $this->startTime) {
|
if (null !== $this->startTime) {
|
||||||
$memoryUsage = memory_get_usage() / 1024 / 1024;
|
$memoryUsage = memory_get_usage() / 1024 / 1024;
|
||||||
$timeSpent = microtime(true) - $this->startTime;
|
$timeSpent = microtime(true) - $this->startTime;
|
||||||
|
@ -125,30 +139,30 @@ class ConsoleIO extends BaseIO
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true === $stderr && $this->output instanceof ConsoleOutputInterface) {
|
if (true === $stderr && $this->output instanceof ConsoleOutputInterface) {
|
||||||
$this->output->getErrorOutput()->write($messages, $newline);
|
$this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity);
|
||||||
$this->lastMessageErr = join($newline ? "\n" : '', (array) $messages);
|
$this->lastMessageErr = join($newline ? "\n" : '', (array) $messages);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->output->write($messages, $newline);
|
$this->output->write($messages, $newline, $sfVerbosity);
|
||||||
$this->lastMessage = join($newline ? "\n" : '', (array) $messages);
|
$this->lastMessage = join($newline ? "\n" : '', (array) $messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function overwrite($messages, $newline = true, $size = null)
|
public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
$this->doOverwrite($messages, $newline, $size, false);
|
$this->doOverwrite($messages, $newline, $size, false, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function overwriteError($messages, $newline = true, $size = null)
|
public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
$this->doOverwrite($messages, $newline, $size, true);
|
$this->doOverwrite($messages, $newline, $size, true, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,8 +170,9 @@ class ConsoleIO extends BaseIO
|
||||||
* @param bool $newline
|
* @param bool $newline
|
||||||
* @param int|null $size
|
* @param int|null $size
|
||||||
* @param bool $stderr
|
* @param bool $stderr
|
||||||
|
* @param int $verbosity
|
||||||
*/
|
*/
|
||||||
private function doOverwrite($messages, $newline, $size, $stderr)
|
private function doOverwrite($messages, $newline, $size, $stderr, $verbosity)
|
||||||
{
|
{
|
||||||
// messages can be an array, let's convert it to string anyway
|
// messages can be an array, let's convert it to string anyway
|
||||||
$messages = join($newline ? "\n" : '', (array) $messages);
|
$messages = join($newline ? "\n" : '', (array) $messages);
|
||||||
|
@ -168,21 +183,21 @@ class ConsoleIO extends BaseIO
|
||||||
$size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage));
|
$size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage));
|
||||||
}
|
}
|
||||||
// ...let's fill its length with backspaces
|
// ...let's fill its length with backspaces
|
||||||
$this->doWrite(str_repeat("\x08", $size), false, $stderr);
|
$this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity);
|
||||||
|
|
||||||
// write the new message
|
// write the new message
|
||||||
$this->doWrite($messages, false, $stderr);
|
$this->doWrite($messages, false, $stderr, $verbosity);
|
||||||
|
|
||||||
$fill = $size - strlen(strip_tags($messages));
|
$fill = $size - strlen(strip_tags($messages));
|
||||||
if ($fill > 0) {
|
if ($fill > 0) {
|
||||||
// whitespace whatever has left
|
// whitespace whatever has left
|
||||||
$this->doWrite(str_repeat(' ', $fill), false, $stderr);
|
$this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity);
|
||||||
// move the cursor back
|
// move the cursor back
|
||||||
$this->doWrite(str_repeat("\x08", $fill), false, $stderr);
|
$this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($newline) {
|
if ($newline) {
|
||||||
$this->doWrite('', true, $stderr);
|
$this->doWrite('', true, $stderr, $verbosity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stderr) {
|
if ($stderr) {
|
||||||
|
|
|
@ -21,6 +21,12 @@ use Composer\Config;
|
||||||
*/
|
*/
|
||||||
interface IOInterface
|
interface IOInterface
|
||||||
{
|
{
|
||||||
|
const QUIET = 1;
|
||||||
|
const NORMAL = 2;
|
||||||
|
const VERBOSE = 4;
|
||||||
|
const VERY_VERBOSE = 8;
|
||||||
|
const DEBUG = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this input means interactive?
|
* Is this input means interactive?
|
||||||
*
|
*
|
||||||
|
@ -61,16 +67,18 @@ interface IOInterface
|
||||||
*
|
*
|
||||||
* @param string|array $messages The message as an array of lines or a single string
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
* @param bool $newline Whether to add a newline or not
|
* @param bool $newline Whether to add a newline or not
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
*/
|
*/
|
||||||
public function write($messages, $newline = true);
|
public function write($messages, $newline = true, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a message to the error output.
|
* Writes a message to the error output.
|
||||||
*
|
*
|
||||||
* @param string|array $messages The message as an array of lines or a single string
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
* @param bool $newline Whether to add a newline or not
|
* @param bool $newline Whether to add a newline or not
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
*/
|
*/
|
||||||
public function writeError($messages, $newline = true);
|
public function writeError($messages, $newline = true, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwrites a previous message to the output.
|
* Overwrites a previous message to the output.
|
||||||
|
@ -78,8 +86,9 @@ interface IOInterface
|
||||||
* @param string|array $messages The message as an array of lines or a single string
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
* @param bool $newline Whether to add a newline or not
|
* @param bool $newline Whether to add a newline or not
|
||||||
* @param int $size The size of line
|
* @param int $size The size of line
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
*/
|
*/
|
||||||
public function overwrite($messages, $newline = true, $size = null);
|
public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwrites a previous message to the error output.
|
* Overwrites a previous message to the error output.
|
||||||
|
@ -87,8 +96,9 @@ interface IOInterface
|
||||||
* @param string|array $messages The message as an array of lines or a single string
|
* @param string|array $messages The message as an array of lines or a single string
|
||||||
* @param bool $newline Whether to add a newline or not
|
* @param bool $newline Whether to add a newline or not
|
||||||
* @param int $size The size of line
|
* @param int $size The size of line
|
||||||
|
* @param int $verbosity Verbosity level from the VERBOSITY_* constants
|
||||||
*/
|
*/
|
||||||
public function overwriteError($messages, $newline = true, $size = null);
|
public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asks a question to the user.
|
* Asks a question to the user.
|
||||||
|
|
|
@ -62,28 +62,28 @@ class NullIO extends BaseIO
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function write($messages, $newline = true)
|
public function write($messages, $newline = true, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function writeError($messages, $newline = true)
|
public function writeError($messages, $newline = true, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function overwrite($messages, $newline = true, $size = 80)
|
public function overwrite($messages, $newline = true, $size = 80, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public function overwriteError($messages, $newline = true, $size = 80)
|
public function overwriteError($messages, $newline = true, $size = 80, $verbosity = self::NORMAL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -529,10 +529,8 @@ class Installer
|
||||||
return max(1, $e->getCode());
|
return max(1, $e->getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies");
|
$this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies");
|
|
||||||
}
|
|
||||||
|
|
||||||
// force dev packages to be updated if we update or install from a (potentially new) lock
|
// force dev packages to be updated if we update or install from a (potentially new) lock
|
||||||
$operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations);
|
$operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations);
|
||||||
|
@ -578,10 +576,8 @@ class Installer
|
||||||
&& (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference())
|
&& (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference())
|
||||||
&& (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference())
|
&& (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference())
|
||||||
) {
|
) {
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError(' - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version');
|
$this->io->writeError('', true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('');
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@ use Composer\IO\IOInterface;
|
||||||
use Composer\Repository\InstalledRepositoryInterface;
|
use Composer\Repository\InstalledRepositoryInterface;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
use Composer\Util\Silencer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package installation manager.
|
* Package installation manager.
|
||||||
|
@ -130,7 +132,7 @@ class LibraryInstaller implements InstallerInterface
|
||||||
if (strpos($package->getName(), '/')) {
|
if (strpos($package->getName(), '/')) {
|
||||||
$packageVendorDir = dirname($downloadPath);
|
$packageVendorDir = dirname($downloadPath);
|
||||||
if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) {
|
if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) {
|
||||||
@rmdir($packageVendorDir);
|
Silencer::call('rmdir', $packageVendorDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,14 +235,14 @@ class LibraryInstaller implements InstallerInterface
|
||||||
// likely leftover from a previous install, make sure
|
// likely leftover from a previous install, make sure
|
||||||
// that the target is still executable in case this
|
// that the target is still executable in case this
|
||||||
// is a fresh install of the vendor.
|
// is a fresh install of the vendor.
|
||||||
@chmod($link, 0777 & ~umask());
|
Silencer::call('chmod', $link, 0777 & ~umask());
|
||||||
}
|
}
|
||||||
$this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
|
$this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->binCompat === "auto") {
|
if ($this->binCompat === "auto") {
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||||
} else {
|
} else {
|
||||||
$this->installSymlinkBinaries($binPath, $link);
|
$this->installSymlinkBinaries($binPath, $link);
|
||||||
|
@ -248,7 +250,7 @@ class LibraryInstaller implements InstallerInterface
|
||||||
} elseif ($this->binCompat === "full") {
|
} elseif ($this->binCompat === "full") {
|
||||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||||
}
|
}
|
||||||
@chmod($link, 0777 & ~umask());
|
Silencer::call('chmod', $link, 0777 & ~umask());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +300,7 @@ class LibraryInstaller implements InstallerInterface
|
||||||
|
|
||||||
// attempt removing the bin dir in case it is left empty
|
// attempt removing the bin dir in case it is left empty
|
||||||
if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) {
|
if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) {
|
||||||
@rmdir($this->binDir);
|
Silencer::call('rmdir', $this->binDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use Composer\Composer;
|
||||||
use Composer\Downloader\PearPackageExtractor;
|
use Composer\Downloader\PearPackageExtractor;
|
||||||
use Composer\Repository\InstalledRepositoryInterface;
|
use Composer\Repository\InstalledRepositoryInterface;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,7 +54,7 @@ class PearInstaller extends LibraryInstaller
|
||||||
parent::installCode($package);
|
parent::installCode($package);
|
||||||
parent::initializeBinDir();
|
parent::initializeBinDir();
|
||||||
|
|
||||||
$isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
|
$isWindows = Platform::isWindows();
|
||||||
$php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');
|
$php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');
|
||||||
|
|
||||||
if (!$isWindows) {
|
if (!$isWindows) {
|
||||||
|
@ -75,9 +76,7 @@ class PearInstaller extends LibraryInstaller
|
||||||
$pearExtractor = new PearPackageExtractor($packageArchive);
|
$pearExtractor = new PearPackageExtractor($packageArchive);
|
||||||
$pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars);
|
$pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars);
|
||||||
|
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError(' Cleaning up', true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError(' Cleaning up');
|
|
||||||
}
|
|
||||||
$this->filesystem->unlink($packageArchive);
|
$this->filesystem->unlink($packageArchive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,11 @@ class RootPackageLoader extends ArrayLoader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($links[$config['name']])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
|
||||||
|
'Did you accidentally name your root package after an external package?', $config['name']));
|
||||||
|
}
|
||||||
|
|
||||||
$realPackage->setAliases($aliases);
|
$realPackage->setAliases($aliases);
|
||||||
$realPackage->setStabilityFlags($stabilityFlags);
|
$realPackage->setStabilityFlags($stabilityFlags);
|
||||||
$realPackage->setReferences($references);
|
$realPackage->setReferences($references);
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Plugin\Capability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for Plugin capabilities.
|
||||||
|
* Every new Capability which is added to the Plugin API must implement this interface.
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
interface Capability
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugins which need to expose various implementations
|
||||||
|
* of the Composer Plugin Capabilities must have their
|
||||||
|
* declared Plugin class implementing this interface.
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
interface Capable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Method by which a Plugin announces its API implementations, through an array
|
||||||
|
* with a special structure.
|
||||||
|
*
|
||||||
|
* The key must be a string, representing a fully qualified class/interface name
|
||||||
|
* which Composer Plugin API exposes.
|
||||||
|
* The value must be a string as well, representing the fully qualified class name
|
||||||
|
* of the implementing class.
|
||||||
|
*
|
||||||
|
* @tutorial
|
||||||
|
*
|
||||||
|
* return array(
|
||||||
|
* 'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider',
|
||||||
|
* 'Composer\Plugin\Capability\Validator' => 'My\Validator',
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getCapabilities();
|
||||||
|
}
|
|
@ -23,14 +23,14 @@ use Composer\IO\IOInterface;
|
||||||
interface PluginInterface
|
interface PluginInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Version number of the fake composer-plugin-api package
|
* Version number of the internal composer-plugin-api package
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
const PLUGIN_API_VERSION = '1.0.0';
|
const PLUGIN_API_VERSION = '1.0.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply plugin modifications to composer
|
* Apply plugin modifications to Composer
|
||||||
*
|
*
|
||||||
* @param Composer $composer
|
* @param Composer $composer
|
||||||
* @param IOInterface $io
|
* @param IOInterface $io
|
||||||
|
|
|
@ -23,6 +23,7 @@ use Composer\Package\PackageInterface;
|
||||||
use Composer\Package\Link;
|
use Composer\Package\Link;
|
||||||
use Composer\Semver\Constraint\Constraint;
|
use Composer\Semver\Constraint\Constraint;
|
||||||
use Composer\DependencyResolver\Pool;
|
use Composer\DependencyResolver\Pool;
|
||||||
|
use Composer\Plugin\Capability\Capability;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin manager
|
* Plugin manager
|
||||||
|
@ -122,8 +123,11 @@ class PluginManager
|
||||||
$currentPluginApiVersion = $this->getPluginApiVersion();
|
$currentPluginApiVersion = $this->getPluginApiVersion();
|
||||||
$currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion));
|
$currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion));
|
||||||
|
|
||||||
if (!$requiresComposer->matches($currentPluginApiConstraint)) {
|
if ($requiresComposer->getPrettyString() === '1.0.0' && $this->getPluginApiVersion() === '1.0.0') {
|
||||||
|
$this->io->writeError('<warning>The "' . $package->getName() . '" plugin requires composer-plugin-api 1.0.0, this *WILL* break in the future and it should be fixed ASAP (require ^1.0 for example).</warning>');
|
||||||
|
} elseif (!$requiresComposer->matches($currentPluginApiConstraint)) {
|
||||||
$this->io->writeError('<warning>The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.</warning>');
|
$this->io->writeError('<warning>The "' . $package->getName() . '" plugin was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.</warning>');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,9 +206,7 @@ class PluginManager
|
||||||
*/
|
*/
|
||||||
private function addPlugin(PluginInterface $plugin)
|
private function addPlugin(PluginInterface $plugin)
|
||||||
{
|
{
|
||||||
if ($this->io->isDebug()) {
|
$this->io->writeError('Loading plugin '.get_class($plugin), true, IOInterface::DEBUG);
|
||||||
$this->io->writeError('Loading plugin '.get_class($plugin));
|
|
||||||
}
|
|
||||||
$this->plugins[] = $plugin;
|
$this->plugins[] = $plugin;
|
||||||
$plugin->activate($this->composer, $this->io);
|
$plugin->activate($this->composer, $this->io);
|
||||||
|
|
||||||
|
@ -299,4 +301,58 @@ class PluginManager
|
||||||
|
|
||||||
return $this->globalComposer->getInstallationManager()->getInstallPath($package);
|
return $this->globalComposer->getInstallationManager()->getInstallPath($package);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $plugin
|
||||||
|
* @param string $capability
|
||||||
|
* @throws \RuntimeException On empty or non-string implementation class name value
|
||||||
|
* @return null|string The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it
|
||||||
|
*/
|
||||||
|
protected function getCapabilityImplementationClassName(PluginInterface $plugin, $capability)
|
||||||
|
{
|
||||||
|
if (!($plugin instanceof Capable)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$capabilities = (array) $plugin->getCapabilities();
|
||||||
|
|
||||||
|
if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) {
|
||||||
|
return trim($capabilities[$capability]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
array_key_exists($capability, $capabilities)
|
||||||
|
&& (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability]))
|
||||||
|
) {
|
||||||
|
throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $plugin
|
||||||
|
* @param string $capabilityClassName The fully qualified name of the API interface which the plugin may provide
|
||||||
|
* an implementation of.
|
||||||
|
* @param array $ctorArgs Arguments passed to Capability's constructor.
|
||||||
|
* Keeping it an array will allow future values to be passed w\o changing the signature.
|
||||||
|
* @return null|Capability
|
||||||
|
*/
|
||||||
|
public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = array())
|
||||||
|
{
|
||||||
|
if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) {
|
||||||
|
if (!class_exists($capabilityClass)) {
|
||||||
|
throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$capabilityObj = new $capabilityClass($ctorArgs);
|
||||||
|
|
||||||
|
// FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9
|
||||||
|
if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $capabilityObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,16 +67,12 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
|
|
||||||
$package = $this->getComposerInformation($file);
|
$package = $this->getComposerInformation($file);
|
||||||
if (!$package) {
|
if (!$package) {
|
||||||
if ($io->isVerbose()) {
|
$io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
|
||||||
$io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($io->isVerbose()) {
|
|
||||||
$template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
|
$template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
|
||||||
$io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()));
|
$io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);
|
||||||
}
|
|
||||||
|
|
||||||
$this->addPackage($package);
|
$this->addPackage($package);
|
||||||
}
|
}
|
||||||
|
|
|
@ -747,6 +747,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
|
||||||
$this->io->writeError('<warning>'.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</warning>');
|
$this->io->writeError('<warning>'.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</warning>');
|
||||||
}
|
}
|
||||||
$this->degradedMode = true;
|
$this->degradedMode = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
parent::initialize();
|
parent::initialize();
|
||||||
|
|
||||||
foreach ($this->getUrlMatches() as $url) {
|
foreach ($this->getUrlMatches() as $url) {
|
||||||
$path = realpath($url) . '/';
|
$path = realpath($url) . DIRECTORY_SEPARATOR;
|
||||||
$composerFilePath = $path.'composer.json';
|
$composerFilePath = $path.'composer.json';
|
||||||
|
|
||||||
if (!file_exists($composerFilePath)) {
|
if (!file_exists($composerFilePath)) {
|
||||||
|
@ -125,16 +125,16 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
$package['dist'] = array(
|
$package['dist'] = array(
|
||||||
'type' => 'path',
|
'type' => 'path',
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
'reference' => '',
|
'reference' => sha1($json),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isset($package['version'])) {
|
if (!isset($package['version'])) {
|
||||||
$package['version'] = $this->versionGuesser->guessVersion($package, $path) ?: 'dev-master';
|
$package['version'] = $this->versionGuesser->guessVersion($package, $path) ?: 'dev-master';
|
||||||
}
|
}
|
||||||
if (is_dir($path.'/.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) {
|
|
||||||
|
$output = '';
|
||||||
|
if (is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) {
|
||||||
$package['dist']['reference'] = trim($output);
|
$package['dist']['reference'] = trim($output);
|
||||||
} else {
|
|
||||||
$package['dist']['reference'] = Locker::getContentHash($json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$package = $this->loader->load($package);
|
$package = $this->loader->load($package);
|
||||||
|
@ -153,6 +153,9 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
*/
|
*/
|
||||||
private function getUrlMatches()
|
private function getUrlMatches()
|
||||||
{
|
{
|
||||||
return glob($this->url, GLOB_MARK | GLOB_ONLYDIR);
|
// Ensure environment-specific path separators are normalized to URL separators
|
||||||
|
return array_map(function ($val) {
|
||||||
|
return str_replace(DIRECTORY_SEPARATOR, '/', $val);
|
||||||
|
}, glob($this->url, GLOB_MARK | GLOB_ONLYDIR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,9 +105,7 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
try {
|
try {
|
||||||
$normalizedVersion = $versionParser->normalize($version);
|
$normalizedVersion = $versionParser->normalize($version);
|
||||||
} catch (\UnexpectedValueException $e) {
|
} catch (\UnexpectedValueException $e) {
|
||||||
if ($this->io->isVerbose()) {
|
$this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage(), true, IOInterface::VERBOSE);
|
||||||
$this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage());
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,7 @@ class PlatformRepository extends ArrayRepository
|
||||||
if (isset($this->overrides[strtolower($package->getName())])) {
|
if (isset($this->overrides[strtolower($package->getName())])) {
|
||||||
$overrider = $this->findPackage($package->getName(), '*');
|
$overrider = $this->findPackage($package->getName(), '*');
|
||||||
$overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')');
|
$overrider->setDescription($overrider->getDescription().' (actual: '.$package->getPrettyVersion().')');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parent::addPackage($package);
|
parent::addPackage($package);
|
||||||
|
|
|
@ -105,7 +105,6 @@ class RepositoryManager
|
||||||
|
|
||||||
$class = $this->repositoryClasses[$type];
|
$class = $this->repositoryClasses[$type];
|
||||||
|
|
||||||
|
|
||||||
$reflMethod = new \ReflectionMethod($class, '__construct');
|
$reflMethod = new \ReflectionMethod($class, '__construct');
|
||||||
$params = $reflMethod->getParameters();
|
$params = $reflMethod->getParameters();
|
||||||
if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') {
|
if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') {
|
||||||
|
|
|
@ -160,9 +160,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extension_loaded('openssl')) {
|
if (!extension_loaded('openssl')) {
|
||||||
if ($io->isVerbose()) {
|
$io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);
|
||||||
$io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,9 +268,7 @@ class GitHubDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extension_loaded('openssl')) {
|
if (!extension_loaded('openssl')) {
|
||||||
if ($io->isVerbose()) {
|
$io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);
|
||||||
$io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -367,9 +367,7 @@ class GitLabDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('https' === $scheme && !extension_loaded('openssl')) {
|
if ('https' === $scheme && !extension_loaded('openssl')) {
|
||||||
if ($io->isVerbose()) {
|
$io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);
|
||||||
$io->write('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,9 +170,7 @@ class HgBitbucketDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extension_loaded('openssl')) {
|
if (!extension_loaded('openssl')) {
|
||||||
if ($io->isVerbose()) {
|
$io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);
|
||||||
$io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ use Composer\Json\JsonValidationException;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\Spdx\SpdxLicenses;
|
use Composer\Spdx\SpdxLicenses;
|
||||||
use Composer\Factory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a composer configuration.
|
* Validates a composer configuration.
|
||||||
|
|
|
@ -36,8 +36,8 @@ class ErrorHandler
|
||||||
*/
|
*/
|
||||||
public static function handle($level, $message, $file, $line)
|
public static function handle($level, $message, $file, $line)
|
||||||
{
|
{
|
||||||
// respect error_reporting being disabled
|
// error code is not included in error_reporting
|
||||||
if (!error_reporting()) {
|
if (!(error_reporting() & $level)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ class ErrorHandler
|
||||||
public static function register(IOInterface $io = null)
|
public static function register(IOInterface $io = null)
|
||||||
{
|
{
|
||||||
set_error_handler(array(__CLASS__, 'handle'));
|
set_error_handler(array(__CLASS__, 'handle'));
|
||||||
|
error_reporting(E_ALL | E_STRICT);
|
||||||
self::$io = $io;
|
self::$io = $io;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Filesystem
|
||||||
return $this->removeDirectoryPhp($directory);
|
return $this->removeDirectoryPhp($directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory)));
|
$cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory)));
|
||||||
} else {
|
} else {
|
||||||
$cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory));
|
$cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory));
|
||||||
|
@ -181,10 +181,10 @@ class Filesystem
|
||||||
{
|
{
|
||||||
if (!@$this->unlinkImplementation($path)) {
|
if (!@$this->unlinkImplementation($path)) {
|
||||||
// retry after a bit on windows since it tends to be touchy with mass removals
|
// retry after a bit on windows since it tends to be touchy with mass removals
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@$this->unlinkImplementation($path))) {
|
if (!Platform::isWindows() || (usleep(350000) && !@$this->unlinkImplementation($path))) {
|
||||||
$error = error_get_last();
|
$error = error_get_last();
|
||||||
$message = 'Could not delete '.$path.': ' . @$error['message'];
|
$message = 'Could not delete '.$path.': ' . @$error['message'];
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
|
$message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,10 +206,10 @@ class Filesystem
|
||||||
{
|
{
|
||||||
if (!@rmdir($path)) {
|
if (!@rmdir($path)) {
|
||||||
// retry after a bit on windows since it tends to be touchy with mass removals
|
// retry after a bit on windows since it tends to be touchy with mass removals
|
||||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') || (usleep(350000) && !@rmdir($path))) {
|
if (!Platform::isWindows() || (usleep(350000) && !@rmdir($path))) {
|
||||||
$error = error_get_last();
|
$error = error_get_last();
|
||||||
$message = 'Could not delete '.$path.': ' . @$error['message'];
|
$message = 'Could not delete '.$path.': ' . @$error['message'];
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
|
$message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ class Filesystem
|
||||||
return $this->copyThenRemove($source, $target);
|
return $this->copyThenRemove($source, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
// Try to copy & delete - this is a workaround for random "Access denied" errors.
|
// Try to copy & delete - this is a workaround for random "Access denied" errors.
|
||||||
$command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
|
$command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
|
||||||
$result = $this->processExecutor->execute($command, $output);
|
$result = $this->processExecutor->execute($command, $output);
|
||||||
|
@ -460,7 +460,7 @@ class Filesystem
|
||||||
|
|
||||||
public static function getPlatformPath($path)
|
public static function getPlatformPath($path)
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$path = preg_replace('{^(?:file:///([a-z])/)}i', 'file://$1:/', $path);
|
$path = preg_replace('{^(?:file:///([a-z])/)}i', 'file://$1:/', $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,7 +498,7 @@ class Filesystem
|
||||||
*/
|
*/
|
||||||
private function unlinkImplementation($path)
|
private function unlinkImplementation($path)
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && is_dir($path) && is_link($path)) {
|
if (Platform::isWindows() && is_dir($path) && is_link($path)) {
|
||||||
return rmdir($path);
|
return rmdir($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*/
|
||||||
|
class Keys
|
||||||
|
{
|
||||||
|
public static function fingerprint($path)
|
||||||
|
{
|
||||||
|
$hash = strtoupper(hash('sha256', preg_replace('{\s}', '', file_get_contents($path))));
|
||||||
|
|
||||||
|
return implode(' ', array(
|
||||||
|
substr($hash, 0, 8),
|
||||||
|
substr($hash, 8, 8),
|
||||||
|
substr($hash, 16, 8),
|
||||||
|
substr($hash, 24, 8),
|
||||||
|
'', // Extra space
|
||||||
|
substr($hash, 32, 8),
|
||||||
|
substr($hash, 40, 8),
|
||||||
|
substr($hash, 48, 8),
|
||||||
|
substr($hash, 56, 8),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,10 +51,7 @@ class Perforce
|
||||||
|
|
||||||
public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io)
|
public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io)
|
||||||
{
|
{
|
||||||
$isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
|
return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
|
||||||
$perforce = new Perforce($repoConfig, $port, $path, $process, $isWindows, $io);
|
|
||||||
|
|
||||||
return $perforce;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function checkServerExists($url, ProcessExecutor $processExecutor)
|
public static function checkServerExists($url, ProcessExecutor $processExecutor)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform helper for uniform platform-specific tests.
|
||||||
|
*
|
||||||
|
* @author Niels Keurentjes <niels.keurentjes@omines.com>
|
||||||
|
*/
|
||||||
|
class Platform
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool Whether the host machine is running a Windows OS
|
||||||
|
*/
|
||||||
|
public static function isWindows()
|
||||||
|
{
|
||||||
|
return defined('PHP_WINDOWS_VERSION_BUILD');
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ class ProcessExecutor
|
||||||
|
|
||||||
// make sure that null translate to the proper directory in case the dir is a symlink
|
// make sure that null translate to the proper directory in case the dir is a symlink
|
||||||
// and we call a git command, because msysgit does not handle symlinks properly
|
// and we call a git command, because msysgit does not handle symlinks properly
|
||||||
if (null === $cwd && defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($command, 'git') && getcwd()) {
|
if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) {
|
||||||
$cwd = realpath(getcwd());
|
$cwd = realpath(getcwd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,14 @@ class RemoteFilesystem
|
||||||
private $progress;
|
private $progress;
|
||||||
private $lastProgress;
|
private $lastProgress;
|
||||||
private $options = array();
|
private $options = array();
|
||||||
|
private $peerCertificateMap = array();
|
||||||
private $disableTls = false;
|
private $disableTls = false;
|
||||||
private $retryAuthFailure;
|
private $retryAuthFailure;
|
||||||
private $lastHeaders;
|
private $lastHeaders;
|
||||||
private $storeAuth;
|
private $storeAuth;
|
||||||
private $degradedMode = false;
|
private $degradedMode = false;
|
||||||
|
private $redirects;
|
||||||
|
private $maxRedirects = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -54,15 +57,7 @@ class RemoteFilesystem
|
||||||
// Setup TLS options
|
// Setup TLS options
|
||||||
// The cafile option can be set via config.json
|
// The cafile option can be set via config.json
|
||||||
if ($disableTls === false) {
|
if ($disableTls === false) {
|
||||||
$this->options = $this->getTlsDefaults();
|
$this->options = $this->getTlsDefaults($options);
|
||||||
if (isset($options['ssl']['cafile'])
|
|
||||||
&& (
|
|
||||||
!is_readable($options['ssl']['cafile'])
|
|
||||||
|| !$this->validateCaFile($options['ssl']['cafile'])
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new TransportException('The configured cafile was not valid or could not be read.');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$this->disableTls = true;
|
$this->disableTls = true;
|
||||||
}
|
}
|
||||||
|
@ -206,24 +201,34 @@ class RemoteFilesystem
|
||||||
$this->lastProgress = null;
|
$this->lastProgress = null;
|
||||||
$this->retryAuthFailure = true;
|
$this->retryAuthFailure = true;
|
||||||
$this->lastHeaders = array();
|
$this->lastHeaders = array();
|
||||||
|
$this->redirects = 1; // The first request counts.
|
||||||
|
|
||||||
// capture username/password from URL if there is one
|
// capture username/password from URL if there is one
|
||||||
if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) {
|
if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) {
|
||||||
$this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
|
$this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($additionalOptions['retry-auth-failure'])) {
|
$tempAdditionalOptions = $additionalOptions;
|
||||||
$this->retryAuthFailure = (bool) $additionalOptions['retry-auth-failure'];
|
if (isset($tempAdditionalOptions['retry-auth-failure'])) {
|
||||||
|
$this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];
|
||||||
|
|
||||||
unset($additionalOptions['retry-auth-failure']);
|
unset($tempAdditionalOptions['retry-auth-failure']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$options = $this->getOptionsForUrl($originUrl, $additionalOptions);
|
$isRedirect = false;
|
||||||
|
if (isset($tempAdditionalOptions['redirects'])) {
|
||||||
|
$this->redirects = $tempAdditionalOptions['redirects'];
|
||||||
|
$isRedirect = true;
|
||||||
|
|
||||||
if ($this->io->isDebug()) {
|
unset($tempAdditionalOptions['redirects']);
|
||||||
$this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
|
||||||
|
unset($tempAdditionalOptions);
|
||||||
|
$userlandFollow = isset($options['http']['follow_location']) && !$options['http']['follow_location'];
|
||||||
|
|
||||||
|
$this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl, true, IOInterface::DEBUG);
|
||||||
|
|
||||||
if (isset($options['github-token'])) {
|
if (isset($options['github-token'])) {
|
||||||
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
|
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
|
||||||
unset($options['github-token']);
|
unset($options['github-token']);
|
||||||
|
@ -245,7 +250,7 @@ class RemoteFilesystem
|
||||||
|
|
||||||
$ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
|
$ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
|
||||||
|
|
||||||
if ($this->progress) {
|
if ($this->progress && !$isRedirect) {
|
||||||
$this->io->writeError(" Downloading: <comment>Connecting...</comment>", false);
|
$this->io->writeError(" Downloading: <comment>Connecting...</comment>", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,6 +265,18 @@ class RemoteFilesystem
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
$result = file_get_contents($fileUrl, false, $ctx);
|
$result = file_get_contents($fileUrl, false, $ctx);
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) {
|
||||||
|
// Emulate fingerprint validation on PHP < 5.6
|
||||||
|
$params = stream_context_get_params($ctx);
|
||||||
|
$expectedPeerFingerprint = $options['ssl']['peer_fingerprint'];
|
||||||
|
$peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']);
|
||||||
|
|
||||||
|
// Constant time compare??!
|
||||||
|
if ($expectedPeerFingerprint !== $peerFingerprint) {
|
||||||
|
throw new TransportException('Peer fingerprint did not match');
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
if ($e instanceof TransportException && !empty($http_response_header[0])) {
|
if ($e instanceof TransportException && !empty($http_response_header[0])) {
|
||||||
$e->setHeaders($http_response_header);
|
$e->setHeaders($http_response_header);
|
||||||
|
@ -293,6 +310,11 @@ class RemoteFilesystem
|
||||||
$statusCode = $this->findStatusCode($http_response_header);
|
$statusCode = $this->findStatusCode($http_response_header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle 3xx redirects for php<5.6, 304 Not Modified is excluded
|
||||||
|
if ($userlandFollow && $statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
|
||||||
|
$result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
|
||||||
|
}
|
||||||
|
|
||||||
// fail 4xx and 5xx responses and capture the response
|
// fail 4xx and 5xx responses and capture the response
|
||||||
if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
|
if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
|
||||||
if (!$this->retry) {
|
if (!$this->retry) {
|
||||||
|
@ -305,7 +327,7 @@ class RemoteFilesystem
|
||||||
$result = false;
|
$result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->progress && !$this->retry) {
|
if ($this->progress && !$this->retry && !$isRedirect) {
|
||||||
$this->io->overwriteError(" Downloading: <comment>100%</comment>");
|
$this->io->overwriteError(" Downloading: <comment>100%</comment>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +364,7 @@ class RemoteFilesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle copy command if download was successful
|
// handle copy command if download was successful
|
||||||
if (false !== $result && null !== $fileName) {
|
if (false !== $result && null !== $fileName && !$isRedirect) {
|
||||||
if ('' === $result) {
|
if ('' === $result) {
|
||||||
throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
|
throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
|
||||||
}
|
}
|
||||||
|
@ -361,14 +383,50 @@ class RemoteFilesystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle SSL cert match issues
|
||||||
|
if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) {
|
||||||
|
// Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6
|
||||||
|
// The procedure to handle sAN for older PHP's is:
|
||||||
|
//
|
||||||
|
// 1. Open socket to remote server and fetch certificate (disabling peer
|
||||||
|
// validation because PHP errors without giving up the certificate.)
|
||||||
|
//
|
||||||
|
// 2. Verifying the domain in the URL against the names in the sAN field.
|
||||||
|
// If there is a match record the authority [host/port], certificate
|
||||||
|
// common name, and certificate fingerprint.
|
||||||
|
//
|
||||||
|
// 3. Retry the original request but changing the CN_match parameter to
|
||||||
|
// the common name extracted from the certificate in step 2.
|
||||||
|
//
|
||||||
|
// 4. To prevent any attempt at being hoodwinked by switching the
|
||||||
|
// certificate between steps 2 and 3 the fingerprint of the certificate
|
||||||
|
// presented in step 3 is compared against the one recorded in step 2.
|
||||||
|
if (TlsHelper::isOpensslParseSafe()) {
|
||||||
|
$certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options);
|
||||||
|
|
||||||
|
if ($certDetails) {
|
||||||
|
$this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;
|
||||||
|
|
||||||
|
$this->retry = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->io->writeError(sprintf(
|
||||||
|
'<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
|
||||||
|
PHP_VERSION
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->retry) {
|
if ($this->retry) {
|
||||||
$this->retry = false;
|
$this->retry = false;
|
||||||
|
|
||||||
$result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
|
$result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
|
||||||
|
|
||||||
|
if ($this->storeAuth && $this->config) {
|
||||||
$authHelper = new AuthHelper($this->io, $this->config);
|
$authHelper = new AuthHelper($this->io, $this->config);
|
||||||
$authHelper->storeAuth($this->originUrl, $this->storeAuth);
|
$authHelper->storeAuth($this->originUrl, $this->storeAuth);
|
||||||
$this->storeAuth = false;
|
$this->storeAuth = false;
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
@ -522,19 +580,42 @@ class RemoteFilesystem
|
||||||
$tlsOptions = array();
|
$tlsOptions = array();
|
||||||
|
|
||||||
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
|
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
|
||||||
if ($this->disableTls === false && PHP_VERSION_ID < 50600) {
|
if ($this->disableTls === false && PHP_VERSION_ID < 50600 && !stream_is_local($this->fileUrl)) {
|
||||||
if (!preg_match('{^https?://}', $this->fileUrl)) {
|
|
||||||
$host = $originUrl;
|
|
||||||
} else {
|
|
||||||
$host = parse_url($this->fileUrl, PHP_URL_HOST);
|
$host = parse_url($this->fileUrl, PHP_URL_HOST);
|
||||||
}
|
|
||||||
|
if (PHP_VERSION_ID >= 50304) {
|
||||||
|
// Must manually follow when setting CN_match because this causes all
|
||||||
|
// redirects to be validated against the same CN_match value.
|
||||||
|
$userlandFollow = true;
|
||||||
|
} else {
|
||||||
|
// PHP < 5.3.4 does not support follow_location, for those people
|
||||||
|
// do some really nasty hard coded transformations. These will
|
||||||
|
// still breakdown if the site redirects to a domain we don't
|
||||||
|
// expect.
|
||||||
|
|
||||||
if ($host === 'github.com' || $host === 'api.github.com') {
|
if ($host === 'github.com' || $host === 'api.github.com') {
|
||||||
$host = '*.github.com';
|
$host = '*.github.com';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$tlsOptions['ssl']['CN_match'] = $host;
|
$tlsOptions['ssl']['CN_match'] = $host;
|
||||||
$tlsOptions['ssl']['SNI_server_name'] = $host;
|
$tlsOptions['ssl']['SNI_server_name'] = $host;
|
||||||
|
|
||||||
|
$urlAuthority = $this->getUrlAuthority($this->fileUrl);
|
||||||
|
|
||||||
|
if (isset($this->peerCertificateMap[$urlAuthority])) {
|
||||||
|
// Handle subjectAltName on lesser PHP's.
|
||||||
|
$certMap = $this->peerCertificateMap[$urlAuthority];
|
||||||
|
|
||||||
|
$this->io->writeError(sprintf(
|
||||||
|
'Using <info>%s</info> as CN for subjectAltName enabled host <info>%s</info>',
|
||||||
|
$certMap['cn'],
|
||||||
|
$urlAuthority
|
||||||
|
), true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
$tlsOptions['ssl']['CN_match'] = $certMap['cn'];
|
||||||
|
$tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = array();
|
$headers = array();
|
||||||
|
@ -551,6 +632,10 @@ class RemoteFilesystem
|
||||||
$headers[] = 'Connection: close';
|
$headers[] = 'Connection: close';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($userlandFollow)) {
|
||||||
|
$options['http']['follow_location'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->io->hasAuthentication($originUrl)) {
|
if ($this->io->hasAuthentication($originUrl)) {
|
||||||
$auth = $this->io->getAuthentication($originUrl);
|
$auth = $this->io->getAuthentication($originUrl);
|
||||||
if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
|
if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
|
||||||
|
@ -575,7 +660,55 @@ class RemoteFilesystem
|
||||||
return $options;
|
return $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTlsDefaults()
|
private function handleRedirect(array $http_response_header, array $additionalOptions, $result)
|
||||||
|
{
|
||||||
|
if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) {
|
||||||
|
if (parse_url($locationHeader, PHP_URL_SCHEME)) {
|
||||||
|
// Absolute URL; e.g. https://example.com/composer
|
||||||
|
$targetUrl = $locationHeader;
|
||||||
|
} elseif (parse_url($locationHeader, PHP_URL_HOST)) {
|
||||||
|
// Scheme relative; e.g. //example.com/foo
|
||||||
|
$targetUrl = $this->scheme.':'.$locationHeader;
|
||||||
|
} elseif ('/' === $locationHeader[0]) {
|
||||||
|
// Absolute path; e.g. /foo
|
||||||
|
$urlHost = parse_url($this->fileUrl, PHP_URL_HOST);
|
||||||
|
|
||||||
|
// Replace path using hostname as an anchor.
|
||||||
|
$targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl);
|
||||||
|
} else {
|
||||||
|
// Relative path; e.g. foo
|
||||||
|
// This actually differs from PHP which seems to add duplicate slashes.
|
||||||
|
$targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($targetUrl)) {
|
||||||
|
$this->redirects++;
|
||||||
|
|
||||||
|
$this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
$additionalOptions['redirects'] = $this->redirects;
|
||||||
|
|
||||||
|
return $this->get($this->originUrl, $targetUrl, $additionalOptions, $this->fileName, $this->progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->retry) {
|
||||||
|
$e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')');
|
||||||
|
$e->setHeaders($http_response_header);
|
||||||
|
$e->setResponse($result);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getTlsDefaults(array $options)
|
||||||
{
|
{
|
||||||
$ciphers = implode(':', array(
|
$ciphers = implode(':', array(
|
||||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||||
|
@ -613,7 +746,7 @@ class RemoteFilesystem
|
||||||
'!DES',
|
'!DES',
|
||||||
'!3DES',
|
'!3DES',
|
||||||
'!MD5',
|
'!MD5',
|
||||||
'!PSK'
|
'!PSK',
|
||||||
));
|
));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -622,55 +755,60 @@ class RemoteFilesystem
|
||||||
*
|
*
|
||||||
* cafile or capath can be overridden by passing in those options to constructor.
|
* cafile or capath can be overridden by passing in those options to constructor.
|
||||||
*/
|
*/
|
||||||
$options = array(
|
$defaults = array(
|
||||||
'ssl' => array(
|
'ssl' => array(
|
||||||
'ciphers' => $ciphers,
|
'ciphers' => $ciphers,
|
||||||
'verify_peer' => true,
|
'verify_peer' => true,
|
||||||
'verify_depth' => 7,
|
'verify_depth' => 7,
|
||||||
'SNI_enabled' => true,
|
'SNI_enabled' => true,
|
||||||
)
|
'capture_peer_cert' => true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isset($options['ssl'])) {
|
||||||
|
$defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to find a local cafile or throw an exception if none pre-set
|
* Attempt to find a local cafile or throw an exception if none pre-set
|
||||||
* The user may go download one if this occurs.
|
* The user may go download one if this occurs.
|
||||||
*/
|
*/
|
||||||
if (!isset($this->options['ssl']['cafile'])) {
|
if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
|
||||||
$result = $this->getSystemCaRootBundlePath();
|
$result = $this->getSystemCaRootBundlePath();
|
||||||
if ($result) {
|
|
||||||
if (preg_match('{^phar://}', $result)) {
|
if (preg_match('{^phar://}', $result)) {
|
||||||
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert.pem';
|
$hash = hash_file('sha256', $result);
|
||||||
|
$targetPath = rtrim(sys_get_temp_dir(), '\\/') . '/composer-cacert-' . $hash . '.pem';
|
||||||
|
|
||||||
// use stream_copy_to_stream instead of copy
|
if (!file_exists($targetPath) || $hash !== hash_file('sha256', $targetPath)) {
|
||||||
// to work around https://bugs.php.net/bug.php?id=64634
|
$this->streamCopy($result, $targetPath);
|
||||||
$source = fopen($result, 'r');
|
chmod($targetPath, 0666);
|
||||||
$target = fopen($targetPath, 'w+');
|
}
|
||||||
stream_copy_to_stream($source, $target);
|
|
||||||
fclose($source);
|
|
||||||
fclose($target);
|
|
||||||
unset($source, $target);
|
|
||||||
|
|
||||||
$options['ssl']['cafile'] = $targetPath;
|
$defaults['ssl']['cafile'] = $targetPath;
|
||||||
|
} elseif (is_dir($result)) {
|
||||||
|
$defaults['ssl']['capath'] = $result;
|
||||||
} else {
|
} else {
|
||||||
if (is_dir($result)) {
|
$defaults['ssl']['cafile'] = $result;
|
||||||
$options['ssl']['capath'] = $result;
|
|
||||||
} elseif ($result) {
|
|
||||||
$options['ssl']['cafile'] = $result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw new TransportException('A valid cafile could not be located automatically.');
|
if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !$this->validateCaFile($defaults['ssl']['cafile']))) {
|
||||||
|
throw new TransportException('The configured cafile was not valid or could not be read.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($defaults['ssl']['capath']))) {
|
||||||
|
throw new TransportException('The configured capath was not valid or could not be read.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable TLS compression to prevent CRIME attacks where supported.
|
* Disable TLS compression to prevent CRIME attacks where supported.
|
||||||
*/
|
*/
|
||||||
if (PHP_VERSION_ID >= 50413) {
|
if (PHP_VERSION_ID >= 50413) {
|
||||||
$options['ssl']['disable_compression'] = true;
|
$defaults['ssl']['disable_compression'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $options;
|
return $defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -704,6 +842,8 @@ class RemoteFilesystem
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function getSystemCaRootBundlePath()
|
private function getSystemCaRootBundlePath()
|
||||||
{
|
{
|
||||||
|
@ -721,6 +861,11 @@ class RemoteFilesystem
|
||||||
return $caPath = $envCertFile;
|
return $caPath = $envCertFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$configured = ini_get('openssl.cafile');
|
||||||
|
if ($configured && strlen($configured) > 0 && is_readable($configured) && $this->validateCaFile($configured)) {
|
||||||
|
return $caPath = $configured;
|
||||||
|
}
|
||||||
|
|
||||||
$caBundlePaths = array(
|
$caBundlePaths = array(
|
||||||
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
|
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
|
||||||
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
|
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
|
||||||
|
@ -732,16 +877,10 @@ class RemoteFilesystem
|
||||||
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
|
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
|
||||||
'/etc/ssl/cert.pem', // OpenBSD
|
'/etc/ssl/cert.pem', // OpenBSD
|
||||||
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
|
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
|
||||||
__DIR__.'/../../../res/cacert.pem', // Bundled with Composer
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$configured = ini_get('openssl.cafile');
|
|
||||||
if ($configured && strlen($configured) > 0 && is_readable($configured) && $this->validateCaFile($configured)) {
|
|
||||||
return $caPath = $configured;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($caBundlePaths as $caBundle) {
|
foreach ($caBundlePaths as $caBundle) {
|
||||||
if (@is_readable($caBundle) && $this->validateCaFile($caBundle)) {
|
if (Silencer::call('is_readable', $caBundle) && $this->validateCaFile($caBundle)) {
|
||||||
return $caPath = $caBundle;
|
return $caPath = $caBundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -753,26 +892,124 @@ class RemoteFilesystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $caPath = false;
|
return $caPath = __DIR__.'/../../../res/cacert.pem'; // Bundled with Composer, last resort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
private function validateCaFile($filename)
|
private function validateCaFile($filename)
|
||||||
{
|
{
|
||||||
if ($this->io->isDebug()) {
|
static $files = array();
|
||||||
$this->io->writeError('Checking CA file '.realpath($filename));
|
|
||||||
|
if (isset($files[$filename])) {
|
||||||
|
return $files[$filename];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->io->writeError('Checking CA file '.realpath($filename), true, IOInterface::DEBUG);
|
||||||
$contents = file_get_contents($filename);
|
$contents = file_get_contents($filename);
|
||||||
|
|
||||||
// assume the CA is valid if php is vulnerable to
|
// assume the CA is valid if php is vulnerable to
|
||||||
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
|
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
|
||||||
if (
|
if (!TlsHelper::isOpensslParseSafe()) {
|
||||||
PHP_VERSION_ID <= 50327
|
$this->io->writeError(sprintf(
|
||||||
|| (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
|
'<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
|
||||||
|| (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
|
PHP_VERSION
|
||||||
) {
|
));
|
||||||
return !empty($contents);
|
|
||||||
|
return $files[$filename] = !empty($contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (bool) openssl_x509_parse($contents);
|
return $files[$filename] = (bool) openssl_x509_parse($contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses stream_copy_to_stream instead of copy to work around https://bugs.php.net/bug.php?id=64634
|
||||||
|
*
|
||||||
|
* @param string $source
|
||||||
|
* @param string $target
|
||||||
|
*/
|
||||||
|
private function streamCopy($source, $target)
|
||||||
|
{
|
||||||
|
$source = fopen($source, 'r');
|
||||||
|
$target = fopen($target, 'w+');
|
||||||
|
|
||||||
|
stream_copy_to_stream($source, $target);
|
||||||
|
fclose($source);
|
||||||
|
fclose($target);
|
||||||
|
|
||||||
|
unset($source, $target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch certificate common name and fingerprint for validation of SAN.
|
||||||
|
*
|
||||||
|
* @todo Remove when PHP 5.6 is minimum supported version.
|
||||||
|
*/
|
||||||
|
private function getCertificateCnAndFp($url, $options)
|
||||||
|
{
|
||||||
|
if (PHP_VERSION_ID >= 50600) {
|
||||||
|
throw new \BadMethodCallException(sprintf(
|
||||||
|
'%s must not be used on PHP >= 5.6',
|
||||||
|
__METHOD__
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = StreamContextFactory::getContext($url, $options, array('options' => array(
|
||||||
|
'ssl' => array(
|
||||||
|
'capture_peer_cert' => true,
|
||||||
|
'verify_peer' => false, // Yes this is fucking insane! But PHP is lame.
|
||||||
|
), ),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Ideally this would just use stream_socket_client() to avoid sending a
|
||||||
|
// HTTP request but that does not capture the certificate.
|
||||||
|
if (false === $handle = @fopen($url, 'rb', false, $context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close non authenticated connection without reading any content.
|
||||||
|
fclose($handle);
|
||||||
|
$handle = null;
|
||||||
|
|
||||||
|
$params = stream_context_get_params($context);
|
||||||
|
|
||||||
|
if (!empty($params['options']['ssl']['peer_certificate'])) {
|
||||||
|
$peerCertificate = $params['options']['ssl']['peer_certificate'];
|
||||||
|
|
||||||
|
if (TlsHelper::checkCertificateHost($peerCertificate, parse_url($url, PHP_URL_HOST), $commonName)) {
|
||||||
|
return array(
|
||||||
|
'cn' => $commonName,
|
||||||
|
'fp' => TlsHelper::getCertificateFingerprint($peerCertificate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUrlAuthority($url)
|
||||||
|
{
|
||||||
|
$defaultPorts = array(
|
||||||
|
'ftp' => 21,
|
||||||
|
'http' => 80,
|
||||||
|
'https' => 443,
|
||||||
|
'ssh2.sftp' => 22,
|
||||||
|
'ssh2.scp' => 22,
|
||||||
|
);
|
||||||
|
|
||||||
|
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||||
|
|
||||||
|
if (!isset($defaultPorts[$scheme])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf(
|
||||||
|
'Could not get default port for unknown scheme: %s',
|
||||||
|
$scheme
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultPort = $defaultPorts[$scheme];
|
||||||
|
$port = parse_url($url, PHP_URL_PORT) ?: $defaultPort;
|
||||||
|
|
||||||
|
return parse_url($url, PHP_URL_HOST).':'.$port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporarily suppress PHP error reporting, usually warnings and below.
|
||||||
|
*
|
||||||
|
* @author Niels Keurentjes <niels.keurentjes@omines.com>
|
||||||
|
*/
|
||||||
|
class Silencer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int[] Unpop stack
|
||||||
|
*/
|
||||||
|
private static $stack = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suppresses given mask or errors.
|
||||||
|
*
|
||||||
|
* @param int|null $mask Error levels to suppress, default value NULL indicates all warnings and below.
|
||||||
|
* @return int The old error reporting level.
|
||||||
|
*/
|
||||||
|
public static function suppress($mask = null)
|
||||||
|
{
|
||||||
|
if (!isset($mask)) {
|
||||||
|
$mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT;
|
||||||
|
}
|
||||||
|
$old = error_reporting();
|
||||||
|
array_push(self::$stack, $old);
|
||||||
|
error_reporting($old & ~$mask);
|
||||||
|
|
||||||
|
return $old;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores a single state.
|
||||||
|
*/
|
||||||
|
public static function restore()
|
||||||
|
{
|
||||||
|
if (!empty(self::$stack)) {
|
||||||
|
error_reporting(array_pop(self::$stack));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a specified function while silencing warnings and below.
|
||||||
|
*
|
||||||
|
* Future improvement: when PHP requirements are raised add Callable type hint (5.4) and variadic parameters (5.6)
|
||||||
|
*
|
||||||
|
* @param callable $callable Function to execute.
|
||||||
|
* @throws \Exception Any exceptions from the callback are rethrown.
|
||||||
|
* @return mixed Return value of the callback.
|
||||||
|
*/
|
||||||
|
public static function call($callable /*, ...$parameters */)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
self::suppress();
|
||||||
|
$result = call_user_func_array($callable, array_slice(func_get_args(), 1));
|
||||||
|
self::restore();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Use a finally block for this when requirements are raised to PHP 5.5
|
||||||
|
self::restore();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,289 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Util;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\PhpProcess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Chris Smith <chris@cs278.org>
|
||||||
|
*/
|
||||||
|
final class TlsHelper
|
||||||
|
{
|
||||||
|
private static $useOpensslParse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match hostname against a certificate.
|
||||||
|
*
|
||||||
|
* @param mixed $certificate X.509 certificate
|
||||||
|
* @param string $hostname Hostname in the URL
|
||||||
|
* @param string $cn Set to the common name of the certificate iff match found
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function checkCertificateHost($certificate, $hostname, &$cn = null)
|
||||||
|
{
|
||||||
|
$names = self::getCertificateNames($certificate);
|
||||||
|
|
||||||
|
if (empty($names)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$combinedNames = array_merge($names['san'], array($names['cn']));
|
||||||
|
$hostname = strtolower($hostname);
|
||||||
|
|
||||||
|
foreach ($combinedNames as $certName) {
|
||||||
|
$matcher = self::certNameMatcher($certName);
|
||||||
|
|
||||||
|
if ($matcher && $matcher($hostname)) {
|
||||||
|
$cn = $names['cn'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract DNS names out of an X.509 certificate.
|
||||||
|
*
|
||||||
|
* @param mixed $certificate X.509 certificate
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public static function getCertificateNames($certificate)
|
||||||
|
{
|
||||||
|
if (is_array($certificate)) {
|
||||||
|
$info = $certificate;
|
||||||
|
} elseif (self::isOpensslParseSafe()) {
|
||||||
|
$info = openssl_x509_parse($certificate, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($info['subject']['commonName'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$commonName = strtolower($info['subject']['commonName']);
|
||||||
|
$subjectAltNames = array();
|
||||||
|
|
||||||
|
if (isset($info['extensions']['subjectAltName'])) {
|
||||||
|
$subjectAltNames = preg_split('{\s*,\s*}', $info['extensions']['subjectAltName']);
|
||||||
|
$subjectAltNames = array_filter(array_map(function ($name) {
|
||||||
|
if (0 === strpos($name, 'DNS:')) {
|
||||||
|
return strtolower(ltrim(substr($name, 4)));
|
||||||
|
}
|
||||||
|
}, $subjectAltNames));
|
||||||
|
$subjectAltNames = array_values($subjectAltNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'cn' => $commonName,
|
||||||
|
'san' => $subjectAltNames,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the certificate pin.
|
||||||
|
*
|
||||||
|
* By Kevin McArthur of StormTide Digital Studios Inc.
|
||||||
|
* @KevinSMcArthur / https://github.com/StormTide
|
||||||
|
*
|
||||||
|
* See http://tools.ietf.org/html/draft-ietf-websec-key-pinning-02
|
||||||
|
*
|
||||||
|
* This method was adapted from Sslurp.
|
||||||
|
* https://github.com/EvanDotPro/Sslurp
|
||||||
|
*
|
||||||
|
* (c) Evan Coury <me@evancoury.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please see below:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013, Evan Coury
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
public static function getCertificateFingerprint($certificate)
|
||||||
|
{
|
||||||
|
$pubkeydetails = openssl_pkey_get_details(openssl_get_publickey($certificate));
|
||||||
|
$pubkeypem = $pubkeydetails['key'];
|
||||||
|
//Convert PEM to DER before SHA1'ing
|
||||||
|
$start = '-----BEGIN PUBLIC KEY-----';
|
||||||
|
$end = '-----END PUBLIC KEY-----';
|
||||||
|
$pemtrim = substr($pubkeypem, (strpos($pubkeypem, $start) + strlen($start)), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1));
|
||||||
|
$der = base64_decode($pemtrim);
|
||||||
|
|
||||||
|
return sha1($der);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if it is safe to use the PHP function openssl_x509_parse().
|
||||||
|
*
|
||||||
|
* This checks if OpenSSL extensions is vulnerable to remote code execution
|
||||||
|
* via the exploit documented as CVE-2013-6420.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isOpensslParseSafe()
|
||||||
|
{
|
||||||
|
if (null !== self::$useOpensslParse) {
|
||||||
|
return self::$useOpensslParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID >= 50600) {
|
||||||
|
return self::$useOpensslParse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vulnerable:
|
||||||
|
// PHP 5.3.0 - PHP 5.3.27
|
||||||
|
// PHP 5.4.0 - PHP 5.4.22
|
||||||
|
// PHP 5.5.0 - PHP 5.5.6
|
||||||
|
if (
|
||||||
|
(PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
|
||||||
|
|| (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
|
||||||
|
|| (PHP_VERSION_ID < 50600 && PHP_VERSION_ID >= 50507)
|
||||||
|
) {
|
||||||
|
// This version of PHP has the fix for CVE-2013-6420 applied.
|
||||||
|
return self::$useOpensslParse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform::isWindows()) {
|
||||||
|
// Windows is probably insecure in this case.
|
||||||
|
return self::$useOpensslParse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
|
||||||
|
$regex = '{^'.preg_quote($prefix).'([0-9]+)$}';
|
||||||
|
|
||||||
|
if (preg_match($regex, PHP_VERSION, $m)) {
|
||||||
|
return ((int) $m[1]) >= $fixedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hard coded list of PHP distributions with the fix backported.
|
||||||
|
if (
|
||||||
|
$compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
|
||||||
|
|| $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
|
||||||
|
|| $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
|
||||||
|
) {
|
||||||
|
return self::$useOpensslParse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is where things get crazy, because distros backport security
|
||||||
|
// fixes the chances are on NIX systems the fix has been applied but
|
||||||
|
// it's not possible to verify that from the PHP version.
|
||||||
|
//
|
||||||
|
// To verify exec a new PHP process and run the issue testcase with
|
||||||
|
// known safe input that replicates the bug.
|
||||||
|
|
||||||
|
// Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
|
||||||
|
// changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
|
||||||
|
$cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
|
||||||
|
$script = <<<'EOT'
|
||||||
|
|
||||||
|
error_reporting(-1);
|
||||||
|
$info = openssl_x509_parse(base64_decode('%s'));
|
||||||
|
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);
|
||||||
|
|
||||||
|
EOT;
|
||||||
|
$script = '<'."?php\n".sprintf($script, $cert);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$process = new PhpProcess($script);
|
||||||
|
$process->mustRun();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// In the case of any exceptions just accept it is not possible to
|
||||||
|
// determine the safety of openssl_x509_parse and bail out.
|
||||||
|
return self::$useOpensslParse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = preg_split('{\r?\n}', trim($process->getOutput()));
|
||||||
|
$errorOutput = trim($process->getErrorOutput());
|
||||||
|
|
||||||
|
if (
|
||||||
|
count($output) === 3
|
||||||
|
&& $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
|
||||||
|
&& $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
|
||||||
|
&& $output[2] === 'int(-1)'
|
||||||
|
&& preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
|
||||||
|
) {
|
||||||
|
// This PHP has the fix backported probably by a distro security team.
|
||||||
|
return self::$useOpensslParse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$useOpensslParse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert certificate name into matching function.
|
||||||
|
*
|
||||||
|
* @param $certName CN/SAN
|
||||||
|
*
|
||||||
|
* @return callable|null
|
||||||
|
*/
|
||||||
|
private static function certNameMatcher($certName)
|
||||||
|
{
|
||||||
|
$wildcards = substr_count($certName, '*');
|
||||||
|
|
||||||
|
if (0 === $wildcards) {
|
||||||
|
// Literal match.
|
||||||
|
return function ($hostname) use ($certName) {
|
||||||
|
return $hostname === $certName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === $wildcards) {
|
||||||
|
$components = explode('.', $certName);
|
||||||
|
|
||||||
|
if (3 > count($components)) {
|
||||||
|
// Must have 3+ components
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstComponent = $components[0];
|
||||||
|
|
||||||
|
// Wildcard must be the last character.
|
||||||
|
if ('*' !== $firstComponent[strlen($firstComponent) - 1]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wildcardRegex = preg_quote($certName);
|
||||||
|
$wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex);
|
||||||
|
$wildcardRegex = "{^{$wildcardRegex}$}";
|
||||||
|
|
||||||
|
return function ($hostname) use ($wildcardRegex) {
|
||||||
|
return 1 === preg_match($wildcardRegex, $hostname);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,14 +12,15 @@
|
||||||
|
|
||||||
namespace Composer\Test;
|
namespace Composer\Test;
|
||||||
|
|
||||||
use Symfony\Component\Process\Process;
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group slow
|
* @group slow
|
||||||
*/
|
*/
|
||||||
class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
class AllFunctionalTest extends TestCase
|
||||||
{
|
{
|
||||||
protected $oldcwd;
|
protected $oldcwd;
|
||||||
protected $oldenv;
|
protected $oldenv;
|
||||||
|
@ -29,17 +30,21 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->oldcwd = getcwd();
|
$this->oldcwd = getcwd();
|
||||||
|
|
||||||
chdir(__DIR__.'/Fixtures/functional');
|
chdir(__DIR__.'/Fixtures/functional');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
{
|
{
|
||||||
chdir($this->oldcwd);
|
chdir($this->oldcwd);
|
||||||
|
|
||||||
$fs = new Filesystem;
|
$fs = new Filesystem;
|
||||||
|
|
||||||
if ($this->testDir) {
|
if ($this->testDir) {
|
||||||
$fs->removeDirectory($this->testDir);
|
$fs->removeDirectory($this->testDir);
|
||||||
$this->testDir = null;
|
$this->testDir = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->oldenv) {
|
if ($this->oldenv) {
|
||||||
$fs->removeDirectory(getenv('COMPOSER_HOME'));
|
$fs->removeDirectory(getenv('COMPOSER_HOME'));
|
||||||
$_SERVER['COMPOSER_HOME'] = $this->oldenv;
|
$_SERVER['COMPOSER_HOME'] = $this->oldenv;
|
||||||
|
@ -50,7 +55,7 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
self::$pharPath = sys_get_temp_dir().'/composer-phar-test/composer.phar';
|
self::$pharPath = self::getUniqueTmpDirectory() . '/composer.phar';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass()
|
||||||
|
@ -66,9 +71,7 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
$target = dirname(self::$pharPath);
|
$target = dirname(self::$pharPath);
|
||||||
$fs = new Filesystem;
|
$fs = new Filesystem();
|
||||||
$fs->removeDirectory($target);
|
|
||||||
$fs->ensureDirectoryExists($target);
|
|
||||||
chdir($target);
|
chdir($target);
|
||||||
|
|
||||||
$it = new \RecursiveDirectoryIterator(__DIR__.'/../../../', \RecursiveDirectoryIterator::SKIP_DOTS);
|
$it = new \RecursiveDirectoryIterator(__DIR__.'/../../../', \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||||
|
@ -85,9 +88,11 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
$proc = new Process('php '.escapeshellarg('./bin/compile'), $target);
|
$proc = new Process('php '.escapeshellarg('./bin/compile'), $target);
|
||||||
$exitcode = $proc->run();
|
$exitcode = $proc->run();
|
||||||
|
|
||||||
if ($exitcode !== 0 || trim($proc->getOutput())) {
|
if ($exitcode !== 0 || trim($proc->getOutput())) {
|
||||||
$this->fail($proc->getOutput());
|
$this->fail($proc->getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertTrue(file_exists(self::$pharPath));
|
$this->assertTrue(file_exists(self::$pharPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +145,7 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
|
||||||
$data = array();
|
$data = array();
|
||||||
$section = null;
|
$section = null;
|
||||||
|
|
||||||
$testDir = sys_get_temp_dir().'/composer_functional_test'.uniqid(mt_rand(), true);
|
$testDir = self::getUniqueTmpDirectory();
|
||||||
$this->testDir = $testDir;
|
$this->testDir = $testDir;
|
||||||
$varRegex = '#%([a-zA-Z_-]+)%#';
|
$varRegex = '#%([a-zA-Z_-]+)%#';
|
||||||
$variableReplacer = function ($match) use (&$data, $testDir) {
|
$variableReplacer = function ($match) use (&$data, $testDir) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Test;
|
||||||
|
|
||||||
use Composer\Console\Application;
|
use Composer\Console\Application;
|
||||||
use Composer\TestCase;
|
use Composer\TestCase;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class ApplicationTest extends TestCase
|
class ApplicationTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -30,11 +31,19 @@ class ApplicationTest extends TestCase
|
||||||
|
|
||||||
$index = 0;
|
$index = 0;
|
||||||
if (extension_loaded('xdebug')) {
|
if (extension_loaded('xdebug')) {
|
||||||
|
$outputMock->expects($this->at($index++))
|
||||||
|
->method("getVerbosity")
|
||||||
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
|
|
||||||
$outputMock->expects($this->at($index++))
|
$outputMock->expects($this->at($index++))
|
||||||
->method("write")
|
->method("write")
|
||||||
->with($this->equalTo('<warning>You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug</warning>'));
|
->with($this->equalTo('<warning>You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug</warning>'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$outputMock->expects($this->at($index++))
|
||||||
|
->method("getVerbosity")
|
||||||
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
|
|
||||||
$outputMock->expects($this->at($index++))
|
$outputMock->expects($this->at($index++))
|
||||||
->method("write")
|
->method("write")
|
||||||
->with($this->equalTo(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF'])));
|
->with($this->equalTo(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF'])));
|
||||||
|
|
|
@ -88,8 +88,7 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
$this->fs = new Filesystem;
|
$this->fs = new Filesystem;
|
||||||
$that = $this;
|
$that = $this;
|
||||||
|
|
||||||
$this->workingDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest-'.md5(uniqid('', true));
|
$this->workingDir = $this->getUniqueTmpDirectory();
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir);
|
|
||||||
$this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload';
|
$this->vendorDir = $this->workingDir.DIRECTORY_SEPARATOR.'composer-test-autoload';
|
||||||
$this->ensureDirectoryExistsAndClear($this->vendorDir);
|
$this->ensureDirectoryExistsAndClear($this->vendorDir);
|
||||||
|
|
||||||
|
@ -144,6 +143,7 @@ class AutoloadGeneratorTest extends TestCase
|
||||||
if (is_dir($this->workingDir)) {
|
if (is_dir($this->workingDir)) {
|
||||||
$this->fs->removeDirectory($this->workingDir);
|
$this->fs->removeDirectory($this->workingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_dir($this->vendorDir)) {
|
if (is_dir($this->vendorDir)) {
|
||||||
$this->fs->removeDirectory($this->vendorDir);
|
$this->fs->removeDirectory($this->vendorDir);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,11 @@
|
||||||
namespace Composer\Test\Autoload;
|
namespace Composer\Test\Autoload;
|
||||||
|
|
||||||
use Composer\Autoload\ClassMapGenerator;
|
use Composer\Autoload\ClassMapGenerator;
|
||||||
|
use Composer\TestCase;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
|
class ClassMapGeneratorTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @dataProvider getTestCreateMapTests
|
* @dataProvider getTestCreateMapTests
|
||||||
|
@ -127,10 +128,8 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
$this->checkIfFinderIsAvailable();
|
$this->checkIfFinderIsAvailable();
|
||||||
|
|
||||||
$tempDir = sys_get_temp_dir().'/ComposerTestAmbiguousRefs';
|
$tempDir = $this->getUniqueTmpDirectory();
|
||||||
if (!is_dir($tempDir.'/other')) {
|
$this->ensureDirectoryExistsAndClear($tempDir.'/other');
|
||||||
mkdir($tempDir.'/other', 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$finder = new Finder();
|
$finder = new Finder();
|
||||||
$finder->files()->in($tempDir);
|
$finder->files()->in($tempDir);
|
||||||
|
@ -171,13 +170,9 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
|
||||||
*/
|
*/
|
||||||
public function testUnambiguousReference()
|
public function testUnambiguousReference()
|
||||||
{
|
{
|
||||||
$tempDir = sys_get_temp_dir().'/ComposerTestUnambiguousRefs';
|
$tempDir = $this->getUniqueTmpDirectory();
|
||||||
if (!is_dir($tempDir)) {
|
|
||||||
mkdir($tempDir, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($tempDir.'/A.php', "<?php\nclass A {}");
|
file_put_contents($tempDir.'/A.php', "<?php\nclass A {}");
|
||||||
|
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$tempDir.'/B.php',
|
$tempDir.'/B.php',
|
||||||
"<?php
|
"<?php
|
||||||
|
|
|
@ -25,15 +25,15 @@ class CacheTest extends TestCase
|
||||||
$this->markTestSkipped('Test causes intermittent failures on Travis');
|
$this->markTestSkipped('Test causes intermittent failures on Travis');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->root = sys_get_temp_dir() . '/composer_testdir';
|
$this->root = $this->getUniqueTmpDirectory();
|
||||||
$this->ensureDirectoryExistsAndClear($this->root);
|
|
||||||
|
|
||||||
$this->files = array();
|
$this->files = array();
|
||||||
$zeros = str_repeat('0', 1000);
|
$zeros = str_repeat('0', 1000);
|
||||||
|
|
||||||
for ($i = 0; $i < 4; $i++) {
|
for ($i = 0; $i < 4; $i++) {
|
||||||
file_put_contents("{$this->root}/cached.file{$i}.zip", $zeros);
|
file_put_contents("{$this->root}/cached.file{$i}.zip", $zeros);
|
||||||
$this->files[] = new \SplFileInfo("{$this->root}/cached.file{$i}.zip");
|
$this->files[] = new \SplFileInfo("{$this->root}/cached.file{$i}.zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->finder = $this->getMockBuilder('Symfony\Component\Finder\Finder')->disableOriginalConstructor()->getMock();
|
$this->finder = $this->getMockBuilder('Symfony\Component\Finder\Finder')->disableOriginalConstructor()->getMock();
|
||||||
|
|
||||||
$io = $this->getMock('Composer\IO\IOInterface');
|
$io = $this->getMock('Composer\IO\IOInterface');
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "my-vend/my-app",
|
||||||
|
"license": "MIT",
|
||||||
|
"repositories": {
|
||||||
|
"example_tld": {
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://example.tld",
|
||||||
|
"options": {
|
||||||
|
"ssl": {
|
||||||
|
"local_cert": "/home/composer/.ssl/composer.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,9 +14,10 @@ namespace Composer\Test\Json;
|
||||||
|
|
||||||
use Composer\Config\JsonConfigSource;
|
use Composer\Config\JsonConfigSource;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
class JsonConfigSourceTest extends \PHPUnit_Framework_TestCase
|
class JsonConfigSourceTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var Filesystem */
|
/** @var Filesystem */
|
||||||
private $fs;
|
private $fs;
|
||||||
|
@ -31,8 +32,7 @@ class JsonConfigSourceTest extends \PHPUnit_Framework_TestCase
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->fs = new Filesystem;
|
$this->fs = new Filesystem;
|
||||||
$this->workingDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest';
|
$this->workingDir = $this->getUniqueTmpDirectory();
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown()
|
protected function tearDown()
|
||||||
|
@ -52,6 +52,24 @@ class JsonConfigSourceTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertFileEquals($this->fixturePath('config/config-with-exampletld-repository.json'), $config);
|
$this->assertFileEquals($this->fixturePath('config/config-with-exampletld-repository.json'), $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAddRepositoryWithOptions()
|
||||||
|
{
|
||||||
|
$config = $this->workingDir.'/composer.json';
|
||||||
|
copy($this->fixturePath('composer-repositories.json'), $config);
|
||||||
|
$jsonConfigSource = new JsonConfigSource(new JsonFile($config));
|
||||||
|
$jsonConfigSource->addRepository('example_tld', array(
|
||||||
|
'type' => 'composer',
|
||||||
|
'url' => 'https://example.tld',
|
||||||
|
'options' => array(
|
||||||
|
'ssl' => array(
|
||||||
|
'local_cert' => '/home/composer/.ssl/composer.pem',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertFileEquals($this->fixturePath('config/config-with-exampletld-repository-and-options.json'), $config);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRemoveRepository()
|
public function testRemoveRepository()
|
||||||
{
|
{
|
||||||
$config = $this->workingDir.'/composer.json';
|
$config = $this->workingDir.'/composer.json';
|
||||||
|
|
|
@ -148,6 +148,16 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertEquals('/baz', $config->get('cache-dir'));
|
$this->assertEquals('/baz', $config->get('cache-dir'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testStreamWrapperDirs()
|
||||||
|
{
|
||||||
|
$config = new Config(false, '/foo/bar');
|
||||||
|
$config->merge(array('config' => array(
|
||||||
|
'cache-dir' => 's3://baz/',
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->assertEquals('s3://baz', $config->get('cache-dir'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testFetchingRelativePaths()
|
public function testFetchingRelativePaths()
|
||||||
{
|
{
|
||||||
$config = new Config(false, '/foo/bar');
|
$config = new Config(false, '/foo/bar');
|
||||||
|
|
|
@ -24,5 +24,4 @@ class DefaultConfigTest extends \PHPUnit_Framework_TestCase
|
||||||
$config = new Config;
|
$config = new Config;
|
||||||
$this->assertFalse($config->get('disable-tls'));
|
$this->assertFalse($config->get('disable-tls'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -709,7 +709,7 @@ class SolverTest extends TestCase
|
||||||
$msg .= "Potential causes:\n";
|
$msg .= "Potential causes:\n";
|
||||||
$msg .= " - A typo in the package name\n";
|
$msg .= " - A typo in the package name\n";
|
||||||
$msg .= " - The package is not available in a stable-enough version according to your minimum-stability setting\n";
|
$msg .= " - The package is not available in a stable-enough version according to your minimum-stability setting\n";
|
||||||
$msg .= " see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.\n\n";
|
$msg .= " see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n\n";
|
||||||
$msg .= "Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
$msg .= "Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
|
||||||
$this->assertEquals($msg, $e->getMessage());
|
$this->assertEquals($msg, $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
namespace Composer\Test\Downloader;
|
namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\FileDownloader;
|
use Composer\Downloader\FileDownloader;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
class FileDownloaderTest extends \PHPUnit_Framework_TestCase
|
class FileDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $rfs = null, $filesystem = null)
|
protected function getDownloader($io = null, $config = null, $eventDispatcher = null, $cache = null, $rfs = null, $filesystem = null)
|
||||||
{
|
{
|
||||||
|
@ -53,9 +54,9 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
->will($this->returnValue(array('url')))
|
->will($this->returnValue(array('url')))
|
||||||
;
|
;
|
||||||
|
|
||||||
$path = tempnam(sys_get_temp_dir(), 'c');
|
$path = tempnam($this->getUniqueTmpDirectory(), 'c');
|
||||||
|
|
||||||
$downloader = $this->getDownloader();
|
$downloader = $this->getDownloader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$downloader->download($packageMock, $path);
|
$downloader->download($packageMock, $path);
|
||||||
$this->fail();
|
$this->fail();
|
||||||
|
@ -102,10 +103,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
->will($this->returnValue(array()))
|
->will($this->returnValue(array()))
|
||||||
;
|
;
|
||||||
|
|
||||||
do {
|
$path = $this->getUniqueTmpDirectory();
|
||||||
$path = sys_get_temp_dir().'/'.md5(time().mt_rand());
|
|
||||||
} while (file_exists($path));
|
|
||||||
|
|
||||||
$ioMock = $this->getMock('Composer\IO\IOInterface');
|
$ioMock = $this->getMock('Composer\IO\IOInterface');
|
||||||
$ioMock->expects($this->any())
|
$ioMock->expects($this->any())
|
||||||
->method('write')
|
->method('write')
|
||||||
|
@ -187,14 +185,9 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
;
|
;
|
||||||
$filesystem = $this->getMock('Composer\Util\Filesystem');
|
$filesystem = $this->getMock('Composer\Util\Filesystem');
|
||||||
|
|
||||||
do {
|
$path = $this->getUniqueTmpDirectory();
|
||||||
$path = sys_get_temp_dir().'/'.md5(time().mt_rand());
|
|
||||||
} while (file_exists($path));
|
|
||||||
|
|
||||||
$downloader = $this->getDownloader(null, null, null, null, null, $filesystem);
|
$downloader = $this->getDownloader(null, null, null, null, null, $filesystem);
|
||||||
|
|
||||||
// make sure the file expected to be downloaded is on disk already
|
// make sure the file expected to be downloaded is on disk already
|
||||||
mkdir($path, 0777, true);
|
|
||||||
touch($path.'/script.js');
|
touch($path.'/script.js');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -14,9 +14,11 @@ namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\GitDownloader;
|
use Composer\Downloader\GitDownloader;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
|
|
||||||
class GitDownloaderTest extends \PHPUnit_Framework_TestCase
|
class GitDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var Filesystem */
|
/** @var Filesystem */
|
||||||
private $fs;
|
private $fs;
|
||||||
|
@ -26,7 +28,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->fs = new Filesystem;
|
$this->fs = new Filesystem;
|
||||||
$this->workingDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest-'.md5(uniqid('', true));
|
$this->workingDir = $this->getUniqueTmpDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown()
|
protected function tearDown()
|
||||||
|
@ -352,7 +354,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
private function winCompat($cmd)
|
private function winCompat($cmd)
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$cmd = str_replace('cd ', 'cd /D ', $cmd);
|
$cmd = str_replace('cd ', 'cd /D ', $cmd);
|
||||||
$cmd = str_replace('composerPath', getcwd().'/composerPath', $cmd);
|
$cmd = str_replace('composerPath', getcwd().'/composerPath', $cmd);
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,18 @@
|
||||||
namespace Composer\Test\Downloader;
|
namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\HgDownloader;
|
use Composer\Downloader\HgDownloader;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
|
|
||||||
class HgDownloaderTest extends \PHPUnit_Framework_TestCase
|
class HgDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $workingDir;
|
private $workingDir;
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->workingDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'cmptest-'.md5(uniqid('', true));
|
$this->workingDir = $this->getUniqueTmpDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown()
|
protected function tearDown()
|
||||||
|
@ -155,10 +157,6 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
private function getCmd($cmd)
|
private function getCmd($cmd)
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
return Platform::isWindows() ? strtr($cmd, "'", '"') : $cmd;
|
||||||
return strtr($cmd, "'", '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cmd;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
namespace Composer\Test\Downloader;
|
namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\PearPackageExtractor;
|
use Composer\Downloader\PearPackageExtractor;
|
||||||
|
use Composer\TestCase;
|
||||||
|
|
||||||
class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase
|
class PearPackageExtractorTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testShouldExtractPackage_1_0()
|
public function testShouldExtractPackage_1_0()
|
||||||
{
|
{
|
||||||
|
@ -122,7 +123,7 @@ class PearPackageExtractorTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
public function testShouldPerformReplacements()
|
public function testShouldPerformReplacements()
|
||||||
{
|
{
|
||||||
$from = tempnam(sys_get_temp_dir(), 'pear-extract');
|
$from = tempnam($this->getUniqueTmpDirectory(), 'pear-extract');
|
||||||
$to = $from.'-to';
|
$to = $from.'-to';
|
||||||
|
|
||||||
$original = 'replaced: @placeholder@; not replaced: @another@; replaced again: @placeholder@';
|
$original = 'replaced: @placeholder@; not replaced: @another@; replaced again: @placeholder@';
|
||||||
|
|
|
@ -16,12 +16,13 @@ use Composer\Downloader\PerforceDownloader;
|
||||||
use Composer\Config;
|
use Composer\Config;
|
||||||
use Composer\Repository\VcsRepository;
|
use Composer\Repository\VcsRepository;
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Matt Whittom <Matt.Whittom@veteransunited.com>
|
* @author Matt Whittom <Matt.Whittom@veteransunited.com>
|
||||||
*/
|
*/
|
||||||
class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase
|
class PerforceDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
protected $config;
|
protected $config;
|
||||||
protected $downloader;
|
protected $downloader;
|
||||||
|
@ -34,7 +35,7 @@ class PerforceDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp()
|
||||||
{
|
{
|
||||||
$this->testPath = sys_get_temp_dir() . '/composer-test';
|
$this->testPath = $this->getUniqueTmpDirectory();
|
||||||
$this->repoConfig = $this->getRepoConfig();
|
$this->repoConfig = $this->getRepoConfig();
|
||||||
$this->config = $this->getConfig();
|
$this->config = $this->getConfig();
|
||||||
$this->io = $this->getMockIoInterface();
|
$this->io = $this->getMockIoInterface();
|
||||||
|
|
|
@ -13,10 +13,12 @@
|
||||||
namespace Composer\Test\Downloader;
|
namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\XzDownloader;
|
use Composer\Downloader\XzDownloader;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
use Composer\Util\Platform;
|
||||||
use Composer\Util\RemoteFilesystem;
|
use Composer\Util\RemoteFilesystem;
|
||||||
|
|
||||||
class XzDownloaderTest extends \PHPUnit_Framework_TestCase
|
class XzDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var Filesystem
|
* @var Filesystem
|
||||||
|
@ -30,10 +32,10 @@ class XzDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
if (Platform::isWindows()) {
|
||||||
$this->markTestSkipped('Skip test on Windows');
|
$this->markTestSkipped('Skip test on Windows');
|
||||||
}
|
}
|
||||||
$this->testDir = sys_get_temp_dir().'/composer-xz-test-vendor';
|
$this->testDir = $this->getUniqueTmpDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
|
@ -67,7 +69,7 @@ class XzDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
$downloader = new XzDownloader($io, $config, null, null, null, new RemoteFilesystem($io));
|
$downloader = new XzDownloader($io, $config, null, null, null, new RemoteFilesystem($io));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$downloader->download($packageMock, sys_get_temp_dir().'/composer-xz-test');
|
$downloader->download($packageMock, $this->getUniqueTmpDirectory());
|
||||||
$this->fail('Download of invalid tarball should throw an exception');
|
$this->fail('Download of invalid tarball should throw an exception');
|
||||||
} catch (\RuntimeException $e) {
|
} catch (\RuntimeException $e) {
|
||||||
$this->assertContains('File format not recognized', $e->getMessage());
|
$this->assertContains('File format not recognized', $e->getMessage());
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
namespace Composer\Test\Downloader;
|
namespace Composer\Test\Downloader;
|
||||||
|
|
||||||
use Composer\Downloader\ZipDownloader;
|
use Composer\Downloader\ZipDownloader;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
|
|
||||||
class ZipDownloaderTest extends \PHPUnit_Framework_TestCase
|
class ZipDownloaderTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
@ -28,7 +28,8 @@ class ZipDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
if (!class_exists('ZipArchive')) {
|
if (!class_exists('ZipArchive')) {
|
||||||
$this->markTestSkipped('zip extension missing');
|
$this->markTestSkipped('zip extension missing');
|
||||||
}
|
}
|
||||||
$this->testDir = sys_get_temp_dir().'/composer-zip-test-vendor';
|
|
||||||
|
$this->testDir = $this->getUniqueTmpDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
|
@ -64,6 +65,10 @@ class ZipDownloaderTest extends \PHPUnit_Framework_TestCase
|
||||||
->with('cafile')
|
->with('cafile')
|
||||||
->will($this->returnValue(null));
|
->will($this->returnValue(null));
|
||||||
$config->expects($this->at(2))
|
$config->expects($this->at(2))
|
||||||
|
->method('get')
|
||||||
|
->with('capath')
|
||||||
|
->will($this->returnValue(null));
|
||||||
|
$config->expects($this->at(3))
|
||||||
->method('get')
|
->method('get')
|
||||||
->with('vendor-dir')
|
->with('vendor-dir')
|
||||||
->will($this->returnValue($this->testDir));
|
->will($this->returnValue($this->testDir));
|
||||||
|
|
|
@ -15,9 +15,11 @@ namespace Composer\Test\EventDispatcher;
|
||||||
use Composer\EventDispatcher\Event;
|
use Composer\EventDispatcher\Event;
|
||||||
use Composer\Installer\InstallerEvents;
|
use Composer\Installer\InstallerEvents;
|
||||||
use Composer\TestCase;
|
use Composer\TestCase;
|
||||||
|
use Composer\IO\BufferIO;
|
||||||
use Composer\Script\ScriptEvents;
|
use Composer\Script\ScriptEvents;
|
||||||
use Composer\Script\CommandEvent;
|
use Composer\Script\CommandEvent;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class EventDispatcherTest extends TestCase
|
class EventDispatcherTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -101,7 +103,7 @@ class EventDispatcherTest extends TestCase
|
||||||
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
||||||
->setConstructorArgs(array(
|
->setConstructorArgs(array(
|
||||||
$this->getMock('Composer\Composer'),
|
$this->getMock('Composer\Composer'),
|
||||||
$io = $this->getMock('Composer\IO\IOInterface'),
|
$io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
|
||||||
$process,
|
$process,
|
||||||
))
|
))
|
||||||
->setMethods(array(
|
->setMethods(array(
|
||||||
|
@ -123,23 +125,12 @@ class EventDispatcherTest extends TestCase
|
||||||
->method('getListeners')
|
->method('getListeners')
|
||||||
->will($this->returnValue($listeners));
|
->will($this->returnValue($listeners));
|
||||||
|
|
||||||
$io->expects($this->any())
|
|
||||||
->method('isVerbose')
|
|
||||||
->willReturn(1);
|
|
||||||
|
|
||||||
$io->expects($this->at(1))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> post-install-cmd: echo -n foo'));
|
|
||||||
|
|
||||||
$io->expects($this->at(3))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'));
|
|
||||||
|
|
||||||
$io->expects($this->at(5))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> post-install-cmd: echo -n bar'));
|
|
||||||
|
|
||||||
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
|
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
|
||||||
|
|
||||||
|
$expected = '> post-install-cmd: echo -n foo'.PHP_EOL.
|
||||||
|
'> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL.
|
||||||
|
'> post-install-cmd: echo -n bar'.PHP_EOL;
|
||||||
|
$this->assertEquals($expected, $io->getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDispatcherCanExecuteComposerScriptGroups()
|
public function testDispatcherCanExecuteComposerScriptGroups()
|
||||||
|
@ -148,7 +139,7 @@ class EventDispatcherTest extends TestCase
|
||||||
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
||||||
->setConstructorArgs(array(
|
->setConstructorArgs(array(
|
||||||
$composer = $this->getMock('Composer\Composer'),
|
$composer = $this->getMock('Composer\Composer'),
|
||||||
$io = $this->getMock('Composer\IO\IOInterface'),
|
$io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
|
||||||
$process,
|
$process,
|
||||||
))
|
))
|
||||||
->setMethods(array(
|
->setMethods(array(
|
||||||
|
@ -174,31 +165,13 @@ class EventDispatcherTest extends TestCase
|
||||||
return array();
|
return array();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$io->expects($this->any())
|
|
||||||
->method('isVerbose')
|
|
||||||
->willReturn(1);
|
|
||||||
|
|
||||||
$io->expects($this->at(1))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> root: @group'));
|
|
||||||
|
|
||||||
$io->expects($this->at(3))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> group: echo -n foo'));
|
|
||||||
|
|
||||||
$io->expects($this->at(5))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> group: @subgroup'));
|
|
||||||
|
|
||||||
$io->expects($this->at(7))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> subgroup: echo -n baz'));
|
|
||||||
|
|
||||||
$io->expects($this->at(9))
|
|
||||||
->method('writeError')
|
|
||||||
->with($this->equalTo('> group: echo -n bar'));
|
|
||||||
|
|
||||||
$dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
|
$dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
|
||||||
|
$expected = '> root: @group'.PHP_EOL.
|
||||||
|
'> group: echo -n foo'.PHP_EOL.
|
||||||
|
'> group: @subgroup'.PHP_EOL.
|
||||||
|
'> subgroup: echo -n baz'.PHP_EOL.
|
||||||
|
'> group: echo -n bar'.PHP_EOL;
|
||||||
|
$this->assertEquals($expected, $io->getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,12 +24,12 @@ Abandoned packages are flagged
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies (including require-dev)</info>
|
Installing dependencies (including require-dev)
|
||||||
<warning>Package a/a is abandoned, you should avoid using it. No replacement was suggested.</warning>
|
<warning>Package a/a is abandoned, you should avoid using it. No replacement was suggested.</warning>
|
||||||
<warning>Package c/c is abandoned, you should avoid using it. Use b/b instead.</warning>
|
<warning>Package c/c is abandoned, you should avoid using it. Use b/b instead.</warning>
|
||||||
<info>Writing lock file</info>
|
Writing lock file
|
||||||
<info>Generating autoload files</info>
|
Generating autoload files
|
||||||
|
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Installing a/a (1.0.0)
|
Installing a/a (1.0.0)
|
||||||
|
|
|
@ -21,9 +21,9 @@ Broken dependencies should not lead to a replacer being installed which is not m
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies (including require-dev)</info>
|
Installing dependencies (including require-dev)
|
||||||
<error>Your requirements could not be resolved to an installable set of packages.</error>
|
Your requirements could not be resolved to an installable set of packages.
|
||||||
|
|
||||||
Problem 1
|
Problem 1
|
||||||
- c/c 1.0.0 requires x/x 1.0 -> no matching package found.
|
- c/c 1.0.0 requires x/x 1.0 -> no matching package found.
|
||||||
|
@ -33,7 +33,7 @@ install
|
||||||
Potential causes:
|
Potential causes:
|
||||||
- A typo in the package name
|
- A typo in the package name
|
||||||
- The package is not available in a stable-enough version according to your minimum-stability setting
|
- The package is not available in a stable-enough version according to your minimum-stability setting
|
||||||
see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.
|
see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.
|
||||||
|
|
||||||
Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
|
Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
--TEST--
|
||||||
|
Tries to require a package with the same name as the root package
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"name": "foo/bar",
|
||||||
|
"require": {
|
||||||
|
"foo/bar": "@dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--RUN--
|
||||||
|
install
|
||||||
|
--EXPECT-EXCEPTION--
|
||||||
|
InvalidArgumentException
|
||||||
|
--EXPECT--
|
||||||
|
Root package 'foo/bar' cannot require itself in its composer.json
|
||||||
|
Did you accidentally name your root package after an external package?
|
|
@ -19,10 +19,10 @@ Suggestions are not displayed for installed packages
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies (including require-dev)</info>
|
Installing dependencies (including require-dev)
|
||||||
<info>Writing lock file</info>
|
Writing lock file
|
||||||
<info>Generating autoload files</info>
|
Generating autoload files
|
||||||
|
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Installing a/a (1.0.0)
|
Installing a/a (1.0.0)
|
||||||
|
|
|
@ -17,10 +17,10 @@ Suggestions are not displayed in non-dev mode
|
||||||
--RUN--
|
--RUN--
|
||||||
install --no-dev
|
install --no-dev
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies</info>
|
Installing dependencies
|
||||||
<info>Writing lock file</info>
|
Writing lock file
|
||||||
<info>Generating autoload files</info>
|
Generating autoload files
|
||||||
|
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Installing a/a (1.0.0)
|
Installing a/a (1.0.0)
|
||||||
|
|
|
@ -19,10 +19,10 @@ Suggestions are not displayed for packages if they are replaced
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies (including require-dev)</info>
|
Installing dependencies (including require-dev)
|
||||||
<info>Writing lock file</info>
|
Writing lock file
|
||||||
<info>Generating autoload files</info>
|
Generating autoload files
|
||||||
|
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Installing c/c (1.0.0)
|
Installing c/c (1.0.0)
|
||||||
|
|
|
@ -17,11 +17,11 @@ Suggestions are displayed
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
--EXPECT-OUTPUT--
|
--EXPECT-OUTPUT--
|
||||||
<info>Loading composer repositories with package information</info>
|
Loading composer repositories with package information
|
||||||
<info>Installing dependencies (including require-dev)</info>
|
Installing dependencies (including require-dev)
|
||||||
a/a suggests installing b/b (an obscure reason)
|
a/a suggests installing b/b (an obscure reason)
|
||||||
<info>Writing lock file</info>
|
Writing lock file
|
||||||
<info>Generating autoload files</info>
|
Generating autoload files
|
||||||
|
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
Installing a/a (1.0.0)
|
Installing a/a (1.0.0)
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Test\IO;
|
||||||
|
|
||||||
use Composer\IO\ConsoleIO;
|
use Composer\IO\ConsoleIO;
|
||||||
use Composer\TestCase;
|
use Composer\TestCase;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class ConsoleIOTest extends TestCase
|
class ConsoleIOTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -40,6 +41,9 @@ class ConsoleIOTest extends TestCase
|
||||||
{
|
{
|
||||||
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||||
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
||||||
|
$outputMock->expects($this->once())
|
||||||
|
->method('getVerbosity')
|
||||||
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
$outputMock->expects($this->once())
|
$outputMock->expects($this->once())
|
||||||
->method('write')
|
->method('write')
|
||||||
->with($this->equalTo('some information about something'), $this->equalTo(false));
|
->with($this->equalTo('some information about something'), $this->equalTo(false));
|
||||||
|
@ -53,6 +57,9 @@ class ConsoleIOTest extends TestCase
|
||||||
{
|
{
|
||||||
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||||
$outputMock = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface');
|
$outputMock = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface');
|
||||||
|
$outputMock->expects($this->once())
|
||||||
|
->method('getVerbosity')
|
||||||
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
$outputMock->expects($this->once())
|
$outputMock->expects($this->once())
|
||||||
->method('getErrorOutput')
|
->method('getErrorOutput')
|
||||||
->willReturn($outputMock);
|
->willReturn($outputMock);
|
||||||
|
@ -69,6 +76,9 @@ class ConsoleIOTest extends TestCase
|
||||||
{
|
{
|
||||||
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||||
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
||||||
|
$outputMock->expects($this->once())
|
||||||
|
->method('getVerbosity')
|
||||||
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
$outputMock->expects($this->once())
|
$outputMock->expects($this->once())
|
||||||
->method('write')
|
->method('write')
|
||||||
->with(
|
->with(
|
||||||
|
@ -95,25 +105,28 @@ class ConsoleIOTest extends TestCase
|
||||||
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
$inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||||
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
$outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
||||||
|
|
||||||
$outputMock->expects($this->at(0))
|
$outputMock->expects($this->any())
|
||||||
->method('write')
|
->method('getVerbosity')
|
||||||
->with($this->equalTo('something (<question>strlen = 23</question>)'));
|
->willReturn(OutputInterface::VERBOSITY_NORMAL);
|
||||||
$outputMock->expects($this->at(1))
|
$outputMock->expects($this->at(1))
|
||||||
->method('write')
|
->method('write')
|
||||||
->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
|
->with($this->equalTo('something (<question>strlen = 23</question>)'));
|
||||||
$outputMock->expects($this->at(2))
|
|
||||||
->method('write')
|
|
||||||
->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
|
|
||||||
$outputMock->expects($this->at(3))
|
$outputMock->expects($this->at(3))
|
||||||
->method('write')
|
->method('write')
|
||||||
->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
|
->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
|
||||||
$outputMock->expects($this->at(4))
|
|
||||||
->method('write')
|
|
||||||
->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
|
|
||||||
$outputMock->expects($this->at(5))
|
$outputMock->expects($this->at(5))
|
||||||
|
->method('write')
|
||||||
|
->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
|
||||||
|
$outputMock->expects($this->at(7))
|
||||||
|
->method('write')
|
||||||
|
->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
|
||||||
|
$outputMock->expects($this->at(9))
|
||||||
|
->method('write')
|
||||||
|
->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
|
||||||
|
$outputMock->expects($this->at(11))
|
||||||
->method('write')
|
->method('write')
|
||||||
->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
|
->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
|
||||||
$outputMock->expects($this->at(6))
|
$outputMock->expects($this->at(13))
|
||||||
->method('write')
|
->method('write')
|
||||||
->with($this->equalTo('something longer than initial (<info>34</info>)'));
|
->with($this->equalTo('something longer than initial (<info>34</info>)'));
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ class LibraryInstallerTest extends TestCase
|
||||||
{
|
{
|
||||||
protected $composer;
|
protected $composer;
|
||||||
protected $config;
|
protected $config;
|
||||||
|
protected $rootDir;
|
||||||
protected $vendorDir;
|
protected $vendorDir;
|
||||||
protected $binDir;
|
protected $binDir;
|
||||||
protected $dm;
|
protected $dm;
|
||||||
|
@ -37,10 +38,11 @@ class LibraryInstallerTest extends TestCase
|
||||||
$this->config = new Config();
|
$this->config = new Config();
|
||||||
$this->composer->setConfig($this->config);
|
$this->composer->setConfig($this->config);
|
||||||
|
|
||||||
$this->vendorDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-vendor';
|
$this->rootDir = $this->getUniqueTmpDirectory();
|
||||||
|
$this->vendorDir = $this->rootDir.DIRECTORY_SEPARATOR.'vendor';
|
||||||
$this->ensureDirectoryExistsAndClear($this->vendorDir);
|
$this->ensureDirectoryExistsAndClear($this->vendorDir);
|
||||||
|
|
||||||
$this->binDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-bin';
|
$this->binDir = $this->rootDir.DIRECTORY_SEPARATOR.'bin';
|
||||||
$this->ensureDirectoryExistsAndClear($this->binDir);
|
$this->ensureDirectoryExistsAndClear($this->binDir);
|
||||||
|
|
||||||
$this->config->merge(array(
|
$this->config->merge(array(
|
||||||
|
@ -61,8 +63,7 @@ class LibraryInstallerTest extends TestCase
|
||||||
|
|
||||||
protected function tearDown()
|
protected function tearDown()
|
||||||
{
|
{
|
||||||
$this->fs->removeDirectory($this->vendorDir);
|
$this->fs->removeDirectory($this->rootDir);
|
||||||
$this->fs->removeDirectory($this->binDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInstallerCreationShouldNotCreateVendorDirectory()
|
public function testInstallerCreationShouldNotCreateVendorDirectory()
|
||||||
|
|
|
@ -26,7 +26,10 @@ use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
|
||||||
use Composer\Test\Mock\InstallationManagerMock;
|
use Composer\Test\Mock\InstallationManagerMock;
|
||||||
use Symfony\Component\Console\Input\StringInput;
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
use Symfony\Component\Console\Output\StreamOutput;
|
use Symfony\Component\Console\Output\StreamOutput;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||||
use Composer\TestCase;
|
use Composer\TestCase;
|
||||||
|
use Composer\IO\BufferIO;
|
||||||
|
|
||||||
class InstallerTest extends TestCase
|
class InstallerTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -137,7 +140,7 @@ class InstallerTest extends TestCase
|
||||||
/**
|
/**
|
||||||
* @dataProvider getIntegrationTests
|
* @dataProvider getIntegrationTests
|
||||||
*/
|
*/
|
||||||
public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode)
|
public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectResult)
|
||||||
{
|
{
|
||||||
if ($condition) {
|
if ($condition) {
|
||||||
eval('$res = '.$condition.';');
|
eval('$res = '.$condition.';');
|
||||||
|
@ -146,18 +149,15 @@ class InstallerTest extends TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = null;
|
$io = new BufferIO('', OutputInterface::VERBOSITY_NORMAL, new OutputFormatter(false));
|
||||||
$io = $this->getMock('Composer\IO\IOInterface');
|
|
||||||
$callback = function ($text, $newline) use (&$output) {
|
|
||||||
$output .= $text . ($newline ? "\n" : "");
|
|
||||||
};
|
|
||||||
$io->expects($this->any())
|
|
||||||
->method('write')
|
|
||||||
->will($this->returnCallback($callback));
|
|
||||||
$io->expects($this->any())
|
|
||||||
->method('writeError')
|
|
||||||
->will($this->returnCallback($callback));
|
|
||||||
|
|
||||||
|
// Prepare for exceptions
|
||||||
|
if (!is_int($expectResult)) {
|
||||||
|
$normalizedOutput = rtrim(str_replace("\n", PHP_EOL, $expect));
|
||||||
|
$this->setExpectedException($expectResult, $normalizedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Composer mock object according to configuration
|
||||||
$composer = FactoryMock::create($io, $composerConfig);
|
$composer = FactoryMock::create($io, $composerConfig);
|
||||||
|
|
||||||
$jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
|
$jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
|
||||||
|
@ -233,8 +233,14 @@ class InstallerTest extends TestCase
|
||||||
$appOutput = fopen('php://memory', 'w+');
|
$appOutput = fopen('php://memory', 'w+');
|
||||||
$result = $application->run(new StringInput($run), new StreamOutput($appOutput));
|
$result = $application->run(new StringInput($run), new StreamOutput($appOutput));
|
||||||
fseek($appOutput, 0);
|
fseek($appOutput, 0);
|
||||||
$this->assertEquals($expectExitCode, $result, $output . stream_get_contents($appOutput));
|
|
||||||
|
|
||||||
|
// Shouldn't check output and results if an exception was expected by this point
|
||||||
|
if (!is_int($expectResult)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = str_replace("\r", '', $io->getOutput());
|
||||||
|
$this->assertEquals($expectResult, $result, $output . stream_get_contents($appOutput));
|
||||||
if ($expectLock) {
|
if ($expectLock) {
|
||||||
unset($actualLock['hash']);
|
unset($actualLock['hash']);
|
||||||
unset($actualLock['content-hash']);
|
unset($actualLock['content-hash']);
|
||||||
|
@ -266,7 +272,7 @@ class InstallerTest extends TestCase
|
||||||
$installedDev = array();
|
$installedDev = array();
|
||||||
$lock = array();
|
$lock = array();
|
||||||
$expectLock = array();
|
$expectLock = array();
|
||||||
$expectExitCode = 0;
|
$expectResult = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$message = $testData['TEST'];
|
$message = $testData['TEST'];
|
||||||
|
@ -303,12 +309,21 @@ class InstallerTest extends TestCase
|
||||||
}
|
}
|
||||||
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
|
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
|
||||||
$expect = $testData['EXPECT'];
|
$expect = $testData['EXPECT'];
|
||||||
$expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? (int) $testData['EXPECT-EXIT-CODE'] : 0;
|
if (!empty($testData['EXPECT-EXCEPTION'])) {
|
||||||
|
$expectResult = $testData['EXPECT-EXCEPTION'];
|
||||||
|
if (!empty($testData['EXPECT-EXIT-CODE'])) {
|
||||||
|
throw new \LogicException('EXPECT-EXCEPTION and EXPECT-EXIT-CODE are mutually exclusive');
|
||||||
|
}
|
||||||
|
} elseif (!empty($testData['EXPECT-EXIT-CODE'])) {
|
||||||
|
$expectResult = (int) $testData['EXPECT-EXIT-CODE'];
|
||||||
|
} else {
|
||||||
|
$expectResult = 0;
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
|
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tests;
|
return $tests;
|
||||||
|
@ -328,6 +343,7 @@ class InstallerTest extends TestCase
|
||||||
'EXPECT-LOCK' => false,
|
'EXPECT-LOCK' => false,
|
||||||
'EXPECT-OUTPUT' => false,
|
'EXPECT-OUTPUT' => false,
|
||||||
'EXPECT-EXIT-CODE' => false,
|
'EXPECT-EXIT-CODE' => false,
|
||||||
|
'EXPECT-EXCEPTION' => false,
|
||||||
'EXPECT' => true,
|
'EXPECT' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,12 @@
|
||||||
namespace Composer\Test\Package\Archiver;
|
namespace Composer\Test\Package\Archiver;
|
||||||
|
|
||||||
use Composer\Package\Archiver\ArchivableFilesFinder;
|
use Composer\Package\Archiver\ArchivableFilesFinder;
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Symfony\Component\Process\ExecutableFinder;
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
|
|
||||||
class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase
|
class ArchivableFilesFinderTest extends TestCase
|
||||||
{
|
{
|
||||||
protected $sources;
|
protected $sources;
|
||||||
protected $finder;
|
protected $finder;
|
||||||
|
@ -29,7 +30,7 @@ class ArchivableFilesFinderTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->fs = $fs;
|
$this->fs = $fs;
|
||||||
|
|
||||||
$this->sources = $fs->normalizePath(
|
$this->sources = $fs->normalizePath(
|
||||||
realpath(sys_get_temp_dir()).'/composer_archiver_test'.uniqid(mt_rand(), true)
|
$this->getUniqueTmpDirectory()
|
||||||
);
|
);
|
||||||
|
|
||||||
$fileTree = array(
|
$fileTree = array(
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
|
|
||||||
namespace Composer\Test\Package\Archiver;
|
namespace Composer\Test\Package\Archiver;
|
||||||
|
|
||||||
|
use Composer\TestCase;
|
||||||
use Composer\Util\Filesystem;
|
use Composer\Util\Filesystem;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
use Composer\Package\Package;
|
use Composer\Package\Package;
|
||||||
|
|
||||||
abstract class ArchiverTest extends \PHPUnit_Framework_TestCase
|
abstract class ArchiverTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \Composer\Util\Filesystem
|
* @var \Composer\Util\Filesystem
|
||||||
|
@ -37,8 +38,7 @@ abstract class ArchiverTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
$this->filesystem = new Filesystem();
|
$this->filesystem = new Filesystem();
|
||||||
$this->process = new ProcessExecutor();
|
$this->process = new ProcessExecutor();
|
||||||
$this->testDir = sys_get_temp_dir().'/composer_archiver_test_'.mt_rand();
|
$this->testDir = $this->getUniqueTmpDirectory();
|
||||||
$this->filesystem->ensureDirectoryExists($this->testDir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
|
|
|
@ -21,14 +21,14 @@ class PharArchiverTest extends ArchiverTest
|
||||||
// Set up repository
|
// Set up repository
|
||||||
$this->setupDummyRepo();
|
$this->setupDummyRepo();
|
||||||
$package = $this->setupPackage();
|
$package = $this->setupPackage();
|
||||||
$target = sys_get_temp_dir().'/composer_archiver_test.tar';
|
$target = $this->getUniqueTmpDirectory().'/composer_archiver_test.tar';
|
||||||
|
|
||||||
// Test archive
|
// Test archive
|
||||||
$archiver = new PharArchiver();
|
$archiver = new PharArchiver();
|
||||||
$archiver->archive($package->getSourceUrl(), $target, 'tar', array('foo/bar', 'baz', '!/foo/bar/baz'));
|
$archiver->archive($package->getSourceUrl(), $target, 'tar', array('foo/bar', 'baz', '!/foo/bar/baz'));
|
||||||
$this->assertFileExists($target);
|
$this->assertFileExists($target);
|
||||||
|
|
||||||
unlink($target);
|
$this->filesystem->removeDirectory(dirname($target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testZipArchive()
|
public function testZipArchive()
|
||||||
|
@ -36,14 +36,14 @@ class PharArchiverTest extends ArchiverTest
|
||||||
// Set up repository
|
// Set up repository
|
||||||
$this->setupDummyRepo();
|
$this->setupDummyRepo();
|
||||||
$package = $this->setupPackage();
|
$package = $this->setupPackage();
|
||||||
$target = sys_get_temp_dir().'/composer_archiver_test.zip';
|
$target = $this->getUniqueTmpDirectory().'/composer_archiver_test.zip';
|
||||||
|
|
||||||
// Test archive
|
// Test archive
|
||||||
$archiver = new PharArchiver();
|
$archiver = new PharArchiver();
|
||||||
$archiver->archive($package->getSourceUrl(), $target, 'zip');
|
$archiver->archive($package->getSourceUrl(), $target, 'zip');
|
||||||
$this->assertFileExists($target);
|
$this->assertFileExists($target);
|
||||||
|
|
||||||
unlink($target);
|
$this->filesystem->removeDirectory(dirname($target));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,6 @@
|
||||||
"class": "Installer\\Plugin"
|
"class": "Installer\\Plugin"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer-plugin-api": "1.0.0"
|
"composer-plugin-api": "^1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,6 @@
|
||||||
"class": "Installer\\Plugin2"
|
"class": "Installer\\Plugin2"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer-plugin-api": "1.0.0"
|
"composer-plugin-api": "^1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue