Build/test tools: Cache the verified Gutenberg build across runs - #12702
Build/test tools: Cache the verified Gutenberg build across runs#12702lancewillett wants to merge 2 commits into
Conversation
Each matrix job independently streams the same ~36 MB Gutenberg archive from the GitHub Container Registry, so any one flaky stream can redden a run. Add a prepare-gutenberg reusable workflow that fetches and verifies the build once (SHA-256 and size) and uploads it as a run artifact; every PHPUnit caller consumes the shared artifact and no longer contacts the registry. The prep job runs only when a consumer runs. Test coverage is unchanged. See #65721. Props lance.willett@automattic.com
Download-once cut the per-run registry fetches to one; caching the verified archive removes that fetch on a warm hit and keeps the flaky registry off the hot path. Key the cache on the immutable source SHA and the blob SHA-256, and re-verify (SHA-256 and size) on every restore, failing closed on a mismatch. Fresh metadata resolves before every restore, so the cache can never select the build version. Write the cache only from protected-branch pushes; pull requests and forks restore only. See #65721. Props lance.willett@automattic.com
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| rm -f .ci/gutenberg/archive.tar.gz | ||
| node tools/gutenberg/download.js \ | ||
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ |
| node tools/gutenberg/download.js \ | ||
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ |
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ | ||
| --blob-size "${{ steps.metadata.outputs.blob-size }}" |
| run: | | ||
| node tools/gutenberg/download.js \ | ||
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ |
| node tools/gutenberg/download.js \ | ||
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ |
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ | ||
| --blob-size "${{ steps.metadata.outputs.blob-size }}" |
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ | ||
| --blob-size "${{ steps.metadata.outputs.blob-size }}" | ||
| echo "gutenberg-sha=${{ steps.metadata.outputs.source-sha }}" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
Pull request overview
This PR extends the existing Gutenberg build preparation work by adding cross-run caching of the verified Gutenberg archive in GitHub Actions, while preserving “fail closed” verification on every restore and keeping cache writes limited to protected-branch pushes.
Changes:
- Adds a reusable workflow that resolves immutable Gutenberg blob metadata, restores/saves a cache keyed by immutable SHAs/digests, and uploads the extracted build as a run artifact.
- Updates PHPUnit and coverage workflows to depend on the shared “prepare Gutenberg” job and to avoid re-resolving mutable tags in matrix jobs.
- Refactors the Gutenberg downloader to support metadata output, stable-archive mode (for cache), and verification/extraction semantics with tests.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/gutenberg/utils.js | Allows workflows to supply an expected Gutenberg SHA to avoid re-resolving mutable refs per matrix job. |
| tools/gutenberg/download.js | Implements metadata resolution, verified stable-archive download, retries, and safe extraction/replace behavior for caching. |
| tools/gutenberg/tests/download.test.js | Adds unit tests covering metadata outputs, archive verification, cache-archive path validation, and retry/cleanup behavior. |
| package.json | Adds test:tools to run the new Gutenberg tool tests via Node’s test runner. |
| .gitignore | Ignores the cached Gutenberg archive path within the checkout. |
| .github/workflows/test-coverage.yml | Wires coverage jobs to the reusable prepare-Gutenberg workflow and passes artifact/SHA inputs. |
| .github/workflows/reusable-prepare-gutenberg.yml | New reusable workflow that restores/saves the verified archive cache and uploads the extracted Gutenberg artifact. |
| .github/workflows/reusable-phpunit-tests-v3.yml | Adds inputs for prepared Gutenberg artifact and expected SHA; sets env to prevent mutable-tag re-resolution. |
| .github/workflows/phpunit-tests.yml | Adds a shared prepare-Gutenberg job and makes PHPUnit matrices consume its outputs/artifact. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const workflowSha = process.env.GUTENBERG_EXPECTED_SHA; | ||
| if ( workflowSha ) { | ||
| if ( ! SHA_PATTERN.test( workflowSha ) ) { | ||
| throw new Error( | ||
| `GUTENBERG_EXPECTED_SHA must be a 40-character Git SHA, received "${ workflowSha }"` | ||
| ); | ||
| } | ||
|
|
||
| return workflowSha; | ||
| } | ||
|
|
| - name: Download verified Gutenberg archive | ||
| if: ${{ steps.restore.outputs.cache-hit != 'true' }} | ||
| run: | | ||
| rm -f .ci/gutenberg/archive.tar.gz | ||
| node tools/gutenberg/download.js \ | ||
| --archive .ci/gutenberg/archive.tar.gz \ | ||
| --source-sha "${{ steps.metadata.outputs.source-sha }}" \ | ||
| --blob-sha256 "${{ steps.metadata.outputs.blob-sha256 }}" \ | ||
| --blob-size "${{ steps.metadata.outputs.blob-size }}" | ||
|
|
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
Closing to keep the flake fix minimal. #12701 (download once per run) removes the download flake by cutting ~177 GHCR fetches per run to one. Cross-run caching is a separate speed optimization, not needed for the flake, so deferring it as a possible small follow-up on top of #12701 rather than a parallel change. If revived, two things to fix from this branch: the prepare workflow verified and extracted the archive twice on a cold run, and the archive-path guard defended against input that cannot occur. |
Trac ticket: https://core.trac.wordpress.org/ticket/65721
Second of two changes on this ticket, and it builds on #12701. Because a cross-repo pull request can only target a branch that exists here, this one is opened against
trunk, so its diff carries the download-once commit as well. Review only the second commit, "Cache the verified Gutenberg build across runs." Once #12701 lands, this branch reduces to that single commit.Summary
Cache the verified Gutenberg archive across runs.
Why
Downloading once per run cut the registry fetches from every matrix job to one. Caching removes even that fetch on a warm hit, which keeps a flaky external registry off the hot path entirely rather than just narrowing the window.
Two safety properties are worth calling out. The cache is keyed on content, not on a branch or a date, so a restore either matches the exact archive the metadata asked for or is rejected. And because metadata resolves first, a stale entry can never pin CI to an old Gutenberg build. Restricting writes to protected-branch pushes keeps untrusted pull requests out of a cache that trusted runs read.
Follow up to the Gutenberg artifact-fetching work in #64393 on Trac.
Testing
actionlintpasses on the reusable workflow.npm run test:tools.Note on overlap with #12700
That pull request and this one both add
tools/gutenberg/tests/download.test.jsfrom scratch and both add atest:toolsscript topackage.json, so they conflict with each other. Suggested order is to land #12700 first; this branch then rebases so its cache tests extend that file rather than replace it. Happy to do that rebase whenever it suits the review order.Related
Stacked on the download-once change (#12701); both are Trac https://core.trac.wordpress.org/ticket/65721. This reimplements bounded download retries as part of the refactor, so it supersedes the interim retry fix (#12700, Trac https://core.trac.wordpress.org/ticket/65720). Suggested order: #12700, then #12701, then this; on the overlapping files keep this branch's version.