fix(build): make macOS release builds work natively

The --mac-release path (full-node and --lite-backend) had never run on a real
Mac and broke on several Linux-only assumptions:

- build.sh version parser used GNU sed \+ (BSD sed reads it literally), so it
  aborted before compiling — switched to POSIX [[:space:]][[:space:]]*.
- build.sh libsodium universal check used GNU grep \| — switched to grep -E.
- libwebp's cpu.cmake applies -mno-sse2 to its reference DSP files; under a
  universal build (-arch arm64;x86_64) that lands on the x86_64 slice, where it
  disables _Float16 and breaks the SDK 26 <math.h>. Added an idempotent
  FetchContent PATCH_COMMAND (cmake/patch-libwebp-simd.cmake) to neutralize it.
- The SDXL lite backend static lib needs Security + CoreFoundation frameworks on
  macOS; added them to the imported dragonx_lite_backend target for APPLE.
- Added a DRAGONX_MAC_ARCHS override plus auto-detect: with --lite-backend the
  app is built for the arch(es) the backend .a actually provides (its pinned
  ring 0.16.11 is x86_64-only), instead of failing to link a universal app.
- build-lite-backend-artifact.sh uses bash 4+ (mapfile); added a re-exec guard
  for macOS's stock bash 3.2 and added bash to setup.sh's macOS core deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 20:02:44 -05:00
parent 5a0743a17b
commit b561406c23
5 changed files with 84 additions and 7 deletions

View File

@@ -136,6 +136,13 @@ if(DRAGONX_ENABLE_LITE_BACKEND)
set_target_properties(dragonx_lite_backend PROPERTIES set_target_properties(dragonx_lite_backend PROPERTIES
IMPORTED_LOCATION "${DRAGONX_LITE_BACKEND_LIBRARY}" IMPORTED_LOCATION "${DRAGONX_LITE_BACKEND_LIBRARY}"
) )
if(APPLE)
# The Rust backend's TLS stack (security-framework / core-foundation crates)
# references Secure Transport (SSL*) + CoreFoundation symbols. Link the frameworks
# that provide them, or the static lib leaves ~130 symbols undefined at link time.
set_property(TARGET dragonx_lite_backend APPEND PROPERTY
INTERFACE_LINK_LIBRARIES "-framework Security" "-framework CoreFoundation")
endif()
if(DRAGONX_LITE_BACKEND_INCLUDE_DIR) if(DRAGONX_LITE_BACKEND_INCLUDE_DIR)
if(NOT IS_DIRECTORY "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}") if(NOT IS_DIRECTORY "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_INCLUDE_DIR does not exist: ${DRAGONX_LITE_BACKEND_INCLUDE_DIR}") message(FATAL_ERROR "DRAGONX_LITE_BACKEND_INCLUDE_DIR does not exist: ${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
@@ -284,6 +291,15 @@ FetchContent_Declare(
GIT_REPOSITORY https://github.com/webmproject/libwebp.git GIT_REPOSITORY https://github.com/webmproject/libwebp.git
GIT_TAG v1.4.0 GIT_TAG v1.4.0
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
# libwebp's cpu.cmake applies -mno-sse2/-mno-sse4.1 to its scalar reference DSP
# files when it can't probe SSE support. Under a macOS universal build
# (-arch arm64;x86_64) that probe fails, so the flags land on the x86_64 slice,
# where -mno-sse2 disables _Float16 and breaks the SDK's <math.h>. Neutralize
# those disable flags (SSE2 is x86_64 baseline). Portable + idempotent; a no-op
# for single-arch Linux/Windows/x86_64 builds. See cmake/patch-libwebp-simd.cmake.
PATCH_COMMAND ${CMAKE_COMMAND}
-DCPU_CMAKE=<SOURCE_DIR>/cmake/cpu.cmake
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch-libwebp-simd.cmake
) )
set(WEBP_LINK_STATIC ON CACHE BOOL "" FORCE) set(WEBP_LINK_STATIC ON CACHE BOOL "" FORCE)
set(WEBP_BUILD_ANIM_UTILS OFF CACHE BOOL "" FORCE) set(WEBP_BUILD_ANIM_UTILS OFF CACHE BOOL "" FORCE)

View File

@@ -131,7 +131,7 @@ fi
# truth): the full-node app uses project() VERSION + DRAGONX_VERSION_SUFFIX; ObsidianDragonLite uses # truth): the full-node app uses project() VERSION + DRAGONX_VERSION_SUFFIX; ObsidianDragonLite uses
# DRAGONX_LITE_VERSION + DRAGONX_LITE_VERSION_SUFFIX. # DRAGONX_LITE_VERSION + DRAGONX_LITE_VERSION_SUFFIX.
_cml="$SCRIPT_DIR/CMakeLists.txt" _cml="$SCRIPT_DIR/CMakeLists.txt"
_full_ver=$(sed -n 's/^[[:space:]]*VERSION[[:space:]]\+\([0-9][0-9.]*\).*/\1/p' "$_cml" | head -1) _full_ver=$(sed -n 's/^[[:space:]]*VERSION[[:space:]][[:space:]]*\([0-9][0-9.]*\).*/\1/p' "$_cml" | head -1)
_full_suffix=$(sed -n 's/^set(DRAGONX_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1) _full_suffix=$(sed -n 's/^set(DRAGONX_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
_lite_ver=$(sed -n 's/^set(DRAGONX_LITE_VERSION[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1) _lite_ver=$(sed -n 's/^set(DRAGONX_LITE_VERSION[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
_lite_suffix=$(sed -n 's/^set(DRAGONX_LITE_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1) _lite_suffix=$(sed -n 's/^set(DRAGONX_LITE_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
@@ -901,8 +901,26 @@ build_release_mac() {
fi fi
info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)" info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)"
else else
# Native macOS: build universal binary (arm64 + x86_64) # Native macOS: build universal (arm64 + x86_64) by default. Override with
MAC_ARCH="universal" # DRAGONX_MAC_ARCHS (e.g. "x86_64").
MAC_ARCHS="${DRAGONX_MAC_ARCHS:-arm64;x86_64}"
# When linking the real lite backend, the app can only include architectures
# the backend static library actually provides. Its pinned ring 0.16.11 has no
# Apple-Silicon assembly, so that artifact is x86_64-only — constrain the app
# arch to the backend's (unless the user explicitly forced DRAGONX_MAC_ARCHS),
# otherwise the arm64 slice fails to link.
if $DO_LITE_BACKEND && [[ -z "${DRAGONX_MAC_ARCHS:-}" && -n "${lb_lib:-}" ]] && command -v lipo &>/dev/null; then
local _backend_archs; _backend_archs=$(lipo -archs "$lb_lib" 2>/dev/null | tr ' ' ';')
if [[ -n "$_backend_archs" && "$_backend_archs" != "$MAC_ARCHS" ]]; then
warn "Lite backend provides only [$_backend_archs] — building the app for that instead of universal."
MAC_ARCHS="$_backend_archs"
fi
fi
if [[ "$MAC_ARCHS" == *";"* || "$MAC_ARCHS" == *","* ]]; then
MAC_ARCH="universal"
else
MAC_ARCH="$MAC_ARCHS"
fi
export MACOSX_DEPLOYMENT_TARGET="11.0" export MACOSX_DEPLOYMENT_TARGET="11.0"
fi fi
@@ -990,7 +1008,7 @@ TOOLCHAIN
need_sodium=true need_sodium=true
elif [[ -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]]; then elif [[ -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
# Rebuild if existing lib is not universal (single-arch won't link) # Rebuild if existing lib is not universal (single-arch won't link)
if ! lipo -info "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" 2>/dev/null | grep -q "arm64.*x86_64\|x86_64.*arm64"; then if ! lipo -info "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" 2>/dev/null | grep -Eq "arm64.*x86_64|x86_64.*arm64"; then
info "Existing libsodium is not universal — rebuilding ..." info "Existing libsodium is not universal — rebuilding ..."
rm -rf "$SCRIPT_DIR/libs/libsodium" rm -rf "$SCRIPT_DIR/libs/libsodium"
need_sodium=true need_sodium=true
@@ -1001,13 +1019,13 @@ TOOLCHAIN
"$SCRIPT_DIR/scripts/fetch-libsodium.sh" "$SCRIPT_DIR/scripts/fetch-libsodium.sh"
fi fi
info "Configuring (native universal arm64+x86_64) ..." info "Configuring (native macOS, arch: $MAC_ARCHS) ..."
cmake "$SCRIPT_DIR" \ cmake "$SCRIPT_DIR" \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
-DDRAGONX_USE_SYSTEM_SDL3=OFF \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ -DCMAKE_OSX_ARCHITECTURES="$MAC_ARCHS" \
"${CMAKE_LITE_ARGS[@]}" "${CMAKE_LITE_ARGS[@]}"
fi fi

View File

@@ -0,0 +1,31 @@
# patch-libwebp-simd.cmake — portable, idempotent FetchContent patch for libwebp.
#
# libwebp's cmake/cpu.cmake compiles its scalar *reference* DSP files with the
# SSE-disable flags "-mno-sse4.1;-mno-sse2" whenever it can't positively detect
# SSE support. Under a macOS *universal* build (-arch arm64;x86_64) the per-arch
# SSE flag probe fails (a flag valid for x86_64 is invalid for arm64), so those
# disable flags get applied to the x86_64 slice. clang gates the _Float16 type on
# SSE2 for x86_64, and the macOS 15+/26 SDK's <math.h> declares _Float16 math
# functions unconditionally — so any TU including <math.h> fails to compile with
# "_Float16 is not supported on this target".
#
# SSE2 is part of the x86_64 baseline ABI, so disabling it on the reference files
# is unnecessary on every platform we target. Blanking the SSE entries (indices
# must stay aligned with WEBP_SIMD_FLAGS) fixes the universal build and is a no-op
# for single-arch Linux/Windows/x86_64 builds. Idempotent: re-running is a no-op.
if(NOT DEFINED CPU_CMAKE OR NOT EXISTS "${CPU_CMAKE}")
message(FATAL_ERROR "patch-libwebp-simd: cpu.cmake not found at '${CPU_CMAKE}'")
endif()
file(READ "${CPU_CMAKE}" _contents)
string(REPLACE
"set(SIMD_DISABLE_FLAGS \"-mno-sse4.1;-mno-sse2;;-mno-dspr2;;-mno-msa\")"
"set(SIMD_DISABLE_FLAGS \";;;-mno-dspr2;;-mno-msa\")"
_patched "${_contents}")
if(_patched STREQUAL _contents)
message(STATUS "patch-libwebp-simd: no change (already patched or pattern absent)")
else()
file(WRITE "${CPU_CMAKE}" "${_patched}")
message(STATUS "patch-libwebp-simd: neutralized x86 SSE-disable flags in cpu.cmake")
endif()

View File

@@ -1,5 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# This script uses bash 4+ features (mapfile, safe empty-array expansion under
# `set -u`). macOS ships bash 3.2, so re-exec under a newer bash when one is
# present (Homebrew), and fail with a clear message otherwise.
if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then
for _newer_bash in /opt/homebrew/bin/bash /usr/local/bin/bash; do
[ -x "$_newer_bash" ] && exec "$_newer_bash" "$0" "$@"
done
echo "ERROR: build-lite-backend-artifact.sh requires bash 4+ (found ${BASH_VERSION:-unknown})." >&2
echo " On macOS: brew install bash" >&2
exit 1
fi
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

View File

@@ -133,7 +133,7 @@ pkgs_core_arch="base-devel cmake git pkg-config
libxkbcommon wayland libsodium curl libxkbcommon wayland libsodium curl
autoconf automake libtool wget python xxd" autoconf automake libtool wget python xxd"
pkgs_core_macos="cmake python xxd" pkgs_core_macos="bash cmake python xxd"
# Windows cross-compile (from Linux) # Windows cross-compile (from Linux)
pkgs_win_debian="mingw-w64 zip" pkgs_win_debian="mingw-w64 zip"