Skip to content

Commit 4f27edd

Browse files
authored
Coverage CI: cache OPAM env, rewatch build, and coverage dune state (#8434)
* Coverage CI: cache OPAM env, rewatch build, and coverage dune state The OPAM install step on a cold runner often takes 10+ minutes and periodically times out / fails. This mirrors the cache strategy used by the main CI workflow: store the OPAM tree (incl. dev-setup deps like bisect_ppx), the rewatch cargo target, and the instrumented dune build state. PRs restore the latest master cache; only master pushes write to it, so PRs can't poison it. The OPAM cache key is distinct from the main CI workflow's because coverage installs --with-dev-setup (for bisect_ppx) while CI only uses --with-test. * Update changelog for #8434 * Coverage CI: gate OPAM cache save to master pushes (codex P2) actions/cache/save@v5 on cache miss runs on every event including pull_request, which creates PR-scoped cache entries tied to refs/pull/<n>/merge. Those entries aren't restorable by master or other PRs, so they consume storage and can evict the shared master-saved entry, reducing hit rate. Restrict the save to push events on refs/heads/master, matching the pattern already used for the coverage-build-state cache. PRs still benefit from full restore via the latest master cache; they just don't write new entries. Flagged by Codex review on #8434.
1 parent 8aaa1e8 commit 4f27edd

2 files changed

Lines changed: 131 additions & 2 deletions

File tree

.github/workflows/coverage.yml

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ concurrency:
1212

1313
env:
1414
OCAMLRUNPARAM: b
15+
# When changing the setup-ocaml version, also adjust it in the setup step
16+
# further below.
17+
SETUP_OCAML_VERSION: 3.6.0 # OPAM <2.6.0
1518

1619
jobs:
1720
coverage:
1821
runs-on: ubuntu-24.04-arm
22+
env:
23+
OS: ubuntu-24.04-arm
24+
OCAML_COMPILER: 5.3.0
25+
RUST_TARGET: aarch64-unknown-linux-gnu
1926
steps:
2027
- name: Checkout
2128
uses: actions/checkout@v6
@@ -35,38 +42,159 @@ jobs:
3542
packages: bubblewrap darcs g++-multilib gcc-multilib mercurial musl-tools rsync cmake
3643
version: v4
3744

45+
# --- rewatch build cache ---------------------------------------------
46+
# `make coverage` shells out to `make` which builds rewatch; cache the
47+
# cargo target dir so subsequent runs skip the Rust toolchain install
48+
# and the cargo build when the rewatch sources haven't changed.
49+
- name: Restore rewatch build cache
50+
id: rewatch-build-cache
51+
uses: actions/cache@v5
52+
with:
53+
path: rewatch/target
54+
key: rewatch-build-v3-${{ env.RUST_TARGET }}-${{ hashFiles('rewatch/src/**', 'rewatch/Cargo.lock') }}
55+
3856
- name: Determine Rust toolchain version
3957
id: rust-version
4058
run: |
4159
version="$(awk -F '"' '/^rust-version[[:space:]]*=/ { print $2; exit }' rewatch/Cargo.toml)"
4260
echo "version=${version}" >> "$GITHUB_OUTPUT"
4361
4462
- name: Install Rust toolchain
63+
if: steps.rewatch-build-cache.outputs.cache-hit != 'true'
4564
uses: dtolnay/rust-toolchain@master
4665
with:
4766
toolchain: ${{ steps.rust-version.outputs.version }}
4867
components: clippy, rustfmt
4968

5069
- name: Build rewatch
70+
if: steps.rewatch-build-cache.outputs.cache-hit != 'true'
5171
run: cargo build --manifest-path rewatch/Cargo.toml --release
5272

5373
- name: Copy rewatch binary
5474
run: |
5575
cp rewatch/target/release/rescript rescript
5676
./scripts/copyExes.js --rewatch
5777
58-
- name: Use OCaml
78+
# --- OPAM environment cache ------------------------------------------
79+
# The OPAM install step is the dominant cost on a cold run (often >10
80+
# min). Cache the whole opam tree, keyed on the OS, setup-ocaml
81+
# version, compiler, and the `.opam` files. The cache also includes
82+
# the dev-setup dependencies (e.g. bisect_ppx) so the key is distinct
83+
# from the main CI workflow's cache.
84+
- name: Get OPAM cache key
85+
run: echo "opam_cache_key=opam-coverage-v1-${{ env.OS }}-${{ env.SETUP_OCAML_VERSION }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam') }}" >> $GITHUB_ENV
86+
87+
- name: Restore OPAM environment
88+
id: cache-opam-env
89+
uses: actions/cache/restore@v5
90+
with:
91+
path: |
92+
${{ runner.tool_cache }}/opam
93+
~/.opam
94+
_opam
95+
.opam-path
96+
key: ${{ env.opam_cache_key }}
97+
98+
- name: Use OCaml ${{ env.OCAML_COMPILER }}
99+
if: steps.cache-opam-env.outputs.cache-hit != 'true'
59100
uses: ocaml/setup-ocaml@v3.6.0
60101
with:
61-
ocaml-compiler: 5.3.0
102+
ocaml-compiler: ${{ env.OCAML_COMPILER }}
62103
opam-pin: false
63104

105+
- name: Get OPAM executable path
106+
if: steps.cache-opam-env.outputs.cache-hit != 'true'
107+
uses: actions/github-script@v9
108+
with:
109+
script: |
110+
const opamPath = await io.which('opam', true);
111+
console.log('opam executable found: %s', opamPath);
112+
113+
const fs = require('fs/promises');
114+
await fs.writeFile('.opam-path', opamPath, 'utf-8');
115+
console.log('stored path to .opam-path');
116+
64117
- name: Install OPAM dependencies (incl. bisect_ppx)
118+
if: steps.cache-opam-env.outputs.cache-hit != 'true'
65119
run: opam install . --deps-only --with-test --with-dev-setup
66120

121+
# Only save on master pushes. PR-scoped caches are tied to
122+
# refs/pull/<n>/merge and can't be restored by master or other PRs,
123+
# so they'd just consume storage and evict the shared entry. PRs
124+
# still get full restore from the latest master-saved cache.
125+
- name: Save OPAM environment
126+
if: steps.cache-opam-env.outputs.cache-hit != 'true' && github.event_name == 'push' && github.ref == 'refs/heads/master'
127+
uses: actions/cache/save@v5
128+
with:
129+
path: |
130+
${{ runner.tool_cache }}/opam
131+
~/.opam
132+
_opam
133+
.opam-path
134+
key: ${{ env.opam_cache_key }}
135+
136+
- name: Use cached OPAM environment
137+
if: steps.cache-opam-env.outputs.cache-hit == 'true'
138+
run: |
139+
# https://github.com/ocaml/setup-ocaml/blob/v3.6.0/packages/setup-ocaml/src/installer.ts
140+
echo "OPAMCOLOR=always" >> "$GITHUB_ENV"
141+
echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV"
142+
echo "OPAMDOWNLOADJOBS=4" >> "$GITHUB_ENV"
143+
echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV"
144+
echo "OPAMEXTERNALSOLVER=builtin-0install" >> "$GITHUB_ENV"
145+
echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV"
146+
echo "OPAMRETRIES=10" >> "$GITHUB_ENV"
147+
echo "OPAMSOLVERTIMEOUT=600" >> "$GITHUB_ENV"
148+
echo "OPAMYES=1" >> "$GITHUB_ENV"
149+
echo "CLICOLOR_FORCE=1" >> "$GITHUB_ENV"
150+
echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV"
151+
152+
OPAM_PATH="$(cat .opam-path)"
153+
chmod +x "$OPAM_PATH"
154+
dirname "$OPAM_PATH" >> "$GITHUB_PATH"
155+
156+
# --- Coverage build cache --------------------------------------------
157+
# The bisect_ppx-instrumented dune build is a separate artifact from
158+
# the main CI build, so it gets its own key. Only restore/save on
159+
# master pushes — PRs share the master cache to stay fast but won't
160+
# poison it.
161+
- name: Coverage build state key
162+
id: coverage-build-state-key
163+
run: echo "value=coverage-build-state-v1-${{ env.OS }}-${{ env.SETUP_OCAML_VERSION }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'compiler/**', 'dune-project') }}" >> $GITHUB_OUTPUT
164+
165+
- name: Restore coverage build state
166+
if: github.base_ref == 'master' || github.ref == 'refs/heads/master'
167+
id: coverage-build-state
168+
uses: actions/cache/restore@v5
169+
with:
170+
path: |
171+
~/.cache/dune
172+
_build
173+
key: ${{ steps.coverage-build-state-key.outputs.value }}
174+
67175
- name: Run coverage
68176
run: opam exec -- make coverage
69177

178+
- name: Delete stale coverage build state
179+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
180+
run: |
181+
gh extension install actions/gh-actions-cache
182+
gh actions-cache delete ${{ steps.coverage-build-state-key.outputs.value }} \
183+
-R ${{ github.repository }} \
184+
-B "$GITHUB_REF" \
185+
--confirm || echo "not exist"
186+
env:
187+
GH_TOKEN: ${{ github.token }}
188+
189+
- name: Save coverage build state
190+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
191+
uses: actions/cache/save@v5
192+
with:
193+
path: |
194+
~/.cache/dune
195+
_build
196+
key: ${{ steps.coverage-build-state-key.outputs.value }}
197+
70198
- name: Upload coverage to Codecov
71199
uses: codecov/codecov-action@v5
72200
with:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
- Expand `super_errors` fixture coverage for warnings and errors. https://github.com/rescript-lang/rescript/pull/8429
5151
- Run `super_errors` fixtures in parallel (~2.4× faster locally). https://github.com/rescript-lang/rescript/pull/8430
5252
- Expand `super_errors` fixture coverage for the remaining reachable single-file error variants. https://github.com/rescript-lang/rescript/pull/8432
53+
- Cache OPAM env, rewatch build, and instrumented dune state in the coverage workflow. https://github.com/rescript-lang/rescript/pull/8434
5354

5455

5556
# 13.0.0-alpha.4

0 commit comments

Comments
 (0)