@@ -12,10 +12,17 @@ concurrency:
1212
1313env :
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
1619jobs :
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,155 @@ 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+ - name : Save OPAM environment
122+ if : steps.cache-opam-env.outputs.cache-hit != 'true'
123+ uses : actions/cache/save@v5
124+ with :
125+ path : |
126+ ${{ runner.tool_cache }}/opam
127+ ~/.opam
128+ _opam
129+ .opam-path
130+ key : ${{ env.opam_cache_key }}
131+
132+ - name : Use cached OPAM environment
133+ if : steps.cache-opam-env.outputs.cache-hit == 'true'
134+ run : |
135+ # https://github.com/ocaml/setup-ocaml/blob/v3.6.0/packages/setup-ocaml/src/installer.ts
136+ echo "OPAMCOLOR=always" >> "$GITHUB_ENV"
137+ echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV"
138+ echo "OPAMDOWNLOADJOBS=4" >> "$GITHUB_ENV"
139+ echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV"
140+ echo "OPAMEXTERNALSOLVER=builtin-0install" >> "$GITHUB_ENV"
141+ echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV"
142+ echo "OPAMRETRIES=10" >> "$GITHUB_ENV"
143+ echo "OPAMSOLVERTIMEOUT=600" >> "$GITHUB_ENV"
144+ echo "OPAMYES=1" >> "$GITHUB_ENV"
145+ echo "CLICOLOR_FORCE=1" >> "$GITHUB_ENV"
146+ echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV"
147+
148+ OPAM_PATH="$(cat .opam-path)"
149+ chmod +x "$OPAM_PATH"
150+ dirname "$OPAM_PATH" >> "$GITHUB_PATH"
151+
152+ # --- Coverage build cache --------------------------------------------
153+ # The bisect_ppx-instrumented dune build is a separate artifact from
154+ # the main CI build, so it gets its own key. Only restore/save on
155+ # master pushes — PRs share the master cache to stay fast but won't
156+ # poison it.
157+ - name : Coverage build state key
158+ id : coverage-build-state-key
159+ run : echo "value=coverage-build-state-v1-${{ env.OS }}-${{ env.SETUP_OCAML_VERSION }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'compiler/**', 'dune-project') }}" >> $GITHUB_OUTPUT
160+
161+ - name : Restore coverage build state
162+ if : github.base_ref == 'master' || github.ref == 'refs/heads/master'
163+ id : coverage-build-state
164+ uses : actions/cache/restore@v5
165+ with :
166+ path : |
167+ ~/.cache/dune
168+ _build
169+ key : ${{ steps.coverage-build-state-key.outputs.value }}
170+
67171 - name : Run coverage
68172 run : opam exec -- make coverage
69173
174+ - name : Delete stale coverage build state
175+ if : github.event_name == 'push' && github.ref == 'refs/heads/master'
176+ run : |
177+ gh extension install actions/gh-actions-cache
178+ gh actions-cache delete ${{ steps.coverage-build-state-key.outputs.value }} \
179+ -R ${{ github.repository }} \
180+ -B "$GITHUB_REF" \
181+ --confirm || echo "not exist"
182+ env :
183+ GH_TOKEN : ${{ github.token }}
184+
185+ - name : Save coverage build state
186+ if : github.event_name == 'push' && github.ref == 'refs/heads/master'
187+ uses : actions/cache/save@v5
188+ with :
189+ path : |
190+ ~/.cache/dune
191+ _build
192+ key : ${{ steps.coverage-build-state-key.outputs.value }}
193+
70194 - name : Upload coverage to Codecov
71195 uses : codecov/codecov-action@v5
72196 with :
0 commit comments