From 951ed28d321dbf07fb6235dfca912ca14d19feac Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 22 Apr 2020 15:56:54 +0300 Subject: [PATCH 01/18] ISAICP-5825: Allow 3rd party to alter the configuration. --- src/Event/ConfigEvent.php | 35 +++++++++++++++++++++++ src/TaskRunner.php | 22 ++++++++++++++ tests/CommandsTest.php | 16 +++++++++++ tests/custom/src/TestConfigSubscriber.php | 29 +++++++++++++++++++ tests/fixtures/third_party.yml | 5 ++++ 5 files changed, 107 insertions(+) create mode 100644 src/Event/ConfigEvent.php create mode 100644 tests/custom/src/TestConfigSubscriber.php create mode 100644 tests/fixtures/third_party.yml diff --git a/src/Event/ConfigEvent.php b/src/Event/ConfigEvent.php new file mode 100644 index 00000000..83518c22 --- /dev/null +++ b/src/Event/ConfigEvent.php @@ -0,0 +1,35 @@ +config = $config; + } + + /** + * @return Config + */ + public function getConfig() + { + return $this->config; + } +} diff --git a/src/TaskRunner.php b/src/TaskRunner.php index 1e3228a9..911c70b3 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -11,6 +11,7 @@ use OpenEuropa\TaskRunner\Contract\ComposerAwareInterface; use OpenEuropa\TaskRunner\Contract\RepositoryAwareInterface; use OpenEuropa\TaskRunner\Contract\TimeAwareInterface; +use OpenEuropa\TaskRunner\Event\ConfigEvent; use OpenEuropa\TaskRunner\Services\Composer; use OpenEuropa\TaskRunner\Contract\FilesystemAwareInterface; use League\Container\ContainerAwareTrait; @@ -24,6 +25,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Filesystem\Filesystem; /** @@ -95,6 +97,9 @@ public function __construct(InputInterface $input, OutputInterface $output, Clas $this->application->setAutoExit(false); $this->container = $this->createContainer($this->input, $this->output, $this->application, $this->config, $classLoader); + // Allows 3rd party to modify the configuration. + $this->alterConfig(); + // Create and initialize runner. $this->runner = new RoboRunner(); $this->runner->setRelativePluginNamespace('TaskRunner'); @@ -151,6 +156,20 @@ private function createConfiguration() return $config; } + /** + * Allows 3rd party to alter the configuration. + */ + private function alterConfig() + { + /** @var EventDispatcherInterface $dispatcher */ + $dispatcher = $this->container->get('eventDispatcher'); + $event = new ConfigEvent($this->config); + // @todo Remove Symfony 3.4 BC when Drupal 8.9.x is EOL. + $dispatcher->dispatch(get_class($event), $event); + // Keep the container in sync. + $this->container->share('config', $this->config); + } + /** * Get the local configuration filepath. * @@ -159,6 +178,9 @@ private function createConfiguration() * * @return string|null * The local configuration file path, or null if it doesn't exist. + * + * @todo Move this to an event subscriber. + * @see TaskRunner::alterConfig() */ private function getLocalConfigurationFilepath($configuration_file = 'openeuropa/taskrunner/runner.yml') { diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 8408fee0..cf85a7b7 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -2,12 +2,15 @@ namespace OpenEuropa\TaskRunner\Tests\Commands; +use My\Custom\TestConfigSubscriber; use OpenEuropa\TaskRunner\Commands\ChangelogCommands; use OpenEuropa\TaskRunner\TaskRunner; use OpenEuropa\TaskRunner\Tests\AbstractTest; +use Robo\Robo; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Yaml\Yaml; /** @@ -367,6 +370,19 @@ public function testUserConfigFile() $this->assertEquals($content['wordpress'], $runner->getConfig()->get('wordpress')); $this->assertEquals($content['drupal']['root'], $runner->getConfig()->get('drupal.root')); $this->assertNotEquals($drupalRoot, $runner->getConfig()->get('drupal.root')); + + /** @var EventDispatcherInterface $dispatcher */ + $dispatcher = Robo::getContainer()->get('eventDispatcher'); + $dispatcher->addSubscriber(new TestConfigSubscriber()); + + // Create a new runner. + $input = new StringInput('list --working-dir=' . $this->getSandboxRoot()); + $runner = new TaskRunner($input, new NullOutput(), $this->getClassLoader()); + $runner->run(); + + $this->assertSame(['root' => 'joomla'], $runner->getConfig()->get('joomla')); + $this->assertSame(['root' => 'wordpress'], $runner->getConfig()->get('wordpress')); + $this->assertSame('docroot', $runner->getConfig()->get('drupal.root')); } /** diff --git a/tests/custom/src/TestConfigSubscriber.php b/tests/custom/src/TestConfigSubscriber.php new file mode 100644 index 00000000..c966bc5a --- /dev/null +++ b/tests/custom/src/TestConfigSubscriber.php @@ -0,0 +1,29 @@ + 'addConfigFile']; + } + + /** + * @param ConfigEvent $configEvent + */ + public function addConfigFile(ConfigEvent $configEvent) + { + $config = $configEvent->getConfig(); + Robo::loadConfiguration([ + __DIR__.'/../../fixtures/third_party.yml', + ], $config); + } +} diff --git a/tests/fixtures/third_party.yml b/tests/fixtures/third_party.yml new file mode 100644 index 00000000..53d49973 --- /dev/null +++ b/tests/fixtures/third_party.yml @@ -0,0 +1,5 @@ +joomla: + root: joomla + +drupal: + root: docroot From 35f65c7493601bfe745f892401cccc2e7c61f4ff Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Wed, 22 Apr 2020 16:08:00 +0300 Subject: [PATCH 02/18] ISAICP-5825: Rename the new method to comply with ::createConfiguration(). --- src/TaskRunner.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TaskRunner.php b/src/TaskRunner.php index 911c70b3..e499d76b 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -97,8 +97,8 @@ public function __construct(InputInterface $input, OutputInterface $output, Clas $this->application->setAutoExit(false); $this->container = $this->createContainer($this->input, $this->output, $this->application, $this->config, $classLoader); - // Allows 3rd party to modify the configuration. - $this->alterConfig(); + // Allow 3rd party to modify the configuration. + $this->alterConfiguration(); // Create and initialize runner. $this->runner = new RoboRunner(); @@ -159,7 +159,7 @@ private function createConfiguration() /** * Allows 3rd party to alter the configuration. */ - private function alterConfig() + private function alterConfiguration() { /** @var EventDispatcherInterface $dispatcher */ $dispatcher = $this->container->get('eventDispatcher'); @@ -180,7 +180,7 @@ private function alterConfig() * The local configuration file path, or null if it doesn't exist. * * @todo Move this to an event subscriber. - * @see TaskRunner::alterConfig() + * @see TaskRunner::alterConfiguration() */ private function getLocalConfigurationFilepath($configuration_file = 'openeuropa/taskrunner/runner.yml') { From 19c7ae23fd74e15dd5ef4bab28fbbe7d590895b8 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Thu, 23 Apr 2020 11:50:03 +0300 Subject: [PATCH 03/18] ISAICP-5825: Add a command that returns the resolved config. --- src/Commands/RunnerCommands.php | 21 +++++++++++++++++++++ src/TaskRunner.php | 1 + 2 files changed, 22 insertions(+) create mode 100644 src/Commands/RunnerCommands.php diff --git a/src/Commands/RunnerCommands.php b/src/Commands/RunnerCommands.php new file mode 100644 index 00000000..a98915f4 --- /dev/null +++ b/src/Commands/RunnerCommands.php @@ -0,0 +1,21 @@ +getConfig()->export(), 10, 2); + } +} diff --git a/src/TaskRunner.php b/src/TaskRunner.php index e499d76b..b9d00b71 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -75,6 +75,7 @@ class TaskRunner DrupalCommands::class, DynamicCommands::class, ReleaseCommands::class, + RunnerCommands::class, ]; /** From 75ff634e9c4bb58e5d6abb75f1d725573aafc924 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Thu, 23 Apr 2020 11:52:46 +0300 Subject: [PATCH 04/18] ISAICP-5825: Switch to a discovery config altering. --- .../FileFromEnvironmentConfigModifier.php | 46 ++++++++ .../LocalFileConfigModifier.php | 20 ++++ src/Contract/ConfigModifierInterface.php | 27 +++++ src/Event/ConfigEvent.php | 35 ------ src/TaskRunner.php | 107 ++++++++++++------ tests/CommandsTest.php | 38 ++----- .../EdgeCaseConfigModifier.php | 24 ++++ .../ConfigModifiers/TestConfigModifier.php | 26 +++++ tests/custom/src/TestConfigSubscriber.php | 29 ----- tests/fixtures/third_party.yml | 3 - tests/fixtures/userconfig.yml | 2 + 11 files changed, 228 insertions(+), 129 deletions(-) create mode 100644 src/ConfigModifiers/FileFromEnvironmentConfigModifier.php create mode 100644 src/ConfigModifiers/LocalFileConfigModifier.php create mode 100644 src/Contract/ConfigModifierInterface.php delete mode 100644 src/Event/ConfigEvent.php create mode 100644 tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php create mode 100644 tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php delete mode 100644 tests/custom/src/TestConfigSubscriber.php diff --git a/src/ConfigModifiers/FileFromEnvironmentConfigModifier.php b/src/ConfigModifiers/FileFromEnvironmentConfigModifier.php new file mode 100644 index 00000000..4eaaef9d --- /dev/null +++ b/src/ConfigModifiers/FileFromEnvironmentConfigModifier.php @@ -0,0 +1,46 @@ +config = $config; - } - - /** - * @return Config - */ - public function getConfig() - { - return $this->config; - } -} diff --git a/src/TaskRunner.php b/src/TaskRunner.php index b9d00b71..c0d47d50 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -3,15 +3,20 @@ namespace OpenEuropa\TaskRunner; use Composer\Autoload\ClassLoader; +use Consolidation\AnnotatedCommand\Parser\Internal\DocblockTag; +use Consolidation\AnnotatedCommand\Parser\Internal\TagFactory; use Gitonomy\Git\Repository; use OpenEuropa\TaskRunner\Commands\ChangelogCommands; use OpenEuropa\TaskRunner\Commands\DrupalCommands; use OpenEuropa\TaskRunner\Commands\DynamicCommands; use OpenEuropa\TaskRunner\Commands\ReleaseCommands; +use OpenEuropa\TaskRunner\Commands\RunnerCommands; +use OpenEuropa\TaskRunner\ConfigModifiers\FileFromEnvironmentConfigModifier; +use OpenEuropa\TaskRunner\ConfigModifiers\LocalFileConfigModifier; use OpenEuropa\TaskRunner\Contract\ComposerAwareInterface; +use OpenEuropa\TaskRunner\Contract\ConfigModifierInterface; use OpenEuropa\TaskRunner\Contract\RepositoryAwareInterface; use OpenEuropa\TaskRunner\Contract\TimeAwareInterface; -use OpenEuropa\TaskRunner\Event\ConfigEvent; use OpenEuropa\TaskRunner\Services\Composer; use OpenEuropa\TaskRunner\Contract\FilesystemAwareInterface; use League\Container\ContainerAwareTrait; @@ -25,7 +30,6 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Filesystem\Filesystem; /** @@ -99,7 +103,7 @@ public function __construct(InputInterface $input, OutputInterface $output, Clas $this->container = $this->createContainer($this->input, $this->output, $this->application, $this->config, $classLoader); // Allow 3rd party to modify the configuration. - $this->alterConfiguration(); + $this->modifyConfiguration(); // Create and initialize runner. $this->runner = new RoboRunner(); @@ -150,54 +154,87 @@ private function createConfiguration() Robo::loadConfiguration([ __DIR__.'/../config/runner.yml', 'runner.yml.dist', - 'runner.yml', - $this->getLocalConfigurationFilepath(), ], $config); return $config; } /** - * Allows 3rd party to alter the configuration. + * Allows 3rd party to modify the configuration. */ - private function alterConfiguration() + private function modifyConfiguration() { - /** @var EventDispatcherInterface $dispatcher */ - $dispatcher = $this->container->get('eventDispatcher'); - $event = new ConfigEvent($this->config); - // @todo Remove Symfony 3.4 BC when Drupal 8.9.x is EOL. - $dispatcher->dispatch(get_class($event), $event); + /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */ + $discovery = Robo::service('relativeNamespaceDiscovery'); + $discovery->setRelativeNamespace('TaskRunner\ConfigModifiers') + ->setSearchPattern('/.*ConfigModifier\.php$/'); + + // Add default config modifier classes. Setting very low priorities so + // that we are sure that these modifiers are running at the very end. + // However, in some very specific circumstances, third-party modifiers + // are abie to set priorities lowers than these and, as an effect, they + // are able to override the default config modifiers, + $classes = [ + FileFromEnvironmentConfigModifier::class => -1300, + LocalFileConfigModifier::class => -1000, + ]; + + // Discover 3rd party modifiers. + foreach ($discovery->getClasses() as $class) { + if (is_subclass_of($class, ConfigModifierInterface::class)) { + $classes[$class] = $this->getConfigModifierPriority($class); + } + } + + // High priority modifiers run first. + arsort($classes, SORT_NUMERIC); + + foreach (array_keys($classes) as $class) { + $class::modify($this->config); + } + // Keep the container in sync. $this->container->share('config', $this->config); } /** - * Get the local configuration filepath. - * - * @param string $configuration_file - * The default filepath. - * - * @return string|null - * The local configuration file path, or null if it doesn't exist. - * - * @todo Move this to an event subscriber. - * @see TaskRunner::alterConfiguration() + * @param string $class + * @return float + * @throws \ReflectionException */ - private function getLocalConfigurationFilepath($configuration_file = 'openeuropa/taskrunner/runner.yml') + private function getConfigModifierPriority($class) { - if ($config = getenv('OPENEUROPA_TASKRUNNER_CONFIG')) { - return $config; - } - - if ($config = getenv('XDG_CONFIG_HOME')) { - return $config . '/' . $configuration_file; + $priority = 0.0; + $reflectionClass = new \ReflectionClass($class); + if ($docBlock = $reflectionClass->getDocComment()) { + // Remove the leading /** and the trailing */ + $docBlock = preg_replace('#^\s*/\*+\s*#', '', $docBlock); + $docBlock = preg_replace('#\s*\*+/\s*#', '', $docBlock); + + // Nothing left? Exit. + if (empty($docBlock)) { + return $priority; + } + + $tagFactory = new TagFactory(); + foreach (explode("\n", $docBlock) as $row) { + // Remove trailing whitespace and leading space + '*'s + $row = rtrim($row); + $row = preg_replace('#^[ \t]*\**#', '', $row); + $tagFactory->parseLine($row); + } + + $priority = array_reduce($tagFactory->getTags(), function ($priority, DocblockTag $tag) { + if ($tag->getTag() === 'priority') { + $value = $tag->getContent(); + if (is_numeric($value)) { + $priority = (float) $value; + } + } + return $priority; + }, $priority); } - - if ($home = getenv('HOME')) { - return getenv('HOME') . '/.config/' . $configuration_file; - } - - return null; + return $priority; } /** diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index cf85a7b7..50d63a1a 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -346,43 +346,27 @@ public function testWorkingDirectoryToken() */ public function testUserConfigFile() { - $fixtureName = 'userconfig.yml'; - - // Create a runner. - $input = new StringInput('list --working-dir=' . $this->getSandboxRoot()); - $runner = new TaskRunner($input, new NullOutput(), $this->getClassLoader()); - $runner->run(); - - // Extract a value from the default configuration. - $drupalRoot = $runner->getConfig()->get('drupal.root'); - // Add the environment setting. putenv('OPENEUROPA_TASKRUNNER_CONFIG=' . __DIR__ . '/fixtures/userconfig.yml'); // Create a new runner. $input = new StringInput('list --working-dir=' . $this->getSandboxRoot()); $runner = new TaskRunner($input, new NullOutput(), $this->getClassLoader()); - $runner->run(); - - // Get the content of the fixture. - $content = $this->getFixtureContent($fixtureName); - $this->assertEquals($content['wordpress'], $runner->getConfig()->get('wordpress')); - $this->assertEquals($content['drupal']['root'], $runner->getConfig()->get('drupal.root')); - $this->assertNotEquals($drupalRoot, $runner->getConfig()->get('drupal.root')); + // Set as `build` by config/runner.yml. + // Overwritten as `drupal` by tests/fixtures/userconfig.yml. + $this->assertEquals('drupal', $runner->getConfig()->get('drupal.root')); - /** @var EventDispatcherInterface $dispatcher */ - $dispatcher = Robo::getContainer()->get('eventDispatcher'); - $dispatcher->addSubscriber(new TestConfigSubscriber()); - - // Create a new runner. - $input = new StringInput('list --working-dir=' . $this->getSandboxRoot()); - $runner = new TaskRunner($input, new NullOutput(), $this->getClassLoader()); - $runner->run(); + // Set as `['root' => 'drupal']` by TestConfigModifier::modify(). + // Overwritten as `['root' => 'wordpress']` by userconfig.yml. + $this->assertSame(['root' => 'wordpress'], $runner->getConfig()->get('wordpress')); + // Set as `['root' => 'joomla']` by tests/fixtures/third_party.yml. $this->assertSame(['root' => 'joomla'], $runner->getConfig()->get('joomla')); - $this->assertSame(['root' => 'wordpress'], $runner->getConfig()->get('wordpress')); - $this->assertSame('docroot', $runner->getConfig()->get('drupal.root')); + + // Set as `overwritten by edge case` by tests/fixtures/userconfig.yml. + // Overwritten as `overwritten` by EdgeCaseConfigModifier::modify(). + $this->assertSame('overwritten', $runner->getConfig()->get('whatever')); } /** diff --git a/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php b/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php new file mode 100644 index 00000000..9b7e8811 --- /dev/null +++ b/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php @@ -0,0 +1,24 @@ +combine(['whatever' => 'overwritten']); + } +} diff --git a/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php b/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php new file mode 100644 index 00000000..7a4d97e6 --- /dev/null +++ b/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php @@ -0,0 +1,26 @@ +combine(['whatever' => ['root' => 'drupal']]); + // Interleave third_party.yml between runner.yml.dist and runner.yml. + Robo::loadConfiguration([ + __DIR__ . '/../../../../fixtures/third_party.yml', + ], $config); + } +} diff --git a/tests/custom/src/TestConfigSubscriber.php b/tests/custom/src/TestConfigSubscriber.php deleted file mode 100644 index c966bc5a..00000000 --- a/tests/custom/src/TestConfigSubscriber.php +++ /dev/null @@ -1,29 +0,0 @@ - 'addConfigFile']; - } - - /** - * @param ConfigEvent $configEvent - */ - public function addConfigFile(ConfigEvent $configEvent) - { - $config = $configEvent->getConfig(); - Robo::loadConfiguration([ - __DIR__.'/../../fixtures/third_party.yml', - ], $config); - } -} diff --git a/tests/fixtures/third_party.yml b/tests/fixtures/third_party.yml index 53d49973..e01c4f6f 100644 --- a/tests/fixtures/third_party.yml +++ b/tests/fixtures/third_party.yml @@ -1,5 +1,2 @@ joomla: root: joomla - -drupal: - root: docroot diff --git a/tests/fixtures/userconfig.yml b/tests/fixtures/userconfig.yml index c0e02103..312235d4 100644 --- a/tests/fixtures/userconfig.yml +++ b/tests/fixtures/userconfig.yml @@ -3,3 +3,5 @@ drupal: wordpress: root: "wordpress" + +whatever: 'this will be overwritten by the edge case modifier' From 6acae8b509e4269443f6fb53207fa01a56799911 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Thu, 23 Apr 2020 12:19:08 +0300 Subject: [PATCH 05/18] ISAICP-5825: Add documentation. --- README.md | 32 ++++++++++++++++++++++++ src/Contract/ConfigModifierInterface.php | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6edc2fe6..6a4b1977 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,38 @@ Task Runner commands can be customized in two ways: this file to declare default options which are expected to work with your application under regular circumstances. This file should be committed in the project. + * Third parties might implement config modifiers to act on config as it has + been built at this stage. A config modifier is a class implementing the + `\OpenEuropa\TaskRunner\Contract\ConfigModifierInterface`. Such a class + should be placed under the `TaskRunner\ConfigModifiers` relative + namespace. For instance when `Some\Namespace` points to the `src/` + directory, then the config modifier class should be placed in the + `src/TaskRunner/ConfigModifiers` directory and will have the namespace set + to `Some\Namespace\TaskRunner\ConfigModifiers`. The class name should end + with the `ConfigModifier` suffix. Use the `::modify()` method to alter the + configuration. A `@priority` annotation tag can be defined in the class + docblock in order to determine the order of config modifiers. If missed, + the "0 priority" is assumed. This mechanism allows also to insert custom + YAML config files in the flow, see the following example: + ``` + namespace Some\Namespace\TaskRunner\ConfigModifiers; + + use Consolidation\Config\ConfigInterface; + use OpenEuropa\TaskRunner\Contract\ConfigModifierInterface; + use Robo\Robo; + + /** + * @priority 100 + */ + class AddCustomFileConfigModifier implements ConfigModifierInterface + { + public static function modify(ConfigInterface $config) + { + // Interleave custom.yml between runner.yml.dist and runner.yml. + Robo::loadConfiguration(['custom.yml'], $config); + } + } + ``` * `runner.yml` - project specific user overrides. This file is also located in the root folder of the project that depends on the Task Runner. This file can be used to override options with values that are specific to the diff --git a/src/Contract/ConfigModifierInterface.php b/src/Contract/ConfigModifierInterface.php index 3472d253..c4736fab 100644 --- a/src/Contract/ConfigModifierInterface.php +++ b/src/Contract/ConfigModifierInterface.php @@ -8,7 +8,7 @@ * Provides an interface allowing alterations of task runner configuration. * * Classes implementing this interface: - * - Should have TaskRunner\ConfigModifiers` as relative namespace. For + * - Should have `TaskRunner\ConfigModifiers` as relative namespace. For * instance when `Some\Namespace` points to the `src/` directory, then the * class should be placed in `src/TaskRunner/ConfigModifiers` and will have * `Some\Namespace\TaskRunner\ConfigModifiers` as namespace. From 334ac9f568a413ca5282e82380d1088b42231524 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Thu, 23 Apr 2020 13:06:04 +0300 Subject: [PATCH 06/18] ISAICP-5825: Typos & wording improvements. --- src/TaskRunner.php | 8 ++++---- .../TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/TaskRunner.php b/src/TaskRunner.php index c0d47d50..1cf1dfef 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -169,11 +169,11 @@ private function modifyConfiguration() $discovery->setRelativeNamespace('TaskRunner\ConfigModifiers') ->setSearchPattern('/.*ConfigModifier\.php$/'); - // Add default config modifier classes. Setting very low priorities so + // Add default config modifier classes. Setting a very low priorities so // that we are sure that these modifiers are running at the very end. - // However, in some very specific circumstances, third-party modifiers - // are abie to set priorities lowers than these and, as an effect, they - // are able to override the default config modifiers, + // However, in some very specific circumstances, third-party config + // modifiers are abie to set priorities lower than these and, as an + // effect, they can override even these default config modifiers. $classes = [ FileFromEnvironmentConfigModifier::class => -1300, LocalFileConfigModifier::class => -1000, diff --git a/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php b/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php index 9b7e8811..98b6e3bf 100644 --- a/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php +++ b/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php @@ -7,8 +7,8 @@ use Robo\Robo; /** - * We set here a very low priority to run even the default modifiers. This is - * here just as proof that also the default modifiers can be overridden. + * We set here a very low priority to run even after the default modifiers. This + * is here just as proof that the default modifiers can be also overridden. * * @priority -2000 */ From 55b4a3c0ff55f3979c622a4cba8f2ac2a7c471f5 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Thu, 23 Apr 2020 22:39:43 +0300 Subject: [PATCH 07/18] ISAICP-5825: Make sure that variables are resolved only at the end. --- README.md | 42 +++++++----- .../LocalFileConfigModifier.php | 20 ------ src/ConfigProviders/DefaultConfigProvider.php | 22 +++++++ .../FileFromEnvironmentConfigProvider.php} | 17 ++--- .../LocalFileConfigProvider.php | 19 ++++++ src/Contract/ConfigModifierInterface.php | 27 -------- src/Contract/ConfigProviderInterface.php | 28 ++++++++ src/TaskRunner.php | 66 ++++++++----------- src/Traits/ConfigFromFilesTrait.php | 23 +++++++ tests/CommandsTest.php | 15 ++++- .../EdgeCaseConfigModifier.php | 24 ------- .../ConfigModifiers/TestConfigModifier.php | 26 -------- .../EdgeCaseConfigProvider.php | 22 +++++++ .../ConfigProviders/TestConfigProvider.php | 27 ++++++++ tests/fixtures/third_party.yml | 5 ++ tests/fixtures/userconfig.yml | 2 +- tests/sandbox/.gitkeep | 0 17 files changed, 221 insertions(+), 164 deletions(-) delete mode 100644 src/ConfigModifiers/LocalFileConfigModifier.php create mode 100644 src/ConfigProviders/DefaultConfigProvider.php rename src/{ConfigModifiers/FileFromEnvironmentConfigModifier.php => ConfigProviders/FileFromEnvironmentConfigProvider.php} (70%) create mode 100644 src/ConfigProviders/LocalFileConfigProvider.php delete mode 100644 src/Contract/ConfigModifierInterface.php create mode 100644 src/Contract/ConfigProviderInterface.php create mode 100644 src/Traits/ConfigFromFilesTrait.php delete mode 100644 tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php delete mode 100644 tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php create mode 100644 tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php create mode 100644 tests/custom/src/TaskRunner/ConfigProviders/TestConfigProvider.php mode change 100644 => 100755 tests/sandbox/.gitkeep diff --git a/README.md b/README.md index 6a4b1977..d8bcc0bf 100644 --- a/README.md +++ b/README.md @@ -94,35 +94,41 @@ Task Runner commands can be customized in two ways: this file to declare default options which are expected to work with your application under regular circumstances. This file should be committed in the project. - * Third parties might implement config modifiers to act on config as it has - been built at this stage. A config modifier is a class implementing the - `\OpenEuropa\TaskRunner\Contract\ConfigModifierInterface`. Such a class - should be placed under the `TaskRunner\ConfigModifiers` relative - namespace. For instance when `Some\Namespace` points to the `src/` - directory, then the config modifier class should be placed in the - `src/TaskRunner/ConfigModifiers` directory and will have the namespace set - to `Some\Namespace\TaskRunner\ConfigModifiers`. The class name should end - with the `ConfigModifier` suffix. Use the `::modify()` method to alter the - configuration. A `@priority` annotation tag can be defined in the class - docblock in order to determine the order of config modifiers. If missed, - the "0 priority" is assumed. This mechanism allows also to insert custom - YAML config files in the flow, see the following example: + * Third parties might implement config providers to modify the config, + passed as an array, by adding, overriding or removing array elements. A + config provider is a class implementing the `ConfigProviderInterface`. + Such a class should be placed under the `TaskRunner\ConfigProviders` + relative namespace. For instance when `Some\Namespace` points to `src/` + directory, then the config provider class should be placed under the + `src/TaskRunner/ConfigProviders` directory and will have the namespace set + to `Some\Namespace\TaskRunner\ConfigProviders`. The class name should end + with the `ConfigProvider` suffix. Use the `::provide()` method to alter + the configuration. A `@priority` annotation tag can be defined in the + class docblock in order to determine the order in which the config + providers are running. If missed, the "0 priority" is assumed. This + mechanism allows also to insert custom YAML config files in the flow, see + the following example: ``` - namespace Some\Namespace\TaskRunner\ConfigModifiers; + namespace Some\Namespace\TaskRunner\ConfigProviders; use Consolidation\Config\ConfigInterface; - use OpenEuropa\TaskRunner\Contract\ConfigModifierInterface; - use Robo\Robo; + use OpenEuropa\TaskRunner\Contract\ConfigProviderInterface; + use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; /** * @priority 100 */ - class AddCustomFileConfigModifier implements ConfigModifierInterface + class AddCustomFileConfigProvider implements ConfigProviderInterface { - public static function modify(ConfigInterface $config) + use ConfigFromFilesTrait; + public static function provide(array &$config) { // Interleave custom.yml between runner.yml.dist and runner.yml. Robo::loadConfiguration(['custom.yml'], $config); + static::importFromFiles($config, [ + 'custom.yml', + 'custom2.yml', + ]); } } ``` diff --git a/src/ConfigModifiers/LocalFileConfigModifier.php b/src/ConfigModifiers/LocalFileConfigModifier.php deleted file mode 100644 index 7c385438..00000000 --- a/src/ConfigModifiers/LocalFileConfigModifier.php +++ /dev/null @@ -1,20 +0,0 @@ -workingDir = $this->getWorkingDir($this->input); chdir($this->workingDir); - $this->config = $this->createConfiguration(); + $this->config = new Config(); $this->application = $this->createApplication(); $this->application->setAutoExit(false); $this->container = $this->createContainer($this->input, $this->output, $this->application, $this->config, $classLoader); - // Allow 3rd party to modify the configuration. - $this->modifyConfiguration(); + $this->createConfiguration(); // Create and initialize runner. $this->runner = new RoboRunner(); @@ -144,55 +145,44 @@ public function getCommands($class) /** * Create default configuration. - * - * @return Config */ private function createConfiguration() - { - $config = new Config(); - $config->set('runner.working_dir', realpath($this->workingDir)); - Robo::loadConfiguration([ - __DIR__.'/../config/runner.yml', - 'runner.yml.dist', - ], $config); - - return $config; - } - - /** - * Allows 3rd party to modify the configuration. - */ - private function modifyConfiguration() { /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */ $discovery = Robo::service('relativeNamespaceDiscovery'); - $discovery->setRelativeNamespace('TaskRunner\ConfigModifiers') - ->setSearchPattern('/.*ConfigModifier\.php$/'); - - // Add default config modifier classes. Setting a very low priorities so - // that we are sure that these modifiers are running at the very end. - // However, in some very specific circumstances, third-party config - // modifiers are abie to set priorities lower than these and, as an - // effect, they can override even these default config modifiers. + $discovery->setRelativeNamespace('TaskRunner\ConfigProviders') + ->setSearchPattern('/.*ConfigProvider\.php$/'); + + // Add default config provider classes. Setting extreme priorities so + // that we are sure that the default config provider runs first and the + // other two are running at the very end. However, in some very specific + // circumstances, third-party config providers are abie to set + // priorities, either higher or lower than these, and, as an effect, + // they can override even these default config providers. $classes = [ - FileFromEnvironmentConfigModifier::class => -1300, - LocalFileConfigModifier::class => -1000, + DefaultConfigProvider::class => 1500, + LocalFileConfigProvider::class => -1000, + FileFromEnvironmentConfigProvider::class => -1500, ]; - // Discover 3rd party modifiers. + // Discover 3rd party config providers. foreach ($discovery->getClasses() as $class) { - if (is_subclass_of($class, ConfigModifierInterface::class)) { - $classes[$class] = $this->getConfigModifierPriority($class); + if (is_subclass_of($class, ConfigProviderInterface::class)) { + $classes[$class] = $this->getConfigProviderPriority($class); } } // High priority modifiers run first. arsort($classes, SORT_NUMERIC); + $configArray = []; foreach (array_keys($classes) as $class) { - $class::modify($this->config); + $class::provide($configArray); } + // Resolve variables and import into config. + $processor = (new ConfigProcessor())->add($configArray); + $this->config->import($processor->export()); // Keep the container in sync. $this->container->share('config', $this->config); } @@ -202,7 +192,7 @@ private function modifyConfiguration() * @return float * @throws \ReflectionException */ - private function getConfigModifierPriority($class) + private function getConfigProviderPriority($class) { $priority = 0.0; $reflectionClass = new \ReflectionClass($class); diff --git a/src/Traits/ConfigFromFilesTrait.php b/src/Traits/ConfigFromFilesTrait.php new file mode 100644 index 00000000..55990ff6 --- /dev/null +++ b/src/Traits/ConfigFromFilesTrait.php @@ -0,0 +1,23 @@ +load($file)->export()); + } + } +} diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 50d63a1a..24cb62ed 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -346,6 +346,10 @@ public function testWorkingDirectoryToken() */ public function testUserConfigFile() { + // Create a local config file. + $runnerYaml = $this->getSandboxRoot().'/runner.yml'; + file_put_contents($runnerYaml, Yaml::dump(['foo' => 'baz'])); + // Add the environment setting. putenv('OPENEUROPA_TASKRUNNER_CONFIG=' . __DIR__ . '/fixtures/userconfig.yml'); @@ -357,7 +361,7 @@ public function testUserConfigFile() // Overwritten as `drupal` by tests/fixtures/userconfig.yml. $this->assertEquals('drupal', $runner->getConfig()->get('drupal.root')); - // Set as `['root' => 'drupal']` by TestConfigModifier::modify(). + // Set as `['root' => 'drupal']` by TestConfigProvider::modify(). // Overwritten as `['root' => 'wordpress']` by userconfig.yml. $this->assertSame(['root' => 'wordpress'], $runner->getConfig()->get('wordpress')); @@ -365,8 +369,15 @@ public function testUserConfigFile() $this->assertSame(['root' => 'joomla'], $runner->getConfig()->get('joomla')); // Set as `overwritten by edge case` by tests/fixtures/userconfig.yml. - // Overwritten as `overwritten` by EdgeCaseConfigModifier::modify(). + // Overwritten as `overwritten` by EdgeCaseConfigProvider::modify(). $this->assertSame('overwritten', $runner->getConfig()->get('whatever')); + + // The 'qux' value is computed by using the ${foo} variable. We test + // that the replacements are done at the very end, when all the config + // providers had the chance to resolve the variables. ${foo} equals + // 'bar', in the tests/fixtures/third_party.yml file but is overwritten + // at the end, in tests/sandbox/runner.yml with 'baz'. + $this->assertSame('is-baz', $runner->getConfig()->get('qux')); } /** diff --git a/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php b/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php deleted file mode 100644 index 98b6e3bf..00000000 --- a/tests/custom/src/TaskRunner/ConfigModifiers/EdgeCaseConfigModifier.php +++ /dev/null @@ -1,24 +0,0 @@ -combine(['whatever' => 'overwritten']); - } -} diff --git a/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php b/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php deleted file mode 100644 index 7a4d97e6..00000000 --- a/tests/custom/src/TaskRunner/ConfigModifiers/TestConfigModifier.php +++ /dev/null @@ -1,26 +0,0 @@ -combine(['whatever' => ['root' => 'drupal']]); - // Interleave third_party.yml between runner.yml.dist and runner.yml. - Robo::loadConfiguration([ - __DIR__ . '/../../../../fixtures/third_party.yml', - ], $config); - } -} diff --git a/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php b/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php new file mode 100644 index 00000000..4b9d731f --- /dev/null +++ b/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php @@ -0,0 +1,22 @@ + Date: Fri, 24 Apr 2020 09:36:27 +0300 Subject: [PATCH 08/18] ISAICP-5825: Init the working dir (was lost on refactoring). --- src/TaskRunner.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/TaskRunner.php b/src/TaskRunner.php index e4ba3df7..213c15f9 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -175,7 +175,11 @@ private function createConfiguration() // High priority modifiers run first. arsort($classes, SORT_NUMERIC); - $configArray = []; + $configArray = [ + 'runner' => [ + 'working_dir' => realpath($this->workingDir), + ], + ]; foreach (array_keys($classes) as $class) { $class::provide($configArray); } From 8bff17f401bdd2940fe458b6b9e87a27f16d5d9d Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Fri, 24 Apr 2020 11:21:12 +0300 Subject: [PATCH 09/18] ISAICP-5825: Fix some test docs. Remove unused statemenets. --- tests/CommandsTest.php | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 24cb62ed..183b419f 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -2,15 +2,12 @@ namespace OpenEuropa\TaskRunner\Tests\Commands; -use My\Custom\TestConfigSubscriber; use OpenEuropa\TaskRunner\Commands\ChangelogCommands; use OpenEuropa\TaskRunner\TaskRunner; use OpenEuropa\TaskRunner\Tests\AbstractTest; -use Robo\Robo; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Yaml\Yaml; /** @@ -357,26 +354,26 @@ public function testUserConfigFile() $input = new StringInput('list --working-dir=' . $this->getSandboxRoot()); $runner = new TaskRunner($input, new NullOutput(), $this->getClassLoader()); - // Set as `build` by config/runner.yml. - // Overwritten as `drupal` by tests/fixtures/userconfig.yml. + // Set as `build` by `config/runner.yml`. + // Overwritten as `drupal` by `tests/fixtures/userconfig.yml`. $this->assertEquals('drupal', $runner->getConfig()->get('drupal.root')); - // Set as `['root' => 'drupal']` by TestConfigProvider::modify(). - // Overwritten as `['root' => 'wordpress']` by userconfig.yml. + // Set as `['root' => 'drupal']` by `TestConfigProvider::provide()`. + // Overwritten as `['root' => 'wordpress']` by `userconfig.yml`. $this->assertSame(['root' => 'wordpress'], $runner->getConfig()->get('wordpress')); - // Set as `['root' => 'joomla']` by tests/fixtures/third_party.yml. + // Set as `['root' => 'joomla']` by `tests/fixtures/third_party.yml`. $this->assertSame(['root' => 'joomla'], $runner->getConfig()->get('joomla')); - // Set as `overwritten by edge case` by tests/fixtures/userconfig.yml. - // Overwritten as `overwritten` by EdgeCaseConfigProvider::modify(). + // Set as `overwritten by edge case` by `tests/fixtures/userconfig.yml`. + // Overwritten as `overwritten` by `EdgeCaseConfigProvider::provide()`. $this->assertSame('overwritten', $runner->getConfig()->get('whatever')); - // The 'qux' value is computed by using the ${foo} variable. We test + // The `qux` value is computed by using the `${foo}` token. We test // that the replacements are done at the very end, when all the config - // providers had the chance to resolve the variables. ${foo} equals - // 'bar', in the tests/fixtures/third_party.yml file but is overwritten - // at the end, in tests/sandbox/runner.yml with 'baz'. + // providers had the chance to resolve the tokens. `${foo}` equals + // `bar`, in the `tests/fixtures/third_party.yml` file but is + // overwritten at the end, in `tests/sandbox/runner.yml` with `baz`. $this->assertSame('is-baz', $runner->getConfig()->get('qux')); } From 50a48f3de9798c485c0b2d00ea192abb438e9e5b Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Fri, 24 Apr 2020 16:34:29 +0300 Subject: [PATCH 10/18] ISAICP-5825: Use the Robo config class instead of manipulating arrays. --- README.md | 12 +++++------- src/ConfigProviders/DefaultConfigProvider.php | 5 ++++- .../FileFromEnvironmentConfigProvider.php | 7 +++++-- src/ConfigProviders/LocalFileConfigProvider.php | 5 ++++- src/Contract/ConfigProviderInterface.php | 15 +++++++++++---- src/TaskRunner.php | 14 ++++++-------- src/Traits/ConfigFromFilesTrait.php | 10 ++++++---- .../ConfigProviders/EdgeCaseConfigProvider.php | 7 +++++-- .../ConfigProviders/TestConfigProvider.php | 7 +++++-- 9 files changed, 51 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index d8bcc0bf..f7094bc3 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,7 @@ Task Runner commands can be customized in two ways: this file to declare default options which are expected to work with your application under regular circumstances. This file should be committed in the project. - * Third parties might implement config providers to modify the config, - passed as an array, by adding, overriding or removing array elements. A + * Third parties might implement config providers to modify the config. A config provider is a class implementing the `ConfigProviderInterface`. Such a class should be placed under the `TaskRunner\ConfigProviders` relative namespace. For instance when `Some\Namespace` points to `src/` @@ -103,17 +102,17 @@ Task Runner commands can be customized in two ways: `src/TaskRunner/ConfigProviders` directory and will have the namespace set to `Some\Namespace\TaskRunner\ConfigProviders`. The class name should end with the `ConfigProvider` suffix. Use the `::provide()` method to alter - the configuration. A `@priority` annotation tag can be defined in the - class docblock in order to determine the order in which the config + the configuration object. A `@priority` annotation tag can be defined in + the class docblock in order to determine the order in which the config providers are running. If missed, the "0 priority" is assumed. This mechanism allows also to insert custom YAML config files in the flow, see the following example: ``` namespace Some\Namespace\TaskRunner\ConfigProviders; - use Consolidation\Config\ConfigInterface; use OpenEuropa\TaskRunner\Contract\ConfigProviderInterface; use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; + use Robo\Config\Config; /** * @priority 100 @@ -121,10 +120,9 @@ Task Runner commands can be customized in two ways: class AddCustomFileConfigProvider implements ConfigProviderInterface { use ConfigFromFilesTrait; - public static function provide(array &$config) + public static function provide(Config $config): void { // Interleave custom.yml between runner.yml.dist and runner.yml. - Robo::loadConfiguration(['custom.yml'], $config); static::importFromFiles($config, [ 'custom.yml', 'custom2.yml', diff --git a/src/ConfigProviders/DefaultConfigProvider.php b/src/ConfigProviders/DefaultConfigProvider.php index f7b373f7..1e6c7716 100644 --- a/src/ConfigProviders/DefaultConfigProvider.php +++ b/src/ConfigProviders/DefaultConfigProvider.php @@ -1,9 +1,12 @@ (). * - * @param array $config + * @param \Robo\Config\Config $config */ - public static function provide(array &$config); + public static function provide(Config $config): void; } diff --git a/src/TaskRunner.php b/src/TaskRunner.php index 213c15f9..470e7f0a 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -144,10 +144,13 @@ public function getCommands($class) } /** - * Create default configuration. + * Create configuration. */ private function createConfiguration() { + $config = new Config(); + $config->set('runner.working_dir', realpath($this->workingDir)); + /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */ $discovery = Robo::service('relativeNamespaceDiscovery'); $discovery->setRelativeNamespace('TaskRunner\ConfigProviders') @@ -175,17 +178,12 @@ private function createConfiguration() // High priority modifiers run first. arsort($classes, SORT_NUMERIC); - $configArray = [ - 'runner' => [ - 'working_dir' => realpath($this->workingDir), - ], - ]; foreach (array_keys($classes) as $class) { - $class::provide($configArray); + $class::provide($config); } // Resolve variables and import into config. - $processor = (new ConfigProcessor())->add($configArray); + $processor = (new ConfigProcessor())->add($config->export()); $this->config->import($processor->export()); // Keep the container in sync. $this->container->share('config', $this->config); diff --git a/src/Traits/ConfigFromFilesTrait.php b/src/Traits/ConfigFromFilesTrait.php index 55990ff6..eb4914ff 100644 --- a/src/Traits/ConfigFromFilesTrait.php +++ b/src/Traits/ConfigFromFilesTrait.php @@ -4,20 +4,22 @@ use Consolidation\Config\Loader\YamlConfigLoader; use Dflydev\DotAccessData\Util; +use Robo\Config\Config; trait ConfigFromFilesTrait { /** - * Loads configs as arrays from $files and merge them in $config. + * Loads configs from $files and merge them in $config. * - * @param array $config + * @param \Robo\Config\Config $config * @param array $files */ - private static function importFromFiles(array &$config, array $files) + private static function importFromFiles(Config $config, array $files) { $loader = new YamlConfigLoader(); foreach ($files as $file) { - $config = Util::mergeAssocArray($config, $loader->load($file)->export()); + $configArray = Util::mergeAssocArray($config->export(), $loader->load($file)->export()); + $config->replace($configArray); } } } diff --git a/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php b/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php index 4b9d731f..0c976f95 100644 --- a/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php +++ b/tests/custom/src/TaskRunner/ConfigProviders/EdgeCaseConfigProvider.php @@ -1,8 +1,11 @@ set('whatever', 'overwritten'); } } diff --git a/tests/custom/src/TaskRunner/ConfigProviders/TestConfigProvider.php b/tests/custom/src/TaskRunner/ConfigProviders/TestConfigProvider.php index ded63642..7b88bbef 100644 --- a/tests/custom/src/TaskRunner/ConfigProviders/TestConfigProvider.php +++ b/tests/custom/src/TaskRunner/ConfigProviders/TestConfigProvider.php @@ -1,9 +1,12 @@ set('whatever.root', 'drupal'); // Interleave third_party.yml between runner.yml.dist and runner.yml. static::importFromFiles($config, [ __DIR__ . '/../../../../fixtures/third_party.yml', From 8eee7213abbf0680ab81842fde7f7612fb7ae63a Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Fri, 24 Apr 2020 16:53:26 +0300 Subject: [PATCH 11/18] ISAICP-5825: Bump the Robo minimum version to avoid a config bug. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 12483f33..0591c70b 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "prefer-stable": true, "require": { "php": ">=7.2", - "consolidation/robo": "^1.4", + "consolidation/robo": "^1.4.7", "gitonomy/gitlib": "^1.0", "jakeasmith/http_build_url": "^1.0.1", "nuvoleweb/robo-config": "^0.2.1" From d5bc78d08ea158f40db10017fc66bdcf152da028 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Sat, 25 Apr 2020 11:41:40 +0300 Subject: [PATCH 12/18] ISAICP-5825: Allow the 'config' command to show a key. --- src/Commands/RunnerCommands.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Commands/RunnerCommands.php b/src/Commands/RunnerCommands.php index a98915f4..d788a852 100644 --- a/src/Commands/RunnerCommands.php +++ b/src/Commands/RunnerCommands.php @@ -1,7 +1,10 @@ getConfig()->export(), 10, 2); + if (!$key) { + $config = $this->getConfig()->export(); + } elseif (!$config = $this->getConfig()->get($key)) { + throw new AbortTasksException("Invalid '$key' key."); + } + return trim(Yaml::dump($config, 10, 2)); } } From 858ca068c918a305d6a114aaec96ccd80ba8ff1b Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Sat, 25 Apr 2020 12:53:39 +0300 Subject: [PATCH 13/18] ISAICP-5825: Fix coding standards. --- src/Commands/RunnerCommands.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Commands/RunnerCommands.php b/src/Commands/RunnerCommands.php index d788a852..97c2392e 100644 --- a/src/Commands/RunnerCommands.php +++ b/src/Commands/RunnerCommands.php @@ -31,8 +31,11 @@ public function config(?string $key = null): string { if (!$key) { $config = $this->getConfig()->export(); - } elseif (!$config = $this->getConfig()->get($key)) { - throw new AbortTasksException("Invalid '$key' key."); + } else { + if (!$this->getConfig()->has($key)) { + throw new AbortTasksException("Invalid '$key' key."); + } + $config = $this->getConfig()->get($key); } return trim(Yaml::dump($config, 10, 2)); } From 5a0aabce12cefcca04bf61cff86be0f9b3e6df36 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Tue, 12 May 2020 15:58:24 +0300 Subject: [PATCH 14/18] ISAICP-5825: Update documentation. --- README.md | 7 +++++-- src/Commands/RunnerCommands.php | 8 ++++---- src/ConfigProviders/DefaultConfigProvider.php | 10 ++++++++++ .../FileFromEnvironmentConfigProvider.php | 10 ++++++++++ src/ConfigProviders/LocalFileConfigProvider.php | 7 +++++++ src/TaskRunner.php | 2 +- src/Traits/ConfigFromFilesTrait.php | 7 ++++++- 7 files changed, 43 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f7094bc3..8b48d209 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Task Runner commands can be customized in two ways: with the `ConfigProvider` suffix. Use the `::provide()` method to alter the configuration object. A `@priority` annotation tag can be defined in the class docblock in order to determine the order in which the config - providers are running. If missed, the "0 priority" is assumed. This + providers are running. If omitted, `@priority 0` is assumed. This mechanism allows also to insert custom YAML config files in the flow, see the following example: ``` @@ -122,7 +122,10 @@ Task Runner commands can be customized in two ways: use ConfigFromFilesTrait; public static function provide(Config $config): void { - // Interleave custom.yml between runner.yml.dist and runner.yml. + // Load the configuration from custom.yml and custom2.yml and + // apply it to the configuration object. This will override config + // from runner.yml.dist (which has priority 1500) but get + // overridden by the config from runner.yml (priority -1000). static::importFromFiles($config, [ 'custom.yml', 'custom2.yml', diff --git a/src/Commands/RunnerCommands.php b/src/Commands/RunnerCommands.php index 97c2392e..b072fb3a 100644 --- a/src/Commands/RunnerCommands.php +++ b/src/Commands/RunnerCommands.php @@ -8,24 +8,24 @@ use Symfony\Component\Yaml\Yaml; /** - * Runner commands. + * Commands to interact with the task runner itself. */ class RunnerCommands extends AbstractCommands { /** - * Displays the current configuration with YAML representation. + * Displays the current configuration in YAML format. * * If no argument is passed the whole configuration is outputted. To display * a specific configuration, pass the key as argument (e.g. `drupal.root`). * * @command config * - * @param string|null $key + * @param string|null $key Optional configuration key * @return string * @throws \Robo\Exception\AbortTasksException * - * @todo Implement a `--format` option to allow formatted output. + * @todo Implement a `--format` option to allow different output formats. */ public function config(?string $key = null): string { diff --git a/src/ConfigProviders/DefaultConfigProvider.php b/src/ConfigProviders/DefaultConfigProvider.php index 1e6c7716..b6482c2a 100644 --- a/src/ConfigProviders/DefaultConfigProvider.php +++ b/src/ConfigProviders/DefaultConfigProvider.php @@ -8,6 +8,16 @@ use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; use Robo\Config\Config; +/** + * Provides the basic default configuration for the task runner. + * + * This will import the following files: + * - The example configuration provided in the task runner library itself. + * - The default configuration "runner.yml.dist" shipped in the root folder of the project which uses the task runner. + * + * This serves as a safe default implementation which can be overridden by environment specific configuration and user + * preferences. + */ class DefaultConfigProvider implements ConfigProviderInterface { use ConfigFromFilesTrait; diff --git a/src/ConfigProviders/FileFromEnvironmentConfigProvider.php b/src/ConfigProviders/FileFromEnvironmentConfigProvider.php index 910cd123..f4fc6066 100644 --- a/src/ConfigProviders/FileFromEnvironmentConfigProvider.php +++ b/src/ConfigProviders/FileFromEnvironmentConfigProvider.php @@ -8,6 +8,16 @@ use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; use Robo\Config\Config; +/** + * Provides configuration for the local environment. + * + * This will look in the following locations in order, and will apply the configuration of the first found file. Any + * subsequent matches will be ignored. + * + * 1. The location specified in the OPENEUROPA_TASKRUNNER_CONFIG environment variable. + * 2. The config location following the freedesktop.org specification: $XDG_CONFIG_HOME/openeuropa/taskrunner/runner.yml + * 3. The configuration in the user's home folder: $HOME/.config/openeuropa/taskrunner/runner.yml + */ class FileFromEnvironmentConfigProvider implements ConfigProviderInterface { use ConfigFromFilesTrait; diff --git a/src/ConfigProviders/LocalFileConfigProvider.php b/src/ConfigProviders/LocalFileConfigProvider.php index abdb46b5..67f508f3 100644 --- a/src/ConfigProviders/LocalFileConfigProvider.php +++ b/src/ConfigProviders/LocalFileConfigProvider.php @@ -8,6 +8,13 @@ use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; use Robo\Config\Config; +/** + * Provides local configuration. + * + * Developers working on projects using the task runner can put a "runner.yml" file in the root directory of the project + * and provide any local configuration overrides in this file. The file is meant for personal use and should be added + * to .gitignore so it will not accidentally be committed. + */ class LocalFileConfigProvider implements ConfigProviderInterface { use ConfigFromFilesTrait; diff --git a/src/TaskRunner.php b/src/TaskRunner.php index 470e7f0a..91eae5fe 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -144,7 +144,7 @@ public function getCommands($class) } /** - * Create configuration. + * Discovers and parses the configuration files, and merges them into the Config object. */ private function createConfiguration() { diff --git a/src/Traits/ConfigFromFilesTrait.php b/src/Traits/ConfigFromFilesTrait.php index eb4914ff..c5df5272 100644 --- a/src/Traits/ConfigFromFilesTrait.php +++ b/src/Traits/ConfigFromFilesTrait.php @@ -6,13 +6,18 @@ use Dflydev\DotAccessData\Util; use Robo\Config\Config; +/** + * Reusable code for loading configuration stored in files. + */ trait ConfigFromFilesTrait { /** - * Loads configs from $files and merge them in $config. + * Loads configuration from YAML files and merges it in the given config object. * * @param \Robo\Config\Config $config + * The configuration object to update. * @param array $files + * An array of paths to configuration files in YAML format. */ private static function importFromFiles(Config $config, array $files) { From 1ac9b1cdbf064c0078dc111011cfbdc219368075 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Fri, 15 May 2020 10:15:50 +0300 Subject: [PATCH 15/18] ISAICP-5825: Use the generic discovery also for default config providers. --- .../LocalFileConfigProvider.php | 29 ----------- src/TaskRunner.php | 48 +++++++++---------- .../ConfigProviders/DefaultConfigProvider.php | 17 +++++-- .../FileFromEnvironmentConfigProvider.php | 22 ++++++--- .../LocalFileConfigProvider.php | 37 ++++++++++++++ 5 files changed, 87 insertions(+), 66 deletions(-) delete mode 100644 src/ConfigProviders/LocalFileConfigProvider.php rename src/{ => TaskRunner}/ConfigProviders/DefaultConfigProvider.php (61%) rename src/{ => TaskRunner}/ConfigProviders/FileFromEnvironmentConfigProvider.php (59%) create mode 100644 src/TaskRunner/ConfigProviders/LocalFileConfigProvider.php diff --git a/src/ConfigProviders/LocalFileConfigProvider.php b/src/ConfigProviders/LocalFileConfigProvider.php deleted file mode 100644 index 67f508f3..00000000 --- a/src/ConfigProviders/LocalFileConfigProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -set('runner.working_dir', realpath($this->workingDir)); + foreach ($this->getConfigProviders() as $class) { + $class::provide($config); + } + + // Resolve variables and import into config. + $processor = (new ConfigProcessor())->add($config->export()); + $this->config->import($processor->export()); + // Keep the container in sync. + $this->container->share('config', $this->config); + } + + /** + * Discovers all config provider classes. + * + * @return array + * @throws \ReflectionException + */ + private function getConfigProviders(): array + { /** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */ $discovery = Robo::service('relativeNamespaceDiscovery'); $discovery->setRelativeNamespace('TaskRunner\ConfigProviders') ->setSearchPattern('/.*ConfigProvider\.php$/'); - // Add default config provider classes. Setting extreme priorities so - // that we are sure that the default config provider runs first and the - // other two are running at the very end. However, in some very specific - // circumstances, third-party config providers are abie to set - // priorities, either higher or lower than these, and, as an effect, - // they can override even these default config providers. - $classes = [ - DefaultConfigProvider::class => 1500, - LocalFileConfigProvider::class => -1000, - FileFromEnvironmentConfigProvider::class => -1500, - ]; - - // Discover 3rd party config providers. + // Discover config providers. foreach ($discovery->getClasses() as $class) { if (is_subclass_of($class, ConfigProviderInterface::class)) { $classes[$class] = $this->getConfigProviderPriority($class); @@ -178,15 +182,7 @@ private function createConfiguration() // High priority modifiers run first. arsort($classes, SORT_NUMERIC); - foreach (array_keys($classes) as $class) { - $class::provide($config); - } - - // Resolve variables and import into config. - $processor = (new ConfigProcessor())->add($config->export()); - $this->config->import($processor->export()); - // Keep the container in sync. - $this->container->share('config', $this->config); + return array_keys($classes); } /** diff --git a/src/ConfigProviders/DefaultConfigProvider.php b/src/TaskRunner/ConfigProviders/DefaultConfigProvider.php similarity index 61% rename from src/ConfigProviders/DefaultConfigProvider.php rename to src/TaskRunner/ConfigProviders/DefaultConfigProvider.php index b6482c2a..f1483dca 100644 --- a/src/ConfigProviders/DefaultConfigProvider.php +++ b/src/TaskRunner/ConfigProviders/DefaultConfigProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenEuropa\TaskRunner\ConfigProviders; +namespace OpenEuropa\TaskRunner\TaskRunner\ConfigProviders; use OpenEuropa\TaskRunner\Contract\ConfigProviderInterface; use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; @@ -13,10 +13,17 @@ * * This will import the following files: * - The example configuration provided in the task runner library itself. - * - The default configuration "runner.yml.dist" shipped in the root folder of the project which uses the task runner. + * - The default configuration "runner.yml.dist" shipped in the root folder of + * the project which uses the task runner. * - * This serves as a safe default implementation which can be overridden by environment specific configuration and user - * preferences. + * This serves as a safe default implementation which can be overridden by + * environment specific configuration and user preferences. + * + * The config provider priority is very high in order to ensure that will run at + * the very beginning. However, in some very special circumstances, third-party + * config providers are abie to set priorities higher than this. + * + * @priority 1500 */ class DefaultConfigProvider implements ConfigProviderInterface { @@ -28,7 +35,7 @@ class DefaultConfigProvider implements ConfigProviderInterface public static function provide(Config $config): void { static::importFromFiles($config, [ - __DIR__.'/../../config/runner.yml', + __DIR__.'/../../../config/runner.yml', 'runner.yml.dist', ]); } diff --git a/src/ConfigProviders/FileFromEnvironmentConfigProvider.php b/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php similarity index 59% rename from src/ConfigProviders/FileFromEnvironmentConfigProvider.php rename to src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php index f4fc6066..e8cb7daa 100644 --- a/src/ConfigProviders/FileFromEnvironmentConfigProvider.php +++ b/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenEuropa\TaskRunner\ConfigProviders; +namespace OpenEuropa\TaskRunner\TaskRunner\ConfigProviders; use OpenEuropa\TaskRunner\Contract\ConfigProviderInterface; use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; @@ -11,12 +11,22 @@ /** * Provides configuration for the local environment. * - * This will look in the following locations in order, and will apply the configuration of the first found file. Any - * subsequent matches will be ignored. + * This will look in the following locations, and will apply the configuration + * of the first found file. Any subsequent matches will be ignored. * - * 1. The location specified in the OPENEUROPA_TASKRUNNER_CONFIG environment variable. - * 2. The config location following the freedesktop.org specification: $XDG_CONFIG_HOME/openeuropa/taskrunner/runner.yml - * 3. The configuration in the user's home folder: $HOME/.config/openeuropa/taskrunner/runner.yml + * 1. Location specified in the OPENEUROPA_TASKRUNNER_CONFIG environment variable. + * 2. Location following the freedesktop.org specification: + * ${XDG_CONFIG_HOME}/openeuropa/taskrunner/runner.yml + * 3. The configuration in the user's home folder: + * {$HOME}/.config/openeuropa/taskrunner/runner.yml + * + * The config provider priority is very low to make sure this config provider + * runs at the very end, being able override configurations from all other + * providers in the chain. However, in some very special circumstances, + * third-party config providers are still abie to set priorities lower than + * this, making possible to override even config provided by this plugin. + * + * @priority -1500 */ class FileFromEnvironmentConfigProvider implements ConfigProviderInterface { diff --git a/src/TaskRunner/ConfigProviders/LocalFileConfigProvider.php b/src/TaskRunner/ConfigProviders/LocalFileConfigProvider.php new file mode 100644 index 00000000..94d642ce --- /dev/null +++ b/src/TaskRunner/ConfigProviders/LocalFileConfigProvider.php @@ -0,0 +1,37 @@ + Date: Fri, 15 May 2020 10:53:55 +0300 Subject: [PATCH 16/18] ISAICP-5825: Remove stale '@package' tag from interface. --- src/Contract/ConfigProviderInterface.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Contract/ConfigProviderInterface.php b/src/Contract/ConfigProviderInterface.php index 934b8007..52048ef6 100644 --- a/src/Contract/ConfigProviderInterface.php +++ b/src/Contract/ConfigProviderInterface.php @@ -4,6 +4,7 @@ namespace OpenEuropa\TaskRunner\Contract; +use OpenEuropa\TaskRunner\Traits\ConfigFromFilesTrait; use Robo\Config\Config; /** @@ -15,8 +16,6 @@ * class should be placed in `src/TaskRunner/ConfigProviders` and will have * `Some\Namespace\TaskRunner\ConfigProviders` as namespace. * - The class name should end with the `ConfigProvider` suffix. - * - * @package OpenEuropa\TaskRunner\Contract */ interface ConfigProviderInterface { @@ -30,6 +29,8 @@ interface ConfigProviderInterface * be manipulated also directly using its methods, e.g. $config->(). * * @param \Robo\Config\Config $config + * + * @see ConfigFromFilesTrait::importFromFiles() */ public static function provide(Config $config): void; } From 2410a96bd2b5db614d6a3c7b31fc86187c6c4442 Mon Sep 17 00:00:00 2001 From: Claudiu Cristea Date: Fri, 15 May 2020 17:03:23 +0300 Subject: [PATCH 17/18] ISAICP-5825: Move the relative config path to a constant. --- src/Contract/ConfigProviderInterface.php | 5 +++++ .../FileFromEnvironmentConfigProvider.php | 9 +++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Contract/ConfigProviderInterface.php b/src/Contract/ConfigProviderInterface.php index 52048ef6..90bc3268 100644 --- a/src/Contract/ConfigProviderInterface.php +++ b/src/Contract/ConfigProviderInterface.php @@ -19,6 +19,11 @@ */ interface ConfigProviderInterface { + /** + * @var string + */ + const DEFAULT_CONFIG_LOCATION = 'openeuropa/taskrunner/runner.yml'; + /** * Adds or overrides configuration. * diff --git a/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php b/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php index e8cb7daa..4beda74b 100644 --- a/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php +++ b/src/TaskRunner/ConfigProviders/FileFromEnvironmentConfigProvider.php @@ -45,24 +45,21 @@ public static function provide(Config $config): void /** * Gets the configuration filepath from environment variables. * - * @param string $configuration_file - * The default filepath. - * * @return string|null * The local configuration file path, or null if it doesn't exist. */ - private static function getLocalConfigurationFilepath(string $configuration_file = 'openeuropa/taskrunner/runner.yml'): ?string + private static function getLocalConfigurationFilepath(): ?string { if ($config = getenv('OPENEUROPA_TASKRUNNER_CONFIG')) { return $config; } if ($config = getenv('XDG_CONFIG_HOME')) { - return $config . '/' . $configuration_file; + return $config . '/' . static::DEFAULT_CONFIG_LOCATION; } if ($home = getenv('HOME')) { - return getenv('HOME') . '/.config/' . $configuration_file; + return getenv('HOME') . '/.config/' . static::DEFAULT_CONFIG_LOCATION; } return null; From 98114fe7a7f4504c677b0e71f4425b04b4961c36 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Mon, 18 May 2020 10:52:03 +0300 Subject: [PATCH 18/18] ISAICP-5825: Update documentation. --- src/Contract/ConfigProviderInterface.php | 9 +++++++++ src/TaskRunner.php | 8 ++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Contract/ConfigProviderInterface.php b/src/Contract/ConfigProviderInterface.php index 90bc3268..86bd1989 100644 --- a/src/Contract/ConfigProviderInterface.php +++ b/src/Contract/ConfigProviderInterface.php @@ -20,6 +20,15 @@ interface ConfigProviderInterface { /** + * The relative path for an environment specific config file. + * + * If this file is found in the local environment's default configuration + * path (such as $XDG_CONFIG_HOME or $HOME/.config/) then it will + * automatically be included. + * + * Any custom config providers that read config from a hierarchical data + * storage are suggested to use this path. + * * @var string */ const DEFAULT_CONFIG_LOCATION = 'openeuropa/taskrunner/runner.yml'; diff --git a/src/TaskRunner.php b/src/TaskRunner.php index 11f4b012..d5a4afc8 100644 --- a/src/TaskRunner.php +++ b/src/TaskRunner.php @@ -149,6 +149,7 @@ private function createConfiguration() $config->set('runner.working_dir', realpath($this->workingDir)); foreach ($this->getConfigProviders() as $class) { + /** @var \OpenEuropa\TaskRunner\Contract\ConfigProviderInterface $class */ $class::provide($config); } @@ -160,10 +161,13 @@ private function createConfiguration() } /** - * Discovers all config provider classes. + * Discovers all config providers. + * + * @return string[] + * An array of fully qualified class names of available config providers. * - * @return array * @throws \ReflectionException + * Thrown if a config provider doesn't have a valid annotation. */ private function getConfigProviders(): array {