99 # `labeled` lets the `ci-run-qemu` label start a run. Every job except changes/cross-qemu is
1010 # gated on `github.event.action != 'labeled'`, so labeling re-runs only QEMU, not the whole suite.
1111 types : [opened, synchronize, reopened, labeled]
12+ # Manual "Run workflow" from the Actions tab. The changes job's fail-safe (unreachable base) runs
13+ # the full suite including QEMU on a dispatch, which is what you want from a manual trigger.
14+ workflow_dispatch :
1215
1316# Least privilege: every job here only reads the repo (checkout, build, test, cache). Jobs that
1417# need more should add a narrower job-level permissions block rather than widening this default.
1518permissions :
1619 contents : read
1720
21+ concurrency :
22+ group : ${{ github.workflow }}-${{ github.ref }}
23+ # Don't cancel an in-progress run when a label is added. The `labeled` trigger fires for every
24+ # label (it exists so `ci-run-qemu` can force a QEMU run), and a labeled event shares this group,
25+ # so cancelling here would kill a running suite just because the PR was labeled. New commits
26+ # (push/synchronize) still supersede a run.
27+ cancel-in-progress : ${{ github.event.action != 'labeled' }}
28+
1829jobs :
1930 pre-commit :
2031 name : Pre-commit checks
2132 # Skip on label-only events so labeling runs only the QEMU job (see pull_request types).
2233 if : github.event.action != 'labeled'
2334 runs-on : ubuntu-latest
35+ timeout-minutes : 10
2436 steps :
2537 - uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
2638
3446 name : Static analysis
3547 if : github.event.action != 'labeled'
3648 runs-on : ubuntu-latest
49+ timeout-minutes : 15
3750 steps :
3851 - uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
3952 with :
@@ -42,22 +55,41 @@ jobs:
4255 - name : Install clang-tidy
4356 run : |
4457 sudo apt-get update
45- sudo apt-get install -y clang-tidy
58+ sudo apt-get install -y clang-tidy-18
4659
4760 - name : Run clang-tidy
61+ env :
62+ CLANG_TIDY : clang-tidy-18
4863 run : ./script/clang-tidy.sh
4964
5065 build :
5166 name : Build
5267 if : github.event.action != 'labeled'
5368 runs-on : ubuntu-latest
69+ timeout-minutes : 15
70+ # libopus is ~260 .c files rebuilt from scratch in every host job; ccache reuses objects across runs.
71+ env :
72+ CCACHE_DIR : ${{ github.workspace }}/.ccache
5473 steps :
5574 - uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
5675 with :
5776 submodules : recursive
5877
78+ - name : Install ccache
79+ run : sudo apt-get update && sudo apt-get install -y ccache
80+
81+ - name : Cache ccache
82+ uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
83+ with :
84+ path : ${{ github.workspace }}/.ccache
85+ key : ccache-build-${{ github.sha }}
86+ restore-keys : ccache-build-
87+
5988 - name : Configure CMake
60- run : cmake -B build host_examples/opus_to_wav
89+ run : >
90+ cmake -B build
91+ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
92+ host_examples/opus_to_wav
6193
6294 - name : Build
6395 run : cmake --build build
6698 name : Unit tests (${{ matrix.alloc_mode }})
6799 if : github.event.action != 'labeled'
68100 runs-on : ubuntu-latest
101+ timeout-minutes : 20
102+ env :
103+ CCACHE_DIR : ${{ github.workspace }}/.ccache
69104 strategy :
70105 fail-fast : false
71106 matrix :
@@ -77,9 +112,20 @@ jobs:
77112 with :
78113 submodules : recursive
79114
115+ - name : Install ccache
116+ run : sudo apt-get update && sudo apt-get install -y ccache
117+
118+ - name : Cache ccache
119+ uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
120+ with :
121+ path : ${{ github.workspace }}/.ccache
122+ key : ccache-test-${{ matrix.alloc_mode }}-${{ github.sha }}
123+ restore-keys : ccache-test-${{ matrix.alloc_mode }}-
124+
80125 - name : Configure CMake with sanitizers
81126 run : >
82127 cmake -B build -DENABLE_SANITIZERS=ON
128+ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
83129 -DOPUS_ALLOCATION_MODE=${{ matrix.alloc_mode }} tests
84130
85131 - name : Build
@@ -92,11 +138,24 @@ jobs:
92138 name : opus_compare conformance
93139 if : github.event.action != 'labeled'
94140 runs-on : ubuntu-latest
141+ timeout-minutes : 30
142+ env :
143+ CCACHE_DIR : ${{ github.workspace }}/.ccache
95144 steps :
96145 - uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
97146 with :
98147 submodules : recursive
99148
149+ - name : Install ccache
150+ run : sudo apt-get update && sudo apt-get install -y ccache
151+
152+ - name : Cache ccache
153+ uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
154+ with :
155+ path : ${{ github.workspace }}/.ccache
156+ key : ccache-conformance-${{ github.sha }}
157+ restore-keys : ccache-conformance-
158+
100159 # The RFC 8251 test vectors are static; cache them so we don't re-download every run. Keying on
101160 # the fetch script means the cache auto-invalidates if the vector URL ever changes.
102161 - name : Cache test vectors
@@ -111,7 +170,10 @@ jobs:
111170 run : tests/fetch_vectors.sh
112171
113172 - name : Configure CMake
114- run : cmake -B build tests
173+ run : >
174+ cmake -B build
175+ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
176+ tests
115177
116178 - name : Build
117179 run : cmake --build build
@@ -128,6 +190,7 @@ jobs:
128190 changes :
129191 name : Detect on-target changes
130192 runs-on : ubuntu-latest
193+ timeout-minutes : 10
131194 outputs :
132195 target : ${{ steps.filter.outputs.target }}
133196 steps :
@@ -162,11 +225,13 @@ jobs:
162225 cross-qemu :
163226 name : Cross (Xtensa QEMU conformance, ${{ matrix.variant }})
164227 needs : changes
165- # Skip on wrapper-only changes. Add the `ci-run-qemu` label to force a run.
228+ # Skip on wrapper-only changes. Add the `ci-run-qemu` label to force a run. On a `labeled` event
229+ # only that label triggers QEMU, so adding an unrelated label doesn't kick off a redundant run.
166230 if : >
167- needs.changes.outputs.target == 'true' ||
231+ (github.event.action != 'labeled' && needs.changes.outputs.target == 'true') ||
168232 contains(github.event.pull_request.labels.*.name, 'ci-run-qemu')
169233 runs-on : ubuntu-latest
234+ timeout-minutes : 40
170235 # esp-idf version matches the PlatformIO framework (esp-idf v5.4.x) so the
171236 # xtensa-gcc codegen is the same as the local run. Xtensa emulation is
172237 # native-speed on the amd64 runner.
@@ -240,6 +305,7 @@ jobs:
240305 name : PlatformIO Build
241306 if : github.event.action != 'labeled'
242307 runs-on : ubuntu-latest
308+ timeout-minutes : 30
243309 strategy :
244310 matrix :
245311 example : [decode_benchmark, encode_benchmark]
@@ -267,3 +333,31 @@ jobs:
267333
268334 - name : Build ${{ matrix.example }}
269335 run : pio run -d examples/${{ matrix.example }}
336+
337+ # Single aggregating status for branch protection: require only "CI success" instead of every job.
338+ # always() so it reports even when a job fails or the conditional cross-qemu job is skipped; skipped
339+ # dependencies are treated as passing (only failure/cancelled blocks).
340+ ci-success :
341+ name : CI success
342+ if : always()
343+ needs :
344+ - pre-commit
345+ - lint
346+ - build
347+ - test
348+ - conformance
349+ - changes
350+ - cross-qemu
351+ - platformio
352+ runs-on : ubuntu-latest
353+ timeout-minutes : 5
354+ steps :
355+ - name : Fail if any required job failed or was cancelled
356+ if : ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
357+ run : |
358+ echo "One or more CI jobs failed or were cancelled:"
359+ echo '${{ toJSON(needs) }}'
360+ exit 1
361+
362+ - name : Report success
363+ run : echo "All required CI jobs passed (skipped jobs are allowed)."
0 commit comments