Skip to content

Add the shared pull-request template (taphouse sync) #208

Add the shared pull-request template (taphouse sync)

Add the shared pull-request template (taphouse sync) #208

Workflow file for this run

name: CI
on:
push:
pull_request:
concurrency:
# Superseded pushes cancel their own in-flight runs: the ratchet stack
# (three QEMU targets, a toolchain download, minutes of runner time) was
# piling up once per push during baseline harvests.
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Linux GCC
os: ubuntu-latest
cc: gcc
cxx: g++
werror: ON
- name: Linux Clang
os: ubuntu-latest
cc: clang
cxx: clang++
werror: ON
- name: macOS AppleClang
os: macos-latest
werror: ON
# Warnings stay non-fatal on MSVC until /W4 output has been
# triaged (docs/PERFORMANCE.md "Known debt").
- name: Windows MSVC
os: windows-latest
werror: OFF
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Configure
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=Release
-DSRT_WERROR=${{ matrix.werror }}
- name: Build
run: cmake --build build --config Release -j 4
- name: Test
run: ctest --test-dir build -C Release --output-on-failure
sanitizers:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: ASan + UBSan
flags: -fsanitize=address,undefined -fno-sanitize-recover=all
- name: TSan
flags: -fsanitize=thread
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Configure
env:
CC: clang
CXX: clang++
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DSRT_BUILD_EXAMPLES=OFF
-DCMAKE_CXX_FLAGS="${{ matrix.flags }}"
- name: Build
run: cmake --build build -j 4
- name: Test
env:
TSAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1
run: ctest --test-dir build --output-on-failure
# Cross-compile for Qualcomm Hexagon (Linux/musl) with the open-source
# toolchain and run a subset of the suite under qemu-hexagon user-mode
# emulation. Validates ISA-level correctness on a 32-bit audio DSP target
# (size_t width, atomics lowering, musl libc, soft-float doubles); the
# long-running quality/lock simulations and the 10M-element thread stress
# are excluded — they prove DSP math and concurrency, which emulation
# neither speeds up nor measures meaningfully.
hexagon-qemu:
name: Hexagon cross (QEMU)
runs-on: ubuntu-latest
timeout-minutes: 45
env:
# Prebuilt open-source toolchain (BSD-3) published by Qualcomm/Quicinc;
# binary artifacts are hosted on CodeLinaro and linked from the
# quic/toolchain_for_hexagon release notes.
HEXAGON_TOOLCHAIN_URL: https://artifacts.codelinaro.org/artifactory/codelinaro-toolchain-for-hexagon/19.1.5/clang+llvm-19.1.5-cross-hexagon-unknown-linux-musl.tar.zst
# Hard pin, taken from the "toolchain sha256:" line of run #13 (which
# also matched the published SHA256SUMS).
HEXAGON_TOOLCHAIN_SHA256: "55b41922318f6331590ab7baa7f5dbdd99c109327a9c44a52c5e9878fab148c1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Cache toolchain
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/hexagon
# Keyed on the pinned digest: every job that can write this key
# verifies its download against the same pin, so no unverified
# writer can poison the trusted entry.
key: hexagon-toolchain-${{ env.HEXAGON_TOOLCHAIN_SHA256 }}-1
- name: Download toolchain
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p ~/hexagon && cd ~/hexagon
curl -sfLo toolchain.tar.zst "$HEXAGON_TOOLCHAIN_URL"
# Integrity check against the published SHA256SUMS, plus the hard
# pin when set. The SUMS file catches corruption and
# cache-poisoning; only the pin catches an origin compromise.
curl -sfLo SHA256SUMS "$(dirname "$HEXAGON_TOOLCHAIN_URL")/SHA256SUMS"
expected=$(grep "$(basename "$HEXAGON_TOOLCHAIN_URL")" SHA256SUMS | awk '{print $1}' | head -1)
actual=$(sha256sum toolchain.tar.zst | cut -d' ' -f1)
echo "toolchain sha256: $actual (pin this in HEXAGON_TOOLCHAIN_SHA256)"
if [ -z "$expected" ] || [ "$actual" != "$expected" ]; then
echo "::error::toolchain does not match published SHA256SUMS"; exit 1
fi
if [ -n "${HEXAGON_TOOLCHAIN_SHA256:-}" ] && \
[ "$actual" != "$HEXAGON_TOOLCHAIN_SHA256" ]; then
echo "::error::toolchain checksum mismatch against pinned value"; exit 1
fi
tar --zstd -xf toolchain.tar.zst
rm toolchain.tar.zst SHA256SUMS
- name: Set up toolchain and QEMU paths
run: |
# No -type f (symlinks count); dirname of an empty find result is
# ".", so assert on the find output itself.
clangxx=$(find "$HOME/hexagon" -name 'hexagon-unknown-linux-musl-clang++' | head -1)
test -n "$clangxx"
echo "$(dirname "$clangxx")" >> "$GITHUB_PATH"
# Prefer a qemu-hexagon bundled with the toolchain; else use distro qemu.
qemu=$(find "$HOME/hexagon" -name 'qemu-hexagon' -type f | head -1 || true)
if [ -n "$qemu" ]; then
echo "$(dirname "$qemu")" >> "$GITHUB_PATH"
else
sudo apt-get update -q && sudo apt-get install -y -q qemu-user
fi
- name: Verify tools
run: |
hexagon-unknown-linux-musl-clang++ --version
qemu-hexagon --version
- name: Configure
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=cmake/hexagon-linux-musl.cmake
-DSRT_BUILD_EXAMPLES=OFF
- name: Build
run: cmake --build build -j 4
- name: Test under emulation
run: >
ctest --test-dir build --output-on-failure
-E 'AsrcQuality|AsrcLock|TwoThreadStress|TransparentPrototypeMeetsSpec|MultiChannel\.|Feasibility|Reset\.|ConfigValidation'
# ConfigValidation: this static-musl toolchain cannot unwind across
# frames — the constructor throws correctly but EXPECT_THROW never
# catches and libc++abi terminates. Validation is target-independent
# and covered on every other leg; limitation tracked in
# docs/PERFORMANCE.md "Known debt".
# Cross-compile for Arm Cortex-M55 (bare metal, newlib + semihosting) and
# run the emulation-sized test subset on QEMU's MPS3 AN547 board model.
# Validates the library on a 32-bit MCU-class target with no OS, no
# threads and no double-precision FPU; the fixed-point datapaths are the
# performance-appropriate formats here.
cortex-m55-qemu:
name: Cortex-M55 cross (QEMU)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Install toolchain and QEMU
run: >
sudo apt-get update -q &&
sudo apt-get install -y -q gcc-arm-none-eabi qemu-system-arm
- name: Configure
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=MinSizeRel
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m55-mps3.cmake
-DSRT_BUILD_EXAMPLES=OFF
- name: Build
run: cmake --build build -j 4
- name: Test under emulation
run: ctest --test-dir build --output-on-failure
# Cortex-M33 (Raspberry Pi Pico 2 / RP2350 class: single-precision FPU,
# no FP64, no MVE) on QEMU's MPS2+ AN505 model. Shares the Armv8-M
# startup with the M55 target; quantifies the soft-double float path and
# anchors the Q15/Q31 budgets for Pico-class parts.
cortex-m33-qemu:
name: Cortex-M33 cross (QEMU)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Install toolchain and QEMU
run: >
sudo apt-get update -q &&
sudo apt-get install -y -q gcc-arm-none-eabi qemu-system-arm
- name: Configure
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=MinSizeRel
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m33-mps2.cmake
-DSRT_BUILD_EXAMPLES=OFF
- name: Build
run: cmake --build build -j 4
- name: Test under emulation
run: ctest --test-dir build --output-on-failure
# ------------------------------------------------------------------------
# Template: genuine Tensilica HiFi4/HiFi5 coverage. The HiFi audio ISA,
# xt-clang compiler and xt-run instruction-set simulator are proprietary
# (FlexLM-licensed Cadence tools), so this must run on a self-hosted
# runner with access to the license server. Uncomment and adapt once such
# a runner is registered (suggested labels: [self-hosted, cadence-xtensa]).
#
# hifi-iss:
# name: HiFi ISS (self-hosted)
# runs-on: [self-hosted, cadence-xtensa]
# env:
# XTENSA_CORE: <your HiFi core name>
# steps:
# - uses: actions/checkout@v4
# - name: Configure
# run: >
# cmake -B build
# -DCMAKE_BUILD_TYPE=Release
# -DCMAKE_SYSTEM_NAME=Generic
# -DCMAKE_C_COMPILER=xt-clang
# -DCMAKE_CXX_COMPILER=xt-clang++
# "-DCMAKE_CROSSCOMPILING_EMULATOR=xt-run;--xtensa-core=$XTENSA_CORE"
# -DSRT_BUILD_EXAMPLES=OFF
# - name: Build
# run: cmake --build build -j
# - name: Test on ISS
# run: >
# ctest --test-dir build --output-on-failure
# -E 'AsrcQuality|AsrcLock|TwoThreadStress|TransparentPrototypeMeetsSpec'
# ------------------------------------------------------------------------
# Deterministic instruction-count ratchet (docs/PERFORMANCE.md): fixed
# workloads under QEMU with a counting plugin, gated against
# bench/baselines.json. Unlike wall-clock numbers these are noise-free,
# so a hard >3% gate is safe on shared runners.
icount-ratchet:
name: Instruction-count ratchet
runs-on: ubuntu-latest
timeout-minutes: 45
env:
# Commit the v8.2.2 tag pointed at when pinned (tags are movable;
# commit SHAs are not), with the header's digest verified on download.
QEMU_PLUGIN_HEADER_URL: https://raw.githubusercontent.com/qemu/qemu/11aa0b1ff115b86160c4d37e7c37e6a6b13b77ea/include/qemu/qemu-plugin.h
QEMU_PLUGIN_HEADER_SHA256: "c53a2af163e80e3f4bc6c60dbdfc84003db329d757e37cd8a16a77e1d82606ff"
QEMU_SRC_URL: https://download.qemu.org/qemu-8.2.2.tar.xz
# Hard pin from the "qemu source sha256:" line of run #24.
QEMU_SRC_SHA256: "847346c1b82c1a54b2c38f6edbd85549edeb17430b7d4d3da12620e2962bc4f3"
HEXAGON_TOOLCHAIN_URL: https://artifacts.codelinaro.org/artifactory/codelinaro-toolchain-for-hexagon/19.1.5/clang+llvm-19.1.5-cross-hexagon-unknown-linux-musl.tar.zst
# Same hard pin as the hexagon-qemu job: this job also writes the
# shared toolchain cache, so it must verify against the same digest.
HEXAGON_TOOLCHAIN_SHA256: "55b41922318f6331590ab7baa7f5dbdd99c109327a9c44a52c5e9878fab148c1"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Install toolchains and QEMU
run: >
sudo apt-get update -q &&
sudo apt-get install -y -q gcc-arm-none-eabi qemu-system-arm
libglib2.0-dev ninja-build meson flex bison
- name: Build counting plugin
run: |
curl -sfLo /tmp/qemu-plugin.h "$QEMU_PLUGIN_HEADER_URL"
actual=$(sha256sum /tmp/qemu-plugin.h | cut -d' ' -f1)
if [ "$actual" != "$QEMU_PLUGIN_HEADER_SHA256" ]; then
echo "::error::qemu-plugin.h checksum mismatch"; exit 1
fi
gcc -shared -fPIC $(pkg-config --cflags glib-2.0) -I/tmp \
-o /tmp/libinsncount.so tools/qemu_insn_plugin/insn_count.c
# Release (-O2), matching how the baselines were recorded.
- name: Build M55 workloads
run: >
cmake -B build-m55
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m55-mps3.cmake
-DSRT_BUILD_TESTS=OFF -DSRT_BUILD_EXAMPLES=OFF
-DSRT_BUILD_ICOUNT_BENCH=ON
&& cmake --build build-m55 -j 4
- name: Ratchet M55
run: >
python3 scripts/icount.py --target m55
--build-dir build-m55 --plugin /tmp/libinsncount.so
- name: Build M33 workloads
if: ${{ !cancelled() }}
run: >
cmake -B build-m33
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m33-mps2.cmake
-DSRT_BUILD_TESTS=OFF -DSRT_BUILD_EXAMPLES=OFF
-DSRT_BUILD_ICOUNT_BENCH=ON
&& cmake --build build-m33 -j 4
- name: Ratchet M33
if: ${{ !cancelled() }}
run: >
python3 scripts/icount.py --target m33
--build-dir build-m33 --plugin /tmp/libinsncount.so
# Neither Debian's nor the CodeLinaro toolchain's qemu-hexagon enables
# TCG plugins, so the Hexagon leg builds its own from the pinned QEMU
# release (linux-user target only, ~4 min, cached thereafter).
# The remaining ratchet steps run even if an earlier target failed
# (each target's numbers are independent evidence; stopping at the
# first failure forces a serial harvest when baselines legitimately
# move). The job still fails if any step failed.
- name: Cache plugin-enabled qemu-hexagon
if: ${{ !cancelled() }}
id: qemu-hex
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/qemu-hexagon-plugins
key: qemu-hexagon-plugins-${{ env.QEMU_SRC_URL }}-1
- name: Build plugin-enabled qemu-hexagon
if: ${{ !cancelled() && steps.qemu-hex.outputs.cache-hit != 'true' }}
run: |
curl -sfLo /tmp/qemu-src.tar.xz "$QEMU_SRC_URL"
actual=$(sha256sum /tmp/qemu-src.tar.xz | cut -d' ' -f1)
echo "qemu source sha256: $actual (pin this in QEMU_SRC_SHA256)"
if [ -n "${QEMU_SRC_SHA256:-}" ] && [ "$actual" != "$QEMU_SRC_SHA256" ]; then
echo "::error::qemu source checksum mismatch"; exit 1
fi
tar -xJf /tmp/qemu-src.tar.xz -C /tmp
cd /tmp/qemu-*/
./configure --target-list=hexagon-linux-user --enable-plugins \
--disable-docs --disable-tools --disable-system
ninja -C build qemu-hexagon
mkdir -p ~/qemu-hexagon-plugins
cp build/qemu-hexagon ~/qemu-hexagon-plugins/
- name: Cache Hexagon toolchain
if: ${{ !cancelled() }}
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/hexagon
# Same digest-keyed name as the hexagon-qemu job; the download
# below verifies the same pin before anything is saved under it.
key: hexagon-toolchain-${{ env.HEXAGON_TOOLCHAIN_SHA256 }}-1
- name: Ratchet Hexagon
if: ${{ !cancelled() }}
run: |
if [ "${{ steps.cache.outputs.cache-hit }}" != "true" ]; then
mkdir -p ~/hexagon && cd ~/hexagon
curl -sfLo toolchain.tar.zst "$HEXAGON_TOOLCHAIN_URL"
actual=$(sha256sum toolchain.tar.zst | cut -d' ' -f1)
if [ "$actual" != "$HEXAGON_TOOLCHAIN_SHA256" ]; then
echo "::error::toolchain checksum mismatch against pinned value"
exit 1
fi
tar --zstd -xf toolchain.tar.zst && rm toolchain.tar.zst
cd "$GITHUB_WORKSPACE"
fi
# No -type f: the compiler may be a symlink in the restored tree.
clangxx=$(find "$HOME/hexagon" -name 'hexagon-unknown-linux-musl-clang++' | head -1)
if [ -z "$clangxx" ]; then
echo "contents of ~/hexagon:"
ls -la "$HOME/hexagon" 2>&1 | head -20
find "$HOME/hexagon" -maxdepth 3 | head -40
echo "::error::hexagon cross compiler not found under ~/hexagon"
exit 1
fi
bindir=$(dirname "$clangxx")
export PATH="$HOME/qemu-hexagon-plugins:$bindir:$PATH"
test -x "$HOME/qemu-hexagon-plugins/qemu-hexagon" || {
echo "::error::plugin-enabled qemu-hexagon missing"; exit 1; }
# qemu exits 1 with or without plugin support here (no guest binary
# was given), so probe by the error text, not the exit code.
if qemu-hexagon -plugin help 2>&1 | grep -q "unknown option"; then
echo "::error::built qemu-hexagon lacks plugin support"; exit 1
fi
cmake -B build-hex -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=cmake/hexagon-linux-musl.cmake \
-DSRT_BUILD_TESTS=OFF -DSRT_BUILD_EXAMPLES=OFF \
-DSRT_BUILD_ICOUNT_BENCH=ON
cmake --build build-hex -j 4
python3 scripts/icount.py --target hexagon \
--build-dir build-hex --plugin /tmp/libinsncount.so
# The README instruction-count table derives 1:1 from the committed
# baselines; regenerating it must produce no diff.
- name: Docs freshness
run: |
python3 scripts/update_icount_docs.py
git diff --exit-code README.md || {
echo "::error::README icount table is stale; run scripts/update_icount_docs.py"; exit 1; }
# Keeps the benchmarks compiling and runnable; never a performance gate
# (shared runners are noise — see docs/PERFORMANCE.md).
bench-smoke:
name: Benchmark smoke
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Configure
run: >
cmake -B build
-DCMAKE_BUILD_TYPE=Release
-DSRT_BUILD_BENCHMARKS=ON
-DSRT_BUILD_TESTS=OFF
-DSRT_BUILD_EXAMPLES=OFF
- name: Build
run: cmake --build build -j 4
- name: Run (smoke)
run: ./build/bench/srt_bench --benchmark_min_time=0.01s
# Keeps the manually-triggered comparison paths (compare.yml,
# bench/compare) compiling per push; build-only — the measured numbers
# come from the manual workflow, never from here.
compare-smoke:
name: Comparison build smoke
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Install dependencies
run: >
sudo apt-get update -q &&
sudo apt-get install -y -q libsamplerate0-dev libsoxr-dev
gcc-arm-none-eabi
- name: Build host comparison bench
run: >
cmake -B build-host
-DCMAKE_BUILD_TYPE=Release
-DSRT_BUILD_BENCHMARKS=ON
-DSRT_BUILD_COMPARE_BENCH=ON
&& cmake --build build-host -j 4 --target srt_bench_compare
- name: Build M55 comparison workload
run: >
cmake -B build-m55
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m55-mps3.cmake
-DSRT_BUILD_TESTS=OFF -DSRT_BUILD_EXAMPLES=OFF
-DSRT_BUILD_ICOUNT_BENCH=ON -DSRT_ICOUNT_COMPARE=ON
&& cmake --build build-m55 -j 4 --target cmp_icount_lsr_medium
clang-format:
name: clang-format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
# Run the TapHouse pre-commit hook — the same pinned clang-format
# developers get from `pre-commit install` (.pre-commit-config.yaml), so
# local and CI share one version AND one file scope (all C/C++ minus
# third_party) by construction. pipx is preinstalled on the runner.
- name: Check formatting (pre-commit)
run: |
pipx install pre-commit
pre-commit run --all-files --show-diff-on-failure
# The book (book/) quotes library code via mdBook anchor includes; this
# gate makes a refactor that orphans an excerpt fail CI, the same
# freshness contract as the README's generated tables. Warnings are
# errors: a missing anchor is a warning, and a missing anchor is rot.
book:
name: Book build
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MDBOOK_URL: https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz
MDBOOK_SHA256: "9ef07fd288ba58ff3b99d1c94e6d414d431c9a61fdb20348e5beb74b823d546b"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: recursive
- name: Install mdBook (pinned)
run: |
curl -sfLo /tmp/mdbook.tar.gz "$MDBOOK_URL"
actual=$(sha256sum /tmp/mdbook.tar.gz | cut -d' ' -f1)
if [ "$actual" != "$MDBOOK_SHA256" ]; then
echo "::error::mdbook checksum mismatch"; exit 1
fi
tar -xzf /tmp/mdbook.tar.gz -C /tmp
- name: Build (warnings are errors)
run: |
/tmp/mdbook build book 2>&1 | tee /tmp/book-build.log
if grep -qiE 'warning|error' /tmp/book-build.log; then
echo "::error::mdbook reported warnings/errors (stale anchor or broken include?)"
exit 1
fi
# mdBook does not fail on a missing image, so check every relative
# image reference resolves. (The SVGs are committed, generated by
# scripts/book_figures.py; regeneration is not gated because
# matplotlib's SVG output is not byte-stable across versions.)
- name: Check image references resolve
run: |
python3 - <<'EOF'
import pathlib, re, sys
src = pathlib.Path("book/src")
missing = []
for md in src.rglob("*.md"):
for target in re.findall(r"!\[[^\]]*\]\(([^)#?]+)", md.read_text()):
if target.startswith(("http://", "https://")):
continue
if not (md.parent / target).resolve().exists():
missing.append(f"{md}: {target}")
if missing:
print("::error::broken image reference(s):")
print("\n".join(missing))
sys.exit(1)
EOF