Add a way to control which scripts get args and where (#12086)
Add support for `@no_additional_args` and `@additional_args` tags inside script handlers.pull/11942/head
parent
8bc8c4383a
commit
17930441a1
|
@ -410,6 +410,38 @@ JSON array of commands.
|
||||||
You can also call a shell/bash script, which will have the path to
|
You can also call a shell/bash script, which will have the path to
|
||||||
the PHP executable available in it as a `PHP_BINARY` env var.
|
the PHP executable available in it as a `PHP_BINARY` env var.
|
||||||
|
|
||||||
|
## Controlling additional arguments
|
||||||
|
|
||||||
|
When running scripts like `composer script-name arg arg2` or `composer script-name -- --option`,
|
||||||
|
Composer will by default append `arg`, `arg2` and `--option` to the script's command.
|
||||||
|
|
||||||
|
If you do not want these args in a given command, you can put `@no_additional_args`
|
||||||
|
anywhere in it, that will remove the default behavior and that flag will be removed
|
||||||
|
as well before running the command.
|
||||||
|
|
||||||
|
If you want the args to be added somewhere else than at the very end, then you can put
|
||||||
|
`@additional_args` to be able to choose exactly where they go.
|
||||||
|
|
||||||
|
For example running `composer run-commands ARG` with the below config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"run-commands": [
|
||||||
|
"echo hello @no_additional_args",
|
||||||
|
"command-with-args @additional_args && do-something-without-args --here"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Would end up executing these commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
echo hello
|
||||||
|
command-with-args ARG && do-something-without-args --here
|
||||||
|
```
|
||||||
|
|
||||||
## Setting environment variables
|
## Setting environment variables
|
||||||
|
|
||||||
To set an environment variable in a cross-platform way, you can use `@putenv`:
|
To set an environment variable in a cross-platform way, you can use `@putenv`:
|
||||||
|
|
|
@ -202,7 +202,12 @@ class EventDispatcher
|
||||||
$return = 0;
|
$return = 0;
|
||||||
$this->ensureBinDirIsInPath();
|
$this->ensureBinDirIsInPath();
|
||||||
|
|
||||||
$formattedEventNameWithArgs = $event->getName() . ($event->getArguments() !== [] ? ' (' . implode(', ', $event->getArguments()) . ')' : '');
|
$additionalArgs = $event->getArguments();
|
||||||
|
if (is_string($callable) && str_contains($callable, '@no_additional_args')) {
|
||||||
|
$callable = Preg::replace('{ ?@no_additional_args}', '', $callable);
|
||||||
|
$additionalArgs = [];
|
||||||
|
}
|
||||||
|
$formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : '');
|
||||||
if (!is_string($callable)) {
|
if (!is_string($callable)) {
|
||||||
if (!is_callable($callable)) {
|
if (!is_callable($callable)) {
|
||||||
$className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];
|
$className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];
|
||||||
|
@ -220,7 +225,12 @@ class EventDispatcher
|
||||||
$scriptName = $script[0];
|
$scriptName = $script[0];
|
||||||
unset($script[0]);
|
unset($script[0]);
|
||||||
|
|
||||||
$args = array_merge($script, $event->getArguments());
|
$index = array_search('@additional_args', $script, true);
|
||||||
|
if ($index !== false) {
|
||||||
|
$args = array_splice($script, $index, 0, $additionalArgs);
|
||||||
|
} else {
|
||||||
|
$args = array_merge($script, $additionalArgs);
|
||||||
|
}
|
||||||
$flags = $event->getFlags();
|
$flags = $event->getFlags();
|
||||||
if (isset($flags['script-alias-input'])) {
|
if (isset($flags['script-alias-input'])) {
|
||||||
$argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script));
|
$argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script));
|
||||||
|
@ -294,7 +304,7 @@ class EventDispatcher
|
||||||
$app->add($cmd);
|
$app->add($cmd);
|
||||||
$app->setDefaultCommand((string) $cmd->getName(), true);
|
$app->setDefaultCommand((string) $cmd->getName(), true);
|
||||||
try {
|
try {
|
||||||
$args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $event->getArguments()));
|
$args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs));
|
||||||
// reusing the output from $this->io is mostly needed for tests, but generally speaking
|
// reusing the output from $this->io is mostly needed for tests, but generally speaking
|
||||||
// it does not hurt to keep the same stream as the current Application
|
// it does not hurt to keep the same stream as the current Application
|
||||||
if ($this->io instanceof ConsoleIO) {
|
if ($this->io instanceof ConsoleIO) {
|
||||||
|
@ -313,13 +323,17 @@ class EventDispatcher
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $event->getArguments()));
|
$args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs));
|
||||||
|
|
||||||
// @putenv does not receive arguments
|
// @putenv does not receive arguments
|
||||||
if (strpos($callable, '@putenv ') === 0) {
|
if (strpos($callable, '@putenv ') === 0) {
|
||||||
$exec = $callable;
|
$exec = $callable;
|
||||||
} else {
|
} else {
|
||||||
$exec = $callable . ($args === '' ? '' : ' '.$args);
|
if (str_contains($callable, '@additional_args')) {
|
||||||
|
$exec = str_replace('@additional_args', $args, $callable);
|
||||||
|
} else {
|
||||||
|
$exec = $callable . ($args === '' ? '' : ' '.$args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->io->isVerbose()) {
|
if ($this->io->isVerbose()) {
|
||||||
|
|
|
@ -311,6 +311,51 @@ class EventDispatcherTest extends TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDispatcherSupportForAdditionalArgs(): void
|
||||||
|
{
|
||||||
|
$process = $this->getProcessExecutorMock();
|
||||||
|
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
|
||||||
|
->setConstructorArgs([
|
||||||
|
$this->createComposerInstance(),
|
||||||
|
$io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
|
||||||
|
$process,
|
||||||
|
])
|
||||||
|
->onlyMethods([
|
||||||
|
'getListeners',
|
||||||
|
])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$reflMethod = new \ReflectionMethod($dispatcher, 'getPhpExecCommand');
|
||||||
|
if (PHP_VERSION_ID < 80100) {
|
||||||
|
$reflMethod->setAccessible(true);
|
||||||
|
}
|
||||||
|
$phpCmd = $reflMethod->invoke($dispatcher);
|
||||||
|
|
||||||
|
$args = ProcessExecutor::escape('ARG').' '.ProcessExecutor::escape('ARG2').' '.ProcessExecutor::escape('--arg');
|
||||||
|
$process->expects([
|
||||||
|
'echo -n foo',
|
||||||
|
$phpCmd.' foo.php '.$args.' then the rest',
|
||||||
|
'echo -n bar '.$args,
|
||||||
|
], true);
|
||||||
|
|
||||||
|
$listeners = [
|
||||||
|
'echo -n foo @no_additional_args',
|
||||||
|
'@php foo.php @additional_args then the rest',
|
||||||
|
'echo -n bar',
|
||||||
|
];
|
||||||
|
|
||||||
|
$dispatcher->expects($this->atLeastOnce())
|
||||||
|
->method('getListeners')
|
||||||
|
->will($this->returnValue($listeners));
|
||||||
|
|
||||||
|
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false, ['ARG', 'ARG2', '--arg']);
|
||||||
|
|
||||||
|
$expected = '> post-install-cmd: echo -n foo'.PHP_EOL.
|
||||||
|
'> post-install-cmd: @php foo.php '.$args.' then the rest'.PHP_EOL.
|
||||||
|
'> post-install-cmd: echo -n bar '.$args.PHP_EOL;
|
||||||
|
self::assertEquals($expected, $io->getOutput());
|
||||||
|
}
|
||||||
|
|
||||||
public static function createsVendorBinFolderChecksEnvDoesNotContainsBin(): void
|
public static function createsVendorBinFolderChecksEnvDoesNotContainsBin(): void
|
||||||
{
|
{
|
||||||
mkdir(__DIR__ . '/vendor/bin', 0700, true);
|
mkdir(__DIR__ . '/vendor/bin', 0700, true);
|
||||||
|
|
Loading…
Reference in New Issue