From 283e03db38c0115ce349a2ba7b10210037884daa Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 12:39:36 -0600 Subject: [PATCH 01/14] Try unified CI job with smart pathing --- .github/workflows/builds.yml | 47 +---------- .github/workflows/ci.yml | 119 ++++++++++++++++++++++++++++ .github/workflows/generate-docs.yml | 11 +-- .github/workflows/tests.yml | 31 +------- 4 files changed, 129 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 6fbae309..0b3946e5 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -1,49 +1,10 @@ name: Builds +# Driven by .github/workflows/ci.yml. PR and push-to-main triggers live in +# the parent so a single "CI" check gates every PR regardless of paths. on: - push: - branches: ["main"] - paths: - - src/** - - include/** - - cpp-example-collection/** - - bridge/** - - client-sdk-rust/** - - cmake/** - - scripts/** - - CMakeLists.txt - - build.sh - - build.cmd - - .build-info.json.in - - vcpkg.json - - CMakePresets.json - - docker/Dockerfile.base - - docker/Dockerfile.sdk - - .github/workflows/** - - .clang-format - - scripts/clang-format.sh - pull_request: - branches: ["main"] - paths: - - src/** - - include/** - - cpp-example-collection/** - - bridge/** - - client-sdk-rust/** - - cmake/** - - scripts/** - - CMakeLists.txt - - build.sh - - build.cmd - - .build-info.json.in - - vcpkg.json - - CMakePresets.json - - docker/Dockerfile.base - - docker/Dockerfile.sdk - - .github/workflows/** - - .clang-format - - scripts/clang-format.sh - workflow_dispatch: + workflow_call: {} + workflow_dispatch: {} permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..34c9f0e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,119 @@ +name: CI + +# Aggregator workflow that fans out to every PR-gating workflow as a single, +# always-running check. Configure branch protection to require only the "CI" +# status; downstream workflows will run automatically. This avoids the +# previous footgun where path-filtered workflows skipped entire PRs (so all +# checks showed "passing" without ever executing — see #147). +# +# Each child workflow is conditionally invoked based on the paths a PR +# touched, mirroring the path filters that used to live in each child. +# Children that get skipped via `if:` count as "skipped" in the CI rollup +# (not "failed"), so the parent "CI" status reaches SUCCESS and branch +# protection is satisfied. Push-to-main and manual dispatch always run +# everything regardless of changed paths. + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + branches: ["main"] + push: + branches: ["main"] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + # Compute once which path groups changed; every other job references these + # outputs. dorny/paths-filter handles PR base diff, push base diff, and + # empty results on non-diff events (workflow_dispatch); the `if:` guards + # below explicitly opt non-PR events back into running everything. + changes: + name: Detect changes + runs-on: ubuntu-latest + outputs: + builds: ${{ steps.filter.outputs.builds }} + tests: ${{ steps.filter.outputs.tests }} + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + id: filter + with: + filters: | + builds: + - src/** + - include/** + - cpp-example-collection/** + - bridge/** + - client-sdk-rust/** + - cmake/** + - scripts/** + - CMakeLists.txt + - build.sh + - build.cmd + - .build-info.json.in + - vcpkg.json + - CMakePresets.json + - docker/Dockerfile.base + - docker/Dockerfile.sdk + - .clang-format + - .github/workflows/ci.yml + - .github/workflows/builds.yml + tests: + - src/** + - include/** + - client-sdk-rust/** + - CMakeLists.txt + - CMakePresets.json + - build.sh + - build.cmd + - vcpkg.json + - .token_helpers/** + - .github/workflows/ci.yml + - .github/workflows/tests.yml + docs: + - **/*.md + - include/** + - src/** + - docs/** + - scripts/generate-docs.sh + - .github/workflows/ci.yml + - .github/workflows/generate-docs.yml + - .github/workflows/publish-docs.yml + + builds: + name: Builds + needs: changes + if: ${{ needs.changes.outputs.builds == 'true' || github.event_name != 'pull_request' }} + uses: ./.github/workflows/builds.yml + secrets: inherit + + tests: + name: Tests + needs: changes + if: ${{ needs.changes.outputs.tests == 'true' || github.event_name != 'pull_request' }} + uses: ./.github/workflows/tests.yml + secrets: inherit + + # license-check and pin-check are cheap (seconds) and have no meaningful + # path scope (any source change can affect license headers; any action + # bump can affect pinning), so they always run. + license-check: + name: License Check + uses: ./.github/workflows/license_check.yml + + pin-check: + name: Pin Check + uses: ./.github/workflows/pin_check.yml + + generate-docs: + name: Generate Docs + needs: changes + if: ${{ needs.changes.outputs.docs == 'true' || github.event_name != 'pull_request' }} + uses: ./.github/workflows/generate-docs.yml diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 52ea026e..5c2c6192 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -10,16 +10,9 @@ permissions: # The artifact is attached to the workflow run so reviewers can download a preview, # but also to verify the documentation artifact is valid for subsequent release runs # that will publish them to the LiveKit docs web page: https://docs.livekit.io/reference/client-sdk-cpp/ +# Driven by .github/workflows/ci.yml on PRs. PR trigger lives in the parent +# so a single "CI" check gates every PR regardless of paths. on: - pull_request: - branches: ["main"] - paths: - - include/** - - src/** - - docs/** - - scripts/generate-docs.sh - - .github/workflows/generate-docs.yml - - .github/workflows/publish-docs.yml workflow_call: inputs: version: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b998be8f..08f9e605 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,33 +1,10 @@ name: Tests +# Driven by .github/workflows/ci.yml. PR and push-to-main triggers live in +# the parent so a single "CI" check gates every PR regardless of paths. on: - push: - branches: ["main"] - paths: - - src/** - - include/** - - client-sdk-rust/** - - CMakeLists.txt - - CMakePresets.json - - build.sh - - build.cmd - - vcpkg.json - - .token_helpers/** - - .github/workflows/tests.yml - pull_request: - branches: ["main"] - paths: - - src/** - - include/** - - client-sdk-rust/** - - CMakeLists.txt - - CMakePresets.json - - build.sh - - build.cmd - - vcpkg.json - - .token_helpers/** - - .github/workflows/tests.yml - workflow_dispatch: + workflow_call: {} + workflow_dispatch: {} permissions: contents: read From b9706989be41e7adcaca0a8eefc30c294f245601 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 12:43:17 -0600 Subject: [PATCH 02/14] Fix perms --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34c9f0e5..5893d345 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,11 @@ concurrency: permissions: contents: read + # Reusable child workflows can only request permissions <= what the + # caller has. builds.yml and tests.yml need actions:read (GHA cache) and + # packages:read (GHCR pulls), so grant them at the caller level too. + actions: read + packages: read jobs: # Compute once which path groups changed; every other job references these From 4a540ab99586d5f3fb1f7a53421b63343ab067c5 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 12:50:06 -0600 Subject: [PATCH 03/14] Fix syntax --- .github/workflows/ci.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5893d345..608a588d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,11 +27,6 @@ concurrency: permissions: contents: read - # Reusable child workflows can only request permissions <= what the - # caller has. builds.yml and tests.yml need actions:read (GHA cache) and - # packages:read (GHCR pulls), so grant them at the caller level too. - actions: read - packages: read jobs: # Compute once which path groups changed; every other job references these @@ -83,7 +78,9 @@ jobs: - .github/workflows/ci.yml - .github/workflows/tests.yml docs: - - **/*.md + # Quoted because YAML treats a leading `*` as an alias + # reference and would otherwise reject this scalar. + - "**/*.md" - include/** - src/** - docs/** From 52d8bfff189d7145c76d80c5a955a6804c794202 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 12:52:12 -0600 Subject: [PATCH 04/14] Fix --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 608a588d..925200c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,14 @@ concurrency: permissions: contents: read + # Reusable child workflows (builds.yml, tests.yml) request actions:read + # (GHA cache) and packages:read (GHCR). GitHub clamps a child workflow's + # permissions to the MIN of (caller's permissions, child's request), so + # we must grant these at the caller level even though ci.yml itself + # doesn't use them. Without this, callers get + # "actions: read, packages: read" but is only allowed "actions: none, packages: none" + actions: read + packages: read jobs: # Compute once which path groups changed; every other job references these From cc7bae7ab0ea299d63e87943e1e8604a0db4a0cf Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 14:53:29 -0600 Subject: [PATCH 05/14] See if we improved anything --- .github/workflows/builds.yml | 21 ++++++++++++- .github/workflows/ci.yml | 6 ---- .github/workflows/tests.yml | 59 ++++++++++++++++++++++++++++++++---- build.sh | 13 ++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 0b3946e5..73d64efe 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -18,6 +18,10 @@ env: # transient cache backend failures fall back to uncached rustc instead of # failing the build. SCCACHE_GHA_ENABLED: "true" + # sccache cannot cache Cargo incremental artifacts; without this, sccache + # reports ~0% utilization even on cache misses. Disabling incremental lets + # sccache cache per-crate compilations whenever the cargo target cache misses. + CARGO_INCREMENTAL: "0" # Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection) CPP_EXAMPLE_COLLECTION_REF: 56815733a71c14692569e8adf2916a56a14d4882 # vcpkg binary caching for Windows @@ -133,7 +137,17 @@ jobs: run: | if sccache --start-server >/dev/null 2>&1; then echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled (RUSTC_WRAPPER=sccache)" + # Wire sccache into the C++ build too (the dominant build cost), + # but only where the generator honors compiler launchers. The + # Windows preset uses the Visual Studio generator, which ignores + # CMAKE__COMPILER_LAUNCHER, so we skip it there. + if [[ "$RUNNER_OS" != "Windows" ]]; then + echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" + echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" + echo "::notice::sccache enabled for rustc + C/C++ (RUSTC_WRAPPER + CMAKE_*_COMPILER_LAUNCHER)" + else + echo "::notice::sccache enabled for rustc only (Windows VS generator ignores compiler launchers)" + fi else echo "::warning::sccache backend unreachable; building without compile cache this run" fi @@ -180,6 +194,11 @@ jobs: shell: pwsh run: ${{ matrix.build_cmd }} + - name: sccache stats + if: always() + shell: bash + run: sccache --show-stats || true + # ---------- Smoke test cpp-example-collection binaries ---------- # Built under cpp-example-collection-build/ (not build-dir/bin). Visual Studio # multi-config places executables in per-target Release/ (or Debug/) subdirs. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 925200c2..f6ea5d66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,12 +27,6 @@ concurrency: permissions: contents: read - # Reusable child workflows (builds.yml, tests.yml) request actions:read - # (GHA cache) and packages:read (GHCR). GitHub clamps a child workflow's - # permissions to the MIN of (caller's permissions, child's request), so - # we must grant these at the caller level even though ci.yml itself - # doesn't use them. Without this, callers get - # "actions: read, packages: read" but is only allowed "actions: none, packages: none" actions: read packages: read diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 08f9e605..24f95b0a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,10 @@ env: # transient cache backend failures fall back to uncached rustc instead of # failing the build. SCCACHE_GHA_ENABLED: "true" + # sccache cannot cache Cargo incremental artifacts; without this, sccache + # reports ~0% utilization even on cache misses. Disabling incremental lets + # sccache cache per-crate compilations whenever the cargo target cache misses. + CARGO_INCREMENTAL: "0" # vcpkg binary caching for Windows (mirrors builds.yml) VCPKG_DEFAULT_TRIPLET: x64-windows-static-md VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static-md @@ -123,7 +127,17 @@ jobs: run: | if sccache --start-server >/dev/null 2>&1; then echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled (RUSTC_WRAPPER=sccache)" + # Wire sccache into the C++ build too (the dominant build cost), + # but only where the generator honors compiler launchers. The + # Windows preset uses the Visual Studio generator, which ignores + # CMAKE__COMPILER_LAUNCHER, so we skip it there. + if [[ "$RUNNER_OS" != "Windows" ]]; then + echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" + echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" + echo "::notice::sccache enabled for rustc + C/C++ (RUSTC_WRAPPER + CMAKE_*_COMPILER_LAUNCHER)" + else + echo "::notice::sccache enabled for rustc only (Windows VS generator ignores compiler launchers)" + fi else echo "::warning::sccache backend unreachable; building without compile cache this run" fi @@ -170,6 +184,11 @@ jobs: shell: pwsh run: ${{ matrix.build_cmd }} + - name: sccache stats + if: always() + shell: bash + run: sccache --show-stats || true + # ---------- Run unit tests ---------- - name: Run unit tests (Unix) if: runner.os != 'Windows' @@ -280,6 +299,10 @@ jobs: coverage: name: Code Coverage runs-on: ubuntu-latest + # A debug build instrumented with --coverage is far heavier (RAM + disk) + # than the release builds. Cap the wall-clock so a stuck/OOM build fails + # fast and visibly instead of hanging toward the 6h default. + timeout-minutes: 45 steps: - name: Checkout (with submodules) @@ -291,6 +314,19 @@ jobs: - name: Pull LFS files run: git lfs pull + # Reclaim ~30GB on the runner. The debug+coverage object tree plus gcov + # data is large; prior runs died mid-build from disk/memory pressure. + - name: Free disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false + # ---------- OS deps ---------- - name: Install deps run: | @@ -366,8 +402,13 @@ jobs: -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ -DCMAKE_SHARED_LINKER_FLAGS="--coverage" + # Cap parallelism: a debug build with --coverage instrumentation has a + # much larger per-TU memory footprint, and unbounded --parallel (one job + # per core) is what OOM-killed prior runs (failure surfaced only as an + # abandoned "Build" step). 2 keeps peak RAM in check on the standard + # runner; bump if/when this moves to a larger runner. - name: Build - run: cmake --build build-debug --parallel + run: cmake --build build-debug --parallel 2 # ---------- Run unit tests ---------- - name: Run unit tests @@ -376,6 +417,16 @@ jobs: build-debug/bin/livekit_unit_tests \ --gtest_output=xml:build-debug/unit-test-results.xml + # Make resource-exhaustion failures visible instead of silent. Runs only + # if an earlier step failed (e.g. OOM during Build). + - name: Diagnostics on failure + if: failure() + run: | + echo "===== disk ====="; df -h || true + echo "===== memory ====="; free -h || true + echo "===== dmesg (OOM killer) =====" + sudo dmesg 2>/dev/null | grep -iE 'killed process|out of memory|oom' | tail -20 || true + # ---------- Generate coverage reports ---------- - name: Generate coverage reports run: | @@ -415,7 +466,3 @@ jobs: path: coverage-html/ retention-days: 14 - - name: Upload coverage to Codecov - uses: getsentry/codecov-action@03112cc3b486a3397dc8d17a58680008721fc86f # v0.3.7 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.sh b/build.sh index c3257394..f7c59c30 100755 --- a/build.sh +++ b/build.sh @@ -144,6 +144,19 @@ configure() { echo "==> Setting CMAKE_OSX_ARCHITECTURES=${MACOS_ARCH}" extra_args+=("-DCMAKE_OSX_ARCHITECTURES=${MACOS_ARCH}") fi + # Bridge compiler-launcher env vars (e.g. sccache/ccache) into the CMake + # cache. CMake does NOT read CMAKE__COMPILER_LAUNCHER from the + # environment, so CI exports these and we forward them explicitly. Only the + # Ninja/Makefile generators honor launchers; the Visual Studio generator + # ignores them, so Windows builds simply leave these unset. + if [[ -n "${CMAKE_C_COMPILER_LAUNCHER:-}" ]]; then + echo "==> Using C compiler launcher: ${CMAKE_C_COMPILER_LAUNCHER}" + extra_args+=("-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}") + fi + if [[ -n "${CMAKE_CXX_COMPILER_LAUNCHER:-}" ]]; then + echo "==> Using C++ compiler launcher: ${CMAKE_CXX_COMPILER_LAUNCHER}" + extra_args+=("-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}") + fi if ((${#extra_args[@]})); then if ! cmake --preset "${PRESET}" "${extra_args[@]}"; then echo "Warning: CMake preset '${PRESET}' failed. Falling back to traditional configure..." From 919b1fe8fecc330cb1a4996446c7bdf4993dff0f Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 15:07:10 -0600 Subject: [PATCH 06/14] Rm old bridge ref --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6ea5d66..e86afb72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,6 @@ jobs: - src/** - include/** - cpp-example-collection/** - - bridge/** - client-sdk-rust/** - cmake/** - scripts/** From 47a8f6c802620a5ea51b16116aab19e4c5f0a0f2 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 15:13:23 -0600 Subject: [PATCH 07/14] Fix duplicate job --- .github/workflows/builds.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 4307546c..9fdeb041 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -30,14 +30,6 @@ env: VCPKG_TARGET_TRIPLET: x64-windows-static-md jobs: - license-check: - name: License Check - uses: ./.github/workflows/license_check.yml - - pin-check: - name: Pin Check - uses: ./.github/workflows/pin_check.yml - build: strategy: fail-fast: false From bcb5bf0b70c6131514b6ab2a50a73178bb106786 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 15:27:54 -0600 Subject: [PATCH 08/14] Better paths-filter --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e86afb72..06f9757b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: docs: ${{ steps.filter.outputs.docs }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3 + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter with: filters: | From 0172a2a8686c5b43dfcaf1fae9c74347aebf37c5 Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 17:02:18 -0600 Subject: [PATCH 09/14] Try smarter FFI cache --- .github/workflows/builds.yml | 34 ++++++++++++++++++++--- .github/workflows/tests.yml | 52 +++++++++++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 9fdeb041..433c63a8 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -145,6 +145,16 @@ jobs: fi # ---------- Cache Cargo ---------- + # The Rust FFI is built from the pinned client-sdk-rust submodule. Its + # compiled output only changes when the submodule commit changes, so we + # key the target cache on the submodule SHA (not Cargo.lock, which is + # pinned inside the submodule and effectively immutable -- that froze the + # old cache permanently). Resolve it once here for the restore/save steps. + - name: Resolve Rust submodule SHA + id: rust_sha + shell: bash + run: echo "sha=$(git -C client-sdk-rust rev-parse HEAD)" >> "$GITHUB_OUTPUT" + - name: Cache cargo registry uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: @@ -156,11 +166,17 @@ jobs: restore-keys: | ${{ runner.os }}-${{ matrix.name }}-cargo-registry- - - name: Cache cargo target - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + # Restore-only here; the matching save runs after a successful build (see + # "Save cargo target" below). A combined actions/cache saves in a post-job + # step that runs AFTER the "Clean after build" cleanup wipes target/debug + # and target/release, which is why the old cache was always ~empty. The + # restore/save split lets us snapshot the populated target/ before cleanup. + - name: Restore cargo target + id: cache-cargo-target + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: client-sdk-rust/target/ - key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ hashFiles('client-sdk-rust/Cargo.lock') }} + key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ steps.rust_sha.outputs.sha }} restore-keys: | ${{ runner.os }}-${{ matrix.name }}-cargo-target- @@ -191,6 +207,18 @@ jobs: shell: bash run: sccache --show-stats || true + # Save the populated target/ now, while the build output still exists and + # before the "Clean after build" step runs clean-all. Only on a miss -- + # an exact-key hit means the cache already holds this submodule's output. + # A restore-keys prefix hit still counts as a miss (cache-hit != 'true'), + # so a submodule bump always refreshes the cache under the new SHA. + - name: Save cargo target + if: steps.cache-cargo-target.outputs.cache-hit != 'true' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: client-sdk-rust/target/ + key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ steps.rust_sha.outputs.sha }} + # ---------- Smoke test cpp-example-collection binaries ---------- # Built under cpp-example-collection-build/ (not build-dir/bin). Visual Studio # multi-config places executables in per-target Release/ (or Debug/) subdirs. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 24f95b0a..e73f0333 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -143,6 +143,16 @@ jobs: fi # ---------- Cache Cargo ---------- + # See builds.yml for the rationale: the Rust FFI output only changes with + # the client-sdk-rust submodule commit, so key the target cache on the + # submodule SHA (Cargo.lock is pinned inside the submodule and froze the + # old cache). Restore/save split so the populated target/ is snapshotted + # right after the build instead of in a post-job step. + - name: Resolve Rust submodule SHA + id: rust_sha + shell: bash + run: echo "sha=$(git -C client-sdk-rust rev-parse HEAD)" >> "$GITHUB_OUTPUT" + - name: Cache cargo registry uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: @@ -154,11 +164,12 @@ jobs: restore-keys: | ${{ runner.os }}-${{ matrix.name }}-cargo-registry- - - name: Cache cargo target - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + - name: Restore cargo target + id: cache-cargo-target + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: client-sdk-rust/target/ - key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ hashFiles('client-sdk-rust/Cargo.lock') }} + key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ steps.rust_sha.outputs.sha }} restore-keys: | ${{ runner.os }}-${{ matrix.name }}-cargo-target- @@ -189,6 +200,18 @@ jobs: shell: bash run: sccache --show-stats || true + # Snapshot the populated target/ after a successful build. Placed before + # the test steps so the Rust FFI output is cached regardless of whether + # the unit/integration tests pass. Only on a miss (exact-key hit means the + # cache already holds this submodule's output; a restore-keys prefix hit + # still counts as a miss and refreshes under the new SHA). + - name: Save cargo target + if: steps.cache-cargo-target.outputs.cache-hit != 'true' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: client-sdk-rust/target/ + key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ steps.rust_sha.outputs.sha }} + # ---------- Run unit tests ---------- - name: Run unit tests (Unix) if: runner.os != 'Windows' @@ -366,6 +389,14 @@ jobs: fi # ---------- Cache Cargo ---------- + # Key the target cache on the client-sdk-rust submodule SHA (see builds.yml + # rationale). Restore/save split so the populated target/ is captured right + # after the build. + - name: Resolve Rust submodule SHA + id: rust_sha + shell: bash + run: echo "sha=$(git -C client-sdk-rust rev-parse HEAD)" >> "$GITHUB_OUTPUT" + - name: Cache cargo registry uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: @@ -377,11 +408,12 @@ jobs: restore-keys: | ${{ runner.os }}-coverage-cargo-registry- - - name: Cache cargo target - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + - name: Restore cargo target + id: cache-cargo-target + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: client-sdk-rust/target/ - key: ${{ runner.os }}-coverage-cargo-target-${{ hashFiles('client-sdk-rust/Cargo.lock') }} + key: ${{ runner.os }}-coverage-cargo-target-${{ steps.rust_sha.outputs.sha }} restore-keys: | ${{ runner.os }}-coverage-cargo-target- @@ -410,6 +442,14 @@ jobs: - name: Build run: cmake --build build-debug --parallel 2 + # Snapshot the populated target/ after the build succeeds, only on a miss. + - name: Save cargo target + if: steps.cache-cargo-target.outputs.cache-hit != 'true' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: client-sdk-rust/target/ + key: ${{ runner.os }}-coverage-cargo-target-${{ steps.rust_sha.outputs.sha }} + # ---------- Run unit tests ---------- - name: Run unit tests timeout-minutes: 5 From aebaa8baf11598a35b54e234660c3a731a7061bf Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 17:19:39 -0600 Subject: [PATCH 10/14] Whitespace to trigger second run for cache test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06f9757b..61721136 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ name: CI # Children that get skipped via `if:` count as "skipped" in the CI rollup # (not "failed"), so the parent "CI" status reaches SUCCESS and branch # protection is satisfied. Push-to-main and manual dispatch always run -# everything regardless of changed paths. +# everything regardless of changed paths. on: workflow_dispatch: From 4461e5e2df40e4db2a6ec11774ffb4340a5e3ecf Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 18:35:34 -0600 Subject: [PATCH 11/14] Remove sccache, free disk space on docker runs --- .github/workflows/builds.yml | 69 ++++++++++++++---------------- .github/workflows/make-release.yml | 28 ++++-------- .github/workflows/tests.yml | 55 ++---------------------- 3 files changed, 45 insertions(+), 107 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index b40cdd82..2b84c433 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -13,14 +13,10 @@ permissions: env: CARGO_TERM_COLOR: always - # sccache caches rustc invocations across runs. RUSTC_WRAPPER is exported - # only after the setup step verifies the GHA cache backend is reachable, so - # transient cache backend failures fall back to uncached rustc instead of - # failing the build. - SCCACHE_GHA_ENABLED: "true" - # sccache cannot cache Cargo incremental artifacts; without this, sccache - # reports ~0% utilization even on cache misses. Disabling incremental lets - # sccache cache per-crate compilations whenever the cargo target cache misses. + # Disable Cargo incremental artifacts. The Rust FFI is built once per + # submodule SHA and cached whole (see the cargo target cache below), so + # incremental dirs add nothing but bloat the cached target/ directory and + # slow the cache upload/download. CARGO_INCREMENTAL: "0" # Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection) CPP_EXAMPLE_COLLECTION_REF: bbf0fdf72dac2239117213475449565686f8c58b @@ -120,30 +116,6 @@ jobs: if: matrix.name == 'macos-x64' run: rustup target add x86_64-apple-darwin - # ---------- Setup sccache ---------- - - name: Setup sccache - uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7 - - - name: Enable sccache wrapping (probe first) - shell: bash - run: | - if sccache --start-server >/dev/null 2>&1; then - echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - # Wire sccache into the C++ build too (the dominant build cost), - # but only where the generator honors compiler launchers. The - # Windows preset uses the Visual Studio generator, which ignores - # CMAKE__COMPILER_LAUNCHER, so we skip it there. - if [[ "$RUNNER_OS" != "Windows" ]]; then - echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" - echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled for rustc + C/C++ (RUSTC_WRAPPER + CMAKE_*_COMPILER_LAUNCHER)" - else - echo "::notice::sccache enabled for rustc only (Windows VS generator ignores compiler launchers)" - fi - else - echo "::warning::sccache backend unreachable; building without compile cache this run" - fi - # ---------- Cache Cargo ---------- # The Rust FFI is built from the pinned client-sdk-rust submodule. Its # compiled output only changes when the submodule commit changes, so we @@ -202,11 +174,6 @@ jobs: shell: pwsh run: ${{ matrix.build_cmd }} - - name: sccache stats - if: always() - shell: bash - run: sccache --show-stats || true - # Save the populated target/ now, while the build output still exists and # before the "Clean after build" step runs clean-all. Only on a miss -- # an exact-key hit means the cache already holds this submodule's output. @@ -508,6 +475,20 @@ jobs: if: github.event_name == 'pull_request' steps: + # Reclaim ~30GB before loading the multi-GB SDK image and building the + # example collection inside it. Mirrors the docker-build jobs; without it + # the x64 collection build has hit "no space left on device". + - name: Free disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + - name: Download Docker image artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: @@ -535,6 +516,20 @@ jobs: if: github.event_name == 'pull_request' steps: + # Reclaim ~30GB before loading the multi-GB SDK image and building the + # example collection inside it. The standard ubuntu-latest runner has hit + # "no space left on device" here without this step. + - name: Free disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + - name: Download Docker image artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index bfce4586..09dc275e 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -13,11 +13,6 @@ on: env: CARGO_TERM_COLOR: always - # sccache caches rustc invocations across runs. RUSTC_WRAPPER is exported - # only after the setup step verifies the GHA cache backend is reachable, so - # transient cache backend failures fall back to uncached rustc instead of - # failing the build. - SCCACHE_GHA_ENABLED: "true" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_DEFAULT_TRIPLET: x64-windows-static-md VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static-md @@ -140,21 +135,16 @@ jobs: if: matrix.name == 'macos-x64' run: rustup target add x86_64-apple-darwin - # ---------- Setup sccache ---------- - - name: Setup sccache - uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7 - - - name: Enable sccache wrapping (probe first) + # ---------- Cache Cargo ---------- + # Key the target cache on the client-sdk-rust submodule SHA (Cargo.lock is + # pinned inside the submodule and froze the old cache). No clean step runs + # in this workflow, so the combined actions/cache post-job save captures + # the populated target/ correctly once the key busts on a submodule bump. + - name: Resolve Rust submodule SHA + id: rust_sha shell: bash - run: | - if sccache --start-server >/dev/null 2>&1; then - echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled (RUSTC_WRAPPER=sccache)" - else - echo "::warning::sccache backend unreachable; building without compile cache this run" - fi + run: echo "sha=$(git -C client-sdk-rust rev-parse HEAD)" >> "$GITHUB_OUTPUT" - # ---------- Cache Cargo ---------- - name: Cache Cargo registry uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: @@ -170,7 +160,7 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: client-sdk-rust/target/ - key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ hashFiles('client-sdk-rust/Cargo.lock') }} + key: ${{ runner.os }}-${{ matrix.name }}-cargo-target-${{ steps.rust_sha.outputs.sha }} restore-keys: | ${{ runner.os }}-${{ matrix.name }}-cargo-target- diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e73f0333..248736e9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,14 +13,10 @@ permissions: env: CARGO_TERM_COLOR: always - # sccache caches rustc invocations across runs. RUSTC_WRAPPER is exported - # only after the setup step verifies the GHA cache backend is reachable, so - # transient cache backend failures fall back to uncached rustc instead of - # failing the build. - SCCACHE_GHA_ENABLED: "true" - # sccache cannot cache Cargo incremental artifacts; without this, sccache - # reports ~0% utilization even on cache misses. Disabling incremental lets - # sccache cache per-crate compilations whenever the cargo target cache misses. + # Disable Cargo incremental artifacts. The Rust FFI is built once per + # submodule SHA and cached whole (see the cargo target cache below), so + # incremental dirs add nothing but bloat the cached target/ directory and + # slow the cache upload/download. CARGO_INCREMENTAL: "0" # vcpkg binary caching for Windows (mirrors builds.yml) VCPKG_DEFAULT_TRIPLET: x64-windows-static-md @@ -118,30 +114,6 @@ jobs: if: matrix.name == 'macos-x64' run: rustup target add x86_64-apple-darwin - # ---------- Setup sccache ---------- - - name: Setup sccache - uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7 - - - name: Enable sccache wrapping (probe first) - shell: bash - run: | - if sccache --start-server >/dev/null 2>&1; then - echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - # Wire sccache into the C++ build too (the dominant build cost), - # but only where the generator honors compiler launchers. The - # Windows preset uses the Visual Studio generator, which ignores - # CMAKE__COMPILER_LAUNCHER, so we skip it there. - if [[ "$RUNNER_OS" != "Windows" ]]; then - echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" - echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled for rustc + C/C++ (RUSTC_WRAPPER + CMAKE_*_COMPILER_LAUNCHER)" - else - echo "::notice::sccache enabled for rustc only (Windows VS generator ignores compiler launchers)" - fi - else - echo "::warning::sccache backend unreachable; building without compile cache this run" - fi - # ---------- Cache Cargo ---------- # See builds.yml for the rationale: the Rust FFI output only changes with # the client-sdk-rust submodule commit, so key the target cache on the @@ -195,11 +167,6 @@ jobs: shell: pwsh run: ${{ matrix.build_cmd }} - - name: sccache stats - if: always() - shell: bash - run: sccache --show-stats || true - # Snapshot the populated target/ after a successful build. Placed before # the test steps so the Rust FFI output is cached regardless of whether # the unit/integration tests pass. Only on a miss (exact-key hit means the @@ -374,20 +341,6 @@ jobs: with: toolchain: stable - # ---------- Setup sccache ---------- - - name: Setup sccache - uses: mozilla-actions/sccache-action@054db53350805f83040bf3e6e9b8cf5a139aa7c9 # v0.0.7 - - - name: Enable sccache wrapping (probe first) - shell: bash - run: | - if sccache --start-server >/dev/null 2>&1; then - echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - echo "::notice::sccache enabled (RUSTC_WRAPPER=sccache)" - else - echo "::warning::sccache backend unreachable; building without compile cache this run" - fi - # ---------- Cache Cargo ---------- # Key the target cache on the client-sdk-rust submodule SHA (see builds.yml # rationale). Restore/save split so the populated target/ is captured right From 76c421bb1a13f41968743ff2318878f48ffa926d Mon Sep 17 00:00:00 2001 From: Alan George Date: Mon, 1 Jun 2026 19:20:04 -0600 Subject: [PATCH 12/14] try windows cacheing --- .github/workflows/builds.yml | 7 ++++++- .github/workflows/tests.yml | 6 +++++- build.cmd | 32 ++++++++++++++++++++++++++++++++ build.sh | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 2b84c433..91135238 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -20,7 +20,12 @@ env: CARGO_INCREMENTAL: "0" # Pinned commit for cpp-example-collection smoke build (https://github.com/livekit-examples/cpp-example-collection) CPP_EXAMPLE_COLLECTION_REF: bbf0fdf72dac2239117213475449565686f8c58b - # vcpkg binary caching for Windows + # vcpkg binary caching for Windows. x-gha stores compiled vcpkg packages + # (protobuf, abseil, spdlog, ...) in the GitHub Actions cache, so the + # Windows manifest-mode dep build is skipped on a warm cache. `clear` + # drops the default (source-only) provider first. Requires the + # ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN export step below. + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_DEFAULT_TRIPLET: x64-windows-static-md VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static-md VCPKG_TARGET_TRIPLET: x64-windows-static-md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 248736e9..ec2b55ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,11 @@ env: # incremental dirs add nothing but bloat the cached target/ directory and # slow the cache upload/download. CARGO_INCREMENTAL: "0" - # vcpkg binary caching for Windows (mirrors builds.yml) + # vcpkg binary caching for Windows (mirrors builds.yml). x-gha stores + # compiled vcpkg packages in the GitHub Actions cache so the Windows + # manifest-mode dep build is skipped on a warm cache. Requires the + # ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN export step below. + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_DEFAULT_TRIPLET: x64-windows-static-md VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static-md VCPKG_TARGET_TRIPLET: x64-windows-static-md diff --git a/build.cmd b/build.cmd index 76c1ac57..1598dbbc 100644 --- a/build.cmd +++ b/build.cmd @@ -202,6 +202,7 @@ echo build.cmd clean-all goto :eof :configure_build +call :now _T_CONFIG_START echo ==^> Configuring CMake (%BUILD_TYPE%)... if not defined VCPKG_ROOT ( echo Warning: VCPKG_ROOT is not set. Attempting to configure without vcpkg... @@ -213,6 +214,7 @@ if errorlevel 1 ( echo Configuration failed! exit /b 1 ) +call :elapsed "configure (%PRESET%)" %_T_CONFIG_START% goto build_only :build_only @@ -225,15 +227,45 @@ if not defined BUILD_PARALLEL_JOBS ( set "BUILD_PARALLEL_JOBS=2" ) ) +call :now _T_BUILD_START echo ==^> Building (%BUILD_TYPE%) with %BUILD_PARALLEL_JOBS% parallel jobs... + +rem Optional Rust-FFI-first split: build the build_rust_ffi target on its own +rem first so CI logs report the Rust dep/FFI compile time separately from the +rem C++ compile time. Off by default (single build invocation). +if "%LK_BUILD_TIMING%"=="1" ( + call :now _T_FFI_START + echo ==^> [timing] Building Rust FFI target ^(build_rust_ffi^) first... + cmake --build "%BUILD_DIR%" --config %BUILD_TYPE% --parallel "%BUILD_PARALLEL_JOBS%" --target build_rust_ffi + if errorlevel 1 ( + echo Build failed! + exit /b 1 + ) + call :elapsed "build: Rust FFI (build_rust_ffi)" %_T_FFI_START% +) + cmake --build "%BUILD_DIR%" --config %BUILD_TYPE% --parallel "%BUILD_PARALLEL_JOBS%" if errorlevel 1 ( echo Build failed! exit /b 1 ) +call :elapsed "build total (%PRESET%)" %_T_BUILD_START% echo ==^> Build complete! goto :eof +rem ---- timing helpers ---- +rem :now -> sets to current Unix epoch seconds. +:now +for /f %%s in ('powershell -NoProfile -Command "[int][double]::Parse((Get-Date -UFormat %%s))"') do set "%~1=%%s" +goto :eof + +rem :elapsed "