mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 09:57:57 -04:00
backend(audio-cpp): build for Darwin Metal
Bespoke C++ Darwin path like ds4 and privacy-filter: an includeDarwin matrix
entry, a backends/audio-cpp-darwin make target, a gated workflow step, and the
metal image entries plus metal/metal-darwin-arm64 capability keys in the
backend gallery.
The build script deliberately does NOT reassemble the package the way
privacy-filter-darwin.sh does. It runs the backend's own `make package` and
copies the result, so the Darwin package keeps the root-level layout the Linux
one has: grpc-server, run.sh, the ggml objects and assets/ in one directory,
with lib/ for the dylib closure. Hand-assembling would drop assets/, and
assets/ is what makes the bundled: model paths resolve with nothing downloaded.
The dylib walk is a full transitive closure rather than the single level ds4
and llama-cpp do, because Homebrew's grpc++ pulls libgrpc, abseil, upb, cares
and OpenSSL that grpc-server does not link itself, and a level-1 walk ships a
package that only works on a machine that already has Homebrew grpc.
Two fixes folded in, both in the backend Makefile:
- an EMPTY CUDA_MAJOR_VERSION fell through to the CUDA 12 architecture list,
which contains 120a-real and so needs nvcc >= 12.8. A local
BUILD_TYPE=cublas build on a 12.0-12.7 host failed to compile where
upstream's documented default (native) worked. EMPTY now maps to native,
12 and 13 keep their lists, and any other non-empty value is an error on
cublas builds. CI always passes a major, so CI is unaffected.
- the Darwin branch now points CMake at Homebrew's keg-only libomp. AppleClang
ships no OpenMP runtime and nothing is symlinked into /opt/homebrew, so
FindOpenMP finds neither the library nor the header, and audio.cpp calls
find_package(OpenMP REQUIRED) whenever ENGINE_ENABLE_OPENMP is on. Without
the hint the macOS build would have died at configure time. If the keg is
absent the build disables OpenMP instead of failing.
Assisted-by: Claude:claude-opus-5 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
localai-org-maint-bot
parent
5af51273c2
commit
3a8692ff71
16
.github/backend-matrix.yml
vendored
16
.github/backend-matrix.yml
vendored
@@ -3165,8 +3165,8 @@ include:
|
||||
# builder image: this is the ds4 shape, not the llama-cpp one.
|
||||
#
|
||||
# No ROCm entry: upstream has no HIP configuration. No CUDA arm64 or L4T
|
||||
# entry: upstream documents and validates CUDA on x86 only. Darwin/Metal
|
||||
# lands with scripts/build/audio-cpp-darwin.sh, which does not exist yet.
|
||||
# entry: upstream documents and validates CUDA on x86 only. Darwin/Metal is in
|
||||
# the includeDarwin matrix below, built by scripts/build/audio-cpp-darwin.sh.
|
||||
#
|
||||
# No vulkan entry either, though Dockerfile.audio-cpp and the backend Makefile
|
||||
# both handle BUILD_TYPE=vulkan for local builds. Every other vulkan backend
|
||||
@@ -6291,6 +6291,18 @@ includeDarwin:
|
||||
- backend: "privacy-filter"
|
||||
tag-suffix: "-metal-darwin-arm64-privacy-filter"
|
||||
lang: "go"
|
||||
# audio-cpp is the same shape: a C++/ggml backend built by a bespoke darwin
|
||||
# script (make backends/audio-cpp-darwin), which reuses the backend's own
|
||||
# package.sh so the Darwin package keeps the root-level layout the Linux image
|
||||
# has (grpc-server, run.sh and assets/ in one directory, dylibs in lib/).
|
||||
# No build-type: the backend Makefile turns ENGINE_ENABLE_METAL on from
|
||||
# uname -s. lang=go drives runner/toolchain selection only - there is no
|
||||
# backend/go/audio-cpp, which is why backend_build_darwin.yml and
|
||||
# DARWIN_BESPOKE_BUILDERS in scripts/lib/backend-filter.mjs both route this
|
||||
# backend away from the generic Go path.
|
||||
- backend: "audio-cpp"
|
||||
tag-suffix: "-metal-darwin-arm64-audio-cpp"
|
||||
lang: "go"
|
||||
# LocalVQE has no Metal path; on Apple Silicon it builds CPU-only (GGML_METAL
|
||||
# OFF) but is still a native arm64 image. Uses the darwin/metal build profile.
|
||||
- backend: "localvqe"
|
||||
|
||||
13
.github/workflows/backend_build_darwin.yml
vendored
13
.github/workflows/backend_build_darwin.yml
vendored
@@ -244,8 +244,19 @@ jobs:
|
||||
make protogen-go
|
||||
make backends/privacy-filter-darwin
|
||||
|
||||
# audio-cpp is a C++/ggml backend like ds4 and privacy-filter - a single
|
||||
# grpc-server with otool dylib bundling, plus bundled VAD assets - so it
|
||||
# gets its own bespoke darwin script rather than the generic
|
||||
# build-darwin-go-backend path, which would look for a backend/go/audio-cpp
|
||||
# that does not exist. Keep this set in sync with DARWIN_BESPOKE_BUILDERS
|
||||
# in scripts/lib/backend-filter.mjs.
|
||||
- name: Build audio-cpp backend (Darwin Metal)
|
||||
if: inputs.backend == 'audio-cpp'
|
||||
run: |
|
||||
make backends/audio-cpp-darwin
|
||||
|
||||
- name: Build ${{ inputs.backend }}-darwin
|
||||
if: inputs.backend != 'llama-cpp' && inputs.backend != 'ds4' && inputs.backend != 'privacy-filter'
|
||||
if: inputs.backend != 'llama-cpp' && inputs.backend != 'ds4' && inputs.backend != 'privacy-filter' && inputs.backend != 'audio-cpp'
|
||||
run: |
|
||||
make protogen-go
|
||||
BACKEND=${{ inputs.backend }} BUILD_TYPE=${{ inputs.build-type }} USE_PIP=${{ inputs.use-pip }} make build-darwin-${{ inputs.lang }}-backend
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,5 +1,5 @@
|
||||
# Disable parallel execution for backend builds
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/bonsai backends/outetts backends/piper backends/stablediffusion-ggml backends/trellis2cpp backends/trellis2cpp-darwin backends/whisper backends/crispasr backends/parakeet-cpp backends/moss-transcribe-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/valkey-store backends/cloud-proxy backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/longcat-video backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/moss-tts-cpp backends/magpie-tts-cpp backends/vllm-cpp backends/omnivoice-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio backends/supertonic backends/depth-anything-cpp backends/privacy-filter backends/privacy-filter-darwin backends/audio-cpp
|
||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/bonsai backends/outetts backends/piper backends/stablediffusion-ggml backends/trellis2cpp backends/trellis2cpp-darwin backends/whisper backends/crispasr backends/parakeet-cpp backends/moss-transcribe-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/valkey-store backends/cloud-proxy backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/longcat-video backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/moss-tts-cpp backends/magpie-tts-cpp backends/vllm-cpp backends/omnivoice-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio backends/supertonic backends/depth-anything-cpp backends/privacy-filter backends/privacy-filter-darwin backends/audio-cpp backends/audio-cpp-darwin
|
||||
|
||||
GOCMD=go
|
||||
GOTEST=$(GOCMD) test
|
||||
@@ -1201,6 +1201,10 @@ backends/privacy-filter-darwin: build
|
||||
bash ./scripts/build/privacy-filter-darwin.sh
|
||||
./local-ai backends install "ocifile://$(abspath ./backend-images/privacy-filter.tar)"
|
||||
|
||||
backends/audio-cpp-darwin: build
|
||||
bash ./scripts/build/audio-cpp-darwin.sh
|
||||
./local-ai backends install "ocifile://$(abspath ./backend-images/audio-cpp.tar)"
|
||||
|
||||
build-darwin-python-backend: build
|
||||
bash ./scripts/build/python-darwin.sh
|
||||
|
||||
|
||||
@@ -50,11 +50,27 @@ CMAKE_ARGS ?= -DCMAKE_BUILD_TYPE=Release -DAUDIOCPP_DEPLOYMENT_BUILD=ON
|
||||
# Setting it here also pins ggml's copy, since its default is guarded by
|
||||
# `if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)`. CUDA_MAJOR_VERSION is the CI
|
||||
# build-arg, forwarded by Dockerfile.audio-cpp.
|
||||
#
|
||||
# An EMPTY major maps to `native`, NOT to the CUDA 12 list. Only CI declares a
|
||||
# major; a developer running `BUILD_TYPE=cublas make` locally declares none, and
|
||||
# the CUDA 12 list contains 120a-real, which needs nvcc >= 12.8. Falling through
|
||||
# to it turned every local build on a CUDA 12.0-12.7 host into a compile error,
|
||||
# where upstream's documented behaviour ("Leave CMAKE_CUDA_ARCHITECTURES unset
|
||||
# to build for the GPUs present at build time") worked. `native` restores that.
|
||||
# It does require a GPU to enumerate, so the escape hatch for a GPU-less local
|
||||
# cross-build is to set CUDA_ARCHITECTURES on the command line, which the ?=
|
||||
# assignments below leave untouched.
|
||||
CUDA_MAJOR_VERSION ?=
|
||||
ifeq ($(CUDA_MAJOR_VERSION),13)
|
||||
CUDA_ARCHITECTURES ?= 75-virtual;80-virtual;86-real;89-real;120a-real;121a-real
|
||||
else
|
||||
else ifeq ($(CUDA_MAJOR_VERSION),12)
|
||||
CUDA_ARCHITECTURES ?= 50-virtual;61-virtual;70-virtual;75-virtual;80-virtual;86-real;89-real;120a-real
|
||||
else ifeq ($(CUDA_MAJOR_VERSION),)
|
||||
CUDA_ARCHITECTURES ?= native
|
||||
else ifeq ($(BUILD_TYPE),cublas)
|
||||
# Gated on cublas so that `make clean` with a stray CUDA_MAJOR_VERSION in the
|
||||
# environment still works; the variable means nothing to any other build.
|
||||
$(error CUDA_MAJOR_VERSION=$(CUDA_MAJOR_VERSION) has no architecture list here (12 and 13 do). Leave it empty for a native build, or pass CUDA_ARCHITECTURES explicitly.)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_TYPE),cublas)
|
||||
@@ -62,7 +78,32 @@ ifeq ($(BUILD_TYPE),cublas)
|
||||
else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS += -DENGINE_ENABLE_VULKAN=ON
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
# Metal. ggml embeds the shader library by default (GGML_METAL_EMBED_LIBRARY
|
||||
# defaults to GGML_METAL), so the package needs no .metallib beside the
|
||||
# binary. Darwin builds go through scripts/build/audio-cpp-darwin.sh.
|
||||
CMAKE_ARGS += -DENGINE_ENABLE_METAL=ON
|
||||
# AppleClang ships no OpenMP runtime and Homebrew's libomp is keg-only, so
|
||||
# neither libomp.dylib nor omp.h is symlinked into /opt/homebrew and CMake's
|
||||
# FindOpenMP cannot find them on its own (the workflow's `brew link libomp`
|
||||
# is a no-op for a keg-only formula, and its failure is swallowed).
|
||||
# audio.cpp calls find_package(OpenMP REQUIRED COMPONENTS CXX) whenever
|
||||
# ENGINE_ENABLE_OPENMP is ON, so with no hint the macOS build dies at
|
||||
# configure time before compiling anything. OpenMP_ROOT is honoured by the
|
||||
# find_library/find_path calls inside FindOpenMP under CMP0074, which is NEW
|
||||
# here because audio.cpp requires CMake 3.20.
|
||||
#
|
||||
# If the keg is absent, turn OpenMP off rather than fail: the tree's only
|
||||
# <omp.h> include is guarded by #ifdef _OPENMP and a #pragma omp without
|
||||
# -fopenmp is simply ignored, so an OpenMP-less build is correct, just
|
||||
# single-threaded in the host-side DSP loops.
|
||||
ifeq ($(origin LIBOMP_PREFIX),undefined)
|
||||
LIBOMP_PREFIX := $(shell brew --prefix libomp 2>/dev/null)
|
||||
endif
|
||||
ifneq ($(and $(wildcard $(LIBOMP_PREFIX)/lib/libomp.dylib),$(wildcard $(LIBOMP_PREFIX)/include/omp.h)),)
|
||||
CMAKE_ARGS += -DOpenMP_ROOT=$(LIBOMP_PREFIX)
|
||||
else
|
||||
CMAKE_ARGS += -DENGINE_ENABLE_OPENMP=OFF
|
||||
endif
|
||||
else
|
||||
# Portable Linux CPU. Upstream wires this to GGML_BACKEND_DL +
|
||||
# GGML_CPU_ALL_VARIANTS + $ORIGIN rpath, so one build serves every CPU
|
||||
|
||||
@@ -48,18 +48,18 @@ for asset in silero_vad marblenet_vad; do
|
||||
done
|
||||
|
||||
# Everything below this point is Linux-only: a bundled ELF loader, an ldd walk
|
||||
# and an ld.so --list validation. The macOS equivalents (otool -L, install_name
|
||||
# rewriting, a codesign pass) belong to a scripts/build/audio-cpp-darwin.sh that
|
||||
# DOES NOT EXIST YET; writing it is Task 16's job.
|
||||
# and an ld.so --list validation. The macOS equivalent is the otool -L closure in
|
||||
# scripts/build/audio-cpp-darwin.sh, which picks up from the exit 0 below.
|
||||
#
|
||||
# WARNING FOR WHOEVER WRITES IT. The obvious move is to copy
|
||||
# WARNING FOR ANYONE REWORKING THAT SCRIPT. The obvious move is to copy
|
||||
# scripts/build/privacy-filter-darwin.sh, and that script assembles its own
|
||||
# package under build/darwin and never calls package.sh at all. Adapted as-is it
|
||||
# will silently omit assets/, and the bundled: model path form then resolves to
|
||||
# nothing, which takes the only zero-download verification path in this backend
|
||||
# with it. The Darwin package needs the same root-level layout as the Linux one:
|
||||
# grpc-server, run.sh, the ggml dylibs and assets/ in ONE directory, with
|
||||
# lib/ for the rest. run.sh's Darwin branch execs grpc-server directly, so
|
||||
# with it. That is why audio-cpp-darwin.sh copies THIS directory instead of
|
||||
# rebuilding one. The Darwin package needs the same root-level layout as the
|
||||
# Linux one: grpc-server, run.sh, the ggml dylibs and assets/ in ONE directory,
|
||||
# with lib/ for the rest. run.sh's Darwin branch execs grpc-server directly, so
|
||||
# _NSGetExecutablePath already names the package root; nothing else is needed
|
||||
# beyond putting the files there.
|
||||
UNAME_S=$(uname -s)
|
||||
|
||||
@@ -148,16 +148,19 @@
|
||||
- audio-transcription
|
||||
- CPU
|
||||
- CUDA
|
||||
# No metal/metal-darwin-arm64 key (no Darwin matrix entry yet) and no vulkan
|
||||
# key (the vulkan image would carry a Vulkan loader with no Mesa ICD; see the
|
||||
# audio-cpp block in .github/backend-matrix.yml). Pointing a capability at a
|
||||
# tag CI never builds hands the user a pull failure; leaving it out lets
|
||||
# SystemState.Capability() fall through to `default` and the CPU image.
|
||||
- Metal
|
||||
# No vulkan key: the vulkan image would carry a Vulkan loader with no Mesa ICD
|
||||
# (see the audio-cpp block in .github/backend-matrix.yml). Pointing a
|
||||
# capability at a tag CI never builds hands the user a pull failure; leaving it
|
||||
# out lets SystemState.Capability() fall through to `default` and the CPU
|
||||
# image.
|
||||
capabilities:
|
||||
default: "cpu-audio-cpp"
|
||||
nvidia: "cuda12-audio-cpp"
|
||||
nvidia-cuda-12: "cuda12-audio-cpp"
|
||||
nvidia-cuda-13: "cuda13-audio-cpp"
|
||||
metal: "metal-audio-cpp"
|
||||
metal-darwin-arm64: "metal-audio-cpp"
|
||||
- &whispercpp
|
||||
name: "whisper"
|
||||
alias: "whisper"
|
||||
@@ -2037,6 +2040,8 @@
|
||||
nvidia: "cuda12-audio-cpp-development"
|
||||
nvidia-cuda-12: "cuda12-audio-cpp-development"
|
||||
nvidia-cuda-13: "cuda13-audio-cpp-development"
|
||||
metal: "metal-audio-cpp-development"
|
||||
metal-darwin-arm64: "metal-audio-cpp-development"
|
||||
- !!merge <<: *stablediffusionggml
|
||||
name: "stablediffusion-ggml-development"
|
||||
capabilities:
|
||||
@@ -6895,3 +6900,13 @@
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-audio-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-gpu-nvidia-cuda-13-audio-cpp
|
||||
- !!merge <<: *audiocpp
|
||||
name: "metal-audio-cpp"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-audio-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:latest-metal-darwin-arm64-audio-cpp
|
||||
- !!merge <<: *audiocpp
|
||||
name: "metal-audio-cpp-development"
|
||||
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-audio-cpp"
|
||||
mirrors:
|
||||
- localai/localai-backends:master-metal-darwin-arm64-audio-cpp
|
||||
|
||||
178
scripts/build/audio-cpp-darwin.sh
Executable file
178
scripts/build/audio-cpp-darwin.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
# Darwin/Metal build for the audio-cpp backend.
|
||||
#
|
||||
# Modelled on scripts/build/ds4-darwin.sh rather than llama-cpp-darwin.sh:
|
||||
# audio-cpp is a single grpc-server, like ds4 and privacy-filter, whereas
|
||||
# llama-cpp ships three binaries and splits its ggml objects between the package
|
||||
# root and lib/. One deliberate departure from BOTH of those scripts, and from
|
||||
# privacy-filter-darwin.sh in particular:
|
||||
#
|
||||
# THIS SCRIPT DOES NOT REASSEMBLE THE PACKAGE. It runs the backend's own
|
||||
# `make package` and copies the result.
|
||||
#
|
||||
# privacy-filter-darwin.sh hand-assembles build/darwin and never calls its
|
||||
# package.sh. Copied here that would silently drop assets/, and assets/ is what
|
||||
# makes the "bundled:silero_vad" / "bundled:marblenet_vad" model paths resolve
|
||||
# with nothing downloaded - the only zero-download path this backend has.
|
||||
# backend/cpp/audio-cpp/package.sh already produces the exact layout the image
|
||||
# needs, and its Darwin branch stops right before the Linux-only loader/ldd work
|
||||
# and hands over to this script. Reusing it means the Linux and Darwin packages
|
||||
# cannot drift.
|
||||
#
|
||||
# What this script adds on top of package.sh is the part that is genuinely
|
||||
# macOS-only: walking otool -L to bundle the non-system dylibs the binary needs.
|
||||
set -euo pipefail
|
||||
set -x
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME:-localai/audio-cpp-darwin}"
|
||||
PLATFORMARCH="${PLATFORMARCH:-darwin/arm64}"
|
||||
|
||||
# NATIVE=false matches ds4-darwin.sh and the backend Makefile's own default: the
|
||||
# image is published for every Apple Silicon Mac, not just the CI runner's, so
|
||||
# the CPU kernels must not be compiled for the build host's exact ISA.
|
||||
pushd backend/cpp/audio-cpp
|
||||
make NATIVE=false grpc-server package
|
||||
popd
|
||||
|
||||
rm -rf build/darwin
|
||||
mkdir -p build/darwin backend-images
|
||||
# -a, not -r: package.sh may have left symlink chains among the ggml objects
|
||||
# (libggml.dylib -> libggml.0.dylib -> ...), and the SONAME the binary asks for
|
||||
# has to keep naming a file here.
|
||||
cp -a backend/cpp/audio-cpp/package/. build/darwin/
|
||||
|
||||
# THE LAYOUT ASSERTION - the Darwin half of the one in
|
||||
# backend/cpp/audio-cpp/package.sh, and the reason that one is written at such
|
||||
# length. run.sh's Darwin branch execs grpc-server directly, so
|
||||
# _NSGetExecutablePath names the package ROOT. Two independent consumers read
|
||||
# that directory: ggml's backend registry, which discovers dlopened
|
||||
# libggml-cpu-* objects by scanning it, and resolve_model_path(), which expands
|
||||
# "bundled:<name>" to <exe dir>/assets/<name>. Tidying assets/ or the ggml
|
||||
# objects into lib/, which is where the llama-cpp layout would put them,
|
||||
# produces a package that builds, publishes, and then fails at run time with
|
||||
# "model path does not exist: <pkg>/lib/assets/...". Fail here instead.
|
||||
for required in grpc-server run.sh assets/silero_vad assets/marblenet_vad; do
|
||||
if [ ! -e "build/darwin/$required" ]; then
|
||||
echo "audio-cpp-darwin.sh: missing from the package root: $required" >&2
|
||||
echo "audio-cpp-darwin.sh: grpc-server, run.sh, the ggml dylibs and" >&2
|
||||
echo "audio-cpp-darwin.sh: assets/ all have to sit in ONE directory," >&2
|
||||
echo "audio-cpp-darwin.sh: because run.sh execs the binary from there" >&2
|
||||
echo "audio-cpp-darwin.sh: and both the ggml backend scan and the" >&2
|
||||
echo "audio-cpp-darwin.sh: bundled: asset lookup are relative to it." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Apple Silicon: pick up Homebrew-installed protobuf utf8_validity if present
|
||||
# (same as ds4-darwin.sh - it is a transitive dep otool may not surface).
|
||||
if [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; then
|
||||
ADDITIONAL_LIBS=${ADDITIONAL_LIBS:-$(ls /opt/homebrew/Cellar/protobuf/**/lib/libutf8_validity*.dylib 2>/dev/null || true)}
|
||||
else
|
||||
ADDITIONAL_LIBS=${ADDITIONAL_LIBS:-""}
|
||||
fi
|
||||
for file in $ADDITIONAL_LIBS; do
|
||||
cp -fLv "$file" build/darwin/lib
|
||||
done
|
||||
|
||||
# Bundle the FULL dylib closure, not just grpc-server's direct dependencies.
|
||||
#
|
||||
# ds4-darwin.sh and llama-cpp-darwin.sh walk one level. That is enough only
|
||||
# while every indirect dependency happens to be linked directly by the binary
|
||||
# too. It is not enough here: Homebrew's grpc++ pulls libgrpc, ~40 libabsl_*,
|
||||
# libupb, libcares, libre2 and OpenSSL, and grpc-server links few of those
|
||||
# itself. A missing one resolves on the build host (the install names are
|
||||
# absolute Homebrew paths) and fails on a user's Mac that has no Homebrew grpc,
|
||||
# which is exactly the failure a bundled package exists to prevent. The Linux
|
||||
# side already does a full ldd closure in package.sh; this is its equivalent.
|
||||
#
|
||||
# Leaf-name deduplication is what makes DYLD_LIBRARY_PATH work: run.sh exports
|
||||
# lib/ and the package root, and dyld searches those by leaf name BEFORE
|
||||
# falling back to the absolute install name recorded in the binary.
|
||||
#
|
||||
# /bin/bash on macOS is 3.2, so no associative arrays: the seen-set is a
|
||||
# space-delimited string matched with `case`.
|
||||
BUNDLED_LEAVES=" "
|
||||
UNRESOLVED=0
|
||||
|
||||
bundle_dylib_closure() {
|
||||
local object="$1"
|
||||
local dep leaf src
|
||||
while read -r dep; do
|
||||
case "$dep" in
|
||||
*.dylib) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
leaf=$(basename "$dep")
|
||||
case "$BUNDLED_LEAVES" in
|
||||
*" $leaf "*) continue ;;
|
||||
esac
|
||||
|
||||
src="$dep"
|
||||
case "$dep" in
|
||||
@*)
|
||||
# @rpath / @loader_path / @executable_path are resolved against
|
||||
# the package itself, so such a dependency is satisfiable only
|
||||
# if the file is already inside it. Nothing produces one today
|
||||
# (a Metal build sets neither BUILD_SHARED_LIBS nor
|
||||
# GGML_BACKEND_DL, so ggml is static and package.sh copies no
|
||||
# dylibs at all), but if that changes, a missing one has to be
|
||||
# loud: dyld fails at load with nothing else to go on.
|
||||
if [ -e "build/darwin/$leaf" ]; then
|
||||
src="build/darwin/$leaf"
|
||||
elif [ -e "build/darwin/lib/$leaf" ]; then
|
||||
src="build/darwin/lib/$leaf"
|
||||
else
|
||||
echo "audio-cpp-darwin.sh: unresolved @rpath dependency of ${object}: ${dep}" >&2
|
||||
UNRESOLVED=1
|
||||
continue
|
||||
fi
|
||||
BUNDLED_LEAVES="${BUNDLED_LEAVES}${leaf} "
|
||||
bundle_dylib_closure "$src"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
# System dylibs live in the dyld shared cache and have no file on disk.
|
||||
# That absence is the filter that keeps libSystem, libc++ and the
|
||||
# frameworks out of the package; there is no allow-list to maintain.
|
||||
[ -e "$dep" ] || continue
|
||||
|
||||
BUNDLED_LEAVES="${BUNDLED_LEAVES}${leaf} "
|
||||
# -L dereferences: Homebrew's unversioned names are symlinks into the
|
||||
# Cellar, and copying the link would land a dangling relative symlink.
|
||||
# The binary asks for whatever install name is recorded, so the file has
|
||||
# to exist under that leaf.
|
||||
cp -fLv "$dep" build/darwin/lib/
|
||||
bundle_dylib_closure "$dep"
|
||||
done < <(otool -L "$object" | awk 'NR > 1 { print $1 }')
|
||||
# Explicit, because `set -e` would abort on whatever status the loop
|
||||
# happened to end on.
|
||||
return 0
|
||||
}
|
||||
|
||||
bundle_dylib_closure build/darwin/grpc-server
|
||||
if compgen -G "build/darwin/*.dylib" > /dev/null; then
|
||||
for object in build/darwin/*.dylib; do
|
||||
bundle_dylib_closure "$object"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Bundled libraries:"
|
||||
ls -la build/darwin/lib
|
||||
|
||||
if [ "$UNRESOLVED" -ne 0 ]; then
|
||||
echo "audio-cpp-darwin.sh: refusing to package: see the unresolved dependencies above." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Nothing above rewrites an install name or otherwise edits grpc-server, which
|
||||
# matters on Apple Silicon: the linker ad-hoc signs the binary, and any
|
||||
# in-place edit would invalidate that signature and make the package
|
||||
# unlaunchable. cp preserves it.
|
||||
./local-ai util create-oci-image \
|
||||
build/darwin/. \
|
||||
--output ./backend-images/audio-cpp.tar \
|
||||
--image-name "$IMAGE_NAME" \
|
||||
--platform "$PLATFORMARCH"
|
||||
|
||||
rm -rf build/darwin
|
||||
@@ -107,13 +107,10 @@ export function inferBackendPathDarwin(item) {
|
||||
if (item.backend === "privacy-filter") {
|
||||
return `backend/cpp/privacy-filter/`;
|
||||
}
|
||||
// audio-cpp is C++ too. There is no Darwin matrix entry for it yet: the
|
||||
// Metal build needs scripts/build/audio-cpp-darwin.sh and a
|
||||
// `backends/audio-cpp-darwin` make target, neither of which exists, so this
|
||||
// case (and the DARWIN_BESPOKE_BUILDERS membership below) is inert today. It
|
||||
// is here so that adding the entry is a one-line change that cannot
|
||||
// accidentally route a C++ backend through the generic
|
||||
// build-darwin-go-backend path, which would look for backend/go/audio-cpp/.
|
||||
// audio-cpp is C++ too (built via `make backends/audio-cpp-darwin`); same
|
||||
// lang=go-for-runner convention, source under backend/cpp. Without this the
|
||||
// generic path below would look for backend/go/audio-cpp/, which does not
|
||||
// exist, and no change to the real sources would ever trigger a Darwin build.
|
||||
if (item.backend === "audio-cpp") {
|
||||
return `backend/cpp/audio-cpp/`;
|
||||
}
|
||||
@@ -155,14 +152,10 @@ export function backendChanged(backend, pathPrefix, changedFiles) {
|
||||
// without it is a Python backend (see .github/backend-matrix.yml).
|
||||
const isDarwinPython = item => !item.lang;
|
||||
|
||||
// backend_build_darwin.yml routes llama-cpp, ds4 and privacy-filter to their
|
||||
// own bespoke make targets; every other lang=go entry goes through
|
||||
// `make build-darwin-go-backend` -> scripts/build/golang-darwin.sh.
|
||||
//
|
||||
// audio-cpp is listed ahead of its Darwin matrix entry (see
|
||||
// inferBackendPathDarwin): it is a C++/ggml backend that will need the same
|
||||
// bespoke treatment, and listing it here now means the entry, when it lands,
|
||||
// cannot be silently claimed by the generic Go path.
|
||||
// backend_build_darwin.yml routes llama-cpp, ds4, privacy-filter and audio-cpp
|
||||
// to their own bespoke make targets; every other lang=go entry goes through
|
||||
// `make build-darwin-go-backend` -> scripts/build/golang-darwin.sh. Keep this
|
||||
// set in sync with the `if:` conditions in that workflow.
|
||||
const DARWIN_BESPOKE_BUILDERS = new Set([
|
||||
"llama-cpp",
|
||||
"ds4",
|
||||
|
||||
Reference in New Issue
Block a user