Merge remote-tracking branch 'composer/master'

main
Mark Ingman 8 years ago
commit 0a3145821f

@ -22,15 +22,19 @@ Composer fires the following named events during its execution process:
### Command Events ### Command Events
- **pre-install-cmd**: occurs before the `install` command is executed with a lock file present. - **pre-install-cmd**: occurs before the `install` command is executed with a
- **post-install-cmd**: occurs after the `install` command has been executed with a lock file present. 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-install-cmd**: occurs after the `install` command has been executed
- **post-update-cmd**: occurs after the `update` command has been executed, or after the `install` command has been executed without a lock file present. 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. - **post-status-cmd**: occurs after the `status` command has been executed.
- **pre-archive-cmd**: occurs before the `archive` command is executed. - **pre-archive-cmd**: occurs before the `archive` command is executed.
- **post-archive-cmd**: occurs after the `archive` command has been executed. - **post-archive-cmd**: occurs after the `archive` command has been executed.
- **pre-autoload-dump**: occurs before the autoloader is dumped, either - **pre-autoload-dump**: occurs before the autoloader is dumped, either during
during `install`/`update`, or via the `dump-autoload` command. `install`/`update`, or via the `dump-autoload` command.
- **post-autoload-dump**: occurs after the autoloader has been dumped, either - **post-autoload-dump**: occurs after the autoloader has been dumped, either
during `install`/`update`, or via the `dump-autoload` command. during `install`/`update`, or via the `dump-autoload` command.
- **post-root-package-install**: occurs after the root package has been - **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 ## Event classes
When an event is fired, your PHP callback receives as first argument a When an event is fired, your PHP callback receives as first argument a

@ -9,21 +9,21 @@ An alternative is to use this script which only works with unix utils:
```bash ```bash
#!/bin/sh #!/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');" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', '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 then
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
else
>&2 echo 'ERROR: Invalid installer signature' >&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php rm composer-setup.php
exit 1 exit 1
fi 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 The script will exit with 1 in case of failure, or 0 on success, and is quiet

@ -77,32 +77,44 @@
"require": { "require": {
"type": "object", "type": "object",
"description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", "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": { "replace": {
"type": "object", "type": "object",
"description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.", "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": { "conflict": {
"type": "object", "type": "object",
"description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.", "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.",
"additionalProperties": true "additionalProperties": {
"type": "string"
}
}, },
"provide": { "provide": {
"type": "object", "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.", "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": { "require-dev": {
"type": "object", "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).", "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": { "suggest": {
"type": "object", "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).", "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": { "config": {
"type": "object", "type": "object",
@ -134,12 +146,16 @@
"github-oauth": { "github-oauth": {
"type": "object", "type": "object",
"description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.", "description": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
"additionalProperties": true "additionalProperties": {
"type": "string"
}
}, },
"gitlab-oauth": { "gitlab-oauth": {
"type": "object", "type": "object",
"description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.", "description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.",
"additionalProperties": true "additionalProperties": {
"type": "string"
}
}, },
"gitlab-token": { "gitlab-token": {
"type": "object", "type": "object",
@ -165,7 +181,20 @@
"http-basic": { "http-basic": {
"type": "object", "type": "object",
"description": "A hash of domain name => {\"username\": \"...\", \"password\": \"...\"}.", "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": { "store-auths": {
"type": ["string", "boolean"], "type": ["string", "boolean"],
@ -174,7 +203,9 @@
"platform": { "platform": {
"type": "object", "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.", "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": { "vendor-dir": {
"type": "string", "type": "string",
@ -280,12 +311,22 @@
"psr-0": { "psr-0": {
"type": "object", "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.", "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": { "psr-4": {
"type": "object", "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.", "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": { "classmap": {
"type": "array", "type": "array",
@ -308,12 +349,22 @@
"psr-0": { "psr-0": {
"type": "object", "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.", "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": { "psr-4": {
"type": "object", "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.", "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": { "classmap": {
"type": "array", "type": "array",

@ -206,6 +206,9 @@ class ConsoleIO extends BaseIO
// write the new message // write the new message
$this->doWrite($messages, false, $stderr, $verbosity); $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)); $fill = $size - strlen(strip_tags($messages));
if ($fill > 0) { if ($fill > 0) {
// whitespace whatever has left // whitespace whatever has left

@ -292,6 +292,9 @@ class Installer
} }
if ($this->runScripts) { if ($this->runScripts) {
$devMode = (int) $this->devMode;
putenv("COMPOSER_DEV_MODE=$devMode");
// dispatch post event // dispatch post event
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
$this->eventDispatcher->dispatchScript($eventName, $this->devMode); $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
@ -493,6 +496,38 @@ class Installer
$devPackages = null; $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("<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
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) { foreach ($operations as $operation) {
// collect suggestions // collect suggestions
if ('install' === $operation->getJobType()) { if ('install' === $operation->getJobType()) {

@ -195,7 +195,7 @@ class HgDriver extends VcsDriver
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) 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; return true;
} }

@ -99,7 +99,17 @@ class ProcessExecutor
return; return;
} }
if (null === $this->io) {
echo $buffer; echo $buffer;
return;
}
if (Process::ERR === $type) {
$this->io->writeError($buffer);
} else {
$this->io->write($buffer);
}
} }
public static function getTimeout() public static function getTimeout()

@ -340,7 +340,7 @@ class EventDispatcherTest extends TestCase
->setConstructorArgs(array( ->setConstructorArgs(array(
$this->createComposerInstance(), $this->createComposerInstance(),
$io = $this->getMock('Composer\IO\IOInterface'), $io = $this->getMock('Composer\IO\IOInterface'),
new ProcessExecutor, new ProcessExecutor($io),
)) ))
->setMethods(array('getListeners')) ->setMethods(array('getListeners'))
->getMock(); ->getMock();
@ -354,9 +354,11 @@ class EventDispatcherTest extends TestCase
->method('writeError') ->method('writeError')
->with($this->equalTo('> echo foo')); ->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); $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
$this->assertEquals('foo', trim(ob_get_clean()));
} }
public function testDispatcherOutputsErrorOnFailedCommand() public function testDispatcherOutputsErrorOnFailedCommand()

@ -26,6 +26,7 @@ install
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies (including require-dev) Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
<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>
Writing lock file Writing lock file

@ -36,6 +36,7 @@ update a b --with-dependencies
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies (including require-dev) Updating dependencies (including require-dev)
Package operations: 0 installs, 2 updates, 0 removals
Writing lock file Writing lock file
Generating autoload files Generating autoload files

@ -21,6 +21,7 @@ install
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies (including require-dev) Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file Writing lock file
Generating autoload files Generating autoload files

@ -19,6 +19,7 @@ install --no-dev
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies Updating dependencies
Package operations: 1 install, 0 updates, 0 removals
Writing lock file Writing lock file
Generating autoload files Generating autoload files

@ -21,6 +21,7 @@ install
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies (including require-dev) Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
Writing lock file Writing lock file
Generating autoload files Generating autoload files

@ -19,6 +19,7 @@ install
--EXPECT-OUTPUT-- --EXPECT-OUTPUT--
Loading composer repositories with package information Loading composer repositories with package information
Updating dependencies (including require-dev) Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
a/a suggests installing b/b (an obscure reason) a/a suggests installing b/b (an obscure reason)
Writing lock file Writing lock file
Generating autoload files Generating autoload files

@ -44,6 +44,14 @@ class ComposerSchemaTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->check($json)); $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() public function testMinimumStabilityValues()
{ {
$json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }'; $json = '{ "name": "vendor/package", "description": "generic description", "minimum-stability": "" }';

@ -0,0 +1,69 @@
<?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\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'),
);
}
}

@ -35,6 +35,17 @@ class ProcessExecutorTest extends TestCase
$this->assertEquals("foo".PHP_EOL, $output); $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() public function testExecuteCapturesStderr()
{ {
$process = new ProcessExecutor; $process = new ProcessExecutor;

Loading…
Cancel
Save