diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index a610bcfa..4df2950d 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -81,12 +81,39 @@ jobs: - name: Install deps (Ubuntu) if: startsWith(matrix.os, 'ubuntu') - run: sudo apt-get update && sudo apt-get install -y zlib1g-dev + run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache + + - name: Install ccache (macOS) + if: startsWith(matrix.os, 'macos') + run: command -v ccache >/dev/null 2>&1 || brew install ccache + + # Verified compiler cache: CCACHE_COMPILERCHECK=content keys every entry + # on the compiler-binary CONTENT plus the fully preprocessed input, so a + # hit is provably the identical compilation — a stale or foreign cache + # can only miss, never return wrong output (see scripts/env.sh). + # Keys embed github.ref on top of GitHub's own branch scoping: caches are + # STRICTLY per-ref by policy — no base-branch fallback, a new PR builds + # cold once and only its own pushes warm it. + - name: Compiler cache (content-verified) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}- - name: Test run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} env: CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }} + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1500M + + - name: ccache stats + if: always() + run: ccache -s || true + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache test-tsan: runs-on: ubuntu-latest @@ -95,10 +122,33 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install deps (Ubuntu) - run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev + run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev ccache + + - name: Compiler cache (content-verified) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-tsan-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ccache-tsan-${{ github.ref }}- - name: ThreadSanitizer tests - run: make -f Makefile.cbm test-tsan CC=clang CXX=clang++ + # This job calls make directly (no env.sh), so route the compilers + # through ccache's Debian masquerade dir; update-ccache-symlinks in the + # ccache postinst has already linked the clang installed above. + run: | + export PATH=/usr/lib/ccache:$PATH + export CCACHE_COMPILERCHECK=content + make -f Makefile.cbm test-tsan CC=clang CXX=clang++ + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1500M + + - name: ccache stats + if: always() + run: ccache -s || true + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache test-windows: needs: setup-matrix @@ -119,9 +169,18 @@ jobs: mingw-w64-clang-${{ matrix.pkg }}-clang mingw-w64-clang-${{ matrix.pkg }}-compiler-rt mingw-w64-clang-${{ matrix.pkg }}-zlib + mingw-w64-clang-${{ matrix.pkg }}-ccache make git + - name: Compiler cache (content-verified) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}- + - name: Test shell: msys2 {0} # AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no @@ -133,6 +192,15 @@ jobs: run: scripts/test.sh CC=clang CXX=clang++ ${{ matrix.os == 'windows-11-arm' && 'SANITIZE=' || '' }} env: CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }} + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1500M + + - name: ccache stats + if: always() + shell: msys2 {0} + run: ccache -s || true + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache # Windows product-surface regression guards. Distinct from test-windows above # (the sanitizer C suite): these drive a real product binary + embedded HTTP UI @@ -155,6 +223,7 @@ jobs: install: >- mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-zlib + mingw-w64-clang-x86_64-ccache make git @@ -162,11 +231,22 @@ jobs: with: node-version: "22" + - name: Compiler cache (content-verified) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-guards-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ccache-guards-${{ github.ref }}- + - name: Build product binary with embedded UI shell: msys2 {0} # --with-ui builds the frontend (npm) and embeds it, so the drive-picker # guard's HTTP UI is available. Functional gate only (no sanitizers). run: scripts/build.sh --with-ui CC=clang CXX=clang++ + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1500M - name: Windows regression guards (#636/#357, #618, #548, #423/#20) shell: pwsh diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 8d49dc6e..a40e8aad 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -82,7 +82,11 @@ jobs: - name: Install deps (Ubuntu) if: matrix.os == 'ubuntu-latest' - run: sudo apt-get update && sudo apt-get install -y zlib1g-dev + run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache + + - name: Install ccache (macOS) + if: matrix.os == 'macos-14' + run: command -v ccache >/dev/null 2>&1 || brew install ccache - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2 if: matrix.os == 'windows-latest' @@ -93,14 +97,28 @@ jobs: mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-zlib mingw-w64-clang-x86_64-python3 + mingw-w64-clang-x86_64-ccache make coreutils + # Verified compiler cache — content-keyed, stale hits impossible by + # construction (see scripts/env.sh). + - name: Compiler cache (content-verified) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-smoke-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ccache-smoke-${{ matrix.os }}-${{ github.ref }}- + - name: Build prod + smoke (Ubuntu) if: matrix.os == 'ubuntu-latest' run: | scripts/build.sh CC=gcc CXX=g++ scripts/smoke-test.sh "$(pwd)/build/c/codebase-memory-mcp" + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1000M - name: Build prod + smoke (macOS) if: matrix.os == 'macos-14' @@ -108,6 +126,9 @@ jobs: scripts/build.sh CC=cc CXX=c++ codesign --sign - --force build/c/codebase-memory-mcp scripts/smoke-test.sh "$(pwd)/build/c/codebase-memory-mcp" + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1000M - name: Build prod + smoke (Windows) if: matrix.os == 'windows-latest' @@ -117,6 +138,9 @@ jobs: BIN="$(pwd)/build/c/codebase-memory-mcp" [ -f "${BIN}.exe" ] && BIN="${BIN}.exe" scripts/smoke-test.sh "$BIN" + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1000M ci-ok: # The one required context (besides dco) — fails unless every PR stage diff --git a/scripts/env.sh b/scripts/env.sh index f7922dd9..e23affe4 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -98,6 +98,33 @@ if [[ -z "${CC:-}" ]]; then fi fi +# ── Verified compiler cache (ccache, opt-out CBM_NO_CCACHE=1) ── +# Activated through ccache's masquerade directories so $CC keeps its plain +# name everywhere (verify_compiler, make, link lines are untouched). +# Zero-staleness guarantee: CCACHE_COMPILERCHECK=content keys every entry on +# the CONTENT of the compiler binary plus the fully preprocessed translation +# unit, so a cache hit is provably the identical compilation — a stale or +# foreign cache can only MISS, never return wrong output. No CCACHE_BASEDIR +# and no path rewriting: debug-info and sanitizer report paths stay exact. +if [[ "${CBM_NO_CCACHE:-0}" != "1" ]] && command -v ccache >/dev/null 2>&1; then + for _cbm_ccache_masq in \ + /usr/lib/ccache \ + /opt/homebrew/opt/ccache/libexec \ + /usr/local/opt/ccache/libexec \ + /clang64/lib/ccache/bin \ + /clangarm64/lib/ccache/bin; do + if [[ -d "$_cbm_ccache_masq" ]]; then + case ":$PATH:" in + *":$_cbm_ccache_masq:"*) ;; + *) PATH="$_cbm_ccache_masq:$PATH" ;; + esac + fi + done + unset _cbm_ccache_masq + export PATH + export CCACHE_COMPILERCHECK=content +fi + # ── Print environment summary ────────────────────────────────── print_env() { local context="$1"