#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BUILD_ROOT="$REPO_ROOT/build/xcframework" OUTPUT="$BUILD_ROOT/AudioCpp.xcframework" BUILD_TYPE="Release" ARCHS="$(uname -m)" DEPLOYMENT_TARGET="13.3" JOBS="$(sysctl -n hw.logicalcpu 2>/dev/null || echo 8)" CLEAN="OFF" LLAMAFILE="ON" NATIVE_CPU="ON" AUDIOCPP_DEPLOYMENT_BUILD="OFF" AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER="OFF" AUDIOCPP_USE_SYSTEM_OPENSSL="OFF" AUDIOCPP_BORINGSSL_ARCHIVE="" AUDIOCPP_MODEL_SET="full" AUDIOCPP_MODELS="" usage() { cat <<'EOF' Usage: scripts/build_xcframework.sh [options] Build audio.cpp as a macOS static XCFramework. Options: --output Output XCFramework path. Default: build/xcframework/AudioCpp.xcframework --build-root Intermediate build root. Default: build/xcframework --build-type CMake build type. Default: Release --deployment-target Minimum macOS deployment target. Default: 13.3 --archs "" Space or comma separated macOS architectures. Default: current machine architecture --universal Build arm64 and x86_64, then lipo a universal macOS lib. --clean Remove intermediate/output directories first. --llamafile ON|OFF Enable ggml llamafile SGEMM. Default: ON --native-cpu ON|OFF Build ggml CPU kernels with native host ISA flags. Default: ON --deployment-build Embed package specs for standalone GGUF/model loading. --native-model-manager Build native model manager and managed WebUI downloads. --system-openssl Use system OpenSSL for native model management. --boringssl-archive Use a local BoringSSL source archive for native model management instead of downloading at configure time. --model-set full|core|custom Model composite to build. Default: full --models "" Comma or semicolon separated model targets when --model-set custom is used. -j, --jobs Parallel build jobs. -h, --help Show this help. Examples: scripts/build_xcframework.sh scripts/build_xcframework.sh --universal scripts/build_xcframework.sh --output /tmp/AudioCpp.xcframework EOF } while [[ $# -gt 0 ]]; do case "$1" in --output) OUTPUT="$2" shift 2 ;; --build-root) BUILD_ROOT="$2" shift 2 ;; --build-type) BUILD_TYPE="$2" shift 2 ;; --deployment-target) DEPLOYMENT_TARGET="$2" shift 2 ;; --archs) ARCHS="$2" shift 2 ;; --universal) ARCHS="arm64 x86_64" shift ;; --clean) CLEAN="ON" shift ;; --llamafile) LLAMAFILE="$2" shift 2 ;; --native-cpu) NATIVE_CPU="$2" shift 2 ;; --deployment-build) AUDIOCPP_DEPLOYMENT_BUILD="ON" shift ;; --native-model-manager) AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER="ON" shift ;; --system-openssl) AUDIOCPP_USE_SYSTEM_OPENSSL="ON" shift ;; --boringssl-archive) AUDIOCPP_BORINGSSL_ARCHIVE="$2" shift 2 ;; --model-set) case "$2" in full|core|custom) AUDIOCPP_MODEL_SET="$2" ;; *) echo "--model-set must be full, core, or custom" >&2 exit 1 ;; esac shift 2 ;; --models) AUDIOCPP_MODELS="$2" shift 2 ;; -j|--jobs) JOBS="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 1 ;; esac done if [[ "$AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER" != "ON" ]]; then if [[ "$AUDIOCPP_USE_SYSTEM_OPENSSL" == "ON" ]]; then echo "--system-openssl requires --native-model-manager" >&2 exit 1 fi if [[ -n "$AUDIOCPP_BORINGSSL_ARCHIVE" ]]; then echo "--boringssl-archive requires --native-model-manager" >&2 exit 1 fi fi if [[ "$(uname -s)" != "Darwin" ]]; then echo "audio.cpp XCFramework builds require macOS." >&2 exit 1 fi for tool in cmake xcodebuild libtool lipo xcrun; do if ! command -v "$tool" >/dev/null 2>&1; then echo "Required tool not found on PATH: $tool" >&2 exit 1 fi done case "$LLAMAFILE" in ON|OFF) ;; on) LLAMAFILE="ON" ;; off) LLAMAFILE="OFF" ;; *) echo "--llamafile must be ON or OFF" >&2 exit 1 ;; esac case "$NATIVE_CPU" in ON|OFF) ;; on) NATIVE_CPU="ON" ;; off) NATIVE_CPU="OFF" ;; *) echo "--native-cpu must be ON or OFF" >&2 exit 1 ;; esac ARCHS="${ARCHS//,/ }" read -r -a ARCH_LIST <<< "$ARCHS" if [[ ${#ARCH_LIST[@]} -eq 0 ]]; then echo "No architectures requested." >&2 exit 1 fi GENERATOR="" if command -v ninja >/dev/null 2>&1; then GENERATOR="Ninja" fi ARTIFACT_ROOT="$BUILD_ROOT/artifacts" HEADER_DIR="$ARTIFACT_ROOT/Headers" UNIVERSAL_LIB="$ARTIFACT_ROOT/libMiniTTS.a" if [[ "$CLEAN" == "ON" ]]; then rm -rf "$BUILD_ROOT" "$OUTPUT" fi mkdir -p "$ARTIFACT_ROOT" rm -rf "$HEADER_DIR" mkdir -p "$HEADER_DIR" ditto "$REPO_ROOT/include" "$HEADER_DIR" ditto "$REPO_ROOT/external/ggml/include" "$HEADER_DIR" archive_for_arch() { local arch="$1" local build_dir="$BUILD_ROOT/macos-$arch" local out_dir="$ARTIFACT_ROOT/macos-$arch" local out_lib="$out_dir/libMiniTTS.a" mkdir -p "$out_dir" echo "==> Configuring audio.cpp for macOS $arch" local cmake_cmd=( cmake -S "$REPO_ROOT" \ -B "$build_dir" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_OSX_ARCHITECTURES="$arch" \ -DCMAKE_OSX_DEPLOYMENT_TARGET="$DEPLOYMENT_TARGET" \ -DENGINE_ENABLE_CUDA=OFF \ -DENGINE_ENABLE_VULKAN=OFF \ -DENGINE_ENABLE_METAL=ON \ -DENGINE_ENABLE_LLAMAFILE="$LLAMAFILE" \ -DENGINE_ENABLE_NATIVE_CPU="$NATIVE_CPU" \ -DGGML_METAL_EMBED_LIBRARY=ON \ -DENGINE_BUILD_TESTS=OFF \ -DENGINE_BUILD_EXAMPLES=OFF \ -DAUDIOCPP_DEPLOYMENT_BUILD="$AUDIOCPP_DEPLOYMENT_BUILD" \ -DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER="$AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER" \ -DAUDIOCPP_USE_SYSTEM_OPENSSL="$AUDIOCPP_USE_SYSTEM_OPENSSL" \ -UAUDIOCPP_BORINGSSL_ARCHIVE \ -DAUDIOCPP_MODEL_SET="$AUDIOCPP_MODEL_SET" \ -DAUDIOCPP_MODELS="$AUDIOCPP_MODELS" ) if [[ -n "$AUDIOCPP_BORINGSSL_ARCHIVE" ]]; then cmake_cmd+=(-DAUDIOCPP_BORINGSSL_ARCHIVE="$AUDIOCPP_BORINGSSL_ARCHIVE") fi if [[ -n "$GENERATOR" ]]; then cmake_cmd+=(-G "$GENERATOR") fi "${cmake_cmd[@]}" echo "==> Building engine_runtime for macOS $arch" cmake --build "$build_dir" --target engine_runtime -j "$JOBS" local libs=( "$build_dir/libengine_runtime.a" "$build_dir/libcjson_vendor.a" "$build_dir/libyaml_vendor.a" "$build_dir/external/sentencepiece/src/libsentencepiece.a" "$build_dir/external/ggml/src/libggml.a" "$build_dir/external/ggml/src/libggml-base.a" "$build_dir/external/ggml/src/libggml-cpu.a" "$build_dir/external/ggml/src/ggml-metal/libggml-metal.a" ) local optional_libs=( "$build_dir/external/ggml/src/ggml-blas/libggml-blas.a" ) for lib in "${optional_libs[@]}"; do if [[ -f "$lib" ]]; then libs+=("$lib") fi done for lib in "${libs[@]}"; do if [[ ! -f "$lib" ]]; then echo "Expected static library missing: $lib" >&2 exit 1 fi done echo "==> Merging static libraries for macOS $arch" rm -f "$out_lib" libtool -static -o "$out_lib" "${libs[@]}" lipo -info "$out_lib" } for arch in "${ARCH_LIST[@]}"; do archive_for_arch "$arch" done rm -f "$UNIVERSAL_LIB" if [[ ${#ARCH_LIST[@]} -eq 1 ]]; then cp "$ARTIFACT_ROOT/macos-${ARCH_LIST[0]}/libMiniTTS.a" "$UNIVERSAL_LIB" else lipo_inputs=() for arch in "${ARCH_LIST[@]}"; do lipo_inputs+=("$ARTIFACT_ROOT/macos-$arch/libMiniTTS.a") done lipo -create "${lipo_inputs[@]}" -output "$UNIVERSAL_LIB" fi lipo -info "$UNIVERSAL_LIB" echo "==> Creating XCFramework" rm -rf "$OUTPUT" xcodebuild -create-xcframework \ -library "$UNIVERSAL_LIB" \ -headers "$HEADER_DIR" \ -output "$OUTPUT" echo "Created: $OUTPUT"