Skip to content

Commit 5d279ed

Browse files
Add publishing handoff of cached results from merge-queue to main []
1 parent 9bc2ed3 commit 5d279ed

3 files changed

Lines changed: 122 additions & 1 deletion

File tree

‎.github/workflows/build-and-test-ci.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
# the coverage report, with its line numbers moved to match the sources
3939
# being tested (.github/scripts/remap-gcovr-lines.py).
4040
# actions/cache scopes entries to the ref that saved them (plus the default
41-
# branch), so one pull request can never vouch for another's build.
41+
# branch), so one pull request can never vouch for another's build. The
42+
# merge queue's entries are saved again for main by promote-test-cache.yml
43+
# once the commit they tested lands, so later pull requests can reuse them.
4244
#
4345
# The bench job runs the cachegrind benchmarks the way
4446
# `edg-docker-bench --posture=release` does and uploads them as bench-results

‎.github/workflows/ci-toolchain.yml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ jobs:
342342
RUN_ID: ${{ github.run_id }}
343343
RUN_ATTEMPT: ${{ github.run_attempt }}
344344
TESTED_SHA: ${{ github.sha }}
345+
CACHE_KEY: ${{ steps.key.outputs.key }}
345346
run: |
346347
mkdir -p .tmp/tested/coverage .tmp/tested/sources
347348
cp "coverage/${CONFIG}.json" .tmp/tested/coverage/
@@ -355,6 +356,7 @@ jobs:
355356
'tested_sha': os.environ['TESTED_SHA'],
356357
'run_id': int(os.environ['RUN_ID']),
357358
'run_attempt': int(os.environ['RUN_ATTEMPT']),
359+
'cache_key': os.environ['CACHE_KEY'],
358360
}))
359361
EOF
360362
@@ -364,3 +366,19 @@ jobs:
364366
with:
365367
key: ${{ steps.key.outputs.key }}
366368
path: .tmp/tested
369+
370+
# A merge queue run's cache entries are visible only to its own
371+
# temporary ref, so the record is also published for
372+
# promote-test-cache.yml, which saves it again for main (where every
373+
# pull request can see it) once the tested commit lands.
374+
- name: Publish record for main
375+
if: >-
376+
${{ steps.record.outcome == 'success' &&
377+
github.event_name == 'merge_group' }}
378+
uses: actions/upload-artifact@v7
379+
with:
380+
name: tested-record-${{ matrix.config }}
381+
path: .tmp/tested
382+
if-no-files-found: error
383+
retention-days: 3
384+
overwrite: true
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM
2+
# Exceptions.
3+
# See https://edgcpp.org/LICENSE.txt for license information.
4+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
#
6+
# Saves the test cache entries of the merge queue run that tested a commit
7+
# again for main, once that commit lands there.
8+
#
9+
# Each test leg of build-and-test-ci.yml saves a cache entry when it passes,
10+
# so that a later run whose build products and test inputs are the same can
11+
# skip its tests. But actions/cache scopes an entry to the ref that saved it
12+
# (plus the base and default branches), and the merge queue runs on a
13+
# temporary gh-readonly-queue/... ref that nothing else can see, while
14+
# nothing runs on main itself. So the merge queue's test legs also publish
15+
# their records as tested-record-<config> artifacts, and this workflow saves
16+
# each one under its original key from main, where every pull request can
17+
# restore it. Only commits that the merge queue tested and that then landed
18+
# on main are promoted, so one pull request still can't vouch for another's
19+
# build.
20+
#
21+
# A push the merge queue didn't test (e.g., from collect-changes.yml) has no
22+
# such run and promotes nothing.
23+
24+
name: Promote Test Cache
25+
26+
on:
27+
push:
28+
branches: [main]
29+
30+
permissions:
31+
contents: read
32+
# To find the merge queue run and download its artifacts.
33+
actions: read
34+
35+
jobs:
36+
find:
37+
name: Find merge queue records
38+
runs-on: ubuntu-latest
39+
timeout-minutes: 5
40+
outputs:
41+
run_id: ${{ steps.find.outputs.run_id }}
42+
records: ${{ steps.find.outputs.records }}
43+
steps:
44+
- name: Find the merge queue run of this commit
45+
id: find
46+
env:
47+
GH_TOKEN: ${{ github.token }}
48+
run: |
49+
# The merge queue fast-forwards main to the commit it tested, so
50+
# that run has this push's SHA.
51+
run_id="$(gh api \
52+
"repos/${GITHUB_REPOSITORY}/actions/workflows/build-and-test-ci.yml/runs?event=merge_group&head_sha=${GITHUB_SHA}" \
53+
--jq '.workflow_runs[0].id // empty')"
54+
records='[]'
55+
if [ -n "${run_id}" ]; then
56+
records="$(gh api --paginate \
57+
"repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts" \
58+
--jq '.artifacts[] | select(.name | startswith("tested-record-"))
59+
| select(.expired | not) | .name' |
60+
jq -cRs 'split("\n") | map(select(. != ""))')"
61+
fi
62+
echo "Merge queue run: ${run_id:-none}; records: ${records}"
63+
echo "run_id=${run_id}" >> "${GITHUB_OUTPUT}"
64+
echo "records=${records}" >> "${GITHUB_OUTPUT}"
65+
66+
promote:
67+
name: Promote ${{ matrix.record }}
68+
needs: find
69+
if: ${{ needs.find.outputs.records != '[]' }}
70+
runs-on: ubuntu-latest
71+
timeout-minutes: 10
72+
# The test legs' image, so that actions/cache picks the same compression
73+
# (part of an entry's version) and the test legs can restore the entry.
74+
container:
75+
image: ghcr.io/edgcpp/ci-runner@sha256:da140ca90c420977f187279d7939fe0c7cb3c4b656e5ab2dc0f6b9ac2a43a255
76+
strategy:
77+
fail-fast: false
78+
matrix:
79+
record: ${{ fromJSON(needs.find.outputs.records) }}
80+
steps:
81+
- name: Download ${{ matrix.record }}
82+
uses: actions/download-artifact@v7
83+
with:
84+
name: ${{ matrix.record }}
85+
path: .tmp/tested
86+
run-id: ${{ needs.find.outputs.run_id }}
87+
github-token: ${{ github.token }}
88+
89+
- name: Read the record's cache key
90+
id: key
91+
shell: bash
92+
run: |
93+
key="$(python3 -c 'import json, sys
94+
print(json.load(sys.stdin)["cache_key"])' < .tmp/tested/ci-meta.json)"
95+
echo "key=${key}" >> "${GITHUB_OUTPUT}"
96+
97+
- name: Save the record for main
98+
uses: actions/cache/save@v6
99+
with:
100+
key: ${{ steps.key.outputs.key }}
101+
path: .tmp/tested

0 commit comments

Comments
 (0)