From bb73f497bf716194b57ff716d70678489985e483 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Fri, 26 Apr 2024 14:55:32 +0200 Subject: [PATCH] Update (#68) * Add Castor tasks and update Infection workflow This commit introduces a new Castor PHP script with different tasks for test execution, mutation testing, coding standards check, PHPStan, and others. The Infection GitHub workflow was also updated to use the new Castor 'infect' task instead of 'make ci-mu'. The 'Infection' workflow name was also corrected. * Refactor castor validation and integrate tasks in workflows The code refactors the validation of the composer configuration and autoload dumping in the 'castor.php' file. It also modifies the '.github/workflows/integrate.yml' to replace previous tasks with equivalent 'castor' commands for validation, testing, static analysis, code style checking, Deptrac analysis, and make rector. Additionally, '.castor.stub.php' is added to the '.gitignore' file to prevent it from being tracked. * Add license check functionality to castor A new function has been introduced in `castor.php` to check licenses and ensure compliance with defined acceptable licenses. This function has also been integrated into the CI process via the `integrate.yml` GitHub workflow file. It checks license compliance for each dependency before a pull request is merged. --- .gitattributes | 3 +- .github/workflows/infection.yml | 5 +- .github/workflows/integrate.yml | 46 +++++-- .gitignore | 1 + castor.php | 178 ++++++++++++++++++++++++++ ecs.php | 8 +- infection.json.dist => infection.json | 0 7 files changed, 227 insertions(+), 14 deletions(-) create mode 100644 castor.php rename infection.json.dist => infection.json (100%) diff --git a/.gitattributes b/.gitattributes index 06f47c0..b67aae4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,9 +8,10 @@ /CODE_OF_CONDUCT.md export-ignore /deptrac.yaml export-ignore /ecs.php export-ignore -/infection.json.dist export-ignore +/infection.json export-ignore /Makefile export-ignore /phpstan.neon export-ignore /phpstan-baseline.neon export-ignore /phpunit.xml.dist export-ignore /rector.php export-ignore +/castor.php export-ignore diff --git a/.github/workflows/infection.yml b/.github/workflows/infection.yml index edf763c..4747edd 100644 --- a/.github/workflows/infection.yml +++ b/.github/workflows/infection.yml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow -name: "Integrate" +name: "Infection" on: push: @@ -18,6 +18,7 @@ jobs: php-version: "8.3" extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" coverage: "xdebug" + tools: castor - name: "Checkout code" uses: "actions/checkout@v3" @@ -32,4 +33,4 @@ jobs: composer-options: "--optimize-autoloader" - name: "Execute Infection" - run: "make ci-mu" + run: "castor infect --ci" diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index 2b186f2..baae9ba 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -68,6 +68,7 @@ jobs: with: php-version: "${{ matrix.php-version }}" extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + tools: castor coverage: "xdebug" - name: "Checkout code" @@ -80,7 +81,7 @@ jobs: composer-options: "--optimize-autoloader" - name: "Execute tests (PHP)" - run: "make ci-cc" + run: "castor test --coverage-text" # - name: Send coverage to Coveralls # if: "matrix.php-version == '8.1' && matrix.dependencies == 'highest'" @@ -102,13 +103,14 @@ jobs: with: php-version: "8.3" extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + tools: castor coverage: "none" - name: "Checkout code" uses: "actions/checkout@v3" - name: "Validate Composer configuration" - run: "composer validate --strict" + run: "castor validate" - name: "Install dependencies" uses: "ramsey/composer-install@v3" @@ -116,11 +118,8 @@ jobs: dependency-versions: "highest" composer-options: "--optimize-autoloader" - - name: "Check PSR-4 mapping" - run: "composer dump-autoload --optimize --strict-psr" - - name: "Execute static analysis" - run: "make st" + run: "castor stan" coding_standards: name: "4️⃣ Coding Standards" @@ -134,6 +133,7 @@ jobs: with: php-version: "8.3" extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + tools: castor coverage: "none" - name: "Checkout code" @@ -146,11 +146,36 @@ jobs: composer-options: "--optimize-autoloader" - name: "Check coding style" - run: "make ci-cs" + run: "castor cs" - name: "Deptrac" - run: | - vendor/bin/deptrac analyse --fail-on-uncovered --no-cache + run: 'castor deptrac' + + check_licenses: + name: "5️⃣ Check Licenses" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.3" + extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + tools: castor + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v3" + with: + dependency-versions: "highest" + composer-options: "--optimize-autoloader" + + - name: "Execute license check" + run: "castor check-licenses" rector_checkstyle: name: "6️⃣ Rector Checkstyle" @@ -164,6 +189,7 @@ jobs: with: php-version: "8.3" extensions: "ctype, dom, json, libxml, mbstring, openssl, phar, simplexml, tokenizer, xml, xmlwriter" + tools: castor coverage: "xdebug" - name: "Checkout code" @@ -179,7 +205,7 @@ jobs: composer-options: "--optimize-autoloader" - name: "Execute Rector" - run: "make rector" + run: "castor rector" exported_files: name: "7️⃣ Exported files" diff --git a/.gitignore b/.gitignore index 8da2f5c..ec1249c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ yarn-error.log /composer.lock /vendor infection.txt +/.castor.stub.php diff --git a/castor.php b/castor.php new file mode 100644 index 0000000..8123e27 --- /dev/null +++ b/castor.php @@ -0,0 +1,178 @@ +title('Running infection'); + $nproc = run('nproc', quiet: true); + if (! $nproc->isSuccessful()) { + io()->error('Cannot determine the number of processors'); + return; + } + $threads = (int) $nproc->getOutput(); + $command = [ + 'php', + 'vendor/bin/infection', + sprintf('--min-msi=%s', $minMsi), + sprintf('--min-covered-msi=%s', $minCoveredMsi), + sprintf('--threads=%s', $threads), + ]; + if ($ci) { + $command[] = '--logger-github'; + $command[] = '-s'; + } + $environment = [ + 'XDEBUG_MODE' => 'coverage', + ]; + run($command, environment: $environment); +} + +#[AsTask(description: 'Run tests')] +function test(bool $coverageHtml = false, bool $coverageText = false, null|string $group = null): void +{ + io()->title('Running tests'); + $command = ['php', 'vendor/bin/phpunit', '--color']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + if ($coverageHtml) { + $command[] = '--coverage-html=build/coverage'; + $environment['XDEBUG_MODE'] = 'coverage'; + } + if ($coverageText) { + $command[] = '--coverage-text'; + $environment['XDEBUG_MODE'] = 'coverage'; + } + if ($group !== null) { + $command[] = sprintf('--group=%s', $group); + } + run($command, environment: $environment); +} + +#[AsTask(description: 'Coding standards check')] +function cs(bool $fix = false): void +{ + io()->title('Running coding standards check'); + $command = ['php', 'vendor/bin/ecs', 'check']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + if ($fix) { + $command[] = '--fix'; + } + run($command, environment: $environment); +} + +#[AsTask(description: 'Running PHPStan')] +function stan(): void +{ + io()->title('Running PHPStan'); + $command = ['php', 'vendor/bin/phpstan', 'analyse']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + run($command, environment: $environment); +} + +#[AsTask(description: 'Validate Composer configuration')] +function validate(): void +{ + io()->title('Validating Composer configuration'); + $command = ['composer', 'validate', '--strict']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + run($command, environment: $environment); + + $command = ['composer', 'dump-autoload', '--optimize', '--strict-psr']; + run($command, environment: $environment); +} + +/** + * @param array $allowedLicenses + */ +#[AsTask(description: 'Check licenses')] +function checkLicenses( + array $allowedLicenses = ['Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'MIT', 'MPL-2.0', 'OSL-3.0'] +): void { + io()->title('Checking licenses'); + $allowedExceptions = []; + $command = ['composer', 'licenses', '-f', 'json']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + $result = run($command, environment: $environment, quiet: true); + if (! $result->isSuccessful()) { + io()->error('Cannot determine licenses'); + exit(1); + } + $licenses = json_decode($result->getOutput(), true); + $disallowed = array_filter( + $licenses['dependencies'], + static fn (array $info, $name) => ! in_array($name, $allowedExceptions, true) + && count(array_diff($info['license'], $allowedLicenses)) === 1, + \ARRAY_FILTER_USE_BOTH + ); + $allowed = array_filter( + $licenses['dependencies'], + static fn (array $info, $name) => in_array($name, $allowedExceptions, true) + || count(array_diff($info['license'], $allowedLicenses)) === 0, + \ARRAY_FILTER_USE_BOTH + ); + if (count($disallowed) > 0) { + io() + ->table( + ['Package', 'License'], + array_map( + static fn ($name, $info) => [$name, implode(', ', $info['license'])], + array_keys($disallowed), + $disallowed + ) + ); + io() + ->error('Disallowed licenses found'); + exit(1); + } + io() + ->table( + ['Package', 'License'], + array_map( + static fn ($name, $info) => [$name, implode(', ', $info['license'])], + array_keys($allowed), + $allowed + ) + ); + io() + ->success('All licenses are allowed'); +} + +#[AsTask(description: 'Run Rector')] +function rector(bool $fix = false): void +{ + io()->title('Running Rector'); + $command = ['php', 'vendor/bin/rector', 'process', '--ansi']; + if (! $fix) { + $command[] = '--dry-run'; + } + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + run($command, environment: $environment); +} + +#[AsTask(description: 'Run Rector')] +function deptrac(): void +{ + io()->title('Running Rector'); + $command = ['php', 'vendor/bin/deptrac', 'analyse', '--fail-on-uncovered', '--no-cache']; + $environment = [ + 'XDEBUG_MODE' => 'off', + ]; + run($command, environment: $environment); +} diff --git a/ecs.php b/ecs.php index 2d4767b..233deaf 100644 --- a/ecs.php +++ b/ecs.php @@ -87,6 +87,12 @@ $config->parallel(); $config->paths([__DIR__]); $config->skip( - [__DIR__ . '/.github', __DIR__ . '/build', __DIR__ . '/vendor', PhpUnitTestClassRequiresCoversFixer::class] + [ + __DIR__ . '/.github', + __DIR__ . '/build', + __DIR__ . '/vendor', + __DIR__ . '/.castor.stub.php', + PhpUnitTestClassRequiresCoversFixer::class, + ] ); }; diff --git a/infection.json.dist b/infection.json similarity index 100% rename from infection.json.dist rename to infection.json