8000 Add a way to control which scripts get args and where by Seldaek · Pull Request #12086 · composer/composer · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add a way to control which scripts get args and where #12086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions doc/articles/scripts.md
8000
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,38 @@ JSON array of commands.
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.

## 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

To set an environment variable in a cross-platform way, you can use `@putenv`:
Expand Down
24 changes: 19 additions & 5 deletions src/Composer/EventDispatcher/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,12 @@ protected function doDispatch(Event $event)
$return = 0;
$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_callable($callable)) {
$className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];
Expand All @@ -220,7 +225,12 @@ protected function doDispatch(Event $event)
$scriptName = $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();
if (isset($flags['script-alias-input'])) {
$argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script));
Expand Down Expand Up @@ -294,7 +304,7 @@ protected function doDispatch(Event $event)
$app->add($cmd);
$app->setDefaultCommand((string) $cmd->getName(), true);
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
// it does not hurt to keep the same stream as the current Application
if ($this->io instanceof ConsoleIO) {
Expand All @@ -313,13 +323,17 @@ protected function doDispatch(Event $event)
throw $e;
}
} 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
if (strpos($callable, '@putenv ') === 0) {
$exec = $callable;
} 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()) {
Expand Down
45 changes: 45 additions & 0 deletions tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,51 @@ public function testDispatcherAppendsDirBinOnPathForEveryListener(): void
}
}

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
{
mkdir(__DIR__ . '/vendor/bin', 0700, true);
Expand Down
0