From 6afe127cd48feb89646de6fc4425ab127f47b5d3 Mon Sep 17 00:00:00 2001 From: "LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:19:50 +0200 Subject: [PATCH] fix(backends): make the opus backend build and package on macOS/Darwin (#10523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opus Go backend (WebRTC audio codec) never built on macOS, so the published master-metal-darwin-arm64-opus image shipped source only — no opus binary and no libopusshim — because every step assumed Linux. - Makefile: hardcoded libopusshim.so with no OS handling. Mirror sherpa-onnx: SHIM_EXT=so / dylib on Darwin and build libopusshim.$(SHIM_EXT). On Darwin link the shim with -undefined dynamic_lookup so it resolves opus_encoder_ctl from the already globally-loaded libopus (codec.go dlopens it RTLD_GLOBAL first) instead of baking an absolute Homebrew path into the dylib, keeping the packaged shim relocatable. - run.sh: hardcoded LD_LIBRARY_PATH + libopusshim.so even on macOS. Add a Darwin branch exporting DYLD_LIBRARY_PATH and the .dylib shim, like sherpa-onnx/run.sh. - package.sh: bundle libopusshim.$(SHIM_EXT) and libopus*.dylib (not just .so) into package/lib so the OCI image (which ships package/.) is self-contained on a runtime with no Homebrew; add a Darwin arch branch so it doesn't warn/skip. - backend_build_darwin.yml: install + link opus and pkg-config via brew so the Makefile's `pkg-config opus` resolves on the macOS runner, and cache opus' Cellar dir. Go code is unchanged; darwin build is validated in CI. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- .github/workflows/backend_build_darwin.yml | 11 +++++++-- backend/go/opus/Makefile | 28 ++++++++++++++++++---- backend/go/opus/package.sh | 20 ++++++++++++---- backend/go/opus/run.sh | 9 +++++-- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/.github/workflows/backend_build_darwin.yml b/.github/workflows/backend_build_darwin.yml index e0bbdb091..c0ded5b85 100644 --- a/.github/workflows/backend_build_darwin.yml +++ b/.github/workflows/backend_build_darwin.yml @@ -99,6 +99,7 @@ jobs: /opt/homebrew/Cellar/xxhash /opt/homebrew/Cellar/zstd /opt/homebrew/Cellar/nlohmann-json + /opt/homebrew/Cellar/opus key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }} - name: Dependencies @@ -113,7 +114,12 @@ jobs: # nlohmann-json is header-only and required by the ds4 backend # (dsml_renderer.cpp includes ); on Linux it comes # from the apt-installed nlohmann-json3-dev in the build image. - brew install protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json + # opus + pkg-config are required by the opus go backend: its + # Makefile/package.sh call `pkg-config --cflags/--libs opus` to build + # libopusshim.dylib and to locate libopus.dylib for bundling. brew's + # pkg-config defaults its search path to the Homebrew prefix so the + # opus.pc is found. + brew install protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json opus pkg-config # Force-reinstall ccache so brew re-validates its full runtime-dep # closure on every run. This is the durable fix: when the upstream # ccache formula gains a new transitive dep (as it has multiple times @@ -132,7 +138,7 @@ jobs: # and decides "already installed" without re-linking, so on a cache- # hit run the formulas aren't on PATH. Force-link them; --overwrite # tolerates pre-existing symlinks from earlier installs. - brew link --overwrite protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json 2>/dev/null || true + brew link --overwrite protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json opus pkg-config 2>/dev/null || true - name: Save Homebrew cache if: github.event_name != 'pull_request' && steps.brew-cache.outputs.cache-hit != 'true' @@ -153,6 +159,7 @@ jobs: /opt/homebrew/Cellar/xxhash /opt/homebrew/Cellar/zstd /opt/homebrew/Cellar/nlohmann-json + /opt/homebrew/Cellar/opus key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }} # ---- ccache for llama.cpp CMake builds ---- diff --git a/backend/go/opus/Makefile b/backend/go/opus/Makefile index 028b16bc5..49083b3dc 100644 --- a/backend/go/opus/Makefile +++ b/backend/go/opus/Makefile @@ -1,13 +1,30 @@ GOCMD?=go GO_TAGS?= +# The opus shim is a small C wrapper around libopus' variadic +# opus_encoder_ctl (see csrc/opus_shim.c). It is built as a shared library +# and dlopen'd at runtime by the Go backend (codec.go). The extension is +# OS-specific: Linux uses .so, macOS uses .dylib. OS is exported by the root +# Makefile (`export OS := $(shell uname -s)`). +SHIM_EXT=so + OPUS_CFLAGS := $(shell pkg-config --cflags opus) OPUS_LIBS := $(shell pkg-config --libs opus) +SHIM_LDFLAGS := $(OPUS_LIBS) -libopusshim.so: csrc/opus_shim.c - $(CC) -shared -fPIC -o $@ $< $(OPUS_CFLAGS) $(OPUS_LIBS) +ifeq ($(OS),Darwin) + SHIM_EXT=dylib + # Resolve libopus symbols lazily from the already globally-loaded + # libopus (codec.go dlopens it RTLD_GLOBAL before the shim) rather than + # recording an absolute Homebrew path in the dylib. This keeps the + # packaged shim relocatable on machines that have no Homebrew. + SHIM_LDFLAGS := -undefined dynamic_lookup +endif -opus: libopusshim.so +libopusshim.$(SHIM_EXT): csrc/opus_shim.c + $(CC) -shared -fPIC -o $@ $< $(OPUS_CFLAGS) $(SHIM_LDFLAGS) + +opus: libopusshim.$(SHIM_EXT) $(GOCMD) build -tags "$(GO_TAGS)" -o opus ./ package: opus @@ -16,4 +33,7 @@ package: opus build: package clean: - rm -f opus libopusshim.so + rm -f opus libopusshim.$(SHIM_EXT) + rm -rf package + +.PHONY: build package clean diff --git a/backend/go/opus/package.sh b/backend/go/opus/package.sh index a55834f3e..1e1aaeabf 100644 --- a/backend/go/opus/package.sh +++ b/backend/go/opus/package.sh @@ -8,13 +8,23 @@ mkdir -p $CURDIR/package/lib cp -avf $CURDIR/opus $CURDIR/package/ cp -avf $CURDIR/run.sh $CURDIR/package/ -# Copy the opus shim library -cp -avf $CURDIR/libopusshim.so $CURDIR/package/lib/ +# The shim extension is OS-specific (.so on Linux, .dylib on macOS). +SHIM_EXT=so +if [ "$(uname)" = "Darwin" ]; then + SHIM_EXT=dylib +fi -# Copy system libopus +# Copy the opus shim library +cp -avf $CURDIR/libopusshim.$SHIM_EXT $CURDIR/package/lib/ + +# Copy system libopus so the backend is self-contained: the runtime base +# image has neither libopus-dev (Linux) nor Homebrew (macOS), so codec.go's +# dlopen would otherwise fail. Both name patterns are attempted; only the +# host's matching one exists. if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists opus; then LIBOPUS_DIR=$(pkg-config --variable=libdir opus) - cp -avfL $LIBOPUS_DIR/libopus.so* $CURDIR/package/lib/ 2>/dev/null || true + cp -avf $LIBOPUS_DIR/libopus.so* $CURDIR/package/lib/ 2>/dev/null || true + cp -avf $LIBOPUS_DIR/libopus*.dylib $CURDIR/package/lib/ 2>/dev/null || true fi # Detect architecture and copy appropriate libraries @@ -38,6 +48,8 @@ elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2 cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1 cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0 +elif [ "$(uname -s)" = "Darwin" ]; then + echo "Detected Darwin — system libraries linked dynamically, no bundled loader needed" else echo "Warning: Could not detect architecture for system library bundling" fi diff --git a/backend/go/opus/run.sh b/backend/go/opus/run.sh index 1a54b0c40..d2773fd28 100644 --- a/backend/go/opus/run.sh +++ b/backend/go/opus/run.sh @@ -3,8 +3,13 @@ set -ex CURDIR=$(dirname "$(realpath "$0")") -export LD_LIBRARY_PATH="$CURDIR"/lib:$LD_LIBRARY_PATH -export OPUS_SHIM_LIBRARY="$CURDIR"/lib/libopusshim.so +if [ "$(uname)" = "Darwin" ]; then + export DYLD_LIBRARY_PATH="$CURDIR"/lib:$DYLD_LIBRARY_PATH + export OPUS_SHIM_LIBRARY="$CURDIR"/lib/libopusshim.dylib +else + export LD_LIBRARY_PATH="$CURDIR"/lib:$LD_LIBRARY_PATH + export OPUS_SHIM_LIBRARY="$CURDIR"/lib/libopusshim.so +fi # If there is a lib/ld.so, use it if [ -f "$CURDIR"/lib/ld.so ]; then