diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 14e5b5a63..92f9c5bdb 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -200,3 +200,18 @@ simply running `composer test`: > **Note:** Composer's bin-dir is pushed on top of the PATH so that binaries > of dependencies are easily accessible as CLI commands when writing scripts. + +Composer script can also called from other scripts, by prefixing the command name +by `@`. For example the following syntax is valid: + +```json +{ + "scripts": { + "test": [ + "@clearCache", + "phpunit" + ], + "clearCache": "rm -rf cache/*" + } +} +``` diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 721e26a3f..cfab0e8a9 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -150,6 +150,14 @@ class EventDispatcher if (!is_string($callable) && is_callable($callable)) { $event = $this->checkListenerExpectedEvent($callable, $event); $return = false === call_user_func($callable, $event) ? 1 : 0; + } elseif ($this->isComposerScript($callable)) { + if ($this->io->isVerbose()) { + $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable)); + } else { + $this->io->writeError(sprintf('> %s', $callable)); + } + $scriptName = substr($callable, 1); + $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode())); } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); @@ -362,4 +370,15 @@ class EventDispatcher { return false === strpos($callable, ' ') && false !== strpos($callable, '::'); } + + /** + * Checks if string given references a composer run-script + * + * @param string $callable + * @return bool + */ + protected function isComposerScript($callable) + { + return '@' === substr($callable, 0, 1); + } } diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 925772d42..0fde8f48b 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -142,6 +142,65 @@ class EventDispatcherTest extends TestCase $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false); } + public function testDispatcherCanExecuteComposerScriptGroups() + { + $process = $this->getMock('Composer\Util\ProcessExecutor'); + $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher') + ->setConstructorArgs(array( + $composer = $this->getMock('Composer\Composer'), + $io = $this->getMock('Composer\IO\IOInterface'), + $process, + )) + ->setMethods(array( + 'getListeners', + )) + ->getMock(); + + $process->expects($this->exactly(3)) + ->method('execute') + ->will($this->returnValue(0)); + + $dispatcher->expects($this->atLeastOnce()) + ->method('getListeners') + ->will($this->returnCallback(function (Event $event) { + if ($event->getName() === 'root') { + return array('@group'); + } elseif ($event->getName() === 'group') { + return array('echo -n foo', '@subgroup', 'echo -n bar'); + } elseif ($event->getName() === 'subgroup') { + return array('echo -n baz'); + } + + 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)); + } + private function getDispatcherStubForListenersTest($listeners, $io) { $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')