From 3c3fb0062b43fc1cac8839cf2679561f1e999745 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 2 Oct 2024 15:03:01 -0600 Subject: [PATCH] feat: add exclude-patterns to cs fixer shared workflow (#153) --- .github/workflows/code-standards.yml | 42 +++++- .github/workflows/tests.yml | 7 + .php-cs-fixer.default.php | 19 ++- composer.json | 7 +- scripts/php-tools | 32 +++++ src/Utils/Actions/RunCsFixerCommand.php | 171 ++++++++++++++++++++++++ 6 files changed, 264 insertions(+), 14 deletions(-) create mode 100755 scripts/php-tools create mode 100644 src/Utils/Actions/RunCsFixerCommand.php diff --git a/.github/workflows/code-standards.yml b/.github/workflows/code-standards.yml index 8344149..dce7ca4 100644 --- a/.github/workflows/code-standards.yml +++ b/.github/workflows/code-standards.yml @@ -34,6 +34,9 @@ on: add-rules: type: string default: "{}" + exclude-patterns: + type: string + default: "" permissions: contents: read @@ -42,23 +45,50 @@ jobs: php_code_standards: runs-on: ubuntu-latest name: PHP Code Standards + env: + CONFIG: ${{ inputs.config }} + CONFIG_PATH: ${{ inputs.path }} steps: - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.2' + # install Google Cloud Tools if a config is provided which starts with "GoogleCloudPlatform/php-tools/" + - if: ${{ startsWith(inputs.config, 'GoogleCloudPlatform/php-tools/') }} + name: Install Google Cloud Tools + run: | + BRANCH=${CONFIG#GoogleCloudPlatform/php-tools/} + composer global require google/cloud-tools:dev-${BRANCH#*@} -q + echo "CONFIG=$HOME/.composer/vendor/google/cloud-tools/${BRANCH%@*}" >> $GITHUB_ENV - name: 'Setup jq' uses: dcarbone/install-jq-action@v2 + - name: Install PHP CS Fixer + run: composer global require friendsofphp/php-cs-fixer:${{ inputs.version }} - name: Run PHP CS Fixer run: | - composer global require friendsofphp/php-cs-fixer:${{ inputs.version }} -q - CONFIG="${{ inputs.config }}" - RULES=$(echo $'${{ inputs.rules }} ${{ inputs.add-rules }}'|tr -d '\n\t\r '|jq -s '.[0] * .[1]' -crM) + # set environment variables in script + export RULES=$(echo $'${{ inputs.rules }} ${{ inputs.add-rules }}'|tr -d '\n\t\r '|jq -s '.[0] * .[1]' -crM) + export EXCLUDE_PATTERNS=$(echo $'${{ inputs.exclude-patterns }}'|tr -d '\n\t\r ') - set -x + # use config path only if EXCLUDE_PATTERN is empty + CMD_PATH=$([ "$EXCLUDE_PATTERNS" = "" ] && echo "$CONFIG_PATH" || echo "") + CONFIG_OR_RULES=$([ ! -z "$CONFIG" ] && echo "--config=$CONFIG" || echo --rules=$RULES) + + # do not fail if php-cs-fixer fails (so we can print debugging info) + set +e ~/.composer/vendor/bin/php-cs-fixer fix \ - ${{ inputs.path }} \ - $(if [ ! -z "$CONFIG" ]; then echo "--config=$CONFIG"; else echo --rules=$RULES; fi) \ + $CMD_PATH \ + $CONFIG_OR_RULES \ --dry-run --diff + + if [ "$?" -ne 0 ]; then + echo "Run this script locally by executing the following command" \ + "from the root of your ${{ github.repository }} repo:" + echo "" + echo " composer global require google/cloud-tools" + echo " ~/.composer/vendor/bin/php-tools cs-fixer ${{ github.repository }} --ref ${{ github.ref_name }}" + echo "" + exit 1 + fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 653999d..032f87f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,6 +36,13 @@ jobs: with: path: src + code-standards-with-config: + uses: ./.github/workflows/code-standards.yml + with: + path: . + config: .php-cs-fixer.default.php + exclude-patterns: '["vendor", "test", "examples", "scripts"]' + static-analysis: uses: ./.github/workflows/static-analysis.yml diff --git a/.php-cs-fixer.default.php b/.php-cs-fixer.default.php index a78af4a..2f09864 100644 --- a/.php-cs-fixer.default.php +++ b/.php-cs-fixer.default.php @@ -1,16 +1,25 @@ setRules($rules) ->setFinder( PhpCsFixer\Finder::create() - ->in(__DIR__) + ->in($configPath ?: __DIR__) + ->notPath($excludePatterns) ) ; diff --git a/composer.json b/composer.json index 08bbc4d..d0bc6aa 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,15 @@ "symfony/console": "^5.0", "symfony/filesystem": "^5.0", "symfony/process": "^5.0", + "symfony/yaml": "^5", "twig/twig": "~3.0" }, "bin": [ "src/Utils/Flex/flex_exec", "src/Utils/WordPress/wp-gae", "scripts/dump_credentials.php", - "scripts/install_test_deps.sh" + "scripts/install_test_deps.sh", + "scripts/php-tools" ], "autoload": { "psr-4": { @@ -43,7 +45,6 @@ "friendsofphp/php-cs-fixer": "^3.62", "google/cloud-dlp": "^1.10", "google/cloud-storage": "^1.33", - "google/cloud-secret-manager": "^1.12", - "symfony/yaml": "^5" + "google/cloud-secret-manager": "^1.12" } } diff --git a/scripts/php-tools b/scripts/php-tools new file mode 100755 index 0000000..0271e61 --- /dev/null +++ b/scripts/php-tools @@ -0,0 +1,32 @@ +#!/usr/bin/env php +add(new RunCsFixerCommand()); +$app->run(); diff --git a/src/Utils/Actions/RunCsFixerCommand.php b/src/Utils/Actions/RunCsFixerCommand.php new file mode 100644 index 0000000..9beb62f --- /dev/null +++ b/src/Utils/Actions/RunCsFixerCommand.php @@ -0,0 +1,171 @@ +setName('cs-fixer') + ->setDescription('Execute a command with the deployed image') + ->addArgument( + 'repo', + InputArgument::REQUIRED, + 'The name of the repo to run the CS fixer for' + ) + ->addOption( + 'workflow-file', + '', + InputOption::VALUE_REQUIRED, + 'name of the github workflow file which contains the configuration', + 'lint.yaml' + ) + ->addOption( + 'ref', + '', + InputOption::VALUE_REQUIRED, + 'The branch of the repo run the CS fixer for', + 'main' + ) + ->addOption( + 'flags', + '', + InputOption::VALUE_REQUIRED, + 'The flags to pass down to the CS fixer', + '--dry-run --diff' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $repo = $input->getArgument('repo'); + if (false === strpos($repo, '/')) { + throw new \Exception('Invalid repo name. Use the format: owner/repo'); + } + + $url = sprintf( + 'https://raw.githubusercontent.com/%s/%s/.github/workflows/%s', + $repo, + $input->getOption('ref'), + $input->getOption('workflow-file'), + ); + + $client = new Client(['http_errors' => false]); + $response = $client->request('GET', $url); + if ($response->getStatusCode() === 404) { + throw new \Exception(sprintf( + 'Failed to fetch the workflow file at "%s", maybe it doesn\'t exist? ' + . 'Try supplying the "--workflow-file" option.', + $url + )); + } + $workflow = Yaml::parse($response->getBody()); + $job = null; + + foreach ($workflow['jobs'] as $id => $workflowJob) { + if (str_contains($workflowJob['uses'] ?? '', '.github/workflows/code-standards.yml')) { + $output->writeln(sprintf('Found job "%s"', $workflowJob['name'] ?? $id)); + $job = $workflowJob; + break; + } + } + if (!$job) { + throw new \Exception('No job found for php-tools/code-standards.yaml in the workflow file'); + } + + // get the default config + $defaultWorkflow = Yaml::parse(file_get_contents(__DIR__ . '/../../../.github/workflows/code-standards.yml')); + $defaults = []; + foreach ($defaultWorkflow['on']['workflow_call']['inputs'] as $name => $inputOptions) { + $defaults[$name] = $inputOptions['default'] ?? ''; + } + $options = array_merge($defaults, $job['with']); + + if (str_starts_with($options['config'], 'GoogleCloudPlatform/php-tools/')) { + // use local file + $options['config'] = str_replace( + 'GoogleCloudPlatform/php-tools/', + __DIR__ . '/../../../', + $options['config'] + ); + // strip branch (we'll ignore it in favor of the current branch) + if (false !== $i = strpos($options['config'], '@')) { + $options['config'] = substr($options['config'], 0, $i); + } + if (!file_exists($options['config'])) { + throw new \Exception('config file not found: ' . realpath($options['config'])); + } + } + + // go through config options and set env vars accordingly + $rules = json_encode(array_merge( + json_decode($options['rules'], true), + json_decode($options['add-rules'], true) + )); + + $excludePatterns = str_replace(["\n", ' '], '', $options['exclude-patterns']); + + // use config path only if EXCLUDE_PATTERN is empty + if ($options['config']) { + // set environment variables so they're available in the CONFIG file + $env = sprintf( + 'CONFIG_PATH=%s RULES=$\'%s\' EXCLUDE_PATTERNS=$\'%s\'', + $options['path'], + $rules, + $excludePatterns, + ); + // Run command using the --config flag + $cmd = sprintf( + '%s ~/.composer/vendor/bin/php-cs-fixer fix --config=%s %s', + $env, + $options['config'], + $input->getOption('flags') + ); + } else { + // Run command using the --rules flag + $cmd = sprintf( + '~/.composer/vendor/bin/php-cs-fixer fix %s --rules=$\'%s\' %s', + $options['path'], + $rules, + $input->getOption('flags') + ); + } + + $output->writeln('Executing the following command: '); + $output->writeln(''); + $output->writeln("\t" . $cmd . ''); + $output->writeln(''); + + // @TODO use Symfony process component to run this + passthru($cmd, $resultCode); + + return $resultCode; + } +}