From 6b0c5a6d42835256b9a1bc52593dba9786043003 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 26 Aug 2026 18:05:52 +0100 Subject: [PATCH 1/4] fix(macos): bundle OpenAL Soft for stock level audio --- .github/workflows/commit-validation.yml | 32 +- .github/workflows/macos-debug.yml | 36 +- .github/workflows/macos-sanitizer.yml | 15 +- .../workflows/macos-universal2-candidate.yml | 16 +- .github/workflows/manual-release.yml | 22 +- .github/workflows/push-verification.yml | 25 +- BUILDING.md | 21 +- README.md | 3 +- assets/release/README.html | 1 + docs/dev/macos-openal-provider-policy.md | 126 +++--- docs/dev/macos-vm-testing-workflow.md | 14 +- .../2026-06-30-macos-compatibility-support.md | 26 +- docs/dev/plans/2026-06-24-openal.md | 23 +- ...026-06-30-apple-support-no-macos-access.md | 41 +- docs/dev/platform-support.md | 4 +- docs/dev/release-completion.md | 7 +- docs/user/getting-started.md | 1 + meson.build | 19 +- meson_options.txt | 2 +- src/external/openal-soft/SOURCE.md | 13 + src/sound/OpenAL/AL_SoundSample.cpp | 72 ++-- src/sound/OpenAL/AL_SoundSample.h | 1 + tools/build/assemble_macos_universal2.py | 24 +- tools/build/package_nightly.py | 107 +++++- tools/build/prepare_macos_openal_soft.sh | 295 ++++++++++++++ tools/macos/Invoke-openQ4MacOSWorkflow.ps1 | 2 +- .../guest/openq4-macos-sync-build-test.sh | 37 +- tools/tests/macos_metal_bridge.py | 68 +++- tools/tests/macos_openal_provider_policy.py | 359 +++++++++--------- tools/tests/macos_sanitizer_ci.py | 4 +- tools/tests/macos_universal2_assembly.py | 20 +- tools/validation/openq4_validate.py | 22 ++ 32 files changed, 1085 insertions(+), 373 deletions(-) create mode 100644 src/external/openal-soft/SOURCE.md create mode 100755 tools/build/prepare_macos_openal_soft.sh diff --git a/.github/workflows/commit-validation.yml b/.github/workflows/commit-validation.yml index 0a31aeb9..030c474e 100644 --- a/.github/workflows/commit-validation.yml +++ b/.github/workflows/commit-validation.yml @@ -1086,11 +1086,11 @@ jobs: include: - bridge_label: OpenGL macos_graphics_bridge: opengl - macos_openal_provider: apple_framework + macos_openal_provider: system artifact_suffix: opengl - bridge_label: Metal macos_graphics_bridge: metal - macos_openal_provider: apple_framework + macos_openal_provider: system artifact_suffix: metal steps: @@ -1121,6 +1121,18 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja + - name: Prepare pinned OpenAL Soft + shell: bash + run: | + set -euo pipefail + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-arm64" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture arm64 \ + --deployment-target "${MACOSX_DEPLOYMENT_TARGET}" \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Run selected validation profile shell: bash run: | @@ -1210,11 +1222,11 @@ jobs: include: - bridge_label: OpenGL macos_graphics_bridge: opengl - macos_openal_provider: apple_framework + macos_openal_provider: system artifact_suffix: opengl - bridge_label: Metal macos_graphics_bridge: metal - macos_openal_provider: apple_framework + macos_openal_provider: system artifact_suffix: metal steps: @@ -1254,6 +1266,18 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja + - name: Prepare pinned OpenAL Soft + shell: bash + run: | + set -euo pipefail + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-x64" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture x64 \ + --deployment-target "${MACOSX_DEPLOYMENT_TARGET}" \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Run selected validation profile shell: bash run: | diff --git a/.github/workflows/macos-debug.yml b/.github/workflows/macos-debug.yml index 81bf7378..7da5d2f8 100644 --- a/.github/workflows/macos-debug.yml +++ b/.github/workflows/macos-debug.yml @@ -88,6 +88,19 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja + - name: Prepare pinned OpenAL Soft + if: steps.select.outputs.selected == 'true' + shell: bash + run: | + set -euo pipefail + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-arm64" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture arm64 \ + --deployment-target 11.0 \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Host diagnostics if: steps.select.outputs.selected == 'true' shell: bash @@ -192,7 +205,8 @@ jobs: --wrap-mode=forcefallback \ -Dplatform_backend=sdl3 \ -Dbuild_renderer_vk=true \ - -Dmacos_graphics_bridge=${{ matrix.bridge }} + -Dmacos_graphics_bridge=${{ matrix.bridge }} \ + -Dmacos_openal_provider=system - name: Compile if: steps.select.outputs.selected == 'true' @@ -269,11 +283,10 @@ jobs: retention-days: ${{ fromJSON(inputs.retention_days) }} include-hidden-files: true - # Migration-corridor compile check: builds against Homebrew OpenAL Soft with - # -Dmacos_openal_provider=system. This is migration testing only, not release - # evidence; release packages stay on apple_framework. No packaging or signoff. + # Compatibility-only compile check for Apple's deprecated framework. Release, + # package, and standard validation paths use pinned OpenAL Soft instead. macos-openal-migration: - name: macOS OpenAL Soft Migration Corridor (compile only) + name: macOS Apple OpenAL Compatibility Corridor (compile only) runs-on: macos-15 timeout-minutes: 90 env: @@ -303,14 +316,7 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja - - name: Install OpenAL Soft - shell: bash - run: | - set -euo pipefail - brew install openal-soft - echo "PKG_CONFIG_PATH=$(brew --prefix openal-soft)/lib/pkgconfig" >> "${GITHUB_ENV}" - - - name: Configure (system OpenAL) + - name: Configure (Apple framework) shell: bash env: MACOSX_DEPLOYMENT_TARGET: "11.0" @@ -322,9 +328,9 @@ jobs: --wrap-mode=forcefallback \ -Dplatform_backend=sdl3 \ -Dmacos_graphics_bridge=opengl \ - -Dmacos_openal_provider=system + -Dmacos_openal_provider=apple_framework - - name: Compile (system OpenAL) + - name: Compile (Apple framework) shell: bash env: MACOSX_DEPLOYMENT_TARGET: "11.0" diff --git a/.github/workflows/macos-sanitizer.yml b/.github/workflows/macos-sanitizer.yml index 75935f28..8cc570a8 100644 --- a/.github/workflows/macos-sanitizer.yml +++ b/.github/workflows/macos-sanitizer.yml @@ -97,6 +97,19 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja + - name: Prepare pinned OpenAL Soft + if: steps.select.outputs.selected == 'true' + shell: bash + run: | + set -euo pipefail + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-arm64" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture arm64 \ + --deployment-target "${MACOSX_DEPLOYMENT_TARGET}" \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Record host diagnostics if: steps.select.outputs.selected == 'true' shell: bash @@ -132,7 +145,7 @@ jobs: --wrap-mode=forcefallback \ -Dplatform_backend=sdl3 \ -Dmacos_graphics_bridge=${{ matrix.bridge }} \ - -Dmacos_openal_provider=apple_framework \ + -Dmacos_openal_provider=system \ -Duse_pch=false \ -Didlib_asserts=true \ -Db_sanitize=address,undefined diff --git a/.github/workflows/macos-universal2-candidate.yml b/.github/workflows/macos-universal2-candidate.yml index 6c95109f..03050721 100644 --- a/.github/workflows/macos-universal2-candidate.yml +++ b/.github/workflows/macos-universal2-candidate.yml @@ -221,6 +221,18 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja markdown + - name: Prepare pinned OpenAL Soft + shell: bash + run: | + set -euo pipefail + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-${{ matrix.binary_arch }}" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture "${{ matrix.binary_arch }}" \ + --deployment-target "${MACOSX_DEPLOYMENT_TARGET}" \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Build and stage thin macOS runtime shell: bash run: | @@ -236,7 +248,7 @@ jobs: --wrap-mode=forcefallback \ -Dplatform_backend=sdl3 \ -Dmacos_graphics_bridge=${{ matrix.bridge }} \ - -Dmacos_openal_provider=apple_framework \ + -Dmacos_openal_provider=system \ -Dversion_track=stable \ -Dversion_base_override=${{ needs.metadata.outputs.version }} bash tools/build/meson_setup.sh compile -C builddir @@ -285,7 +297,7 @@ jobs: --source-manifest .tmp/gamelibs_stage/openq4_gamelibs_stage_manifest.json \ --arch "${{ matrix.binary_arch }}" \ --graphics-bridge "${{ matrix.bridge }}" \ - --openal-provider apple_framework \ + --openal-provider system \ --deployment-target "${MACOSX_DEPLOYMENT_TARGET}" \ --build-type debugoptimized diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index cba3c38f..de938c2b 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -549,7 +549,7 @@ jobs: "binary_arch": "arm64", "platform_backend": "sdl3", "macos_graphics_bridge": "opengl", - "macos_openal_provider": "apple_framework", + "macos_openal_provider": "system", "macos_release_mode": macos_release_mode, "package_suffix": f"-opengl{macos_unsigned_suffix}", "archive_format": macos_archive_format, @@ -562,7 +562,7 @@ jobs: "binary_arch": "arm64", "platform_backend": "sdl3", "macos_graphics_bridge": "metal", - "macos_openal_provider": "apple_framework", + "macos_openal_provider": "system", "macos_release_mode": macos_release_mode, "package_suffix": f"-metal{macos_unsigned_suffix}", "archive_format": macos_archive_format, @@ -748,12 +748,18 @@ jobs: sleep 10 done - - name: Install macOS native dependencies + - name: Install macOS native dependencies and prepare pinned OpenAL Soft if: matrix.platform == 'macos' shell: bash run: | set -euo pipefail - echo "Using bundled Meson fallbacks for macOS third-party libraries." + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-${{ matrix.binary_arch }}" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture "${{ matrix.binary_arch }}" \ + --deployment-target 11.0 \ + --output-root "${prepared_root}" + echo "OPENQ4_MACOS_OPENAL_ROOT=${prepared_root}" >> "${GITHUB_ENV}" + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" - name: Prepare OpenAL Soft (Windows) if: matrix.platform == 'windows' @@ -841,6 +847,14 @@ jobs: bash tools/build/meson_setup.sh "${setup_args[@]}" bash tools/build/meson_setup.sh compile -C builddir bash tools/build/meson_setup.sh install -C builddir --no-rebuild --skip-subprojects + if [ "${{ matrix.platform }}" = "macos" ]; then + bash tools/build/prepare_macos_openal_soft.sh \ + --verify-only \ + --architecture "${{ matrix.binary_arch }}" \ + --deployment-target 11.0 \ + --output-root "${OPENQ4_MACOS_OPENAL_ROOT}" \ + --stage-install-dir .install + fi - name: Verify checked-out and staged source provenance shell: bash diff --git a/.github/workflows/push-verification.yml b/.github/workflows/push-verification.yml index 88cccbce..2b4d9328 100644 --- a/.github/workflows/push-verification.yml +++ b/.github/workflows/push-verification.yml @@ -423,25 +423,25 @@ jobs: artifact_name: macos-opengl runtime_smoke: true macos_graphics_bridge: opengl - macos_openal_provider: apple_framework + macos_openal_provider: system - name: macOS Metal Push Verification os: macos-15 artifact_name: macos-metal runtime_smoke: true macos_graphics_bridge: metal - macos_openal_provider: apple_framework + macos_openal_provider: system - name: macOS Intel x64 OpenGL Push Verification os: macos-15-intel artifact_name: macos-x64-opengl runtime_smoke: true macos_graphics_bridge: opengl - macos_openal_provider: apple_framework + macos_openal_provider: system - name: macOS Intel x64 Metal Push Verification os: macos-15-intel artifact_name: macos-x64-metal runtime_smoke: true macos_graphics_bridge: metal - macos_openal_provider: apple_framework + macos_openal_provider: system steps: - name: Checkout uses: actions/checkout@v6 @@ -467,6 +467,23 @@ jobs: python -m pip install --upgrade pip python -m pip install meson ninja + - name: Prepare pinned OpenAL Soft (macOS) + if: startsWith(matrix.os, 'macos-') + shell: bash + run: | + set -euo pipefail + architecture=arm64 + if [[ "${{ matrix.os }}" == "macos-15-intel" ]]; then + architecture=x64 + fi + prepared_root="${GITHUB_WORKSPACE}/.tmp/openal-soft-macos-${architecture}" + bash tools/build/prepare_macos_openal_soft.sh \ + --architecture "${architecture}" \ + --deployment-target 11.0 \ + --output-root "${prepared_root}" \ + --stage-install-dir .install + echo "PKG_CONFIG_PATH=${prepared_root}/lib/pkgconfig" >> "${GITHUB_ENV}" + - name: Install Linux native dependencies if: startsWith(matrix.os, 'ubuntu-') shell: bash diff --git a/BUILDING.md b/BUILDING.md index 003ed152..88865787 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -90,16 +90,19 @@ During configure, openQ4 stages the canonical `src/game` and `src/mpgame` source ## Build Setup -Third-party libraries such as SDL3, GLEW, OpenAL Soft, and stb_vorbis are managed as Meson subprojects. On Linux, the default SDL3 backend requires OpenGL/EGL plus the SDL3 runtime integration development packages. The Linux dedicated target uses the same compile-time declarations but a separate headless source/link set, so its executable does not directly depend on OpenAL, OpenGL/GLX, SDL, PipeWire, Wayland, or X11/Xext at runtime. Staged and extracted-package validation apply a fail-closed core-runtime allowlist to both that executable and the multiplayer module it loads. The bundled SDL build compiles against PipeWire, Wayland, xkbcommon, and libdecor headers, then loads their stable runtime SONAMEs dynamically; PipeWire and libdecor remain optional on the player's system. IBus support is enabled when its headers are available, with the DBus-based Fcitx path always available. The native IBus/Fcitx UI owns composition/candidate presentation and is positioned from openQ4's active console/GUI text-input area. Committed text is limited to Quake 4's stock single-byte edit-widget and bitmap-font range; codepoints outside that range are ignored instead of being corrupted into unrelated characters. X11/Xext are optional helpers used only when available for the SDL3 NVIDIA VRAM probe and XWayland fallback. Configure with `-Dlinux_x11=disabled` to build the native Wayland/EGL-only path and exclude SDL's X11 driver, GLX GLEW code, and engine X11 helpers. An explicit `enabled` or `disabled` value forces the bundled SDL fallback so the requested driver set is honored; `auto` may use a suitable system SDL and inherits its compiled driver set. The legacy `-Dplatform_backend=native` path still requires X11/GLX and VidMode development packages on Linux and the legacy Carbon framework on macOS. On experimental macOS builds, SDL3 is the default platform path and does not link Carbon; it is also the only release backend, while the native Cocoa/OpenGL backend is comparison-only diagnostic infrastructure. `-Dmacos_graphics_bridge=metal` enables the Metal-ready SDL3/Cocoa bridge while keeping the stock-compatible OpenGL renderer rather than introducing a native Metal rewrite. The renderer/backend policy is tracked in `docs/dev/macos-renderer-backend-policy.md`, and the legacy native backend containment policy is tracked in `docs/dev/macos-native-backend-containment-policy.md`. Experimental macOS releases currently use Apple's OpenAL framework for audio; `-Dmacos_openal_provider=system` is available only for local OpenAL Soft migration testing with pkg-config OpenAL Soft (`dependency('openal', method: 'pkg-config')`) and `AL/...` headers. The macOS audio-provider policy is tracked in `docs/dev/macos-openal-provider-policy.md`. +Third-party libraries such as SDL3, GLEW, OpenAL Soft, and stb_vorbis are managed as Meson subprojects. On Linux, the default SDL3 backend requires OpenGL/EGL plus the SDL3 runtime integration development packages. The Linux dedicated target uses the same compile-time declarations but a separate headless source/link set, so its executable does not directly depend on OpenAL, OpenGL/GLX, SDL, PipeWire, Wayland, or X11/Xext at runtime. Staged and extracted-package validation apply a fail-closed core-runtime allowlist to both that executable and the multiplayer module it loads. The bundled SDL build compiles against PipeWire, Wayland, xkbcommon, and libdecor headers, then loads their stable runtime SONAMEs dynamically; PipeWire and libdecor remain optional on the player's system. IBus support is enabled when its headers are available, with the DBus-based Fcitx path always available. The native IBus/Fcitx UI owns composition/candidate presentation and is positioned from openQ4's active console/GUI text-input area. Committed text is limited to Quake 4's stock single-byte edit-widget and bitmap-font range; codepoints outside that range are ignored instead of being corrupted into unrelated characters. X11/Xext are optional helpers used only when available for the SDL3 NVIDIA VRAM probe and XWayland fallback. Configure with `-Dlinux_x11=disabled` to build the native Wayland/EGL-only path and exclude SDL's X11 driver, GLX GLEW code, and engine X11 helpers. An explicit `enabled` or `disabled` value forces the bundled SDL fallback so the requested driver set is honored; `auto` may use a suitable system SDL and inherits its compiled driver set. The legacy `-Dplatform_backend=native` path still requires X11/GLX and VidMode development packages on Linux and the legacy Carbon framework on macOS. On experimental macOS builds, SDL3 is the default platform path and does not link Carbon; it is also the only release backend, while the native Cocoa/OpenGL backend is comparison-only diagnostic infrastructure. `-Dmacos_graphics_bridge=metal` enables the Metal-ready SDL3/Cocoa bridge while keeping the stock-compatible OpenGL renderer rather than introducing a native Metal rewrite. The renderer/backend policy is tracked in `docs/dev/macos-renderer-backend-policy.md`, and the legacy native backend containment policy is tracked in `docs/dev/macos-native-backend-containment-policy.md`. Experimental macOS release and validation jobs use `-Dmacos_openal_provider=system` with the checksum-pinned, dynamically linked OpenAL Soft runtime built by `tools/build/prepare_macos_openal_soft.sh`; published apps carry that runtime, its LGPL notice, and its corresponding source archive. Apple's framework remains a compile-only compatibility diagnostic. The macOS audio-provider policy is tracked in `docs/dev/macos-openal-provider-policy.md`. -The macOS `system` OpenAL option is specifically an OpenAL Soft migration -provider, so its dependency lookup is pkg-config-only. A missing package now -stops at Meson configuration instead of silently selecting Apple's framework -and later failing against the incompatible `AL/...` headers. Homebrew installs -OpenAL Soft keg-only; expose it before configuring when necessary: +The macOS `system` option is deliberately pkg-config-only. Prepare the same +pinned OpenAL Soft 1.25.1 dependency used by CI, expose it to Meson, then stage +its runtime/license/source payload after `meson install`: ```bash -export PKG_CONFIG_PATH="$(brew --prefix openal-soft)/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +prepared_root="$PWD/.tmp/openal-soft-macos-arm64" +bash tools/build/prepare_macos_openal_soft.sh --architecture arm64 --output-root "$prepared_root" +export PKG_CONFIG_PATH="$prepared_root/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +# Run the normal Meson setup/compile/install commands with -Dmacos_openal_provider=system. +bash tools/build/prepare_macos_openal_soft.sh --verify-only --architecture arm64 \ + --output-root "$prepared_root" --stage-install-dir .install ``` Meson copies a wrap's `patch_directory` only when it first extracts that subproject. After pulling changes under `subprojects/packagefiles/sdl3/`, an existing ignored `subprojects/SDL3-*` tree can therefore retain stale build definitions even after `setup --wipe`. Refresh that generated extraction before rebuilding; this is especially important when validating `-Dlinux_x11=disabled`, because stale SDL definitions can silently retain X11 libraries: @@ -131,7 +134,7 @@ Pass any of these with `-D