From c562f486d06f2685a9263abfd959bbd425974d14 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Sun, 16 Aug 2026 13:46:47 +1000 Subject: [PATCH] fix(cpn): tie macOS bundle to its own SDL copies, and verify it (#7653) Co-authored-by: Peter Feerick <5500713+pfeerick@users.noreply.github.com> Co-authored-by: Claude Opus 5 --- .github/actions/build_companion/action.yml | 27 ++++- .github/actions/build_companion/setup-sdl.sh | 108 ++++++++++++++++++ .github/actions/build_companion/setup-sdl2.sh | 46 -------- companion/src/CMakeLists.txt | 53 ++++++++- .../targets/mac/bundle_sdl_fixup.cmake.in | 65 +++++++++++ companion/targets/mac/bundle_verify.cmake.in | 92 +++++++++++++++ docs/building/macos-sequoia.md | 14 ++- tools/build-companion.sh | 7 ++ 8 files changed, 357 insertions(+), 55 deletions(-) create mode 100755 .github/actions/build_companion/setup-sdl.sh delete mode 100755 .github/actions/build_companion/setup-sdl2.sh create mode 100644 companion/targets/mac/bundle_sdl_fixup.cmake.in create mode 100644 companion/targets/mac/bundle_verify.cmake.in diff --git a/.github/actions/build_companion/action.yml b/.github/actions/build_companion/action.yml index ed59ffd98..67bd0fdfe 100644 --- a/.github/actions/build_companion/action.yml +++ b/.github/actions/build_companion/action.yml @@ -10,8 +10,17 @@ inputs: description: 'Qt architecture' required: runner.os != 'Linux' sdl2-version: - description: 'SDL2 version' + description: 'Upstream SDL2 version (Windows only)' default: '2.32.8' + sdl3-version: + description: 'SDL3 version built from source for macOS' + default: '3.4.14' + sdl2-compat-version: + description: 'sdl2-compat version built from source for macOS' + default: '2.32.70' + macos-deployment-target: + description: 'Minimum macOS version the SDL libraries are built for' + default: '11.0' runs: using: "composite" @@ -29,14 +38,26 @@ runs: cache: true cache-key-prefix: 'install-qt-action-${{ inputs.qt-arch }}' - - name: Setup SDL2 + # macOS builds SDL from source (see setup-sdl.sh) - cache it so only the first + # run per version pays for it + - name: Cache SDL build + if: runner.os == 'macOS' + uses: actions/cache@v6 + with: + path: ${{ runner.temp }}/sdl + key: sdl-${{ runner.os }}-${{ inputs.sdl3-version }}-${{ inputs.sdl2-compat-version }}-${{ inputs.macos-deployment-target }} + + - name: Setup SDL if: runner.os != 'Linux' shell: bash env: ACTION_PATH: ${{ github.action_path }} SDL2_VERSION: ${{ inputs.sdl2-version }} + SDL3_VERSION: ${{ inputs.sdl3-version }} + SDL2_COMPAT_VERSION: ${{ inputs.sdl2-compat-version }} + MACOS_DEPLOYMENT_TARGET: ${{ inputs.macos-deployment-target }} run: | - "${ACTION_PATH}/setup-sdl2.sh" ${SDL2_VERSION} + "${ACTION_PATH}/setup-sdl.sh" - name: Setup MSVC if: runner.os == 'Windows' diff --git a/.github/actions/build_companion/setup-sdl.sh b/.github/actions/build_companion/setup-sdl.sh new file mode 100755 index 000000000..c2894140f --- /dev/null +++ b/.github/actions/build_companion/setup-sdl.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# setup-sdl.sh +# Provisions SDL for Companion builds. +# +# Windows: upstream SDL2 MSVC development binaries. +# macOS: sdl2-compat + SDL3 built from source. +# Linux: provided by the Docker container. +# +# macOS builds against sdl2-compat rather than classic SDL2 because that is what users get +# from Homebrew, and it is the configuration that exposed the SDL3 bundling bug - CI needs to +# exercise the same code path Companion ships through. +# +# Homebrew itself cannot be used here: its bottles are single-architecture and target the +# runner's macOS version, while Companion ships a universal binary for macOS 11.0+. Building +# from source is what keeps both properties. + +set -euo pipefail + +SDL2_VERSION="${SDL2_VERSION:?SDL2_VERSION must be set}" + +build_cmake_project() { + local name="$1" url="$2" srcdir="$3" + shift 3 + + echo "Building ${name} (universal, macOS ${MACOS_DEPLOYMENT_TARGET}+)..." + curl -fsSL -o "${RUNNER_TEMP}/${name}.tar.gz" "${url}" + mkdir -p "${RUNNER_TEMP}/src" + tar -xzf "${RUNNER_TEMP}/${name}.tar.gz" -C "${RUNNER_TEMP}/src" + + cmake -S "${RUNNER_TEMP}/src/${srcdir}" -B "${RUNNER_TEMP}/build-${name}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET="${MACOS_DEPLOYMENT_TARGET}" \ + -DCMAKE_INSTALL_PREFIX="${SDL_ROOT}" \ + -DCMAKE_PREFIX_PATH="${SDL_ROOT}" \ + "$@" + cmake --build "${RUNNER_TEMP}/build-${name}" --parallel + cmake --install "${RUNNER_TEMP}/build-${name}" +} + +case "$RUNNER_OS" in + Windows) + ARCHIVE="SDL2-devel-${SDL2_VERSION}-VC.zip" + URL="https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/${ARCHIVE}" + DEST="${RUNNER_TEMP}/SDL2" + + echo "Downloading SDL2 ${SDL2_VERSION} (Windows/MSVC)..." + curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}" + unzip -q "${RUNNER_TEMP}/${ARCHIVE}" -d "${DEST}" + + SDL2_ROOT="${DEST}/SDL2-${SDL2_VERSION}" + echo "SDL2_ROOT=${SDL2_ROOT}" >> "${GITHUB_ENV}" + ;; + + macOS) + SDL3_VERSION="${SDL3_VERSION:?SDL3_VERSION must be set}" + SDL2_COMPAT_VERSION="${SDL2_COMPAT_VERSION:?SDL2_COMPAT_VERSION must be set}" + MACOS_DEPLOYMENT_TARGET="${MACOS_DEPLOYMENT_TARGET:-11.0}" + SDL_ROOT="${RUNNER_TEMP}/sdl" + STAMP="${SDL_ROOT}/.stamp-${SDL3_VERSION}-${SDL2_COMPAT_VERSION}-${MACOS_DEPLOYMENT_TARGET}" + + if [[ -f "${STAMP}" ]]; then + echo "Reusing cached SDL build in ${SDL_ROOT}" + else + rm -rf "${SDL_ROOT}" + + # SDL3 first - sdl2-compat needs its headers and CMake package to configure + build_cmake_project "SDL3" \ + "https://github.com/libsdl-org/SDL/releases/download/release-${SDL3_VERSION}/SDL3-${SDL3_VERSION}.tar.gz" \ + "SDL3-${SDL3_VERSION}" \ + -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -DSDL_EXAMPLES=OFF + + build_cmake_project "sdl2-compat" \ + "https://github.com/libsdl-org/sdl2-compat/releases/download/release-${SDL2_COMPAT_VERSION}/sdl2-compat-${SDL2_COMPAT_VERSION}.tar.gz" \ + "sdl2-compat-${SDL2_COMPAT_VERSION}" \ + -DSDL2COMPAT_STATIC=OFF -DSDL2COMPAT_TESTS=OFF + + touch "${STAMP}" + fi + + # Both live under one prefix, so a single entry on CMAKE_PREFIX_PATH finds + # SDL2 (sdl2-compat) and SDL3 alike. + echo "SDL2_ROOT=${SDL_ROOT}" >> "${GITHUB_ENV}" + + echo "Verifying universal slices and deployment target..." + for lib in "${SDL_ROOT}"/lib/libSDL2-2.0.0.dylib "${SDL_ROOT}"/lib/libSDL3.0.dylib; do + [[ -f "${lib}" ]] || { echo "::error::Expected ${lib} to exist"; exit 1; } + archs="$(lipo -archs "${lib}")" + case "${archs}" in + *x86_64*arm64*|*arm64*x86_64*) ;; + *) echo "::error::${lib} is not universal (got: ${archs})"; exit 1 ;; + esac + echo " $(basename "${lib}"): ${archs}" + vtool -show-build "${lib}" | grep -E 'minos|platform' || true + done + ;; + + Linux) + echo "Skipping — SDL is provided by the Docker container" + ;; + + *) + echo "::error::Unsupported RUNNER_OS: ${RUNNER_OS}" + exit 1 + ;; +esac + +echo "SDL setup complete for ${RUNNER_OS}" diff --git a/.github/actions/build_companion/setup-sdl2.sh b/.github/actions/build_companion/setup-sdl2.sh deleted file mode 100755 index dc317846e..000000000 --- a/.github/actions/build_companion/setup-sdl2.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -# setup-sdl2.sh -# Downloads/installs SDL2 for Windows and macOS. -# Linux is handled separately via a Docker container. - -set -euo pipefail - -VERSION="${1:?Usage: setup-sdl2.sh }" - -case "$RUNNER_OS" in - Windows) - ARCHIVE="SDL2-devel-${VERSION}-VC.zip" - URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}" - DEST="${RUNNER_TEMP}/SDL2" - - echo "Downloading SDL2 ${VERSION} (Windows/MSVC)..." - curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}" - unzip -q "${RUNNER_TEMP}/${ARCHIVE}" -d "${DEST}" - - SDL2_ROOT="${DEST}/SDL2-${VERSION}" - echo "SDL2_ROOT=${SDL2_ROOT}" >> "${GITHUB_ENV}" - ;; - - macOS) - ARCHIVE="SDL2-${VERSION}.dmg" - URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}" - - echo "Downloading SDL2 ${VERSION} (macOS framework)..." - curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}" - - hdiutil attach "${RUNNER_TEMP}/${ARCHIVE}" -quiet -mountpoint "${RUNNER_TEMP}/sdl2-mnt" - sudo cp -R "${RUNNER_TEMP}/sdl2-mnt/SDL2.framework" /Library/Frameworks/ - hdiutil detach "${RUNNER_TEMP}/sdl2-mnt" -quiet - ;; - - Linux) - echo "Skipping — SDL2 is provided by the Docker container" - ;; - - *) - echo "::error::Unsupported RUNNER_OS: ${RUNNER_OS}" - exit 1 - ;; -esac - -echo "SDL2 ${VERSION} setup complete for ${RUNNER_OS}" diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 744ecd0ee..a53b10811 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -570,26 +570,35 @@ elseif(APPLE) # Run macdeployqt install(SCRIPT ${qt_deploy_companion} COMPONENT Runtime) + option(COMPANION_VERIFY_BUNDLE + "Fail the install if the macOS bundle references libraries outside itself" ON) + set(COMPANION_VERIFY_BUNDLE_ALLOW "" CACHE STRING + "Regex of absolute library paths the bundle verification may ignore") + # Bundle SDL2 (copy the real dylib; IMPORTED_RUNTIME_ARTIFACTS keeps # Homebrew symlinks and breaks the app inside a .dmg). Homebrew's # sdl2-compat also loads SDL3 from @loader_path/libSDL3.dylib at runtime. + set(_bundle_expects_sdl3 FALSE) + set(_bundled_sdl2 FALSE) if(TARGET SDL2::SDL2) get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION) if(NOT _sdl2_location) get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION_RELEASE) endif() if(_sdl2_location AND _sdl2_location MATCHES "\\.framework(/|$)") - # Genuine upstream SDL2.framework (CI's setup-sdl2.sh, or a manual framework - # install): no SDL3 dlopen shim to worry about, and IMPORTED_RUNTIME_ARTIFACTS - # copies frameworks correctly — it's only the Homebrew dylib case below whose - # symlinks it mangles. This is what CI has shipped all along. + # Genuine upstream SDL2.framework (a manual framework install): no SDL3 dlopen + # shim to worry about, and IMPORTED_RUNTIME_ARTIFACTS copies frameworks + # correctly — it's only the dylib case below whose symlinks it mangles. + # CI no longer takes this path; setup-sdl.sh builds sdl2-compat + SDL3 instead. message(STATUS "Bundling SDL2 framework: ${_sdl2_location}") install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2 FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks" LIBRARY DESTINATION "${companion_app_dir}/Contents/Frameworks" COMPONENT Runtime) + set(_bundled_sdl2 TRUE) elseif(_sdl2_location) message(STATUS "Bundling SDL2 library: ${_sdl2_location}") + set(_bundled_sdl2 TRUE) get_filename_component(_sdl2_real "${_sdl2_location}" REALPATH) get_filename_component(_sdl2_name "${_sdl2_location}" NAME) install(FILES "${_sdl2_real}" @@ -610,6 +619,10 @@ elseif(APPLE) endif() if(NOT _sdl3_real) set(_sdl3_hints "") + # SDL3 usually lives right next to SDL2 (setup-sdl.sh installs both to one prefix) - + # check there before falling back to Homebrew heuristics. + get_filename_component(_sdl2_libdir "${_sdl2_location}" DIRECTORY) + list(APPEND _sdl3_hints "${_sdl2_libdir}") if(DEFINED ENV{HOMEBREW_PREFIX}) list(APPEND _sdl3_hints "$ENV{HOMEBREW_PREFIX}/opt/sdl3/lib" @@ -645,8 +658,12 @@ elseif(APPLE) DESTINATION "${companion_app_dir}/Contents/Frameworks" RENAME "libSDL3.dylib" COMPONENT Runtime) + set(_bundle_expects_sdl3 TRUE) else() - message(WARNING "SDL3 not found; packaged Companion may crash if linked against sdl2-compat") + message(FATAL_ERROR + "SDL3 not found, but the bundled SDL2 (${_sdl2_location}) is sdl2-compat, which " + "dlopens libSDL3.dylib at runtime - Companion would not start without it. Install " + "SDL3, or point CMAKE_PREFIX_PATH/HOMEBREW_PREFIX at a prefix that has it.") endif() install(CODE " @@ -673,6 +690,21 @@ elseif(APPLE) else() message(WARNING "SDL2::SDL2 has no IMPORTED_LOCATION; SDL2 will not be bundled") endif() + + # Point the executable at the SDL copies bundled above. qt_deploy_runtime_dependencies + # ran before they existed, and install_name_tool -id only fixes each dylib's own id, so + # nothing else guarantees this. Runs for both the framework and dylib branches, and must + # stay ahead of the re-sign below so the signature covers the rewritten load commands. + # Skipped when nothing was bundled - rewriting to Frameworks would then point the + # executable at files that are not there. + if(_bundled_sdl2) + configure_file( + "${COMPANION_TARGETS_DIR}/bundle_sdl_fixup.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/bundle_sdl_fixup.cmake" + @ONLY + ) + install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/bundle_sdl_fixup.cmake" COMPONENT Runtime) + endif() endif() # Re-sign bundle after all contents are installed @@ -685,6 +717,17 @@ elseif(APPLE) ) " COMPONENT Runtime) + # Last gate: a bundle that still points at /opt/homebrew or a build-machine Qt prefix + # starts on the machine that built it and nowhere else. Fail here rather than ship it. + if(COMPANION_VERIFY_BUNDLE) + configure_file( + "${COMPANION_TARGETS_DIR}/bundle_verify.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/bundle_verify.cmake" + @ONLY + ) + install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/bundle_verify.cmake" COMPONENT Runtime) + endif() + else() # Detect architecture for linuxdeploy AppImage if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") diff --git a/companion/targets/mac/bundle_sdl_fixup.cmake.in b/companion/targets/mac/bundle_sdl_fixup.cmake.in new file mode 100644 index 000000000..f2da3d9d9 --- /dev/null +++ b/companion/targets/mac/bundle_sdl_fixup.cmake.in @@ -0,0 +1,65 @@ +# Rewrite absolute SDL load commands in the Companion executable to the bundled copies. +# +# Configured from companion/targets/mac/bundle_sdl_fixup.cmake.in - do not edit the copy in +# the build tree. +# +# Companion used to run fixup_bundle, which walked every Mach-O in the bundle and rewrote all +# of their load commands in one pass. That was replaced by qt_deploy_runtime_dependencies, +# which runs *before* the SDL install(FILES) steps, and by an install_name_tool -id call that +# only fixes each dylib's own id. Nothing in that chain guarantees the executable actually +# references the SDL copies sitting in Contents/Frameworks, so make it explicit here. + +set(_app "${CMAKE_INSTALL_PREFIX}/@companion_app_dir@") +set(_bin "${_app}/Contents/MacOS/@COMPANION_OSX_APP_BUNDLE_NAME@") + +if(NOT EXISTS "${_bin}") + message(FATAL_ERROR "Companion executable not found for SDL fixup: ${_bin}") +endif() + +execute_process( + COMMAND otool -L "${_bin}" + OUTPUT_VARIABLE _otool_out + ERROR_VARIABLE _otool_err + RESULT_VARIABLE _otool_res +) +if(NOT _otool_res EQUAL 0) + message(FATAL_ERROR "otool -L failed for ${_bin}: ${_otool_err}") +endif() + +string(REPLACE "\n" ";" _lines "${_otool_out}") +foreach(_line IN LISTS _lines) + # dependency lines are tab-indented: "\t (compatibility version ..., current ...)" + string(REGEX MATCH "^\t([^ ]+)" _matched "${_line}") + if(NOT _matched) + continue() + endif() + set(_ref "${CMAKE_MATCH_1}") + + # @rpath / @executable_path / @loader_path already resolve inside the bundle + if(NOT _ref MATCHES "^/") + continue() + endif() + if(NOT _ref MATCHES "SDL2|SDL3") + continue() + endif() + + # Keep the framework tail (SDL2.framework/Versions/A/SDL2) so the rewritten reference + # matches the layout install(IMPORTED_RUNTIME_ARTIFACTS) produced; plain dylibs land + # directly in Frameworks under their own name. + if(_ref MATCHES "/(SDL[23]\\.framework/.*)$") + set(_tail "${CMAKE_MATCH_1}") + else() + get_filename_component(_tail "${_ref}" NAME) + endif() + + set(_new "@executable_path/../Frameworks/${_tail}") + message(STATUS "Rewriting SDL reference: ${_ref} -> ${_new}") + execute_process( + COMMAND install_name_tool -change "${_ref}" "${_new}" "${_bin}" + ERROR_VARIABLE _change_err + RESULT_VARIABLE _change_res + ) + if(NOT _change_res EQUAL 0) + message(FATAL_ERROR "install_name_tool -change failed for ${_ref}: ${_change_err}") + endif() +endforeach() diff --git a/companion/targets/mac/bundle_verify.cmake.in b/companion/targets/mac/bundle_verify.cmake.in new file mode 100644 index 000000000..8b0102937 --- /dev/null +++ b/companion/targets/mac/bundle_verify.cmake.in @@ -0,0 +1,92 @@ +# Verify the installed Companion .app carries no references to libraries outside itself. +# +# Configured from companion/targets/mac/bundle_verify.cmake.in - do not edit the copy in the +# build tree. +# +# A .dmg that links against /opt/homebrew, /usr/local/Cellar or a build-machine Qt prefix +# starts fine on the machine that built it and fails everywhere else, which is exactly how the +# sdl2-compat breakage reached users. Catch it at install time instead. +# +# Disable with -DCOMPANION_VERIFY_BUNDLE=OFF; widen with -DCOMPANION_VERIFY_BUNDLE_ALLOW=. + +set(_app "${CMAKE_INSTALL_PREFIX}/@companion_app_dir@") +set(_allow "@COMPANION_VERIFY_BUNDLE_ALLOW@") +set(_expect_sdl3 "@_bundle_expects_sdl3@") + +if(NOT IS_DIRECTORY "${_app}") + message(FATAL_ERROR "Bundle not found for verification: ${_app}") +endif() + +# sdl2-compat dlopens "@loader_path/libSDL3.dylib" - invisible to otool, so check by hand +if(_expect_sdl3 AND NOT EXISTS "${_app}/Contents/Frameworks/libSDL3.dylib") + message(FATAL_ERROR + "Contents/Frameworks/libSDL3.dylib is missing. Companion was linked against " + "sdl2-compat, which loads SDL3 at runtime, so the bundle will not start.") +endif() + +file(GLOB_RECURSE _candidates "${_app}/Contents/*") + +set(_offenders "") +set(_checked 0) +foreach(_file IN LISTS _candidates) + if(IS_SYMLINK "${_file}" OR IS_DIRECTORY "${_file}") + continue() + endif() + + execute_process( + COMMAND otool -L "${_file}" + OUTPUT_VARIABLE _otool_out + ERROR_VARIABLE _otool_err + RESULT_VARIABLE _otool_res + ) + # non-Mach-O files (plists, icons, translations) simply fail the scan - skip them + if(NOT _otool_res EQUAL 0 OR _otool_out MATCHES "is not an object file") + continue() + endif() + math(EXPR _checked "${_checked} + 1") + + string(REPLACE "\n" ";" _lines "${_otool_out}") + foreach(_line IN LISTS _lines) + string(REGEX MATCH "^\t([^ ]+)" _matched "${_line}") + if(NOT _matched) + continue() + endif() + set(_ref "${CMAKE_MATCH_1}") + + if(NOT _ref MATCHES "^/") + continue() # @rpath / @executable_path / @loader_path resolve inside the bundle + endif() + if(_ref MATCHES "^/usr/lib/" OR _ref MATCHES "^/System/") + continue() # shipped with macOS + endif() + if(_allow AND _ref MATCHES "${_allow}") + continue() + endif() + + file(RELATIVE_PATH _rel "${_app}" "${_file}") + list(APPEND _offenders "${_rel}: ${_ref}") + endforeach() +endforeach() + +if(_offenders) + list(REMOVE_DUPLICATES _offenders) + string(REPLACE ";" "\n " _report "${_offenders}") + message(FATAL_ERROR + "Bundle references libraries outside itself - this .dmg would not start on another " + "machine:\n ${_report}\n" + "Re-run with -DCOMPANION_VERIFY_BUNDLE=OFF to package anyway.") +endif() + +message(STATUS "Bundle verification: ${_checked} Mach-O files, no external references") + +# The bundle seal is a separate concern from the per-Mach-O signatures: dyld does not consult +# CodeResources when loading, so a stale seal does not stop the app launching. It would matter +# for Gatekeeper/notarization, so report it - but do not fail a build over it. +execute_process( + COMMAND codesign --verify --deep --strict --verbose=2 "${_app}" + ERROR_VARIABLE _codesign_err + RESULT_VARIABLE _codesign_res +) +if(NOT _codesign_res EQUAL 0) + message(WARNING "Bundle signature is not valid (not fatal): ${_codesign_err}") +endif() diff --git a/docs/building/macos-sequoia.md b/docs/building/macos-sequoia.md index e7bc231ab..fed2e0c75 100644 --- a/docs/building/macos-sequoia.md +++ b/docs/building/macos-sequoia.md @@ -164,7 +164,19 @@ Open it and drag **EdgeTX Companion** into `/Applications`: open native/edgetx-companion-*.dmg ``` -The packaging step bundles Qt, SDL2, and SDL3 into the `.app` automatically. +The packaging step bundles Qt, SDL2, and SDL3 into the `.app` automatically, then verifies that +nothing in the bundle still points at a library outside it. If that check fails, the build stops +rather than producing a `.dmg` that only runs on your machine — most often because a dependency +was picked up from Homebrew without being copied in. Pass `-DCOMPANION_VERIFY_BUNDLE=OFF` to +package anyway. + +!!! note + A locally built `.dmg` is not what CI ships. Homebrew's bottles are single-architecture and + target your macOS version, so your build is arm64-only and requires the macOS you built it + on. CI builds sdl2-compat and SDL3 from source as universal (`x86_64` + `arm64`) binaries + targeting macOS 11.0 — see `.github/actions/build_companion/setup-sdl.sh`. Both use the same + sdl2-compat + SDL3 stack, so the code path is identical; only the slices and minimum OS + differ. # Troubleshooting diff --git a/tools/build-companion.sh b/tools/build-companion.sh index 456d18209..eda29d309 100755 --- a/tools/build-companion.sh +++ b/tools/build-companion.sh @@ -34,6 +34,13 @@ if [ "$(uname)" = "Darwin" ]; then COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_OSX_DEPLOYMENT_TARGET='11.0'" fi +# find_package(... CONFIG) skips the lib/cmake/ search pattern for prefixes that only +# come from the CMAKE_PREFIX_PATH env var (not -D) - forward it so CI's source-built SDL3 is +# actually found instead of silently missing from the bundle. +if [[ -n "${CMAKE_PREFIX_PATH:-}" ]]; then + COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" +fi + # Generate EDGETX_VERSION_SUFFIX if not already set if [[ -z ${EDGETX_VERSION_SUFFIX} ]]; then gh_type=$(echo "$GITHUB_REF" | awk -F / '{print $2}') #heads|tags|pull