From b22f733f79c0db797bcd77e633f5e3c9ec443435 Mon Sep 17 00:00:00 2001 From: Sander Muller Date: Mon, 26 Oct 2026 14:08:06 +0100 Subject: [PATCH 1/2] Check the configuration cache only in the main process The parallel workers share the cache directory of the main process. A worker that sees a changed configuration clears the whole cache, and deletes files that the other workers write at the same time. --- src/DependencyInjection/RectorContainerFactory.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/DependencyInjection/RectorContainerFactory.php b/src/DependencyInjection/RectorContainerFactory.php index 0373854a1ae..601c0281a1e 100644 --- a/src/DependencyInjection/RectorContainerFactory.php +++ b/src/DependencyInjection/RectorContainerFactory.php @@ -9,6 +9,7 @@ use Rector\Config\RectorConfig; use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory; use Rector\ValueObject\Bootstrap\BootstrapConfigs; +use Symfony\Component\Console\Input\ArgvInput; final class RectorContainerFactory { @@ -18,7 +19,9 @@ public function createFromBootstrapConfigs(BootstrapConfigs $bootstrapConfigs): $mainConfigFile = $bootstrapConfigs->getMainConfigFile(); - if ($mainConfigFile !== null) { + // only the main process checks the configuration: the parallel workers share its cache directory, + // and a worker that clears the cache would delete files the other workers are writing + if ($mainConfigFile !== null && new ArgvInput()->getFirstArgument() !== 'worker') { /** @var ChangedFilesDetector $changedFilesDetector */ $changedFilesDetector = $rectorConfig->make(ChangedFilesDetector::class); $changedFilesDetector->setFirstResolvedConfigFileInfo($mainConfigFile); From 29961b1a2fd2dfe1b886e28b2d2ceec90a1ddfee Mon Sep 17 00:00:00 2001 From: Sander Muller Date: Mon, 26 Oct 2026 14:33:22 +0100 Subject: [PATCH 2/2] Run the compiled Rector twice on a small project in CI The second run must keep the cache. On 2.8.0 it did not. --- .github/workflows/compiled_rector_cache.yaml | 41 ++++++++++++++++++++ e2e/compiled-rector-cache/.gitignore | 2 + e2e/compiled-rector-cache/composer.json | 13 +++++++ e2e/compiled-rector-cache/rector.php | 12 ++++++ e2e/compiled-rector-cache/src/Customer.php | 18 +++++++++ e2e/compiled-rector-cache/src/Invoice.php | 18 +++++++++ e2e/compiled-rector-cache/src/Order.php | 18 +++++++++ 7 files changed, 122 insertions(+) create mode 100644 .github/workflows/compiled_rector_cache.yaml create mode 100644 e2e/compiled-rector-cache/.gitignore create mode 100644 e2e/compiled-rector-cache/composer.json create mode 100644 e2e/compiled-rector-cache/rector.php create mode 100644 e2e/compiled-rector-cache/src/Customer.php create mode 100644 e2e/compiled-rector-cache/src/Invoice.php create mode 100644 e2e/compiled-rector-cache/src/Order.php diff --git a/.github/workflows/compiled_rector_cache.yaml b/.github/workflows/compiled_rector_cache.yaml new file mode 100644 index 00000000000..db9363ef03f --- /dev/null +++ b/.github/workflows/compiled_rector_cache.yaml @@ -0,0 +1,41 @@ +# Runs the compiled rector/rector on a small project twice, to check that the cache survives a run. +# The compiled build is published from main, so this runs after a merge, not on a pull request. +name: Compiled Rector Cache + +on: + push: + branches: + - main + schedule: + - cron: '0 6 * * *' + workflow_dispatch: null + +jobs: + compiled_rector_cache: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v5 + + - + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + + - run: composer require --dev rector/rector:dev-main --ansi + working-directory: e2e/compiled-rector-cache + + - run: vendor/bin/rector process --dry-run --ansi + working-directory: e2e/compiled-rector-cache + + # a run that clears the cache also removes this marker + - run: touch cache/marker + working-directory: e2e/compiled-rector-cache + + - run: vendor/bin/rector process --dry-run --ansi + working-directory: e2e/compiled-rector-cache + + - run: test -f cache/marker || (echo "The second run cleared the cache" && exit 1) + working-directory: e2e/compiled-rector-cache diff --git a/e2e/compiled-rector-cache/.gitignore b/e2e/compiled-rector-cache/.gitignore new file mode 100644 index 00000000000..e2d5bf368e0 --- /dev/null +++ b/e2e/compiled-rector-cache/.gitignore @@ -0,0 +1,2 @@ +/vendor +/cache diff --git a/e2e/compiled-rector-cache/composer.json b/e2e/compiled-rector-cache/composer.json new file mode 100644 index 00000000000..bde01e54aa4 --- /dev/null +++ b/e2e/compiled-rector-cache/composer.json @@ -0,0 +1,13 @@ +{ + "require": { + "php": "^8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + } +} diff --git a/e2e/compiled-rector-cache/rector.php b/e2e/compiled-rector-cache/rector.php new file mode 100644 index 00000000000..6753e3bc537 --- /dev/null +++ b/e2e/compiled-rector-cache/rector.php @@ -0,0 +1,12 @@ +withPaths([__DIR__ . '/src']) + // the PHPUnit set puts PHPStan types in the configuration, which the cache hashes + ->withComposerBased(phpunit: true) + ->withParallel() + ->withCache(__DIR__ . '/cache'); diff --git a/e2e/compiled-rector-cache/src/Customer.php b/e2e/compiled-rector-cache/src/Customer.php new file mode 100644 index 00000000000..52f7f56c71a --- /dev/null +++ b/e2e/compiled-rector-cache/src/Customer.php @@ -0,0 +1,18 @@ +name; + } +} diff --git a/e2e/compiled-rector-cache/src/Invoice.php b/e2e/compiled-rector-cache/src/Invoice.php new file mode 100644 index 00000000000..7f333b6888b --- /dev/null +++ b/e2e/compiled-rector-cache/src/Invoice.php @@ -0,0 +1,18 @@ +name; + } +} diff --git a/e2e/compiled-rector-cache/src/Order.php b/e2e/compiled-rector-cache/src/Order.php new file mode 100644 index 00000000000..5f84c510daf --- /dev/null +++ b/e2e/compiled-rector-cache/src/Order.php @@ -0,0 +1,18 @@ +name; + } +}