[FIX] Make the dependent comparison audit archive-safe #204
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Documentation must be self-contained and must match the code it describes. | |
| # Both checks are cheap and need no build, so they run first on every push. | |
| # --------------------------------------------------------------------------- | |
| comment-style: | |
| name: Comment style | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check for unresolvable reference codes | |
| run: python3 tools/check_comment_style.py | |
| - name: Check the threading page against the code | |
| run: python3 tools/verify_threading_doc.py | |
| - name: Check dependent member comparisons | |
| run: | | |
| python3 tools/check_dependent_comparisons.py | |
| python3 tools/check_dependent_comparisons.py --self-test | |
| header-audit: | |
| name: Header audit - ${{ matrix.compiler }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| compiler: [g++, clang++] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compile every installed header and umbrella profile | |
| run: > | |
| python3 tools/check_header_self_sufficiency.py | |
| --compiler ${{ matrix.compiler }} --std c++20 --warnings-as-errors | |
| - name: Prove the missing-include control | |
| run: python3 tools/check_header_self_sufficiency.py --self-test-negative-control | |
| # --------------------------------------------------------------------------- | |
| # Build + run the public conformance suite on every platform | |
| # --------------------------------------------------------------------------- | |
| conformance: | |
| name: ${{ matrix.name }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: "Windows MSVC x64", os: windows-latest, compiler: msvc, msvc_arch: x64 } | |
| # Native Windows-on-ARM: the one target where MSVC compiles the | |
| # NEON kernel paths (its arm64_neon.h, not the GCC/Clang one). | |
| - { name: "Windows MSVC arm64", os: windows-11-arm, compiler: msvc, msvc_arch: arm64 } | |
| - { name: "Linux GCC x64", os: ubuntu-latest, compiler: g++ } | |
| - { name: "Linux Clang x64", os: ubuntu-latest, compiler: clang++ } | |
| - { name: "Linux GCC arm64", os: ubuntu-24.04-arm, compiler: g++ } | |
| - { name: "macOS Clang arm64", os: macos-latest, compiler: clang++ } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up MSVC environment | |
| if: matrix.compiler == 'msvc' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: ${{ matrix.msvc_arch }} | |
| - name: Build (MSVC) | |
| if: matrix.compiler == 'msvc' | |
| shell: cmd | |
| run: cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . conformance\dspark_conformance.cpp /Fe:dspark_conformance.exe | |
| - name: Build (GCC/Clang) | |
| if: matrix.compiler != 'msvc' | |
| run: ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . conformance/dspark_conformance.cpp -o dspark_conformance | |
| - name: Run conformance suite | |
| shell: bash | |
| run: ./dspark_conformance* | |
| # Default x64 builds stay on the SSE2 baseline, so the AVX2/FMA kernel | |
| # paths would never execute in CI without a second pass. All GitHub | |
| # hosted x64 runners provide AVX2. | |
| - name: Build + run conformance on the AVX2/FMA paths (MSVC) | |
| if: matrix.name == 'Windows MSVC x64' | |
| shell: cmd | |
| run: | | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /arch:AVX2 /I . conformance\dspark_conformance.cpp /Fe:dspark_conformance_avx2.exe | |
| dspark_conformance_avx2.exe | |
| - name: Build + run conformance on the AVX2/FMA paths (GCC/Clang) | |
| if: matrix.name == 'Linux GCC x64' || matrix.name == 'Linux Clang x64' | |
| run: | | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -march=x86-64-v3 -I . conformance/dspark_conformance.cpp -o dspark_conformance_avx2 | |
| ./dspark_conformance_avx2 | |
| - name: Build example plugin (VST3+CLAP) + smoke hosts (MSVC) | |
| if: matrix.name == 'Windows MSVC x64' | |
| shell: cmd | |
| run: | | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_saturator\saturator.cpp /Fe:DSParkSaturator.vst3 | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\vst3_smoke_host.cpp /Fe:vst3_smoke_host.exe | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\clap_smoke_host.cpp /Fe:clap_smoke_host.exe | |
| vst3_smoke_host.exe DSParkSaturator.vst3 | |
| copy DSParkSaturator.vst3 DSParkSaturator.clap | |
| clap_smoke_host.exe DSParkSaturator.clap | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_template\plugin_template.cpp /Fe:DSParkTemplate.vst3 | |
| vst3_smoke_host.exe DSParkTemplate.vst3 | |
| copy DSParkTemplate.vst3 DSParkTemplate.clap | |
| clap_smoke_host.exe DSParkTemplate.clap | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_ducker\ducker.cpp /Fe:DSParkDucker.vst3 | |
| vst3_smoke_host.exe DSParkDucker.vst3 --expect-sidechain | |
| copy DSParkDucker.vst3 DSParkDucker.clap | |
| clap_smoke_host.exe DSParkDucker.clap --expect-sidechain | |
| rem Host-contract probe: transport, offline, sample-accurate | |
| rem automation, MIDI + pitch bend, latency change, mono, presets. | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . tools\plugin_probe.cpp /Fe:DSParkProbe.vst3 | |
| vst3_smoke_host.exe DSParkProbe.vst3 --probe | |
| copy DSParkProbe.vst3 DSParkProbe.clap | |
| clap_smoke_host.exe DSParkProbe.clap --probe | |
| rem Instrument reference: no audio in, note in, pitch + decay. | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_synth\synth.cpp /Fe:DSParkSynth.vst3 | |
| vst3_smoke_host.exe DSParkSynth.vst3 --expect-instrument | |
| copy DSParkSynth.vst3 DSParkSynth.clap | |
| clap_smoke_host.exe DSParkSynth.clap --expect-instrument | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_webview_editor\webview_saturator.cpp /Fe:DSParkWebSaturator.vst3 | |
| vst3_smoke_host.exe DSParkWebSaturator.vst3 | |
| copy DSParkWebSaturator.vst3 DSParkWebSaturator.clap | |
| clap_smoke_host.exe DSParkWebSaturator.clap | |
| cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\vst3_editor_host.cpp /Fe:vst3_editor_host.exe user32.lib | |
| cmake -S examples\plugin_webview_files -B build_webgain | |
| cmake --build build_webgain --config Release | |
| vst3_smoke_host.exe build_webgain\DSParkWebGain.vst3\Contents\x86_64-win\DSParkWebGain.vst3 | |
| clap_smoke_host.exe build_webgain\DSParkWebGain.clap | |
| - name: Build example plugin (VST3+CLAP) + smoke hosts (GCC/Clang) | |
| if: matrix.compiler != 'msvc' | |
| shell: bash | |
| run: | | |
| EXTRA="" | |
| if [[ "$RUNNER_OS" == "macOS" ]]; then | |
| # The AU macro in the shared translation unit references | |
| # AudioToolbox symbols on Apple platforms. | |
| EXTRA="-framework AudioToolbox -framework CoreFoundation" | |
| fi | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_saturator/saturator.cpp -o DSParkSaturator.vst3 $EXTRA | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/vst3_smoke_host.cpp -o vst3_smoke_host -ldl | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/clap_smoke_host.cpp -o clap_smoke_host -ldl | |
| ./vst3_smoke_host ./DSParkSaturator.vst3 | |
| cp DSParkSaturator.vst3 DSParkSaturator.clap | |
| ./clap_smoke_host ./DSParkSaturator.clap | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_template/plugin_template.cpp -o DSParkTemplate.vst3 | |
| ./vst3_smoke_host ./DSParkTemplate.vst3 | |
| cp DSParkTemplate.vst3 DSParkTemplate.clap | |
| ./clap_smoke_host ./DSParkTemplate.clap | |
| # Sidechain reference plugin: bus layout + functional ducking proof | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_ducker/ducker.cpp -o DSParkDucker.vst3 $EXTRA | |
| ./vst3_smoke_host ./DSParkDucker.vst3 --expect-sidechain | |
| cp DSParkDucker.vst3 DSParkDucker.clap | |
| ./clap_smoke_host ./DSParkDucker.clap --expect-sidechain | |
| # Host-contract probe: transport, offline render mode, | |
| # sample-accurate automation, MIDI + pitch bend, the | |
| # latency-changed notification, mono negotiation, presets. | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . tools/plugin_probe.cpp -o DSParkProbe.vst3 | |
| ./vst3_smoke_host ./DSParkProbe.vst3 --probe | |
| cp DSParkProbe.vst3 DSParkProbe.clap | |
| ./clap_smoke_host ./DSParkProbe.clap --probe | |
| # Instrument reference: no audio in, note in, pitch + decay. | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_synth/synth.cpp -o DSParkSynth.vst3 $EXTRA | |
| ./vst3_smoke_host ./DSParkSynth.vst3 --expect-instrument | |
| cp DSParkSynth.vst3 DSParkSynth.clap | |
| ./clap_smoke_host ./DSParkSynth.clap --expect-instrument | |
| # WebView editor example: WKWebView objc glue on macOS, stub on Linux | |
| WEBVIEW_EXTRA="" | |
| if [[ "$RUNNER_OS" == "macOS" ]]; then | |
| WEBVIEW_EXTRA="-lobjc" | |
| fi | |
| ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_webview_editor/webview_saturator.cpp -o DSParkWebSaturator.vst3 $WEBVIEW_EXTRA | |
| ./vst3_smoke_host ./DSParkWebSaturator.vst3 | |
| cp DSParkWebSaturator.vst3 DSParkWebSaturator.clap | |
| ./clap_smoke_host ./DSParkWebSaturator.clap | |
| # CMake plugin pipeline: separate-files UI embedding + bundle layouts | |
| cmake -S examples/plugin_webview_files -B build_webgain \ | |
| -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} | |
| cmake --build build_webgain | |
| if [[ "$RUNNER_OS" == "macOS" ]]; then | |
| WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/MacOS/DSParkWebGain | |
| WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap/Contents/MacOS/DSParkWebGain | |
| elif [[ "$(uname -m)" == "aarch64" ]]; then | |
| WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/aarch64-linux/DSParkWebGain.so | |
| WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap | |
| else | |
| WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/x86_64-linux/DSParkWebGain.so | |
| WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap | |
| fi | |
| ./vst3_smoke_host "$WEBGAIN_VST3" | |
| ./clap_smoke_host "$WEBGAIN_CLAP" | |
| - name: X11 WebView editor smoke (Linux) | |
| if: matrix.name == 'Linux GCC x64' | |
| shell: bash | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y -q xvfb libwebkit2gtk-4.1-0 \ | |
| || sudo apt-get install -y -q xvfb libwebkit2gtk-4.0-37 | |
| g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/x11_editor_smoke.cpp \ | |
| -o x11_editor_smoke -ldl | |
| # No GPU/DRI on CI runners: keep WebKit on the software paths. | |
| export WEBKIT_DISABLE_COMPOSITING_MODE=1 | |
| export WEBKIT_DISABLE_DMABUF_RENDERER=1 | |
| xvfb-run -a ./x11_editor_smoke ./DSParkSaturator.vst3 --expect-no-editor | |
| xvfb-run -a ./x11_editor_smoke ./DSParkWebSaturator.vst3 --expect-editor 560x330 | |
| - name: AUv2 bundle + auval (macOS) | |
| if: matrix.name == 'macOS Clang arm64' | |
| shell: bash | |
| run: | | |
| clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \ | |
| examples/plugin_saturator/saturator.cpp \ | |
| -framework AudioToolbox -framework CoreFoundation \ | |
| -o DSParkSaturatorAU | |
| COMP="$HOME/Library/Audio/Plug-Ins/Components/DSParkSaturator.component" | |
| mkdir -p "$COMP/Contents/MacOS" | |
| cp DSParkSaturatorAU "$COMP/Contents/MacOS/DSParkSaturator" | |
| cp examples/plugin_saturator/au/Info.plist "$COMP/Contents/Info.plist" | |
| codesign --force -s - "$COMP" | |
| killall -9 AudioComponentRegistrar 2>/dev/null || true | |
| auval -v aufx DSst DSpk | |
| # The sidechain reference plugin: a second input element (auval | |
| # validates the element/stream-format/render contract for both). | |
| clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \ | |
| examples/plugin_ducker/ducker.cpp \ | |
| -framework AudioToolbox -framework CoreFoundation \ | |
| -o DSParkDuckerAU | |
| COMP2="$HOME/Library/Audio/Plug-Ins/Components/DSParkDucker.component" | |
| mkdir -p "$COMP2/Contents/MacOS" | |
| cp DSParkDuckerAU "$COMP2/Contents/MacOS/DSParkDucker" | |
| cp examples/plugin_ducker/au/Info.plist "$COMP2/Contents/Info.plist" | |
| codesign --force -s - "$COMP2" | |
| killall -9 AudioComponentRegistrar 2>/dev/null || true | |
| auval -v aufx DSdk DSpk | |
| # And the CMake-built example (.component comes assembled + signed). | |
| cp -R build_webgain/DSParkWebGain.component \ | |
| "$HOME/Library/Audio/Plug-Ins/Components/" | |
| killall -9 AudioComponentRegistrar 2>/dev/null || true | |
| auval -v aufx DSwg DSpk | |
| # The instrument reference: an aumu music device (no input | |
| # elements, MusicDevice MIDI selectors, factory presets). | |
| clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \ | |
| examples/plugin_synth/synth.cpp \ | |
| -framework AudioToolbox -framework CoreFoundation \ | |
| -o DSParkSynthAU | |
| COMP3="$HOME/Library/Audio/Plug-Ins/Components/DSParkSynth.component" | |
| mkdir -p "$COMP3/Contents/MacOS" | |
| cp DSParkSynthAU "$COMP3/Contents/MacOS/DSParkSynth" | |
| cp examples/plugin_synth/au/Info.plist "$COMP3/Contents/Info.plist" | |
| codesign --force -s - "$COMP3" | |
| killall -9 AudioComponentRegistrar 2>/dev/null || true | |
| auval -v aumu DSsy DSpk | |
| # WebView editor through the AU Cocoa contract: factory class, real | |
| # WKWebView attach, bridge handshake, both teardown orders. | |
| clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/au_editor_smoke.cpp \ | |
| -framework AudioToolbox -framework CoreFoundation -framework AppKit \ | |
| -lobjc -o au_editor_smoke | |
| ./au_editor_smoke aufx DSst DSpk --expect-no-editor | |
| ./au_editor_smoke aufx DSwg DSpk --expect-editor 300x360 | |
| - name: Official validators (pluginval + clap-validator) | |
| if: matrix.name == 'Windows MSVC x64' || matrix.name == 'Linux GCC x64' || matrix.name == 'macOS Clang arm64' | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -e | |
| mkdir -p validators | |
| case "$RUNNER_OS" in | |
| Windows) PV_PAT="*Windows*"; CV_PAT="*windows*" ;; | |
| macOS) PV_PAT="*macOS*"; CV_PAT="*macos*" ;; | |
| *) PV_PAT="*Linux*"; CV_PAT="*ubuntu*" | |
| sudo apt-get update -q && sudo apt-get install -y -q xvfb ;; | |
| esac | |
| # Validator versions are PINNED so a third-party release cannot turn | |
| # this gate red on its own. clap-validator's "0.4.1" (2026-07-22) is a | |
| # development snapshot (assets are tagged 0.4.1-127-g152b982 and the | |
| # files inside them are still named 0.3.2) that also repackaged Linux | |
| # and macOS as a zip WRAPPING a tar.gz, which leaves the extraction | |
| # below with no binary to find. Moving to it is a deliberate upgrade | |
| # with its own findings to work through, not a drift. | |
| gh release download v1.0.4 --repo Tracktion/pluginval --pattern "$PV_PAT" --dir validators | |
| gh release download 0.3.2 --repo free-audio/clap-validator --pattern "$CV_PAT" --dir validators | |
| for a in validators/*.zip validators/*.tar.gz; do | |
| [ -e "$a" ] || continue | |
| case "$a" in | |
| *.zip) | |
| # Git Bash ships GNU tar (no zip support); Windows' own | |
| # bsdtar handles zips fine. unzip is present elsewhere. | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| /c/Windows/System32/tar.exe -xf "$a" -C validators | |
| else | |
| unzip -oq "$a" -d validators | |
| fi ;; | |
| *.tar.gz) tar -xf "$a" -C validators ;; | |
| esac | |
| done | |
| # Release archives differ in layout per OS; locate the binaries. | |
| CLAPVAL=$(find validators -type f \( -name "clap-validator" -o -name "clap-validator.exe" \) | head -1) | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| PLUGINVAL=validators/pluginval.exe | |
| VST3S="DSParkSaturator.vst3 DSParkWebSaturator.vst3 DSParkDucker.vst3 DSParkSynth.vst3 DSParkProbe.vst3 build_webgain/DSParkWebGain.vst3" | |
| CLAPS="DSParkSaturator.clap DSParkWebSaturator.clap DSParkDucker.clap DSParkSynth.clap DSParkProbe.clap build_webgain/DSParkWebGain.clap" | |
| PREFIX="" | |
| elif [[ "$RUNNER_OS" == "macOS" ]]; then | |
| PLUGINVAL=validators/pluginval.app/Contents/MacOS/pluginval | |
| chmod +x "$CLAPVAL" | |
| # Plain-file modules are a Windows/Linux convenience; on macOS | |
| # only the CMake-built proper bundles are loadable by validators. | |
| VST3S="build_webgain/DSParkWebGain.vst3" | |
| CLAPS="build_webgain/DSParkWebGain.clap" | |
| PREFIX="" | |
| else | |
| PLUGINVAL=validators/pluginval | |
| VST3S="build_webgain/DSParkWebGain.vst3" | |
| CLAPS="DSParkSaturator.clap DSParkWebSaturator.clap DSParkDucker.clap DSParkSynth.clap DSParkProbe.clap build_webgain/DSParkWebGain.clap" | |
| PREFIX="xvfb-run -a" | |
| fi | |
| for p in $VST3S; do | |
| $PREFIX "$PLUGINVAL" --strictness-level 8 --skip-gui-tests --validate "$p" | |
| done | |
| "$CLAPVAL" validate $CLAPS | |
| - name: Generate metrics table (KPI K3) | |
| if: matrix.name == 'Linux GCC x64' | |
| shell: bash | |
| run: ./dspark_conformance* --metrics metrics.md | |
| - name: Upload metrics table | |
| if: matrix.name == 'Linux GCC x64' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dspark-metrics-table | |
| path: metrics.md | |
| # --------------------------------------------------------------------------- | |
| # Build + run the test suite on every platform | |
| # | |
| # Driven through CMake/CTest rather than a direct compiler invocation: it is | |
| # the same path a contributor uses locally, so this job also proves the CMake | |
| # integration itself. The suite builds with optimisations but WITHOUT NDEBUG | |
| # (see tests/CMakeLists.txt) so the framework's own assertions stay live. | |
| # --------------------------------------------------------------------------- | |
| tests: | |
| name: Tests - ${{ matrix.name }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: "Windows MSVC x64", os: windows-latest, msvc_arch: x64 } | |
| - { name: "Windows MSVC arm64", os: windows-11-arm, msvc_arch: arm64 } | |
| - { name: "Linux GCC x64", os: ubuntu-latest, cxx: g++ } | |
| - { name: "Linux Clang x64", os: ubuntu-latest, cxx: clang++ } | |
| - { name: "Linux GCC arm64", os: ubuntu-24.04-arm, cxx: g++ } | |
| - { name: "macOS Clang arm64", os: macos-latest, cxx: clang++ } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up MSVC environment | |
| if: matrix.msvc_arch != '' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: ${{ matrix.msvc_arch }} | |
| # Windows resolves to the Visual Studio generator, which is multi-config: | |
| # the target architecture is stated explicitly rather than inherited. | |
| - name: Configure (MSVC) | |
| if: matrix.msvc_arch != '' | |
| shell: bash | |
| run: > | |
| cmake -S . -B build | |
| -A ${{ matrix.msvc_arch == 'arm64' && 'ARM64' || 'x64' }} | |
| -DDSPARK_BUILD_TESTS=ON | |
| -DDSPARK_BUILD_CONFORMANCE=OFF | |
| -DCMAKE_COMPILE_WARNING_AS_ERROR=ON | |
| - name: Configure (GCC/Clang) | |
| if: matrix.msvc_arch == '' | |
| run: > | |
| cmake -S . -B build | |
| -DCMAKE_BUILD_TYPE=Release | |
| -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} | |
| -DDSPARK_BUILD_TESTS=ON | |
| -DDSPARK_BUILD_CONFORMANCE=OFF | |
| -DCMAKE_COMPILE_WARNING_AS_ERROR=ON | |
| - name: Build | |
| shell: bash | |
| run: cmake --build build --config Release --parallel | |
| # --test-output-size-failed 0: do not truncate the output of a failing | |
| # test, so the case the runner died on is always identifiable. | |
| - name: Test | |
| shell: bash | |
| run: > | |
| ctest --test-dir build --build-config Release | |
| --output-on-failure --test-output-size-failed 0 | |
| # --------------------------------------------------------------------------- | |
| # TestIO process isolation and its real fixed-name collision control | |
| # --------------------------------------------------------------------------- | |
| testio-concurrency: | |
| name: TestIO concurrent process isolation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Run three complete suites from one working directory | |
| run: > | |
| python3 tools/run_concurrent_test_suites.py | |
| --copies 3 --working-directory shared --expect-all-pass | |
| - name: Prove fixed names collide under the same runner | |
| run: > | |
| python3 tools/run_concurrent_test_suites.py | |
| --copies 3 --working-directory shared | |
| --mutant fixed-testio-names --expect-collision | |
| # --------------------------------------------------------------------------- | |
| # AddressSanitizer + UndefinedBehaviorSanitizer + float-cast-overflow | |
| # --------------------------------------------------------------------------- | |
| sanitizers: | |
| name: ASan + UBSan + float-cast-overflow (${{ matrix.name }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: GCC, compiler: g++ } | |
| - { name: Clang, compiler: clang++ } | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build with sanitizers | |
| run: > | |
| ${{ matrix.compiler }} -std=c++20 -O1 -g | |
| -Wall -Wextra -Wpedantic -Werror | |
| -fsanitize=address,undefined,float-cast-overflow | |
| -fno-sanitize-recover=all | |
| -I . conformance/dspark_conformance.cpp -o dspark_conformance_san | |
| - name: Run under sanitizers | |
| run: ./dspark_conformance_san | |
| - name: Prove float-cast-overflow instrumentation and flag dependence | |
| run: | | |
| python3 tools/verify_float_cast_overflow_sanitizer.py --compiler ${{ matrix.compiler }} | |
| python3 tools/verify_float_cast_overflow_sanitizer.py --compiler ${{ matrix.compiler }} --self-test-flag-removal | |
| # The suite reaches far more of the framework than the conformance | |
| # checks do, so it is where a stray read or an unsigned overflow is | |
| # most likely to surface. | |
| - name: Build + run the test suite under sanitizers | |
| run: | | |
| cmake -S . -B build-san \ | |
| -DCMAKE_BUILD_TYPE=RelWithDebInfo \ | |
| -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} \ | |
| -DCMAKE_COMPILE_WARNING_AS_ERROR=ON \ | |
| -DDSPARK_BUILD_TESTS=ON -DDSPARK_BUILD_CONFORMANCE=ON \ | |
| -DDSPARK_VERIFY_FLOAT_CAST_OVERFLOW_SANITIZER=ON \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined,float-cast-overflow -fno-sanitize-recover=all" | |
| cmake --build build-san --parallel | |
| ctest --test-dir build-san --output-on-failure --test-output-size-failed 0 | |
| # --------------------------------------------------------------------------- | |
| # ThreadSanitizer (Linux GCC) — data-race detection for the lock-free code | |
| # (SpectrumAnalyzer triple buffer, SpscQueue, atomic parameter handoff). TSan | |
| # models the C++11 memory model precisely, so it is the correct oracle for | |
| # these paths; it CANNOT be combined with ASan/UBSan, hence a separate build. | |
| # (It is unavailable on the local audit sandbox, where the kernel blocks the | |
| # personality() syscall TSan needs at startup; it runs normally on the CI | |
| # Linux runner.) | |
| # --------------------------------------------------------------------------- | |
| tsan: | |
| name: ThreadSanitizer (Linux GCC) | |
| runs-on: ubuntu-latest | |
| # Bounds a pathological hang; a healthy full run fits well inside it | |
| # (run 30712892449: tests 1-143 of the suite took ~8.6 min, dominated by | |
| # the two seqlock stress cases; the remaining 547 cases are | |
| # single-threaded and lighter). | |
| timeout-minutes: 150 | |
| env: | |
| TSAN_OPTIONS: halt_on_error=1 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build with ThreadSanitizer | |
| run: > | |
| g++ -std=c++20 -O1 -g -Wall -Wextra -Wpedantic -Werror -Wno-error=tsan | |
| -fsanitize=thread | |
| -I . conformance/dspark_conformance.cpp -o dspark_conformance_tsan | |
| - name: Run under ThreadSanitizer | |
| run: ./dspark_conformance_tsan | |
| # What this step actually proves concurrently: the suite's seven | |
| # cross-thread cases over six hand-offs (SpinLock contention, SpscQueue | |
| # producer/consumer, FIRFilter and Biquad seqlock publication, | |
| # SpectrumAnalyzer triple buffer - counted once as a hand-off but | |
| # covered by TWO cases, tear-free readout and paired-getter pointer | |
| # lifetime -, AnalogRandom readout publication). Every other component | |
| # runs single-threaded here, so for those TSan checks execution, not | |
| # concurrency. The list above IS the per-hand-off statement: a hand-off | |
| # that no threaded case exercises concurrently is unproven, and gets | |
| # reported as unproven rather than presented as proven. | |
| # | |
| # Timeout: per-test wall times are wildly non-uniform under TSan. In | |
| # run 30712892449 the first 143 cases took ~8.6 min, almost all of it | |
| # in the two seqlock stress cases (atomic-dense, contended, the exact | |
| # thing TSan instruments hardest); the 547 cases that followed them in | |
| # THAT run held no threaded code and ~19x the plain numeric work of the | |
| # first 143. The suite has since grown to 694 cases, four of them | |
| # concurrency-related (three concurrency pins and a lock-free census), | |
| # and one threaded case now sits past that boundary (the AnalogRandom | |
| # readout pin). A fifth such case, the single-threaded prepare() | |
| # exception-safety pin, is no longer part of the suite binary: it | |
| # installs replacement allocation functions, which are program-wide, so | |
| # it now builds and runs as its own executable (ctest test | |
| # alloc_failure) to keep the sanitizers' allocation checks live for the | |
| # suite. This step runs it too, at a cost of milliseconds. 5400 s still | |
| # covers the derived worst case (~35 min) with >2x margin: every | |
| # one of them is a bounded-loop case far lighter than the seqlock | |
| # stress pair. Never trim the suite to fit the budget: raise the | |
| # budget. | |
| # | |
| # ctest -V, not --output-on-failure: on success | |
| # --output-on-failure suppresses the harness's per-case listing, so a | |
| # green log was not self-evidencing. -V surfaces every | |
| # "[ i/N ] case_name" line and the final "N tests | N passed" summary, | |
| # making the green verdict checkable against the case inventory. | |
| - name: Build + run the test suite under ThreadSanitizer | |
| run: | | |
| cmake -S . -B build-tsan \ | |
| -DCMAKE_BUILD_TYPE=RelWithDebInfo \ | |
| -DDSPARK_BUILD_TESTS=ON -DDSPARK_BUILD_CONFORMANCE=OFF \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=thread -Wno-error=tsan" | |
| cmake --build build-tsan --parallel | |
| ctest --test-dir build-tsan -V --timeout 5400 | |
| # --------------------------------------------------------------------------- | |
| # WebAssembly (Emscripten) — compile and execute with node | |
| # --------------------------------------------------------------------------- | |
| wasm: | |
| name: WebAssembly (Emscripten) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: mymindstorm/setup-emsdk@v14 | |
| - name: Build | |
| run: > | |
| em++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . | |
| -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=4MB | |
| conformance/dspark_conformance.cpp -o dspark_conformance.js | |
| - name: Run with node | |
| run: node dspark_conformance.js | |
| # Second pass with Wasm SIMD128: Emscripten lowers the SSE2 intrinsic | |
| # paths of SimdOps to v128 when built with -msimd128 -msse2. | |
| - name: Build (Wasm SIMD128 through the SSE2 mapping) | |
| run: > | |
| em++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . -msimd128 -msse2 | |
| -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=4MB | |
| conformance/dspark_conformance.cpp -o dspark_conformance_simd.js | |
| - name: Run with node (SIMD128) | |
| run: node dspark_conformance_simd.js | |
| # --------------------------------------------------------------------------- | |
| # Embedded profile: -fno-exceptions -fno-rtti, no file IO | |
| # --------------------------------------------------------------------------- | |
| embedded-profile: | |
| name: Embedded profile (no exceptions / no RTTI / no file IO) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compile umbrella with embedded flags | |
| run: | | |
| cat > embedded_check.cpp << 'EOF' | |
| #define DSPARK_NO_FILE_IO | |
| #include "DSPark.h" | |
| int main() | |
| { | |
| dspark::AudioSpec spec { 48000.0, 256, 2 }; | |
| dspark::Compressor<float> comp; comp.prepare(spec); | |
| dspark::FilterEngine<float> filt; filt.prepare(spec); | |
| dspark::AlgorithmicReverb<float> rev; rev.prepare(spec); | |
| dspark::AudioBuffer<float> buf; buf.resize(2, 256); | |
| comp.processBlock(buf.toView()); | |
| filt.processBlock(buf.toView()); | |
| rev.processBlock(buf.toView()); | |
| return buf.getChannel(0)[0] == buf.getChannel(0)[0] ? 0 : 1; | |
| } | |
| EOF | |
| g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fno-exceptions -fno-rtti -I . embedded_check.cpp -o embedded_check | |
| ./embedded_check | |
| # --------------------------------------------------------------------------- | |
| # Single-header amalgamation: generate, compile, publish artifact | |
| # --------------------------------------------------------------------------- | |
| amalgamate: | |
| name: Single-header amalgamation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Generate | |
| run: python3 tools/amalgamate.py dspark_single_header.hpp | |
| - name: Compile the amalgamated header | |
| run: | | |
| cat > amalgam_check.cpp << 'EOF' | |
| #include "dspark_single_header.hpp" | |
| int main() | |
| { | |
| dspark::AudioSpec spec { 48000.0, 256, 2 }; | |
| dspark::Equalizer<float> eq; eq.prepare(spec); | |
| dspark::AudioBuffer<float> buf; buf.resize(2, 256); | |
| eq.processBlock(buf.toView()); | |
| return 0; | |
| } | |
| EOF | |
| g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror amalgam_check.cpp -o amalgam_check | |
| ./amalgam_check | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: dspark_single_header | |
| path: dspark_single_header.hpp | |
| # --------------------------------------------------------------------------- | |
| # Examples and benchmarks stay buildable | |
| # --------------------------------------------------------------------------- | |
| examples: | |
| name: Examples + bench (compile) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build examples | |
| run: | | |
| for src in examples/*.cpp; do | |
| echo "== $src" | |
| g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . "$src" -o /dev/null -fsyntax-only | |
| done | |
| - name: Build and smoke-run bench | |
| run: | | |
| g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . bench/dspark_bench.cpp -o dspark_bench | |
| ./dspark_bench | |
| - name: AVX2-without-FMA build check (FMA gating regression) | |
| run: | | |
| # -mavx2 alone must build: DSPARK_SIMD_FMA may only engage together | |
| # with the fma target feature (or under MSVC, which permits the FMA | |
| # intrinsics with /arch:AVX2). GCC hard-errors if this regresses. | |
| g++ -std=c++20 -O1 -mavx2 -c -I . conformance/dspark_conformance.cpp -o /dev/null | |
| # --------------------------------------------------------------------------- | |
| # EBU R128 conformance against the official EBU loudness test set | |
| # (downloaded from tech.ebu.ch with caching; never redistributed in-repo) | |
| # --------------------------------------------------------------------------- | |
| ebu-r128: | |
| name: EBU R128 conformance (official test set) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache EBU test set | |
| id: cache-ebu | |
| uses: actions/cache@v4 | |
| with: | |
| path: ebu-test-set | |
| key: ebu-loudness-test-set-v05 | |
| - name: Download EBU test set (best effort) | |
| if: steps.cache-ebu.outputs.cache-hit != 'true' | |
| run: | | |
| # tech.ebu.ch sits behind Cloudflare, which rejects curl's default | |
| # User-Agent — and often datacenter IPs altogether. Best effort: | |
| # when the download fails, the suite still runs and the EBU cases | |
| # report SKIP (the vectors are verified locally and whenever the | |
| # cache holds a copy). Never redistributed in-repo (EBU license). | |
| curl -fL --retry 3 -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36" \ | |
| -o ebu.zip "https://tech.ebu.ch/files/live/sites/tech/files/shared/testmaterial/ebu-loudness-test-setv05.zip" \ | |
| && unzip -tq ebu.zip \ | |
| && mkdir -p ebu-test-set \ | |
| && unzip -q ebu.zip -d ebu-test-set \ | |
| || echo "::warning::EBU test set unavailable from this runner; EBU cases will be skipped" | |
| - name: Build conformance suite | |
| run: g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . conformance/dspark_conformance.cpp -o dspark_conformance | |
| - name: Run conformance (EBU cases skip cleanly when the set is absent) | |
| run: | | |
| if [ -d ebu-test-set ] && ls ebu-test-set/**/*.wav >/dev/null 2>&1; then | |
| DSPARK_EBU_TEST_SET=ebu-test-set ./dspark_conformance | |
| else | |
| ./dspark_conformance | |
| fi |