Skip to content

Commit

Permalink
Merge pull request #1090 from veewee/config-options-resolver
Browse files Browse the repository at this point in the history
Config options resolver
  • Loading branch information
veewee committed May 22, 2023
2 parents 29d1dec + b79dc6a commit 4a1235d
Show file tree
Hide file tree
Showing 74 changed files with 329 additions and 179 deletions.
19 changes: 17 additions & 2 deletions doc/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,15 @@ When your task is written, you have to register it to the service manager and ad

interface TaskInterface
{
public static function getConfigurableOptions(): OptionsResolver;
public static function getConfigurableOptions(): ConfigOptionsResolver;
public function canRunInContext(ContextInterface $context): bool;
public function run(ContextInterface $context): TaskResultInterface;
public function getConfig(): TaskConfigInterface;
public function withConfig(TaskConfigInterface $config): TaskInterface;
}
```

* `getConfigurableOptions`: This method has to return all configurable options for the task.
* `getConfigurableOptions`: This method has to return all configurable options for the task.
* `canRunInContext`: Tells GrumPHP if it can run in `pre-commit`, `commit-msg` or `run` context.
* `run`: Executes the task and returns a result
* `getConfig`: Provides the resolved configuration for the task or an empty config for newly instantiated tasks.
Expand All @@ -231,6 +231,21 @@ You now registered your custom task! Pretty cool right?!

**Note:** Be careful with adding dependencies to your task. When GrumPHP runs in parallel mode, the task and all of its dependencies get serialized in order to run in a separate process. This could lead to a delay when e.g. serializing a depenency container or lead to errors on unserializable objects. [More info](https://github.com/phpro/grumphp/issues/815)

**Note:** You can use Symfony's options-resolver to help configure the options for your task. However, if you want your task to work together with the `grumphp-shim` (phar) distribution, you will have to make sure to use `ConfigOptionsResolver::fromClosure()` This is because the Symfony's options-resolver gets scoped in the phar. Example:

```php
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();

// ..... your config

return ConfigOptionsResolver::fromClosure(
static fn (array $options): array => $resolver->resolve($options)
);
}
```


## Testing your custom task.

Expand Down
1 change: 1 addition & 0 deletions resources/config/runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ services:
arguments:
- '@GrumPHP\Configuration\Model\ParallelConfig'
- '@GrumPHP\Runner\Parallel\PoolFactory'
- '@grumphp.io'
tags:
- { name: 'grumphp.task_handler', priority: 150 }
GrumPHP\Runner\TaskHandler\Middleware\ErrorHandlingTaskHandlerMiddleware:
Expand Down
18 changes: 8 additions & 10 deletions spec/Configuration/Resolver/TaskConfigResolverSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace spec\GrumPHP\Configuration\Resolver;

use GrumPHP\Task\Config\ConfigOptionsResolver;
use const GrumPHP\Exception\TaskConfigResolverException;
use GrumPHP\Exception\TaskConfigResolverException;
use GrumPHP\Runner\TaskResult;
Expand All @@ -25,8 +26,8 @@ public function it_is_initializable(): void
{
$this->shouldHaveType(TaskConfigResolver::class);
}
public function it_can_last_task_names(): void

public function it_can_list_task_names(): void
{
$this->beConstructedWith([
'task1' => get_class($this->mockTask()),
Expand All @@ -44,15 +45,12 @@ public function it_can_resolve_config_for_task_without_metadata(): void
]);
}

public function it_fetches_resolver_for_task_only_once(): void
public function it_fetches_resolver_for_task(): void
{
$task1 = $this->mockTask();
$this->beConstructedWith([$taskName = 'task1' => get_class($task1)]);
$result = $this->fetchByName($taskName);
$result->shouldBeLike($task1::getConfigurableOptions());

$result2 = $this->fetchByName($taskName);
$result2->shouldBe($result);
$result = $this->fetchByName($taskName)->resolve([]);
$result->shouldBeLike($task1::getConfigurableOptions()->resolve([]));
}

public function it_fails_when_task_is_unknown(): void
Expand All @@ -75,11 +73,11 @@ private function mockTask(): TaskInterface
{
return new class implements TaskInterface
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$options = new OptionsResolver();
$options->setDefault('class', static::class);
return $options;
return ConfigOptionsResolver::fromOptionsResolver($options);
}

public function canRunInContext(ContextInterface $context): bool
Expand Down
18 changes: 4 additions & 14 deletions src/Configuration/Resolver/TaskConfigResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
namespace GrumPHP\Configuration\Resolver;

use GrumPHP\Exception\TaskConfigResolverException;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\TaskInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskConfigResolver
{
/**
* @var array<string, string>
*/
private $taskMap = [];

/**
* @var array<OptionsResolver>
*/
private $cachedResolvers = [];
private $taskMap;

public function __construct(array $taskMap)
{
Expand All @@ -43,22 +38,17 @@ public function resolve(string $taskName, array $config): array
return $resolver->resolve($config);
}

public function fetchByName(string $taskName): OptionsResolver
public function fetchByName(string $taskName): ConfigOptionsResolver
{
if (!array_key_exists($taskName, $this->taskMap)) {
throw TaskConfigResolverException::unknownTask($taskName);
}

// Try to use cached version first:
$class = $this->taskMap[$taskName];
if (array_key_exists($class, $this->cachedResolvers)) {
return $this->cachedResolvers[$class];
}

if (!class_exists($class) || !is_subclass_of($class, TaskInterface::class)) {
throw TaskConfigResolverException::unknownClass($class);
}

return $this->cachedResolvers[$class] = $class::getConfigurableOptions();
return $class::getConfigurableOptions();
}
}
3 changes: 3 additions & 0 deletions src/Linter/Json/JsonLinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public function setDetectKeyConflicts(bool $detectKeyConflicts): void
$this->detectKeyConflicts = $detectKeyConflicts;
}

/**
* @return int-mask-of<JsonParser::*>
*/
private function calculateFlags(): int
{
$flags = 0;
Expand Down
42 changes: 30 additions & 12 deletions src/Runner/TaskHandler/Middleware/ParallelProcessingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace GrumPHP\Runner\TaskHandler\Middleware;

use GrumPHP\Exception\ParallelException;
use GrumPHP\IO\IOInterface;
use GrumPHP\Runner\Parallel\SerializedClosureTask;
use GrumPHP\Runner\StopOnFailure;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use function Amp\async;
use Amp\Future;
Expand All @@ -16,20 +19,18 @@

class ParallelProcessingMiddleware implements TaskHandlerMiddlewareInterface
{
/**
* @var ParallelConfig
*/
private $config;
private ParallelConfig $config;
private PoolFactory $poolFactory;
private IOInterface $IO;

/**
* @var PoolFactory
*/
private $poolFactory;

public function __construct(ParallelConfig $config, PoolFactory $poolFactory)
{
public function __construct(
ParallelConfig $config,
PoolFactory $poolFactory,
IOInterface $IO
) {
$this->poolFactory = $poolFactory;
$this->config = $config;
$this->IO = $IO;
}

public function handle(
Expand All @@ -55,6 +56,23 @@ static function () use ($task, $runnerContext, $next, $currentEnv): TaskResultIn
$stopOnFailure->cancellation()
);

return $execution->getFuture();
return async(function () use ($task, $runnerContext, $execution): TaskResultInterface {
try {
return $execution->getFuture()->await();
} catch (\Throwable $exception) {
return TaskResult::createFailed(
$task,
$runnerContext->getTaskContext(),
$this->wrapException($exception)->getMessage()
);
}
});
}

private function wrapException(\Throwable $error): ParallelException
{
return $this->IO->isVerbose()
? ParallelException::fromVerboseThrowable($error)
: ParallelException::fromThrowable($error);
}
}
2 changes: 1 addition & 1 deletion src/Task/AbstractLinterTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __construct(LinterInterface $linter)
$this->config = new EmptyTaskConfig();
}

public static function getConfigurableOptions(): OptionsResolver
protected static function sharedOptionsResolver(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand Down
2 changes: 1 addition & 1 deletion src/Task/AbstractParserTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function __construct(ParserInterface $parser)
}
}

public static function getConfigurableOptions(): OptionsResolver
protected static function sharedOptionsResolver(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand Down
5 changes: 3 additions & 2 deletions src/Task/Ant.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
Expand All @@ -17,7 +18,7 @@
*/
class Ant extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand All @@ -30,7 +31,7 @@ public static function getConfigurableOptions(): OptionsResolver
$resolver->addAllowedTypes('build_file', ['null', 'string']);
$resolver->addAllowedTypes('task', ['null', 'string']);

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

public function canRunInContext(ContextInterface $context): bool
Expand Down
5 changes: 3 additions & 2 deletions src/Task/Atoum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
Expand All @@ -17,7 +18,7 @@
*/
class Atoum extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand All @@ -38,7 +39,7 @@ public static function getConfigurableOptions(): OptionsResolver
$resolver->addAllowedTypes('methods', ['array']);
$resolver->addAllowedTypes('tags', ['array']);

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Task/Behat.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
Expand All @@ -17,7 +18,7 @@
*/
class Behat extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand All @@ -34,7 +35,7 @@ public static function getConfigurableOptions(): OptionsResolver
$resolver->addAllowedTypes('profile', ['null', 'string']);
$resolver->addAllowedTypes('stop_on_failure', ['bool']);

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Task/Brunch.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
Expand All @@ -17,7 +18,7 @@
*/
class Brunch extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand All @@ -34,7 +35,7 @@ public static function getConfigurableOptions(): OptionsResolver
$resolver->addAllowedTypes('debug', ['bool']);
$resolver->addAllowedTypes('triggered_by', ['array']);

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Task/CloverCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Config\EmptyTaskConfig;
use GrumPHP\Task\Config\TaskConfigInterface;
use GrumPHP\Task\Context\ContextInterface;
Expand Down Expand Up @@ -47,7 +48,7 @@ public function getConfig(): TaskConfigInterface
return $this->config;
}

public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();

Expand All @@ -63,7 +64,7 @@ public static function getConfigurableOptions(): OptionsResolver

$resolver->setRequired('clover_file');

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Task/Codeception.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
Expand All @@ -17,7 +18,7 @@
*/
class Codeception extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
public static function getConfigurableOptions(): ConfigOptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand All @@ -32,7 +33,7 @@ public static function getConfigurableOptions(): OptionsResolver
$resolver->addAllowedTypes('test', ['null', 'string']);
$resolver->addAllowedTypes('fail_fast', ['bool']);

return $resolver;
return ConfigOptionsResolver::fromOptionsResolver($resolver);
}

/**
Expand Down
Loading

0 comments on commit 4a1235d

Please sign in to comment.