Keep the result cache when composer.lock changes but no package version did#6086
Conversation
…on did When the result-cache metadata differed only in the Composer lock/installed files, PackageDependencyResolver::getChangedComposerPackages() was consulted to re-seed just the files depending on a changed package. An empty result (no package version or reference changed) was treated the same as null (installed.php unparseable), so a composer.lock whose bytes changed without any package change discarded the entire cache and re-analysed every file. This is common in CI for projects that do not commit composer.lock: every job regenerates the lock, and any run-to-run difference that is not a package-version change forced a full re-analysis on top of a cache that was restored correctly. Distinguish the two: null still falls back to a full re-analysis, while an empty change set keeps the restored cache and continues with the normal incremental analysis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This pull request has been marked as ready for review. |
| jq '.["content-hash"]="0000000000000000000000000000000000000000"' composer.lock > composer.lock.new | ||
| mv composer.lock.new composer.lock | ||
| OUTPUT=$(../bashunit -a exit_code "0" "../../bin/phpstan analyse -vv") | ||
| echo "$OUTPUT" | ||
| ../bashunit -a contains 'Composer metadata changed but no package versions changed; keeping the result cache.' "$OUTPUT" | ||
| ../bashunit -a contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT" |
There was a problem hiding this comment.
please add another test in which we change the content hash and in addition a single php file.
after the test we should assert 'Result cache restored. 1 files will be reanalysed.'
There was a problem hiding this comment.
do we have a test which verifies the cache gets invalidated, after we updated a single package?
There was a problem hiding this comment.
Done in a82d77d:
result-cache-composer-locknow also changes the content hash together with a single source file and assertsResult cache restored. 1 file will be reanalysed.(PHPStan prints1 filesingular at count 1). So the kept cache still reanalyses genuinely-changed files.- Added
result-cache-package-update: a source file depends on psr/log, and changing the package's reference invendor/composer/installed.phpinvalidates only the files depending on it (Composer packages changed (psr/log); re-analysing only the files depending on them.+1 file will be reanalysed.). The package-change path was previously only covered byPackageDependencyResolverTestat the unit level.
|
were you able to test this on the tempest repo and verified the fix will make the result-cache work for them in PRs after the 1st commit? |
…t-cache e2e Addresses review feedback on the composer-lock result cache tests: - result-cache-composer-lock: after the lock-only change (cache kept, 0 files), also change the content hash together with a single source file and assert "Result cache restored. 1 file will be reanalysed." so the kept cache still reanalyses genuinely changed files. - result-cache-package-update: a new e2e where a source file depends on psr/log and the package's recorded reference in vendor/composer/installed.php changes. Asserts the cache is invalidated only for files depending on that package (the package-granular path), not for the whole project. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Yes. I built a phar from this branch and ran it against the Tempest repo with its real
One honest caveat on when this actually helps Tempest, since I looked into it: two back-to-back |
they already use a (outdated) but pinned PHPStan version, I think? |
| @@ -0,0 +1,18 @@ | |||
| { | |||
There was a problem hiding this comment.
the initial reproducer (tempest) does not contain a composer.lock. I think our tests shouldn't have one either to reflect the real world?
(should be gitignored)
There was a problem hiding this comment.
Good point, done in 9ce1fc9. Both fixtures now gitignore composer.lock instead of committing it; the e2e step's composer install regenerates it from composer.json before the test runs. I checked that composer install with no lock writes one, so the content-hash churn and the installed.php reference edit both still work.
The real-world case that motivated this (a framework whose CI regenerates the lock every job) gitignores composer.lock, so the fixtures shouldn't ship one either. `composer install` in the e2e step regenerates the lock from composer.json before the test runs, so the reproduction is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
You're right, thanks, scratch that. They pin |
| # Change composer.lock without changing any installed package version/reference | ||
| # (vendor/composer/installed.php is left untouched), like a regenerated lock in CI | ||
| # when composer.lock is not committed. The result cache must be kept, not discarded. | ||
| jq '.["content-hash"]="0000000000000000000000000000000000000000"' composer.lock > composer.lock.new |
There was a problem hiding this comment.
please also update these tests to use a patch file instead
Follow-up to phpstan/phpstan#14984, reported after a discussion with @staabm about result-cache misses on CI for projects that do not commit
composer.lock.When the result-cache metadata differs only in the Composer lock/installed files,
ResultCacheManagerasksPackageDependencyResolver::getChangedComposerPackages()which packages changed and re-seeds only the files depending on them. An empty result (no installed package version or reference changed) was treated identically tonull(installed.php could not be parsed), so both discarded the whole cache and re-analysed every file.That empty case is exactly the situation the surrounding comment already calls safe: the generated container and the analysis are unchanged when no package version changed. It is easy to hit in CI where
composer.lockis not committed, because every job regenerates the lock, and any run-to-run difference that is not a package-version change (a different Composer patch version,dist/timemetadata, or acontent-hashchange from an unrelatedcomposer.jsonedit) flips thecomposerLockshash and triggers a full re-analysis on top of a cache that was restored correctly.This distinguishes the two cases:
null(undetermined, installed.php unparseable) still falls back to a full re-analysis.[](determined: nothing changed) keeps the restored cache and continues with the normal incremental analysis.Added an e2e test (
result-cache-composer-lock) that analyses cold, changescomposer.lockwithout touchingvendor/composer/installed.php, and asserts the cache is kept and no files are reanalysed. It fails before this change (full re-analysis,metadata do not match: composerLocks) and passes after.Closes phpstan/phpstan#14984