From d2a9ab711015bbfcec53ca08469f2275c67dfeb1 Mon Sep 17 00:00:00 2001 From: Joao Silva Date: Wed, 1 Mar 2023 09:09:07 +0100 Subject: [PATCH 1/6] DQA-0: Test config. --- tests/fixtures/commands/configuration.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/fixtures/commands/configuration.yml b/tests/fixtures/commands/configuration.yml index b461b9278..311a8f1a2 100644 --- a/tests/fixtures/commands/configuration.yml +++ b/tests/fixtures/commands/configuration.yml @@ -318,3 +318,19 @@ expectations: - string_contains: "root: build" - string_contains: "base_url: 'http://web:8080'" + +- command: 'config action' + configuration: [] + resources: + - file: runner.yml.dist + content: | + color: red + action: 'Color is ${color}' + - file: runner.yml + content: | + color: yellow + - file: config/runner/colors.yml + content: | + color: blue + expectations: + - string_contains: "Color is yellow" From f1b64becd79ff8d8bda77935f6ffcd80c8683629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Jo=C3=A3o=20Santos?= Date: Wed, 1 Mar 2023 09:56:29 +0100 Subject: [PATCH 2/6] Update configuration.yml --- tests/fixtures/commands/configuration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fixtures/commands/configuration.yml b/tests/fixtures/commands/configuration.yml index 311a8f1a2..7cc492e3d 100644 --- a/tests/fixtures/commands/configuration.yml +++ b/tests/fixtures/commands/configuration.yml @@ -329,8 +329,10 @@ - file: runner.yml content: | color: yellow + action: 'Color is ${color}' - file: config/runner/colors.yml content: | color: blue + action: 'Color is ${color}' expectations: - string_contains: "Color is yellow" From 845f7c6f288e8720ed3053bd10918f885a280658 Mon Sep 17 00:00:00 2001 From: Joao Silva Date: Wed, 1 Mar 2023 20:08:12 +0100 Subject: [PATCH 3/6] Revert "Update configuration.yml" This reverts commit f1b64becd79ff8d8bda77935f6ffcd80c8683629. --- tests/fixtures/commands/configuration.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/fixtures/commands/configuration.yml b/tests/fixtures/commands/configuration.yml index 7cc492e3d..311a8f1a2 100644 --- a/tests/fixtures/commands/configuration.yml +++ b/tests/fixtures/commands/configuration.yml @@ -329,10 +329,8 @@ - file: runner.yml content: | color: yellow - action: 'Color is ${color}' - file: config/runner/colors.yml content: | color: blue - action: 'Color is ${color}' expectations: - string_contains: "Color is yellow" From b0032cc9b2a6314f7f4b85c262747400cb747b74 Mon Sep 17 00:00:00 2001 From: Joao Silva Date: Thu, 2 Mar 2023 09:13:40 +0100 Subject: [PATCH 4/6] DQA-0: Refactor Runner prepare config. --- config/base.yml | 3 - config/default.yml | 2 + src/TaskRunner/Runner.php | 156 +++++++++------------- tests/fixtures/commands/configuration.yml | 26 ++++ 4 files changed, 91 insertions(+), 96 deletions(-) diff --git a/config/base.yml b/config/base.yml index 067cfb18c..e69de29bb 100644 --- a/config/base.yml +++ b/config/base.yml @@ -1,3 +0,0 @@ -runner: - bin_dir: './vendor/bin' - bin_node_dir: './node_modules/.bin' diff --git a/config/default.yml b/config/default.yml index 80d25b222..e971af757 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,6 +1,8 @@ runner: bin_dir: './vendor/bin' bin_node_dir: './node_modules/.bin' + # This property cannot contain a reference to a variable as it will + # not be processed before the load of configurations in given directory. config_dir: './config/runner' symlink_project: ignore: [ '.idea', 'vendor' ] diff --git a/src/TaskRunner/Runner.php b/src/TaskRunner/Runner.php index f3ebd9930..458cfbb7f 100644 --- a/src/TaskRunner/Runner.php +++ b/src/TaskRunner/Runner.php @@ -5,12 +5,11 @@ namespace EcEuropa\Toolkit\TaskRunner; use Composer\Autoload\ClassLoader; -use Consolidation\Config\ConfigInterface; -use Consolidation\Config\Loader\ConfigProcessor; -use Consolidation\Config\Util\ConfigOverlay; +use Dflydev\DotAccessData\Data; use EcEuropa\Toolkit\TaskRunner\Commands\ConfigurationCommands; use EcEuropa\Toolkit\TaskRunner\Inject\ConfigForCommand; use EcEuropa\Toolkit\Toolkit; +use Grasmash\Expander\Expander; use League\Container\Container; use Psr\Container\ContainerInterface; use Robo\Application; @@ -22,6 +21,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Yaml\Yaml; /** * Toolkit Runner. @@ -90,7 +90,7 @@ class Runner * * @var string[] */ - private $overrides = [ + private array $overrides = [ 'toolkit.build.dist.keep', 'toolkit.test.phpcs.standards', 'toolkit.test.phpcs.ignore_patterns', @@ -198,6 +198,26 @@ private function prepareApplication() return $this; } + /** + * Recursively merge config files. + * + * @param array $files + * The file paths to fetch the configs. + * @param array|null $config + * The given, the new configs will be merged. + */ + private function parseConfigFiles(array $files, array $config = null): array + { + $config = $config ?? []; + foreach ($files as $file) { + $content = Yaml::parseFile($file); + if (!empty($content) && is_array($content)) { + $config = array_replace_recursive($config, $content); + } + } + return $config; + } + /** * Create the configurations and process overrides. * @@ -206,41 +226,50 @@ private function prepareApplication() private function prepareConfigurations() { $workingDir = realpath($this->workingDir); - // Load Toolkit default configuration. - $defaultConfig = Robo::createConfiguration([Toolkit::getToolkitRoot() . '/config/default.yml']); - $defaultConfig->set('runner.working_dir', $workingDir); - $this->loadConfigurationFromDirFiles($defaultConfig); - - // Re-build configuration. - $context = $defaultConfig->getContext(ConfigOverlay::DEFAULT_CONTEXT); - - $processor = new ConfigProcessor(); - $processor->add($defaultConfig->export()); - - $currentConfig = $this->getCurrentConfig($workingDir, $defaultConfig); - if (isset($currentConfig)) { - // Allow some configurations to be overridden. If a given property is - // defined on a project level it will replace the default values - // instead of merge. - foreach ($this->overrides as $override) { - if ($value = $currentConfig->get($override)) { - $context->set($override, $value); - } - } + // Load the Toolkit default configurations. + $config = $this->parseConfigFiles([Toolkit::getToolkitRoot() . '/config/default.yml']); + $config['runner']['working_dir'] = $workingDir; + $tkConfigDir = Toolkit::getToolkitRoot() . '/' . $config['runner']['config_dir']; + $files = $this->getConfigDirFilesPaths($tkConfigDir); + $config = $this->parseConfigFiles($files, $config); + + // Save the project configurations separately to allow the overrides. + $projectConfig = []; + // Load the Project configuration. + if (file_exists($workingDir . '/runner.yml.dist')) { + $projectConfig = $this->parseConfigFiles([$workingDir . '/runner.yml.dist']); + } - $processor->add($currentConfig->export()); + // Check if the project has dynamic configs. + $projectConfigDir = $workingDir . '/' . ($projectConfig['runner']['config_dir'] ?? $config['runner']['config_dir']); + if ($tkConfigDir !== $projectConfigDir) { + if (!empty($files = $this->getConfigDirFilesPaths($projectConfigDir))) { + $projectConfig = $this->parseConfigFiles($files, $projectConfig); + } } - // Allow runner.yml to override configurations. Is recommended to keep - // this out of VCS control to allow local config customizations. + // Usually the runner.yml is used for local development and is not committed + // to the repo, load it as last so it can override values properly. if (file_exists($workingDir . '/runner.yml')) { - $configOverride = new Config(); - Robo::loadConfiguration([$workingDir . '/runner.yml'], $configOverride); - $processor->add($configOverride->export()); + $projectConfig = $this->parseConfigFiles([$workingDir . '/runner.yml'], $projectConfig); } - // Import newly built configuration. - $this->config->replace($processor->export()); + // Merge the toolkit and project configurations. + $config = array_replace_recursive($config, $projectConfig); + + $expander = new Expander(); + $result = $expander->expandArrayProperties($config); + $this->config->replace($result); + + // Allow some configurations to be overridden. If a given property is + // defined on a project level it will replace the default values + // instead of merge. + $projectConfigLoaded = new Data($projectConfig); + foreach ($this->overrides as $override) { + if ($value = $projectConfigLoaded->get($override, null)) { + $this->config->set($override, $value); + } + } return $this; } @@ -344,37 +373,6 @@ private function registerConfigurationCommands() } } - /** - * Get current configurations. - * - * @param string $workingDir - * @param ConfigInterface $defaultConfig - * - * @return ConfigInterface|null - */ - private function getCurrentConfig(string $workingDir, ConfigInterface $defaultConfig): ?ConfigInterface - { - $configFile = ''; - if (file_exists($workingDir . '/runner.yml.dist')) { - $configFile = $workingDir . '/runner.yml.dist'; - } - - $defaultConfigDir = $defaultConfig->get(self::CONFIG_DIR_KEY); - $configDirFilesPaths = $this->getConfigDirFilesPaths((string) realpath($defaultConfigDir)); - if (empty($configFile) && empty($configDirFilesPaths)) { - return null; - } - - if (!empty($configFile)) { - $currentConfig = Robo::createConfiguration([realpath($configFile)]); - $this->loadConfigurationFromDirFiles($currentConfig, false, $defaultConfigDir); - - return $currentConfig; - } - - return Robo::createConfiguration($configDirFilesPaths); - } - /** * Get runner config directory files. * @@ -384,35 +382,7 @@ private function getCurrentConfig(string $workingDir, ConfigInterface $defaultCo */ private function getConfigDirFilesPaths(string $runnerConfigDir): array { - return glob($runnerConfigDir . '/*.yml'); - } - - /** - * Load configuration from dir files. - * - * @param ConfigInterface $config - * @param bool $isToolkitRoot - * @param string|null $fallbackConfigDir - * - * @return void - */ - private function loadConfigurationFromDirFiles(ConfigInterface $config, bool $isToolkitRoot = true, ?string $fallbackConfigDir = null): void - { - $configDir = $config->get(self::CONFIG_DIR_KEY, $fallbackConfigDir); - if (empty($configDir)) { - return; - } - - $configDir = $isToolkitRoot - ? Toolkit::getToolkitRoot() . '/' . $configDir - : realpath($configDir); - - $configFilesPaths = $this->getConfigDirFilesPaths((string) $configDir); - if (empty($configFilesPaths)) { - return; - } - - Robo::loadConfiguration($configFilesPaths, $config); + return glob($runnerConfigDir . '/*.yml') ?? []; } } diff --git a/tests/fixtures/commands/configuration.yml b/tests/fixtures/commands/configuration.yml index 311a8f1a2..90507e25e 100644 --- a/tests/fixtures/commands/configuration.yml +++ b/tests/fixtures/commands/configuration.yml @@ -334,3 +334,29 @@ color: blue expectations: - string_contains: "Color is yellow" + +- command: 'config toolkit.test.phpcs.files' + configuration: [] + resources: [] + expectations: + - contains: | + - ./lib + - ./resources + - ./src + +- command: 'config toolkit.test.phpcs.files' + configuration: [] + resources: + - file: runner.yml.dist + content: | + toolkit: + test: + phpcs: + files: + - file1 + - file2 + expectations: + - contains: | + - file1 + - file2 + - not_string_contains: src From bc177e410007589937e2846450847dc85fe716b9 Mon Sep 17 00:00:00 2001 From: Joao Silva Date: Thu, 2 Mar 2023 09:52:57 +0100 Subject: [PATCH 5/6] DQA-0: Allow hook init to set properties. --- src/TaskRunner/Runner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TaskRunner/Runner.php b/src/TaskRunner/Runner.php index 458cfbb7f..91cfe9ad3 100644 --- a/src/TaskRunner/Runner.php +++ b/src/TaskRunner/Runner.php @@ -135,7 +135,6 @@ public function __construct(ClassLoader $classLoader, InputInterface $input, Out // Create application. $this ->prepareApplication() - ->prepareConfigurations() ->prepareContainer() ->prepareRunner(); } @@ -150,6 +149,7 @@ public function run() { $classes = $this->discoverCommandClasses(); $this->runner->registerCommandClasses($this->application, $classes); + $this->prepareConfigurations(); $this->registerConfigurationCommands(); return $this->runner->run($this->input, $this->output, $this->application); } @@ -237,7 +237,7 @@ private function prepareConfigurations() $projectConfig = []; // Load the Project configuration. if (file_exists($workingDir . '/runner.yml.dist')) { - $projectConfig = $this->parseConfigFiles([$workingDir . '/runner.yml.dist']); + $projectConfig = $this->parseConfigFiles([$workingDir . '/runner.yml.dist'], $projectConfig); } // Check if the project has dynamic configs. From ee88abb3f9436564eb8641fe2d9f42bc348a8351 Mon Sep 17 00:00:00 2001 From: Joao Silva Date: Thu, 2 Mar 2023 16:12:07 +0100 Subject: [PATCH 6/6] DQA-0: Cover command options configs. --- resources/git/hooks/pre-commit | 2 +- src/TaskRunner/AbstractCommands.php | 10 ----- src/TaskRunner/Runner.php | 18 +++++++-- src/Toolkit.php | 4 +- tests/fixtures/commands/configuration.yml | 46 +++++++++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/resources/git/hooks/pre-commit b/resources/git/hooks/pre-commit index 6c3421f66..c2d6e636b 100755 --- a/resources/git/hooks/pre-commit +++ b/resources/git/hooks/pre-commit @@ -3,7 +3,7 @@ # Called by "git commit" with no arguments. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the commit. # -# This hook should execute the PHPcs agaisnt the modified files. +# This hook should execute the PHPcs against the modified files. # ./vendor/bin/run toolkit:hooks-run `basename "$0"` diff --git a/src/TaskRunner/AbstractCommands.php b/src/TaskRunner/AbstractCommands.php index a708e1c49..2cb977b73 100644 --- a/src/TaskRunner/AbstractCommands.php +++ b/src/TaskRunner/AbstractCommands.php @@ -32,16 +32,6 @@ public function getConfigurationFile() return Toolkit::getToolkitRoot() . '/config/base.yml'; } - /** - * Command initialization. - * - * @hook pre-command-event * - */ - public function initializeRuntimeConfiguration() - { - Robo::loadConfiguration([$this->getConfigurationFile()], $this->getConfig()); - } - /** * Validate and return the path to given bin. * diff --git a/src/TaskRunner/Runner.php b/src/TaskRunner/Runner.php index 91cfe9ad3..b9cf296b7 100644 --- a/src/TaskRunner/Runner.php +++ b/src/TaskRunner/Runner.php @@ -34,7 +34,6 @@ class Runner public const APPLICATION_NAME = 'Toolkit Runner'; public const REPOSITORY = 'ec-europa/toolkit'; - public const CONFIG_DIR_KEY = 'runner.config_dir'; /** * The input. @@ -85,6 +84,13 @@ class Runner */ private $workingDir; + /** + * The loaded command classes. + * + * @var array + */ + private array $commandClasses; + /** * Configurations that can be replaced by a project. * @@ -147,8 +153,8 @@ public function __construct(ClassLoader $classLoader, InputInterface $input, Out */ public function run() { - $classes = $this->discoverCommandClasses(); - $this->runner->registerCommandClasses($this->application, $classes); + $this->commandClasses = $this->discoverCommandClasses(); + $this->runner->registerCommandClasses($this->application, $this->commandClasses); $this->prepareConfigurations(); $this->registerConfigurationCommands(); return $this->runner->run($this->input, $this->output, $this->application); @@ -233,6 +239,12 @@ private function prepareConfigurations() $files = $this->getConfigDirFilesPaths($tkConfigDir); $config = $this->parseConfigFiles($files, $config); + // Get the command options configurations from loaded command classes. + foreach ($this->commandClasses as $commandClass) { + $f = $this->runner->getContainer()->get("{$commandClass}Commands")->getConfigurationFile() ?? ''; + $config = $this->parseConfigFiles([$f], $config); + } + // Save the project configurations separately to allow the overrides. $projectConfig = []; // Load the Project configuration. diff --git a/src/Toolkit.php b/src/Toolkit.php index fe09ad323..67dcad019 100644 --- a/src/Toolkit.php +++ b/src/Toolkit.php @@ -105,9 +105,9 @@ public static function getNextcloudPass(): string */ public static function filterFolders(array &$files) { - $files = array_filter($files, function ($folder) { + $files = array_values(array_filter($files, function ($folder) { return file_exists($folder); - }); + })); } /** diff --git a/tests/fixtures/commands/configuration.yml b/tests/fixtures/commands/configuration.yml index 90507e25e..25ee0295d 100644 --- a/tests/fixtures/commands/configuration.yml +++ b/tests/fixtures/commands/configuration.yml @@ -360,3 +360,49 @@ - file1 - file2 - not_string_contains: src + +- command: 'toolkit:test-behat --simulate' + configuration: [] + resources: + - file: runner.yml.dist + content: | + toolkit: + test: + behat: + from: test-behat.yml.dist + expectations: + - contains: | + [Simulator] Simulating Exec('./vendor/bin/run toolkit:install-dependencies') + [Simulator] Running ./vendor/bin/run toolkit:install-dependencies + [Simulator] Simulating EcEuropa\Toolkit\Task\File\Process('test-behat.yml.dist', 'behat.yml') + [Simulator] Running ./vendor/bin/behat --profile=default --strict --dry-run + [Simulator] Simulating Exec('./vendor/bin/behat') + ->options(array ( + 'profile' => 'default', + 'strict' => NULL, + ), '=') + [Simulator] Running ./vendor/bin/behat --profile=default --strict + +- command: 'toolkit:test-behat --simulate' + configuration: [] + resources: + - touch: test-behat.yml.dist + - file: runner.yml.dist + content: | + command: + toolkit: + test-behat: + options: + from: test-behat.yml.dist + expectations: + - contains: | + [Simulator] Simulating Exec('./vendor/bin/run toolkit:install-dependencies') + [Simulator] Running ./vendor/bin/run toolkit:install-dependencies + [Simulator] Simulating EcEuropa\Toolkit\Task\File\Process('test-behat.yml.dist', 'behat.yml') + [Simulator] Running ./vendor/bin/behat --profile=default --strict --dry-run + [Simulator] Simulating Exec('./vendor/bin/behat') + ->options(array ( + 'profile' => 'default', + 'strict' => NULL, + ), '=') + [Simulator] Running ./vendor/bin/behat --profile=default --strict