Skip to content

Commit

Permalink
feat: allow setting memory limit for commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ramsey committed Apr 25, 2023
1 parent 695cc23 commit 903634b
Show file tree
Hide file tree
Showing 22 changed files with 812 additions and 27 deletions.
1 change: 1 addition & 0 deletions phpstan.neon.dist
@@ -1,5 +1,6 @@
parameters:
tmpDir: ./build/cache/phpstan
treatPhpDocTypesAsCertain: false
level: max
paths:
- ./src
Expand Down
4 changes: 4 additions & 0 deletions src/Command/Analyze/PhpStanCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Analyze;

use Ramsey\Dev\Tools\Command\MemoryLimitLongOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,6 +25,8 @@
)]
final class PhpStanCommand extends ProcessCommand
{
use MemoryLimitLongOption;

public function getExecutableName(): string
{
return 'phpstan';
Expand All @@ -45,6 +48,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [(string) $this->getExecutablePath(), 'analyze', '--ansi', ...$args];
}
Expand Down
4 changes: 4 additions & 0 deletions src/Command/Analyze/PsalmCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Analyze;

use Ramsey\Dev\Tools\Command\MemoryLimitLongOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,6 +25,8 @@
)]
final class PsalmCommand extends ProcessCommand
{
use MemoryLimitLongOption;

public function getExecutableName(): string
{
return 'psalm';
Expand All @@ -45,6 +48,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [(string) $this->getExecutablePath(), ...$args];
}
Expand Down
57 changes: 39 additions & 18 deletions src/Command/Command.php
Expand Up @@ -11,15 +11,16 @@

namespace Ramsey\Dev\Tools\Command;

use Composer\Composer;
use Composer\EventDispatcher\EventDispatcher;
use Ramsey\Dev\Tools\Composer\ExtraConfiguration;
use Ramsey\Dev\Tools\Configuration;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function assert;
use function implode;
use function ini_set;
use function mb_strlen;
use function preg_replace;
use function preg_split;
Expand All @@ -29,13 +30,18 @@
use const PHP_EOL;
use const PREG_SPLIT_NO_EMPTY;

/**
* @phpstan-import-type CommandDefinition from ExtraConfiguration
*/
abstract class Command extends SymfonyCommand
{
private const WRAP_WIDTH = 78;

private ?EventDispatcher $eventDispatcher = null;
private bool $overrideDefault = false;

protected ExtraConfiguration $extra;

/**
* The execute() method in this class is set `final` to ensure it will
* always handle dispatching scripts found in `composer.json` for commands
Expand All @@ -47,19 +53,20 @@ abstract protected function doExecute(InputInterface $input, OutputInterface $ou
public function __construct(protected readonly Configuration $configuration)
{
parent::__construct(null);

$this->extra = $this->getComposerExtraConfiguration();
}

final protected function execute(InputInterface $input, OutputInterface $output): int
{
// We must configure Composer at execution time because we need to create
// Composer's IO object from the command execution input and output.
$this->configureWithComposer(
$this->configuration->composerFactory->getComposer(
$input,
$output,
$this->getHelperSet(),
),
);
if ($this->extra->memoryLimit !== null) {
ini_set('memory_limit', $this->extra->memoryLimit);
}

// We must configure Composer's event dispatcher at execution time
// because we need to create Composer's IO object from the command
// execution input and output.
$this->setupComposerEventDispatcher($input, $output);

assert($this->eventDispatcher !== null);

Expand Down Expand Up @@ -138,18 +145,17 @@ private function wrapHelp(string $message): string
* Use extra.devtools, if available, but extra.ramsey/devtools
* takes precedence over extra.devtools.
*/
private function configureWithComposer(Composer $composer): void
private function setupComposerEventDispatcher(InputInterface $input, OutputInterface $output): void
{
// We create a new Composer instance to configure it with this
// execution's input and output.
$composer = $this->configuration->composerFactory->getComposer($input, $output, $this->getHelperSet());

$this->eventDispatcher = $composer->getEventDispatcher();
$this->eventDispatcher->setRunScripts(true);

$extra = $composer->getPackage()->getExtra();

/** @var array{command-prefix?: string, commands?: array<string, mixed>} $devtoolsConfig */
$devtoolsConfig = $extra[$this->configuration->composerExtraProperty] ?? $extra['devtools'] ?? [];

/** @var array{override?: bool, script?: array<string> | string} $commandConfig */
$commandConfig = $devtoolsConfig['commands'][(string) $this->getName()] ?? [];
/** @var CommandDefinition $commandConfig */
$commandConfig = $this->extra->commands[(string) $this->getName()] ?? [];

$this->overrideDefault = $commandConfig['override'] ?? false;

Expand All @@ -159,4 +165,19 @@ private function configureWithComposer(Composer $composer): void
$this->eventDispatcher->addListener((string) $this->getName(), $script);
}
}

private function getComposerExtraConfiguration(): ExtraConfiguration
{
$property = $this->configuration->composerExtraProperty;
$extra = $this->configuration->composer->getPackage()->getExtra();

/** @var array{command-prefix?: string, commands?: array<string, array{override?: bool, script: string | string[], memory-limit?: int | string}>, memory-limit?: int | string} $config */
$config = $extra[$property] ?? $extra['devtools'] ?? [];

return new ExtraConfiguration(
commandPrefix: $config['command-prefix'] ?? ExtraConfiguration::DEFAULT_COMMAND_PREFIX,
commands: $config['commands'] ?? [],
memoryLimit: $config['memory-limit'] ?? null,
);
}
}
4 changes: 4 additions & 0 deletions src/Command/Lint/FixCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Lint;

use Ramsey\Dev\Tools\Command\MemoryLimitIniOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,6 +25,8 @@
)]
final class FixCommand extends ProcessCommand
{
use MemoryLimitIniOption;

public function getExecutableName(): string
{
return 'phpcbf';
Expand All @@ -46,6 +49,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [(string) $this->getExecutablePath(), '--cache=build/cache/phpcs.cache', ...$args];
}
Expand Down
4 changes: 4 additions & 0 deletions src/Command/Lint/StyleCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Lint;

use Ramsey\Dev\Tools\Command\MemoryLimitIniOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,6 +25,8 @@
)]
final class StyleCommand extends ProcessCommand
{
use MemoryLimitIniOption;

public function getExecutableName(): string
{
return 'phpcs';
Expand All @@ -46,6 +49,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [(string) $this->getExecutablePath(), '--colors', '--cache=build/cache/phpcs.cache', ...$args];
}
Expand Down
29 changes: 29 additions & 0 deletions src/Command/MemoryLimitIniOption.php
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Ramsey\Dev\Tools\Command;

use Ramsey\Dev\Tools\Composer\ExtraConfiguration;

/**
* @phpstan-import-type CommandDefinition from ExtraConfiguration
*/
trait MemoryLimitIniOption
{
/**
* @return string[]
*/
private function getMemoryLimitOption(): array
{
/** @var CommandDefinition $commandConfig */
$commandConfig = $this->extra->commands[(string) $this->getName()] ?? [];
$memoryLimit = $commandConfig['memory-limit'] ?? $this->extra->memoryLimit ?? null;

if ($memoryLimit !== null) {
return ['-d', "memory_limit=$memoryLimit"];
}

return [];
}
}
29 changes: 29 additions & 0 deletions src/Command/MemoryLimitLongOption.php
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Ramsey\Dev\Tools\Command;

use Ramsey\Dev\Tools\Composer\ExtraConfiguration;

/**
* @phpstan-import-type CommandDefinition from ExtraConfiguration
*/
trait MemoryLimitLongOption
{
/**
* @return string[]
*/
private function getMemoryLimitOption(): array
{
/** @var CommandDefinition $commandConfig */
$commandConfig = $this->extra->commands[(string) $this->getName()] ?? [];
$memoryLimit = $commandConfig['memory-limit'] ?? $this->extra->memoryLimit ?? null;

if ($memoryLimit !== null) {
return ["--memory-limit=$memoryLimit"];
}

return [];
}
}
6 changes: 5 additions & 1 deletion src/Command/Test/Coverage/CiCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Test\Coverage;

use Ramsey\Dev\Tools\Command\MemoryLimitIniOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -23,6 +24,8 @@
)]
final class CiCommand extends ProcessCommand
{
use MemoryLimitIniOption;

public function getExecutableName(): string
{
return 'phpunit';
Expand All @@ -44,6 +47,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [
(string) $this->getExecutablePath(),
Expand All @@ -59,8 +63,8 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
'build/coverage/coverage-xml',
'--log-junit',
'build/junit.xml',
'tests',
...$args,
'tests',
];
}

Expand Down
6 changes: 5 additions & 1 deletion src/Command/Test/Coverage/HtmlCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Test\Coverage;

use Ramsey\Dev\Tools\Command\MemoryLimitIniOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,6 +25,8 @@
)]
final class HtmlCommand extends ProcessCommand
{
use MemoryLimitIniOption;

public function getExecutableName(): string
{
return 'phpunit';
Expand All @@ -45,14 +48,15 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [
(string) $this->getExecutablePath(),
'--colors=always',
'--coverage-html',
'build/coverage/coverage-html',
'tests',
...$args,
'tests',
];
}

Expand Down
4 changes: 4 additions & 0 deletions src/Command/Test/UnitCommand.php
Expand Up @@ -11,6 +11,7 @@

namespace Ramsey\Dev\Tools\Command\Test;

use Ramsey\Dev\Tools\Command\MemoryLimitIniOption;
use Ramsey\Dev\Tools\Command\ProcessCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -23,6 +24,8 @@
)]
final class UnitCommand extends ProcessCommand
{
use MemoryLimitIniOption;

public function getExecutableName(): string
{
return 'phpunit';
Expand All @@ -44,6 +47,7 @@ public function getProcessCommand(InputInterface $input, OutputInterface $output
{
/** @var string[] $args */
$args = $input->getArguments()['args'] ?? [];
$args = [...$this->getMemoryLimitOption(), ...$args];

return [(string) $this->getExecutablePath(), '--colors=always', ...$args];
}
Expand Down

0 comments on commit 903634b

Please sign in to comment.