diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9f4cac466d..ef173a2d93 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,10 +12,17 @@ concurrency: env: OCAMLRUNPARAM: b + # When changing the setup-ocaml version, also adjust it in the setup step + # further below. + SETUP_OCAML_VERSION: 3.6.0 # OPAM <2.6.0 jobs: coverage: runs-on: ubuntu-24.04-arm + env: + OS: ubuntu-24.04-arm + OCAML_COMPILER: 5.3.0 + RUST_TARGET: aarch64-unknown-linux-gnu steps: - name: Checkout uses: actions/checkout@v6 @@ -35,6 +42,17 @@ jobs: packages: bubblewrap darcs g++-multilib gcc-multilib mercurial musl-tools rsync cmake version: v4 + # --- rewatch build cache --------------------------------------------- + # `make coverage` shells out to `make` which builds rewatch; cache the + # cargo target dir so subsequent runs skip the Rust toolchain install + # and the cargo build when the rewatch sources haven't changed. + - name: Restore rewatch build cache + id: rewatch-build-cache + uses: actions/cache@v5 + with: + path: rewatch/target + key: rewatch-build-v3-${{ env.RUST_TARGET }}-${{ hashFiles('rewatch/src/**', 'rewatch/Cargo.lock') }} + - name: Determine Rust toolchain version id: rust-version run: | @@ -42,12 +60,14 @@ jobs: echo "version=${version}" >> "$GITHUB_OUTPUT" - name: Install Rust toolchain + if: steps.rewatch-build-cache.outputs.cache-hit != 'true' uses: dtolnay/rust-toolchain@master with: toolchain: ${{ steps.rust-version.outputs.version }} components: clippy, rustfmt - name: Build rewatch + if: steps.rewatch-build-cache.outputs.cache-hit != 'true' run: cargo build --manifest-path rewatch/Cargo.toml --release - name: Copy rewatch binary @@ -55,18 +75,126 @@ jobs: cp rewatch/target/release/rescript rescript ./scripts/copyExes.js --rewatch - - name: Use OCaml + # --- OPAM environment cache ------------------------------------------ + # The OPAM install step is the dominant cost on a cold run (often >10 + # min). Cache the whole opam tree, keyed on the OS, setup-ocaml + # version, compiler, and the `.opam` files. The cache also includes + # the dev-setup dependencies (e.g. bisect_ppx) so the key is distinct + # from the main CI workflow's cache. + - name: Get OPAM cache key + run: echo "opam_cache_key=opam-coverage-v1-${{ env.OS }}-${{ env.SETUP_OCAML_VERSION }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam') }}" >> $GITHUB_ENV + + - name: Restore OPAM environment + id: cache-opam-env + uses: actions/cache/restore@v5 + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: ${{ env.opam_cache_key }} + + - name: Use OCaml ${{ env.OCAML_COMPILER }} + if: steps.cache-opam-env.outputs.cache-hit != 'true' uses: ocaml/setup-ocaml@v3.6.0 with: - ocaml-compiler: 5.3.0 + ocaml-compiler: ${{ env.OCAML_COMPILER }} opam-pin: false + - name: Get OPAM executable path + if: steps.cache-opam-env.outputs.cache-hit != 'true' + uses: actions/github-script@v9 + with: + script: | + const opamPath = await io.which('opam', true); + console.log('opam executable found: %s', opamPath); + + const fs = require('fs/promises'); + await fs.writeFile('.opam-path', opamPath, 'utf-8'); + console.log('stored path to .opam-path'); + - name: Install OPAM dependencies (incl. bisect_ppx) + if: steps.cache-opam-env.outputs.cache-hit != 'true' run: opam install . --deps-only --with-test --with-dev-setup + # Only save on master pushes. PR-scoped caches are tied to + # refs/pull//merge and can't be restored by master or other PRs, + # so they'd just consume storage and evict the shared entry. PRs + # still get full restore from the latest master-saved cache. + - name: Save OPAM environment + if: steps.cache-opam-env.outputs.cache-hit != 'true' && github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: actions/cache/save@v5 + with: + path: | + ${{ runner.tool_cache }}/opam + ~/.opam + _opam + .opam-path + key: ${{ env.opam_cache_key }} + + - name: Use cached OPAM environment + if: steps.cache-opam-env.outputs.cache-hit == 'true' + run: | + # https://github.com/ocaml/setup-ocaml/blob/v3.6.0/packages/setup-ocaml/src/installer.ts + echo "OPAMCOLOR=always" >> "$GITHUB_ENV" + echo "OPAMCONFIRMLEVEL=unsafe-yes" >> "$GITHUB_ENV" + echo "OPAMDOWNLOADJOBS=4" >> "$GITHUB_ENV" + echo "OPAMERRLOGLEN=0" >> "$GITHUB_ENV" + echo "OPAMEXTERNALSOLVER=builtin-0install" >> "$GITHUB_ENV" + echo "OPAMPRECISETRACKING=1" >> "$GITHUB_ENV" + echo "OPAMRETRIES=10" >> "$GITHUB_ENV" + echo "OPAMSOLVERTIMEOUT=600" >> "$GITHUB_ENV" + echo "OPAMYES=1" >> "$GITHUB_ENV" + echo "CLICOLOR_FORCE=1" >> "$GITHUB_ENV" + echo "OPAMROOT=$HOME/.opam" >> "$GITHUB_ENV" + + OPAM_PATH="$(cat .opam-path)" + chmod +x "$OPAM_PATH" + dirname "$OPAM_PATH" >> "$GITHUB_PATH" + + # --- Coverage build cache -------------------------------------------- + # The bisect_ppx-instrumented dune build is a separate artifact from + # the main CI build, so it gets its own key. Only restore/save on + # master pushes — PRs share the master cache to stay fast but won't + # poison it. + - name: Coverage build state key + id: coverage-build-state-key + run: echo "value=coverage-build-state-v1-${{ env.OS }}-${{ env.SETUP_OCAML_VERSION }}-${{ env.OCAML_COMPILER }}-${{ hashFiles('*.opam', 'compiler/**', 'dune-project') }}" >> $GITHUB_OUTPUT + + - name: Restore coverage build state + if: github.base_ref == 'master' || github.ref == 'refs/heads/master' + id: coverage-build-state + uses: actions/cache/restore@v5 + with: + path: | + ~/.cache/dune + _build + key: ${{ steps.coverage-build-state-key.outputs.value }} + - name: Run coverage run: opam exec -- make coverage + - name: Delete stale coverage build state + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + run: | + gh extension install actions/gh-actions-cache + gh actions-cache delete ${{ steps.coverage-build-state-key.outputs.value }} \ + -R ${{ github.repository }} \ + -B "$GITHUB_REF" \ + --confirm || echo "not exist" + env: + GH_TOKEN: ${{ github.token }} + + - name: Save coverage build state + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: actions/cache/save@v5 + with: + path: | + ~/.cache/dune + _build + key: ${{ steps.coverage-build-state-key.outputs.value }} + - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 85350be7e0..4864ff8e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Expand `super_errors` fixture coverage for warnings and errors. https://github.com/rescript-lang/rescript/pull/8429 - Run `super_errors` fixtures in parallel (~2.4× faster locally). https://github.com/rescript-lang/rescript/pull/8430 - Expand `super_errors` fixture coverage for the remaining reachable single-file error variants. https://github.com/rescript-lang/rescript/pull/8432 +- Cache OPAM env, rewatch build, and instrumented dune state in the coverage workflow. https://github.com/rescript-lang/rescript/pull/8434 # 13.0.0-alpha.4