diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..033f8a6da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.yml] +indent_size = 2 diff --git a/.travis.yml b/.travis.yml index c1511b6c3..f02fefcb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: php -sudo: false - dist: trusty git: @@ -16,54 +14,53 @@ addons: packages: - parallel -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - nightly - matrix: include: - php: 5.3 dist: precise + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 - php: 7.3 env: deps=high + - php: nightly fast_finish: true allow_failures: - php: nightly before_install: - # disable xdebug if available - - phpenv config-rm xdebug.ini || echo "xdebug not available" - # disable default memory limit - - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - echo memory_limit = -1 >> $INI + # disable xdebug if available + - phpenv config-rm xdebug.ini || echo "xdebug not available" + # disable default memory limit + - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - echo memory_limit = -1 >> $INI + - composer validate install: - # flags to pass to install - - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" - # update deps to latest in case of high deps build - - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi - # install dependencies using system provided composer binary - - composer install $flags - # install dependencies using composer from source - - bin/composer install $flags + # flags to pass to install + - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" + # update deps to latest in case of high deps build + - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi + # install dependencies using system provided composer binary + - composer install $flags + # install dependencies using composer from source + - bin/composer install $flags before_script: - # make sure git tests do not complain about user/email not being set - - git config --global user.name travis-ci - - git config --global user.email travis@example.com + # make sure git tests do not complain about user/email not being set + - git config --global user.name travis-ci + - git config --global user.email travis@example.com script: - # run test suite directories in parallel using GNU parallel - - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' + # run test suite directories in parallel using GNU parallel + - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' before_deploy: - - php -d phar.readonly=0 bin/compile + - php -d phar.readonly=0 bin/compile deploy: provider: releases diff --git a/composer.json b/composer.json index 03839772e..3867dd414 100644 --- a/composer.json +++ b/composer.json @@ -73,8 +73,13 @@ "bin/composer" ], "scripts": { + "compile": "@php -dphar.readonly=0 bin/compile", "test": "phpunit" }, + "scripts-descriptions": { + "compile": "Compile composer.phar", + "test": "Run all tests" + }, "support": { "issues": "https://github.com/composer/composer/issues", "irc": "irc://irc.freenode.org/composer" diff --git a/composer.lock b/composer.lock index d2a448608..f5b49a442 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "time": "2019-01-28T09:30:10+00:00" }, { "name": "composer/semver", diff --git a/doc/03-cli.md b/doc/03-cli.md index f0b3dca35..1d0a3cea2 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -796,58 +796,40 @@ COMPOSER=composer-other.json php composer.phar install The generated lock file will use the same name: `composer-other.lock` in this example. -### COMPOSER_ROOT_VERSION +### COMPOSER_ALLOW_SUPERUSER -By setting this var you can specify the version of the root package, if it can -not be guessed from VCS info and is not present in `composer.json`. +If set to 1, this env disables the warning about running commands as root/super user. +It also disables automatic clearing of sudo sessions, so you should really only set this +if you use Composer as super user at all times like in docker containers. -### COMPOSER_VENDOR_DIR +### COMPOSER_AUTH -By setting this var you can make Composer install the dependencies into a -directory other than `vendor`. +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, bitbucket-oauth, ... objects as needed, and following the +[spec from the config](06-config.md#gitlab-oauth). ### COMPOSER_BIN_DIR By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md)) directory to something other than `vendor/bin`. -### http_proxy or HTTP_PROXY - -If you are using Composer from behind an HTTP proxy, you can use the standard -`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy. -Many operating systems already set this variable for you. - -Using `http_proxy` (lowercased) or even defining both might be preferable since -some tools like git or curl will only use the lower-cased `http_proxy` version. -Alternatively you can also define the git proxy using -`git config --global http.proxy `. - -If you are using Composer in a non-CLI context (i.e. integration into a CMS or -similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY` -environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further -details. - -### no_proxy or NO_PROXY +### COMPOSER_CACHE_DIR -If you are behind a proxy and would like to disable it for certain domains, you -can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of -domains the proxy should *not* be used for. +The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, +which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. -The env var accepts domains, IP addresses, and IP address blocks in CIDR -notation. You can restrict the filter to a particular port (e.g. `:80`). You -can also set it to `*` to ignore the proxy for all HTTP requests. +By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and +`C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. -### HTTP_PROXY_REQUEST_FULLURI +### COMPOSER_CAFILE -If you use a proxy but it does not support the request_fulluri flag, then you -should set this env var to `false` or `0` to prevent Composer from setting the -request_fulluri option. +By setting this environmental value, you can set a path to a certificate bundle +file to be used during SSL/TLS peer verification. -### HTTPS_PROXY_REQUEST_FULLURI +### COMPOSER_DISCARD_CHANGES -If you use a proxy but it does not support the request_fulluri flag for HTTPS -requests, then you should set this env var to `false` or `0` to prevent Composer -from setting the request_fulluri option. +This env var controls the [`discard-changes`](06-config.md#discard-changes) config option. ### COMPOSER_HOME @@ -873,60 +855,78 @@ This file allows you to set [repositories](05-repositories.md) and In case global configuration matches _local_ configuration, the _local_ configuration in the project's `composer.json` always wins. -### COMPOSER_CACHE_DIR +### COMPOSER_HTACCESS_PROTECT -The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory, -which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option. +Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the +composer home, cache, and data directories. -By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and -`C:\Users\\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows. +### COMPOSER_MEMORY_LIMIT + +If set, the value is used as php's memory_limit. + +### COMPOSER_MIRROR_PATH_REPOS + +If set to 1, this env changes the default path repository strategy to `mirror` instead +of `symlink`. As it is the default strategy being set it can still be overwritten by +repository options. + +### COMPOSER_NO_INTERACTION + +If set to 1, this env var will make Composer behave as if you passed the +`--no-interaction` flag to every command. This can be set on build boxes/CI. ### COMPOSER_PROCESS_TIMEOUT This env var controls the time Composer waits for commands (such as git commands) to finish executing. The default value is 300 seconds (5 minutes). -### COMPOSER_CAFILE - -By setting this environmental value, you can set a path to a certificate bundle -file to be used during SSL/TLS peer verification. +### COMPOSER_ROOT_VERSION -### COMPOSER_AUTH +By setting this var you can specify the version of the root package, if it can +not be guessed from VCS info and is not present in `composer.json`. -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, bitbucket-oauth, ... objects as needed, and following the -[spec from the config](06-config.md#gitlab-oauth). +### COMPOSER_VENDOR_DIR -### COMPOSER_DISCARD_CHANGES +By setting this var you can make Composer install the dependencies into a +directory other than `vendor`. -This env var controls the [`discard-changes`](06-config.md#discard-changes) config option. +### http_proxy or HTTP_PROXY -### COMPOSER_NO_INTERACTION +If you are using Composer from behind an HTTP proxy, you can use the standard +`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy. +Many operating systems already set this variable for you. -If set to 1, this env var will make Composer behave as if you passed the -`--no-interaction` flag to every command. This can be set on build boxes/CI. +Using `http_proxy` (lowercased) or even defining both might be preferable since +some tools like git or curl will only use the lower-cased `http_proxy` version. +Alternatively you can also define the git proxy using +`git config --global http.proxy `. -### COMPOSER_ALLOW_SUPERUSER +If you are using Composer in a non-CLI context (i.e. integration into a CMS or +similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY` +environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further +details. -If set to 1, this env disables the warning about running commands as root/super user. -It also disables automatic clearing of sudo sessions, so you should really only set this -if you use Composer as super user at all times like in docker containers. +### HTTP_PROXY_REQUEST_FULLURI -### COMPOSER_MEMORY_LIMIT +If you use a proxy but it does not support the request_fulluri flag, then you +should set this env var to `false` or `0` to prevent Composer from setting the +request_fulluri option. -If set, the value is used as php's memory_limit. +### HTTPS_PROXY_REQUEST_FULLURI -### COMPOSER_MIRROR_PATH_REPOS +If you use a proxy but it does not support the request_fulluri flag for HTTPS +requests, then you should set this env var to `false` or `0` to prevent Composer +from setting the request_fulluri option. -If set to 1, this env changes the default path repository strategy to `mirror` instead -of `symlink`. As it is the default strategy being set it can still be overwritten by -repository options. +### no_proxy or NO_PROXY -### COMPOSER_HTACCESS_PROTECT +If you are behind a proxy and would like to disable it for certain domains, you +can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of +domains the proxy should *not* be used for. -Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the -composer home, cache, and data directories. +The env var accepts domains, IP addresses, and IP address blocks in CIDR +notation. You can restrict the filter to a particular port (e.g. `:80`). You +can also set it to `*` to ignore the proxy for all HTTP requests. ### COMPOSER_DISABLE_NETWORK diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4c6749521..bb3b94774 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,8 @@ - diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index ea3b5c892..7997dfb37 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -48,6 +48,7 @@ class RunScriptCommand extends BaseCommand { $this ->setName('run-script') + ->setAliases(array('run')) ->setDescription('Runs the scripts defined in composer.json.') ->setDefinition(array( new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 7b4220724..ad820ce3a 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -216,7 +216,6 @@ class Config case 'cache-vcs-dir': case 'cafile': case 'capath': - case 'htaccess-protect': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); @@ -230,6 +229,13 @@ class Config return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); + case 'htaccess-protect': + $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT'); + if (false === $value) { + $value = $this->config[$key]; + } + return $value !== 'false' && (bool) $value; + case 'cache-ttl': return (int) $this->config[$key]; diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 3398cef69..c89375ce4 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -17,6 +17,8 @@ use Composer\Util\Platform; use Composer\Util\Silencer; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -111,7 +113,9 @@ class Application extends BaseApplication { $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); - $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet()); + $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array( + new QuestionHelper(), + ))); ErrorHandler::register($io); // switch working dir diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 3da43777b..c37d7cf45 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -244,6 +244,12 @@ class EventDispatcher if (substr($exec, 0, 5) === '@php ') { $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5); + } else { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(false); + if ($phpPath) { + putenv('PHP_BINARY=' . $phpPath); + } } if (0 !== ($exitCode = $this->process->execute($exec))) { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index e66e64ed1..206cfce39 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -165,6 +165,16 @@ class Factory 'data-dir' => self::getDataDir($home), ))); + // load global config + $file = new JsonFile($config->get('home').'/config.json'); + if ($file->exists()) { + if ($io && $io->isDebug()) { + $io->writeError('Loading config file ' . $file->getPath()); + } + $config->merge($file->read()); + } + $config->setConfigSource(new JsonConfigSource($file)); + $htaccessProtect = (bool) $config->get('htaccess-protect'); if ($htaccessProtect) { // Protect directory against web access. Since HOME could be @@ -181,16 +191,6 @@ class Factory } } - // load global config - $file = new JsonFile($config->get('home').'/config.json'); - if ($file->exists()) { - if ($io && $io->isDebug()) { - $io->writeError('Loading config file ' . $file->getPath()); - } - $config->merge($file->read()); - } - $config->setConfigSource(new JsonConfigSource($file)); - // load global auth file $file = new JsonFile($config->get('home').'/auth.json'); if ($file->exists()) { diff --git a/src/Composer/IO/BufferIO.php b/src/Composer/IO/BufferIO.php index d47d4eaa5..db4671341 100644 --- a/src/Composer/IO/BufferIO.php +++ b/src/Composer/IO/BufferIO.php @@ -12,8 +12,10 @@ namespace Composer\IO; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Helper\HelperSet; @@ -34,7 +36,9 @@ class BufferIO extends ConsoleIO $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( + new QuestionHelper(), + ))); } public function getOutput() @@ -56,4 +60,27 @@ class BufferIO extends ConsoleIO return $output; } + + public function setUserInputs(array $inputs) + { + if (!$this->input instanceof StreamableInputInterface) { + throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $this->input->setStream($this->createStream($inputs)); + $this->input->setInteractive(true); + } + + private function createStream(array $inputs) + { + $stream = fopen('php://memory', 'r+', false); + + foreach ($inputs as $input) { + fwrite($stream, $input.PHP_EOL); + } + + rewind($stream); + + return $stream; + } } diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 0f709f60b..a14755bf1 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -197,7 +197,7 @@ class BinaryInstaller dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd) if [ -d /proc/cygdrive ] && [[ \$(which php) == \$(readlink -n /proc/cygdrive)/* ]]; then - # We are in Cgywin using Windows php, so the path must be translated + # We are in Cygwin using Windows php, so the path must be translated dir=\$(cygpath -m "\$dir"); fi diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index a61a75c34..dd3509499 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -34,6 +34,8 @@ class JsonFile const JSON_PRETTY_PRINT = 128; const JSON_UNESCAPED_UNICODE = 256; + const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json'; + private $path; private $httpDownloader; private $io; @@ -144,10 +146,11 @@ class JsonFile * Validates the schema of the current json file according to composer-schema.json rules * * @param int $schema a JsonFile::*_SCHEMA constant + * @param string|null $schemaFile a path to the schema file * @throws JsonValidationException * @return bool true on success */ - public function validateSchema($schema = self::STRICT_SCHEMA) + public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null) { $content = file_get_contents($this->path); $data = json_decode($content); @@ -156,7 +159,9 @@ class JsonFile self::validateSyntax($content, $this->path); } - $schemaFile = __DIR__ . '/../../../res/composer-schema.json'; + if (null === $schemaFile) { + $schemaFile = __DIR__ . self::COMPOSER_SCHEMA_PATH; + } // Prepend with file:// only when not using a special schema already (e.g. in the phar) if (false === strpos($schemaFile, '://')) { diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 359d6b053..e354e4454 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -150,10 +150,15 @@ class ArchiveManager $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid(); $filesystem->ensureDirectoryExists($sourcePath); - // Download sources - $promise = $this->downloadManager->download($package, $sourcePath); - $this->loop->wait(array($promise)); - $this->downloadManager->install($package, $sourcePath); + try { + // Download sources + $promise = $this->downloadManager->download($package, $sourcePath); + $this->loop->wait(array($promise)); + $this->downloadManager->install($package, $sourcePath); + } catch (\Exception $e) { + $filesystem->removeDirectory($sourcePath); + throw $e; + } // Check exclude from downloaded composer.json if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { diff --git a/tests/Composer/Test/IO/BufferIOTest.php b/tests/Composer/Test/IO/BufferIOTest.php new file mode 100644 index 000000000..013a3c100 --- /dev/null +++ b/tests/Composer/Test/IO/BufferIOTest.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\IO; + +use Composer\IO\BufferIO; +use Composer\Test\TestCase; +use Symfony\Component\Console\Input\StreamableInputInterface; + +class BufferIOTest extends TestCase +{ + public function testSetUserInputs() + { + $bufferIO = new BufferIO(); + + $refl = new \ReflectionProperty($bufferIO, 'input'); + $refl->setAccessible(true); + $input = $refl->getValue($bufferIO); + + if (!$input instanceof StreamableInputInterface) { + $this->setExpectedException('\RuntimeException', 'Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); + } + + $bufferIO->setUserInputs(array( + 'yes', + 'no', + '', + )); + + $this->assertTrue($bufferIO->askConfirmation('Please say yes!', 'no')); + $this->assertFalse($bufferIO->askConfirmation('Now please say no!', 'yes')); + $this->assertSame('default', $bufferIO->ask('Empty string last', 'default')); + } +}