Przeglądaj źródła

Add ability to call composer from scripts using @composer XXX, fixes #5153

Jordi Boggiano 9 lat temu
rodzic
commit
60ce2324bc

+ 20 - 0
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.

+ 19 - 3
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('<warning>You made a reference to a non-existent script %s</warning>', $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('<error>Script %s handling the %s event returned with an error</error>', $callable, $event->getName()));
+
+                        throw new \RuntimeException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
+                    }
+                } else {
+                    if (!$this->getListeners(new Event($scriptName))) {
+                        $this->io->writeError(sprintf('<warning>You made a reference to a non-existent script %s</warning>', $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);

+ 3 - 0
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']);
             }