Skip to content

Commit 207d655

Browse files
Optimize workflows for merge_queue and introduce caching of build + test CI runs []
1 parent 14e2989 commit 207d655

6 files changed

Lines changed: 187 additions & 67 deletions

File tree

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

Lines changed: 173 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
#
2020
# A shared run timestamp keeps results under the same tests/runs/<timestamp>/
2121
# tree; the publish job republishes failure artifacts from all test legs.
22+
#
23+
# The builds set EDG_BUILD_IDENTIFIER so that identical sources produce
24+
# byte-identical build products. Each test leg keys a cache entry on the
25+
# SHA-256 of the build products it downloaded plus the git trees of
26+
# everything else that affects the results (tests/, dev_tools/, bases/, and
27+
# this workflow, computed once by the prepare job). When a leg passes, it
28+
# saves that entry along with its coverage report; a later run on the same
29+
# ref (a re-run, or a push that leaves the build products and test inputs
30+
# unchanged) restores it, skips edgy, and reuses the coverage report.
31+
# actions/cache scopes entries to the ref that saved them (plus the default
32+
# branch), so one pull request can never vouch for another's build.
33+
#
2234
# The bench job runs the cachegrind benchmarks the way
2335
# `edg-docker-bench --posture=release` does and uploads them as bench-results
2436
# (c/ and cpp/ runs, for comparison against the checked-in
@@ -34,13 +46,6 @@
3446
name: Build & Test CI
3547

3648
on:
37-
push:
38-
branches:
39-
- main
40-
# The nightly collect-changes job's commits only move Changes entries.
41-
paths-ignore:
42-
- '**/Changes'
43-
- '**/pending_changes/**'
4449
pull_request:
4550
merge_group:
4651

@@ -61,19 +66,41 @@ jobs:
6166
timeout-minutes: 5
6267
outputs:
6368
run_timestamp: ${{ steps.timestamp.outputs.run_timestamp }}
69+
test_inputs: ${{ steps.test-inputs.outputs.test_inputs }}
6470
steps:
6571
- name: Declare shared edgy run timestamp
6672
id: timestamp
6773
run: |
6874
# Match edgutil.RUN_TIMESTAMP_FORMAT (%Y.%m.%d-%H.%M.%S).
6975
echo "run_timestamp=$(date -u +'%Y.%m.%d-%H.%M.%S')" >> "${GITHUB_OUTPUT}"
7076
77+
# Only the git trees are needed, so skip blobs and most of the
78+
# working tree.
79+
- name: Check out repository metadata
80+
uses: actions/checkout@v7
81+
with:
82+
persist-credentials: false
83+
filter: blob:none
84+
sparse-checkout: .github
85+
86+
- name: Hash test inputs
87+
id: test-inputs
88+
run: |
89+
# Everything besides the build products that can change a test
90+
# leg's results. The workflow file covers the container image and
91+
# the edgy options.
92+
hash="$(git rev-parse HEAD:tests HEAD:dev_tools HEAD:bases \
93+
HEAD:include_c++ \
94+
HEAD:.github/workflows/build-and-test-ci.yml |
95+
sha256sum | cut -c1-64)"
96+
echo "test_inputs=${hash}" >> "${GITHUB_OUTPUT}"
97+
7198
build-gcc:
7299
name: Build linux-gcc-debug
73100
runs-on: ubuntu-latest
74101
timeout-minutes: 120
75102
container:
76-
image: ghcr.io/edgcpp/ci-runner@sha256:14b3948c2ce12cfa5472e5a6c468393d15426f3a67685cc35d830ea5db2acdf8
103+
image: ghcr.io/edgcpp/ci-runner@sha256:e83cc8c97556cdcb42353915a5164a051ba9c219d5b822f6859c875e07e9851d
77104
defaults:
78105
run:
79106
shell: edg-container-exec "bash -eo pipefail {0}"
@@ -87,7 +114,9 @@ jobs:
87114
- name: Configure linux-gcc-debug
88115
env:
89116
EDG_BASE: ${{ github.workspace }}/bases/docker/dev-env/gcc
90-
run: cmake --preset linux-gcc-debug -DEDG_CPFE_USE_GCOV=ON
117+
run: >-
118+
cmake --preset linux-gcc-debug -DEDG_CPFE_USE_GCOV=ON
119+
-DEDG_BUILD_IDENTIFIER=ci
91120
92121
- name: Build linux-gcc-debug
93122
env:
@@ -118,7 +147,7 @@ jobs:
118147
runs-on: ubuntu-latest
119148
timeout-minutes: 120
120149
container:
121-
image: ghcr.io/edgcpp/ci-runner@sha256:14b3948c2ce12cfa5472e5a6c468393d15426f3a67685cc35d830ea5db2acdf8
150+
image: ghcr.io/edgcpp/ci-runner@sha256:e83cc8c97556cdcb42353915a5164a051ba9c219d5b822f6859c875e07e9851d
122151
defaults:
123152
run:
124153
shell: edg-container-exec "bash -eo pipefail {0}"
@@ -132,7 +161,9 @@ jobs:
132161
- name: Configure linux-i686-gcc-debug
133162
env:
134163
EDG_BASE: ${{ github.workspace }}/bases/docker/dev-env/gcc-i686
135-
run: cmake --preset linux-i686-gcc-debug -DEDG_CPFE_USE_GCOV=ON
164+
run: >-
165+
cmake --preset linux-i686-gcc-debug -DEDG_CPFE_USE_GCOV=ON
166+
-DEDG_BUILD_IDENTIFIER=ci
136167
137168
- name: Build linux-i686-gcc-debug
138169
env:
@@ -168,7 +199,7 @@ jobs:
168199
- edg_x86_64
169200
- edg_x86_64_cp
170201
container:
171-
image: ghcr.io/edgcpp/ci-runner@sha256:14b3948c2ce12cfa5472e5a6c468393d15426f3a67685cc35d830ea5db2acdf8
202+
image: ghcr.io/edgcpp/ci-runner@sha256:e83cc8c97556cdcb42353915a5164a051ba9c219d5b822f6859c875e07e9851d
172203
# Required for setarch ... -R (disable ASLR) used by the test harness.
173204
options: --privileged
174205
defaults:
@@ -194,7 +225,38 @@ jobs:
194225
# Artifact round-trip does not reliably preserve +x.
195226
chmod -R a+x build/gcc/bin
196227
228+
- name: Compute test cache key
229+
id: key
230+
env:
231+
CONFIG: ${{ matrix.config }}
232+
TEST_INPUTS: ${{ needs.prepare.outputs.test_inputs }}
233+
run: |
234+
# Hash the names and contents of the downloaded build products.
235+
build="$(cd build/gcc &&
236+
find . -type f -print0 | LC_ALL=C sort -z |
237+
xargs -0 sha256sum | sha256sum | cut -c1-64)"
238+
echo "Build products: ${build}"
239+
echo "key=tested-${CONFIG}-${TEST_INPUTS}-${build}" \
240+
>> "${GITHUB_OUTPUT}"
241+
242+
- name: Look for an earlier passing run of this build
243+
id: tested
244+
uses: actions/cache/restore@v6
245+
with:
246+
key: ${{ steps.key.outputs.key }}
247+
path: .tmp/tested
248+
249+
- name: Reuse earlier results
250+
if: ${{ steps.tested.outputs.cache-hit == 'true' }}
251+
run: |
252+
echo "::notice::This build already passed with the same test" \
253+
"inputs: $(cat .tmp/tested/ci-meta.json)"
254+
mkdir -p coverage
255+
cp .tmp/tested/coverage/*.json coverage/ || true
256+
197257
- name: Run edgy (${{ matrix.config }})
258+
id: edgy
259+
if: ${{ steps.tested.outputs.cache-hit != 'true' }}
198260
env:
199261
RUN_TIMESTAMP: ${{ needs.prepare.outputs.run_timestamp }}
200262
CONFIG: ${{ matrix.config }}
@@ -230,7 +292,7 @@ jobs:
230292
# Coverage problems must never fail the test leg, hence
231293
# continue-on-error.
232294
- name: Install gcovr
233-
if: ${{ !cancelled() }}
295+
if: ${{ !cancelled() && steps.tested.outputs.cache-hit != 'true' }}
234296
continue-on-error: true
235297
run: |
236298
python3 -m venv "${RUNNER_TEMP}/gcovr"
@@ -242,7 +304,7 @@ jobs:
242304
# hot loops exceed gcovr's "suspicious hits" threshold; warn about them
243305
# instead of failing.
244306
- name: Generate coverage report
245-
if: ${{ !cancelled() }}
307+
if: ${{ !cancelled() && steps.tested.outputs.cache-hit != 'true' }}
246308
continue-on-error: true
247309
env:
248310
CONFIG: ${{ matrix.config }}
@@ -269,6 +331,37 @@ jobs:
269331
if-no-files-found: warn
270332
retention-days: 1
271333

334+
# Record the pass (with its coverage report, so a run that skips edgy
335+
# still contributes coverage). Without a report, don't record it.
336+
- name: Prepare record of this passing run
337+
id: record
338+
if: >-
339+
${{ steps.edgy.outcome == 'success' &&
340+
hashFiles(format('coverage/{0}.json', matrix.config)) != '' }}
341+
env:
342+
CONFIG: ${{ matrix.config }}
343+
RUN_ID: ${{ github.run_id }}
344+
RUN_ATTEMPT: ${{ github.run_attempt }}
345+
TESTED_SHA: ${{ github.sha }}
346+
run: |
347+
mkdir -p .tmp/tested/coverage
348+
cp "coverage/${CONFIG}.json" .tmp/tested/coverage/
349+
python3 - > .tmp/tested/ci-meta.json <<'EOF'
350+
import json, os
351+
print(json.dumps({
352+
'tested_sha': os.environ['TESTED_SHA'],
353+
'run_id': int(os.environ['RUN_ID']),
354+
'run_attempt': int(os.environ['RUN_ATTEMPT']),
355+
}))
356+
EOF
357+
358+
- name: Save record of this passing run
359+
if: ${{ steps.record.outcome == 'success' }}
360+
uses: actions/cache/save@v6
361+
with:
362+
key: ${{ steps.key.outputs.key }}
363+
path: .tmp/tested
364+
272365
test-i686:
273366
name: Test ${{ matrix.config }}
274367
needs:
@@ -283,7 +376,7 @@ jobs:
283376
- edg_x86_32
284377
- edg_x86_32_cp
285378
container:
286-
image: ghcr.io/edgcpp/ci-runner@sha256:14b3948c2ce12cfa5472e5a6c468393d15426f3a67685cc35d830ea5db2acdf8
379+
image: ghcr.io/edgcpp/ci-runner@sha256:e83cc8c97556cdcb42353915a5164a051ba9c219d5b822f6859c875e07e9851d
287380
# Required for setarch ... -R (disable ASLR) used by the test harness.
288381
options: --privileged
289382
defaults:
@@ -309,7 +402,38 @@ jobs:
309402
# Artifact round-trip does not reliably preserve +x.
310403
chmod -R a+x build/gcc-i686/bin
311404
405+
- name: Compute test cache key
406+
id: key
407+
env:
408+
CONFIG: ${{ matrix.config }}
409+
TEST_INPUTS: ${{ needs.prepare.outputs.test_inputs }}
410+
run: |
411+
# Hash the names and contents of the downloaded build products.
412+
build="$(cd build/gcc-i686 &&
413+
find . -type f -print0 | LC_ALL=C sort -z |
414+
xargs -0 sha256sum | sha256sum | cut -c1-64)"
415+
echo "Build products: ${build}"
416+
echo "key=tested-${CONFIG}-${TEST_INPUTS}-${build}" \
417+
>> "${GITHUB_OUTPUT}"
418+
419+
- name: Look for an earlier passing run of this build
420+
id: tested
421+
uses: actions/cache/restore@v6
422+
with:
423+
key: ${{ steps.key.outputs.key }}
424+
path: .tmp/tested
425+
426+
- name: Reuse earlier results
427+
if: ${{ steps.tested.outputs.cache-hit == 'true' }}
428+
run: |
429+
echo "::notice::This build already passed with the same test" \
430+
"inputs: $(cat .tmp/tested/ci-meta.json)"
431+
mkdir -p coverage
432+
cp .tmp/tested/coverage/*.json coverage/ || true
433+
312434
- name: Run edgy (${{ matrix.config }})
435+
id: edgy
436+
if: ${{ steps.tested.outputs.cache-hit != 'true' }}
313437
env:
314438
RUN_TIMESTAMP: ${{ needs.prepare.outputs.run_timestamp }}
315439
CONFIG: ${{ matrix.config }}
@@ -345,7 +469,7 @@ jobs:
345469
# Coverage problems must never fail the test leg, hence
346470
# continue-on-error.
347471
- name: Install gcovr
348-
if: ${{ !cancelled() }}
472+
if: ${{ !cancelled() && steps.tested.outputs.cache-hit != 'true' }}
349473
continue-on-error: true
350474
run: |
351475
python3 -m venv "${RUNNER_TEMP}/gcovr"
@@ -357,7 +481,7 @@ jobs:
357481
# hot loops exceed gcovr's "suspicious hits" threshold; warn about them
358482
# instead of failing.
359483
- name: Generate coverage report
360-
if: ${{ !cancelled() }}
484+
if: ${{ !cancelled() && steps.tested.outputs.cache-hit != 'true' }}
361485
continue-on-error: true
362486
env:
363487
CONFIG: ${{ matrix.config }}
@@ -384,6 +508,37 @@ jobs:
384508
if-no-files-found: warn
385509
retention-days: 1
386510

511+
# Record the pass (with its coverage report, so a run that skips edgy
512+
# still contributes coverage). Without a report, don't record it.
513+
- name: Prepare record of this passing run
514+
id: record
515+
if: >-
516+
${{ steps.edgy.outcome == 'success' &&
517+
hashFiles(format('coverage/{0}.json', matrix.config)) != '' }}
518+
env:
519+
CONFIG: ${{ matrix.config }}
520+
RUN_ID: ${{ github.run_id }}
521+
RUN_ATTEMPT: ${{ github.run_attempt }}
522+
TESTED_SHA: ${{ github.sha }}
523+
run: |
524+
mkdir -p .tmp/tested/coverage
525+
cp "coverage/${CONFIG}.json" .tmp/tested/coverage/
526+
python3 - > .tmp/tested/ci-meta.json <<'EOF'
527+
import json, os
528+
print(json.dumps({
529+
'tested_sha': os.environ['TESTED_SHA'],
530+
'run_id': int(os.environ['RUN_ID']),
531+
'run_attempt': int(os.environ['RUN_ATTEMPT']),
532+
}))
533+
EOF
534+
535+
- name: Save record of this passing run
536+
if: ${{ steps.record.outcome == 'success' }}
537+
uses: actions/cache/save@v6
538+
with:
539+
key: ${{ steps.key.outputs.key }}
540+
path: .tmp/tested
541+
387542
publish-test-results:
388543
name: Publish aggregated test results
389544
needs:
@@ -508,7 +663,7 @@ jobs:
508663
runs-on: ubuntu-latest
509664
timeout-minutes: 120
510665
container:
511-
image: ghcr.io/edgcpp/ci-runner@sha256:14b3948c2ce12cfa5472e5a6c468393d15426f3a67685cc35d830ea5db2acdf8
666+
image: ghcr.io/edgcpp/ci-runner@sha256:e83cc8c97556cdcb42353915a5164a051ba9c219d5b822f6859c875e07e9851d
512667
defaults:
513668
run:
514669
shell: edg-container-exec "bash -eo pipefail {0}"

‎.github/workflows/commit-messages.yml‎

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
# See https://edgcpp.org/LICENSE.txt for license information.
44
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55
#
6-
# Check the messages of the new commits pushed to main and of every pull
7-
# request's commits with edg-check-commit-messages (the same policy the
8-
# pre-push hook applies, from edgchanges). Only commits with a violation are
9-
# reported.
6+
# Check the messages of every pull request's commits and of every merge queue
7+
# entry with edg-check-commit-messages (the same policy the pre-push hook
8+
# applies, from edgchanges). Only commits with a violation are reported.
109
#
1110
# A pull request is checked from its base commit to its head commit, and a
1211
# merge queue entry from the branch it is merged into to its tip (so the
13-
# commits the queue creates are checked too). A push is checked from the
14-
# previous tip of main. If that tip is unavailable (e.g. after a force-push),
15-
# the job fails rather than check nothing.
12+
# commits the queue creates are checked too). Commits land on main only
13+
# through the merge queue, so pushes to main are not checked again.
1614
#
1715
# The checker and the commits come from one checkout of github.sha, the same
1816
# commit GitHub loads this workflow from. For a pull request that is the merge
@@ -26,9 +24,6 @@
2624
name: Commit Messages
2725

2826
on:
29-
push:
30-
branches:
31-
- main
3227
pull_request:
3328
merge_group:
3429

@@ -63,21 +58,12 @@ jobs:
6358
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
6459
QUEUE_BASE_SHA: ${{ github.event.merge_group.base_sha }}
6560
QUEUE_HEAD_SHA: ${{ github.event.merge_group.head_sha }}
66-
PUSH_BEFORE_SHA: ${{ github.event.before }}
6761
ALLOW_MERGE_COMMITS: ${{ vars.ALLOW_MERGE_COMMITS }}
6862
run: |
6963
if [[ "${EVENT_NAME}" == pull_request ]]; then
7064
range=("${PR_BASE_SHA}..${PR_HEAD_SHA}")
71-
elif [[ "${EVENT_NAME}" == merge_group ]]; then
72-
range=("${QUEUE_BASE_SHA}..${QUEUE_HEAD_SHA}")
73-
elif [[ "${PUSH_BEFORE_SHA}" != 0000000000000000000000000000000000000000 ]] &&
74-
git cat-file -e "${PUSH_BEFORE_SHA}^{commit}" 2> /dev/null; then
75-
range=("${PUSH_BEFORE_SHA}..HEAD")
7665
else
77-
echo "::error::No previous tip of the branch to compare against" \
78-
"(push before: ${PUSH_BEFORE_SHA}); the pushed commits" \
79-
"were not checked."
80-
exit 1
66+
range=("${QUEUE_BASE_SHA}..${QUEUE_HEAD_SHA}")
8167
fi
8268
options=()
8369
if [[ "${ALLOW_MERGE_COMMITS}" == true ]]; then

‎.github/workflows/dev-tools-tests.yml‎

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,6 @@
99
name: Dev Tools Tests
1010

1111
on:
12-
push:
13-
branches:
14-
- main
15-
paths:
16-
- .github/workflows/dev-tools-tests.yml
17-
- dev_tools/bin/edg-check-commit-messages
18-
- dev_tools/bin/edg-check-file-changes
19-
- dev_tools/bin/edg-collect-changes
20-
- dev_tools/pylibs/edgchanges/**
21-
- dev_tools/pylibs/edgutil/**
22-
- dev_tools/tests/**
2312
pull_request:
2413
paths:
2514
- .github/workflows/dev-tools-tests.yml

0 commit comments

Comments
 (0)