#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BUILD_DIR="" BUILD_TYPE="RelWithDebInfo" WITH_TESTS="OFF" WITH_EXAMPLES="OFF" WITH_WARMBENCH="OFF" AUDIOCPP_DEPLOYMENT_BUILD="OFF" AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER="OFF" AUDIOCPP_USE_SYSTEM_OPENSSL="OFF" AUDIOCPP_BORINGSSL_ARCHIVE="" AUDIOCPP_MODEL_SET="full" AUDIOCPP_MODELS="" OPENMP_MODE="off" LLAMAFILE="ON" NATIVE_CPU="ON" METAL_EMBED_LIBRARY="ON" DEPLOYMENT_TARGET="" ARCHS="" TARGETS=() JOBS="$(sysctl -n hw.logicalcpu 2>/dev/null || echo 8)" usage() { cat <<'EOF' Usage: scripts/build_metal.sh [options] Build audio.cpp on macOS with the ggml Metal backend enabled. Options: --build-dir CMake build directory. Default: build/macos-metal- --build-type CMake build type. Default: RelWithDebInfo --deployment-target Minimum macOS deployment target. --archs "" Space or comma separated macOS architectures. Default: CMake/native default --openmp ON|OFF|auto Enable OpenMP host parallelism. Default: OFF --llamafile ON|OFF Enable ggml llamafile SGEMM. Default: ON --native-cpu ON|OFF Build ggml CPU kernels with native host ISA flags. Default: ON --embed-metal ON|OFF Embed ggml Metal shader library. Default: ON --with-tests Build framework unit tests. --with-examples Build example binaries. --with-warmbench Build warmbench helper binaries. --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. --target Build a specific CMake target. May be repeated. -j, --jobs Parallel build jobs. -h, --help Show this help. Examples: scripts/build_metal.sh scripts/build_metal.sh --target audiocpp_cli scripts/build_metal.sh --build-type Release --archs arm64 EOF } normalize_on_off() { local name="$1" local value="$2" case "$value" in ON|on|On|1|true|TRUE|yes|YES) printf 'ON' ;; OFF|off|Off|0|false|FALSE|no|NO) printf 'OFF' ;; *) echo "$name must be ON or OFF" >&2 exit 1 ;; esac } while [[ $# -gt 0 ]]; do case "$1" in --build-dir) BUILD_DIR="$2" shift 2 ;; --build-type) BUILD_TYPE="$2" shift 2 ;; --deployment-target) DEPLOYMENT_TARGET="$2" shift 2 ;; --archs) ARCHS="$2" shift 2 ;; --openmp) OPENMP_MODE="$2" shift 2 ;; --llamafile) LLAMAFILE="$(normalize_on_off --llamafile "$2")" shift 2 ;; --native-cpu) NATIVE_CPU="$(normalize_on_off --native-cpu "$2")" shift 2 ;; --embed-metal) METAL_EMBED_LIBRARY="$(normalize_on_off --embed-metal "$2")" shift 2 ;; --with-tests) WITH_TESTS="ON" shift ;; --with-examples) WITH_EXAMPLES="ON" shift ;; --with-warmbench) WITH_WARMBENCH="ON" shift ;; --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 ;; --target) TARGETS+=("$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 Metal builds require macOS." >&2 exit 1 fi for tool in cmake xcrun; do if ! command -v "$tool" >/dev/null 2>&1; then echo "Required tool not found on PATH: $tool" >&2 exit 1 fi done if ! xcrun --sdk macosx --find metal >/dev/null 2>&1; then echo "Xcode Metal toolchain was not found. Install Xcode or the Command Line Tools." >&2 exit 1 fi case "$OPENMP_MODE" in ON|on|On) ENGINE_ENABLE_OPENMP="ON" ;; OFF|off|Off) ENGINE_ENABLE_OPENMP="OFF" ;; auto) ENGINE_ENABLE_OPENMP="OFF" if command -v brew >/dev/null 2>&1; then LIBOMP_PREFIX="$(brew --prefix libomp 2>/dev/null || true)" if [[ -n "$LIBOMP_PREFIX" && -d "$LIBOMP_PREFIX" ]]; then ENGINE_ENABLE_OPENMP="ON" fi fi ;; *) echo "--openmp must be ON, OFF, or auto" >&2 exit 1 ;; esac BUILD_TYPE_NAME="$(printf '%s' "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]')" if [[ "$BUILD_TYPE_NAME" == "relwithdebinfo" ]]; then BUILD_TYPE_NAME="release" fi if [[ -z "$BUILD_DIR" ]]; then BUILD_DIR="$REPO_ROOT/build/macos-metal-${BUILD_TYPE_NAME}" fi GENERATOR="" if command -v ninja >/dev/null 2>&1; then GENERATOR="Ninja" fi CMAKE_CMD=( cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=OFF -DENGINE_ENABLE_METAL=ON -DENGINE_ENABLE_OPENMP="$ENGINE_ENABLE_OPENMP" -DENGINE_ENABLE_LLAMAFILE="$LLAMAFILE" -DENGINE_ENABLE_NATIVE_CPU="$NATIVE_CPU" -DGGML_OPENMP="$ENGINE_ENABLE_OPENMP" -DGGML_METAL_EMBED_LIBRARY="$METAL_EMBED_LIBRARY" -DENGINE_BUILD_TESTS="$WITH_TESTS" -DENGINE_BUILD_EXAMPLES="$WITH_EXAMPLES" -DENGINE_BUILD_WARMBENCH="$WITH_WARMBENCH" -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 if [[ -n "$DEPLOYMENT_TARGET" ]]; then CMAKE_CMD+=(-DCMAKE_OSX_DEPLOYMENT_TARGET="$DEPLOYMENT_TARGET") fi if [[ -n "$ARCHS" ]]; then CMAKE_CMD+=(-DCMAKE_OSX_ARCHITECTURES="${ARCHS//,/;}") fi if [[ "$OPENMP_MODE" == "auto" && "${ENGINE_ENABLE_OPENMP}" == "ON" ]]; then CMAKE_CMD+=(-DOpenMP_ROOT="$LIBOMP_PREFIX") fi echo "Using source dir: $REPO_ROOT" echo "Using build dir: $BUILD_DIR" if [[ -n "$GENERATOR" ]]; then echo "Using generator: $GENERATOR" else echo "Using generator: CMake default" fi echo "Including Metal backend: ON" echo "Embedding Metal library: $METAL_EMBED_LIBRARY" echo "Including OpenMP: $ENGINE_ENABLE_OPENMP" echo "Including llamafile: $LLAMAFILE" echo "Native CPU optimization: $NATIVE_CPU" echo "Building examples: $WITH_EXAMPLES" echo "Building tests: $WITH_TESTS" echo "Building warmbench: $WITH_WARMBENCH" echo "Deployment build: $AUDIOCPP_DEPLOYMENT_BUILD" echo "Native model manager: $AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER" if [[ "$AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER" == "ON" ]]; then echo "System OpenSSL: $AUDIOCPP_USE_SYSTEM_OPENSSL" echo "BoringSSL archive: ${AUDIOCPP_BORINGSSL_ARCHIVE:-}" fi echo "Model composite: $AUDIOCPP_MODEL_SET" if [[ -n "$AUDIOCPP_MODELS" ]]; then echo "Selected models: $AUDIOCPP_MODELS" fi "${CMAKE_CMD[@]}" BUILD_CMD=(cmake --build "$BUILD_DIR" --parallel "$JOBS") # --target is optional, so TARGETS is usually empty. bash 3.2 (stock macOS # /bin/bash) and bash < 4.4 report an empty array as unbound under `set -u`; # the ${arr[@]+"${arr[@]}"} form expands to nothing instead of failing. for target in ${TARGETS[@]+"${TARGETS[@]}"}; do BUILD_CMD+=(--target "$target") done "${BUILD_CMD[@]}"