diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index f394f28e9..66d84b8c2 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -22,15 +22,19 @@ Composer fires the following named events during its execution process: ### Command Events -- **pre-install-cmd**: occurs before the `install` command is executed with a lock file present. -- **post-install-cmd**: occurs after the `install` command has been executed with a lock file present. -- **pre-update-cmd**: occurs before the `update` command is executed, or before the `install` command is executed without a lock file present. -- **post-update-cmd**: occurs after the `update` command has been executed, or after the `install` command has been executed without a lock file present. +- **pre-install-cmd**: occurs before the `install` command is executed with a + lock file present. +- **post-install-cmd**: occurs after the `install` command has been executed + with a lock file present. +- **pre-update-cmd**: occurs before the `update` command is executed, or before + the `install` command is executed without a lock file present. +- **post-update-cmd**: occurs after the `update` command has been executed, or + after the `install` command has been executed without a lock file present. - **post-status-cmd**: occurs after the `status` command has been executed. - **pre-archive-cmd**: occurs before the `archive` command is executed. - **post-archive-cmd**: occurs after the `archive` command has been executed. -- **pre-autoload-dump**: occurs before the autoloader is dumped, either - during `install`/`update`, or via the `dump-autoload` command. +- **pre-autoload-dump**: occurs before the autoloader is dumped, either during + `install`/`update`, or via the `dump-autoload` command. - **post-autoload-dump**: occurs after the autoloader has been dumped, either during `install`/`update`, or via the `dump-autoload` command. - **post-root-package-install**: occurs after the root package has been @@ -150,6 +154,11 @@ class MyClass } ``` +**Note:** During a composer install or update process, a variable named +`COMPOSER_DEV_MODE` will be added to the environment. If the command was run +with the `--no-dev` flag, this variable will be set to 0, otherwise it will be +set to 1. + ## Event classes When an event is fired, your PHP callback receives as first argument a diff --git a/doc/faqs/how-to-install-composer-programmatically.md b/doc/faqs/how-to-install-composer-programmatically.md index ec252825d..4369ec9ce 100644 --- a/doc/faqs/how-to-install-composer-programmatically.md +++ b/doc/faqs/how-to-install-composer-programmatically.md @@ -9,21 +9,21 @@ An alternative is to use this script which only works with unix utils: ```bash #!/bin/sh -EXPECTED_SIGNATURE=$(wget https://composer.github.io/installer.sig -O - -q) +EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig) php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") -if [ "$EXPECTED_SIGNATURE" = "$ACTUAL_SIGNATURE" ] +if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] then - php composer-setup.php --quiet - RESULT=$? - rm composer-setup.php - exit $RESULT -else >&2 echo 'ERROR: Invalid installer signature' rm composer-setup.php exit 1 fi + +php composer-setup.php --quiet +RESULT=$? +rm composer-setup.php +exit $RESULT ``` The script will exit with 1 in case of failure, or 0 on success, and is quiet diff --git a/res/composer-schema.json b/res/composer-schema.json index e7f5bc5f6..19b9bceb7 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -77,32 +77,44 @@ "require": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "replace": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "conflict": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "provide": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "require-dev": { "type": "object", "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "suggest": { "type": "object", "description": "This is a hash of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "config": { "type": "object", @@ -134,12 +146,16 @@ "github-oauth": { "type": "object", "description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"\"}.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "gitlab-oauth": { "type": "object", "description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"\"}.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "gitlab-token": { "type": "object", @@ -165,7 +181,20 @@ "http-basic": { "type": "object", "description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.", - "additionalProperties": true + "additionalProperties": { + "type": "object", + "required": ["username", "password"], + "properties": { + "username": { + "type": "string", + "description": "The username used for HTTP Basic authentication" + }, + "password": { + "type": "string", + "description": "The password used for HTTP Basic authentication" + } + } + } }, "store-auths": { "type": ["string", "boolean"], @@ -174,7 +203,9 @@ "platform": { "type": "object", "description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.", - "additionalProperties": true + "additionalProperties": { + "type": "string" + } }, "vendor-dir": { "type": "string", @@ -280,12 +311,22 @@ "psr-0": { "type": "object", "description": "This is a hash of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", - "additionalProperties": true + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } }, "psr-4": { "type": "object", "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", - "additionalProperties": true + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } }, "classmap": { "type": "array", @@ -308,12 +349,22 @@ "psr-0": { "type": "object", "description": "This is a hash of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", - "additionalProperties": true + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } }, "psr-4": { "type": "object", "description": "This is a hash of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", - "additionalProperties": true + "additionalProperties": { + "type": ["string", "array"], + "items": { + "type": "string" + } + } }, "classmap": { "type": "array", diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 7cd6fd71a..0fd93152e 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -206,6 +206,9 @@ class ConsoleIO extends BaseIO // write the new message $this->doWrite($messages, false, $stderr, $verbosity); + // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to + // track the length of previous output and fill it with spaces to make sure the line is cleared. + // See https://github.com/composer/composer/pull/5836 for more details $fill = $size - strlen(strip_tags($messages)); if ($fill > 0) { // whitespace whatever has left diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b0c4756c9..5f257829b 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -292,6 +292,9 @@ class Installer } if ($this->runScripts) { + $devMode = (int) $this->devMode; + putenv("COMPOSER_DEV_MODE=$devMode"); + // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); @@ -493,6 +496,38 @@ class Installer $devPackages = null; } + if ($operations) { + $installs = $updates = $uninstalls = array(); + foreach ($operations as $operation) { + if ($operation instanceof InstallOperation) { + $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UpdateOperation) { + $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); + } elseif ($operation instanceof UninstallOperation) { + $uninstalls[] = $operation->getPackage()->getPrettyName(); + } + } + + $this->io->writeError( + sprintf("Package operations: %d install%s, %d update%s, %d removal%s", + count($installs), + 1 === count($installs) ? '' : 's', + count($updates), + 1 === count($updates) ? '' : 's', + count($uninstalls), + 1 === count($uninstalls) ? '' : 's') + ); + if ($installs) { + $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); + } + if ($updates) { + $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE); + } + if ($uninstalls) { + $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE); + } + } + foreach ($operations as $operation) { // collect suggestions if ('install' === $operation->getJobType()) { diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index c82c8c4aa..4dc103d38 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -195,7 +195,7 @@ class HgDriver extends VcsDriver */ public static function supports(IOInterface $io, Config $config, $url, $deep = false) { - if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { + if (preg_match('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { return true; } diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 3969a188b..6e9b62ef2 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -99,7 +99,17 @@ class ProcessExecutor return; } - echo $buffer; + if (null === $this->io) { + echo $buffer; + + return; + } + + if (Process::ERR === $type) { + $this->io->writeError($buffer); + } else { + $this->io->write($buffer); + } } public static function getTimeout() diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 7804c6c52..f732f88f5 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -340,7 +340,7 @@ class EventDispatcherTest extends TestCase ->setConstructorArgs(array( $this->createComposerInstance(), $io = $this->getMock('Composer\IO\IOInterface'), - new ProcessExecutor, + new ProcessExecutor($io), )) ->setMethods(array('getListeners')) ->getMock(); @@ -354,9 +354,11 @@ class EventDispatcherTest extends TestCase ->method('writeError') ->with($this->equalTo('> echo foo')); - ob_start(); + $io->expects($this->once()) + ->method('write') + ->with($this->equalTo('foo'.PHP_EOL)); + $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); - $this->assertEquals('foo', trim(ob_get_clean())); } public function testDispatcherOutputsErrorOnFailedCommand() diff --git a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test index bc8a0cdb6..7eba0a6f0 100644 --- a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test +++ b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test @@ -26,6 +26,7 @@ install --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies (including require-dev) +Package operations: 2 installs, 0 updates, 0 removals Package a/a is abandoned, you should avoid using it. No replacement was suggested. Package c/c is abandoned, you should avoid using it. Use b/b instead. Writing lock file diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test index 29fc0d38f..d807c6df8 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test @@ -36,6 +36,7 @@ update a b --with-dependencies --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies (including require-dev) +Package operations: 0 installs, 2 updates, 0 removals Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Fixtures/installer/suggest-installed.test b/tests/Composer/Test/Fixtures/installer/suggest-installed.test index df573c997..198203ce9 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-installed.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-installed.test @@ -21,6 +21,7 @@ install --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies (including require-dev) +Package operations: 2 installs, 0 updates, 0 removals Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Fixtures/installer/suggest-prod.test b/tests/Composer/Test/Fixtures/installer/suggest-prod.test index 0fe9c8853..40546f8d0 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-prod.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-prod.test @@ -19,6 +19,7 @@ install --no-dev --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies +Package operations: 1 install, 0 updates, 0 removals Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Fixtures/installer/suggest-replaced.test b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test index 38755d7fb..f18054d74 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-replaced.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-replaced.test @@ -21,6 +21,7 @@ install --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies (including require-dev) +Package operations: 2 installs, 0 updates, 0 removals Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test index fda020699..ae5ff36e3 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-uninstalled.test @@ -19,6 +19,7 @@ install --EXPECT-OUTPUT-- Loading composer repositories with package information Updating dependencies (including require-dev) +Package operations: 1 install, 0 updates, 0 removals a/a suggests installing b/b (an obscure reason) Writing lock file Generating autoload files diff --git a/tests/Composer/Test/Json/ComposerSchemaTest.php b/tests/Composer/Test/Json/ComposerSchemaTest.php index 9c2ba2f04..0bbfccc72 100644 --- a/tests/Composer/Test/Json/ComposerSchemaTest.php +++ b/tests/Composer/Test/Json/ComposerSchemaTest.php @@ -44,6 +44,14 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase $this->assertTrue($this->check($json)); } + public function testRequireTypes() + { + $json = '{"name": "name", "description": "description", "require": {"a": ["b"]} }'; + $this->assertEquals(array( + array('property' => 'require.a', 'message' => 'Array value found, but a string is required', 'constraint' => 'type'), + ), $this->check($json)); + } + public function testMinimumStabilityValues() { $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }'; diff --git a/tests/Composer/Test/Repository/Vcs/HgDriverTest.php b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php new file mode 100644 index 000000000..da326aaf5 --- /dev/null +++ b/tests/Composer/Test/Repository/Vcs/HgDriverTest.php @@ -0,0 +1,69 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Repository\Vcs; + +use Composer\Repository\Vcs\HgDriver; +use Composer\TestCase; +use Composer\Util\Filesystem; +use Composer\Config; + +class HgDriverTest extends TestCase +{ + + /** @type \Composer\IO\IOInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $io; + /** @type Config */ + private $config; + /** @type string */ + private $home; + + public function setUp() + { + $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->home = $this->getUniqueTmpDirectory(); + $this->config = new Config(); + $this->config->merge(array( + 'config' => array( + 'home' => $this->home, + ), + )); + } + + public function tearDown() + { + $fs = new Filesystem; + $fs->removeDirectory($this->home); + } + + /** + * @dataProvider supportsDataProvider + */ + public function testSupports($repositoryUrl) + { + $this->assertTrue( + HgDriver::supports($this->io, $this->config, $repositoryUrl) + ); + } + + public function supportsDataProvider() + { + return array( + array('ssh://bitbucket.org/user/repo'), + array('ssh://hg@bitbucket.org/user/repo'), + array('ssh://user@bitbucket.org/user/repo'), + array('https://bitbucket.org/user/repo'), + array('https://user@bitbucket.org/user/repo'), + ); + } + +} diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index 27fe00554..a87545625 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -35,6 +35,17 @@ class ProcessExecutorTest extends TestCase $this->assertEquals("foo".PHP_EOL, $output); } + public function testUseIOIsNotNullAndIfNotCaptured() + { + $io = $this->getMock('Composer\IO\IOInterface'); + $io->expects($this->once()) + ->method('write') + ->with($this->equalTo('foo'.PHP_EOL)); + + $process = new ProcessExecutor($io); + $process->execute('echo foo'); + } + public function testExecuteCapturesStderr() { $process = new ProcessExecutor;