From 60ce2324bcf0b784d66444760c6f996b4a6ca902 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 11 Apr 2016 19:03:32 +0100 Subject: [PATCH] Add ability to call composer from scripts using @composer XXX, fixes #5153 --- doc/articles/scripts.md | 20 +++++++++++++++++ .../EventDispatcher/EventDispatcher.php | 22 ++++++++++++++++--- src/Composer/Package/Loader/ArrayLoader.php | 3 +++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 02226526a..f6d1729a3 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -216,3 +216,23 @@ one by prefixing the command name with `@`: } } ``` + +## Calling Composer commands + +To call Composer commands, you can use `@composer` which will automatically +resolve to whatever composer.phar is currently being used: + +```json +{ + "scripts": { + "test": [ + "@composer install", + "phpunit" + ], + } +} +``` + +One limitation of this is that you can not call multiple composer commands in +a row like `@composer install && @composer foo`. You must split them up in a +JSON array of commands. diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 5e5061e6f..57218f676 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -24,6 +24,7 @@ use Composer\Script; use Composer\Script\CommandEvent; use Composer\Script\PackageEvent; use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\PhpExecutableFinder; /** * The Event Dispatcher. @@ -172,10 +173,25 @@ class EventDispatcher $scriptName = substr($callable, 1); $args = $event->getArguments(); $flags = $event->getFlags(); - if (!$this->getListeners(new Event($scriptName))) { - $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable)); + if (substr($callable, 0, 10) === '@composer ') { + $finder = new PhpExecutableFinder(); + $phpPath = $finder->find(); + if (!$phpPath) { + throw new \RuntimeException('Failed to locate PHP binary to execute '.$scriptName); + } + $exec = $phpPath . ' ' . realpath($_SERVER['argv'][0]) . substr($callable, 9); + if (0 !== ($exitCode = $this->process->execute($exec))) { + $this->io->writeError(sprintf('Script %s handling the %s event returned with an error', $callable, $event->getName())); + + throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode); + } + } else { + if (!$this->getListeners(new Event($scriptName))) { + $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable)); + } + + $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); } - $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index ff2ba70d8..10ad4376a 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -171,6 +171,9 @@ class ArrayLoader implements LoaderInterface foreach ($config['scripts'] as $event => $listeners) { $config['scripts'][$event] = (array) $listeners; } + if (isset($config['scripts']['composer'])) { + trigger_error('The `composer` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED); + } $package->setScripts($config['scripts']); }