From cf71e291b47baeb299f531bbc8f257c9866fbc5f Mon Sep 17 00:00:00 2001 From: "LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 12 Jun 2026 23:13:50 +0200 Subject: [PATCH] fix(darwin): fix vibevoice-cpp build linkage + fail-safe go backend packaging (#10276) * fix(darwin): never package a go backend build tree as a working image The darwin/arm64 vibevoice-cpp image shipped the source tree with a half-built CMake directory (build-libgovibevoicecpp-fallback.so/) and no backend binary, so the backend could never start: run.sh exec'd a vibevoice-cpp binary that was not in the package and LocalAI timed out waiting for the gRPC service. Two durable, backend-agnostic defenses: - backend/go/vibevoice-cpp/Makefile: mirror whisper's cleanup discipline so a partial CMake tree cannot survive into packaging. Run `make purge` before each variant build and `rm -rfv build*` after. The old recipe only removed its build dir after a successful `mv`, so a failed build left the half-built tree behind. - scripts/build/golang-darwin.sh: before creating the OCI image, remove any stray build-* directory and assert that the binary run.sh launches actually exists. A build that produced no binary now fails the job loudly instead of publishing a source tree as a working backend. The binary name is derived from run.sh's `exec $CURDIR/` line (parakeet-cpp launches parakeet-cpp-grpc, so it is not always ${BACKEND}) with a ${BACKEND} fallback. The underlying native build failure that left vibevoice-cpp half-built still needs to be reproduced and fixed on Apple Silicon; this change ensures such a failure can never again be published as a working image. Refs #10267 Signed-off-by: Ettore Di Giacinto Assisted-by: Claude:claude-opus-4-8 [Claude Code] * fix(vibevoice-cpp): build libvibevoice.a on darwin (link target, not path) The darwin build failed with: No rule to make target 'vibevoice/libvibevoice.a', needed by 'libgovibevoicecpp.so'. Stop. The upstream vibevoice project is added with add_subdirectory(... EXCLUDE_FROM_ALL), so its `vibevoice` static-library target is only built when something links it as a target. The Apple branch linked only `$` - a bare archive path with no target reference - so CMake never emitted a rule to build libvibevoice.a, while the Linux branch worked because it passes the `vibevoice` target name inside the --whole-archive flags. Link the `vibevoice` target on Apple (establishing the build dependency) and apply -force_load as a separate link option to keep whole-archive semantics so purego can dlsym the vv_capi_* symbols. Refs #10267 Signed-off-by: Ettore Di Giacinto Assisted-by: Claude:claude-opus-4-8 [Claude Code] --------- Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- backend/go/vibevoice-cpp/CMakeLists.txt | 10 +++++++++- backend/go/vibevoice-cpp/Makefile | 12 ++++++++---- scripts/build/golang-darwin.sh | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/backend/go/vibevoice-cpp/CMakeLists.txt b/backend/go/vibevoice-cpp/CMakeLists.txt index dde8807fe..358e5d7bd 100644 --- a/backend/go/vibevoice-cpp/CMakeLists.txt +++ b/backend/go/vibevoice-cpp/CMakeLists.txt @@ -26,8 +26,16 @@ add_library(govibevoicecpp MODULE cpp/govibevoicecpp.cpp) # vv_capi_* symbols (purego dlopens them by name, nothing in our # translation unit references them). Force the static archive's # entire contents into the MODULE so dlsym finds vv_capi_load etc. +# +# Link the `vibevoice` TARGET (not a bare archive path) so CMake builds +# libvibevoice.a first and tracks the dependency: the upstream project is added +# with EXCLUDE_FROM_ALL, so without a target-level link there is no rule to +# build it. Passing only $ as a path on Apple left the +# build with "No rule to make target 'vibevoice/libvibevoice.a'" (issue #10267). +# force_load is then applied as a separate link option. if(APPLE) - target_link_libraries(govibevoicecpp PRIVATE -Wl,-force_load $) + target_link_libraries(govibevoicecpp PRIVATE vibevoice) + target_link_options(govibevoicecpp PRIVATE "-Wl,-force_load,$") elseif(MSVC) target_link_libraries(govibevoicecpp PRIVATE vibevoice) set_property(TARGET govibevoicecpp APPEND PROPERTY LINK_FLAGS "/WHOLEARCHIVE:vibevoice") diff --git a/backend/go/vibevoice-cpp/Makefile b/backend/go/vibevoice-cpp/Makefile index 6b061a2ea..199df9cc4 100644 --- a/backend/go/vibevoice-cpp/Makefile +++ b/backend/go/vibevoice-cpp/Makefile @@ -94,26 +94,30 @@ purge: # Build all variants (Linux only) ifeq ($(UNAME_S),Linux) libgovibevoicecpp-avx.so: sources/vibevoice.cpp + $(MAKE) purge $(info ${GREEN}I vibevoice-cpp build info:avx${RESET}) SO_TARGET=libgovibevoicecpp-avx.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgovibevoicecpp-custom - rm -rf build-libgovibevoicecpp-avx.so + rm -rfv build* libgovibevoicecpp-avx2.so: sources/vibevoice.cpp + $(MAKE) purge $(info ${GREEN}I vibevoice-cpp build info:avx2${RESET}) SO_TARGET=libgovibevoicecpp-avx2.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libgovibevoicecpp-custom - rm -rf build-libgovibevoicecpp-avx2.so + rm -rfv build* libgovibevoicecpp-avx512.so: sources/vibevoice.cpp + $(MAKE) purge $(info ${GREEN}I vibevoice-cpp build info:avx512${RESET}) SO_TARGET=libgovibevoicecpp-avx512.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=on -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libgovibevoicecpp-custom - rm -rf build-libgovibevoicecpp-avx512.so + rm -rfv build* endif # Build fallback variant (all platforms) libgovibevoicecpp-fallback.so: sources/vibevoice.cpp + $(MAKE) purge $(info ${GREEN}I vibevoice-cpp build info:fallback${RESET}) SO_TARGET=libgovibevoicecpp-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgovibevoicecpp-custom - rm -rf build-libgovibevoicecpp-fallback.so + rm -rfv build* libgovibevoicecpp-custom: CMakeLists.txt cpp/govibevoicecpp.cpp cpp/govibevoicecpp.h mkdir -p build-$(SO_TARGET) && \ diff --git a/scripts/build/golang-darwin.sh b/scripts/build/golang-darwin.sh index 02d502d23..02e75f736 100644 --- a/scripts/build/golang-darwin.sh +++ b/scripts/build/golang-darwin.sh @@ -5,6 +5,28 @@ export BUILD_TYPE="${BUILD_TYPE:-metal}" mkdir -p backend-images make -C backend/go/${BACKEND} build +BACKEND_DIR="backend/go/${BACKEND}" + +# Never package a stray CMake build tree (e.g. build-libgo*-*.so/, a directory +# left behind by a partial native build) into the backend image. +rm -rf "${BACKEND_DIR}"/build-* + +# Fail loudly if the build did not produce the backend binary, instead of +# silently packaging the source/build tree as a "backend" that can never start +# (issue #10267: the darwin vibevoice-cpp image shipped sources, no binary). +# run.sh's final `exec $CURDIR/` is the contract for what gets launched; +# the binary is not always named after the backend (e.g. parakeet-cpp launches +# parakeet-cpp-grpc), so derive it from run.sh and fall back to ${BACKEND}. +RUN_BINARY="" +if [ -f "${BACKEND_DIR}/run.sh" ]; then + RUN_BINARY=$(grep -oE '\$CURDIR/[A-Za-z0-9._-]+' "${BACKEND_DIR}/run.sh" | grep -v 'ld\.so' | tail -1 | sed 's|\$CURDIR/||') +fi +RUN_BINARY="${RUN_BINARY:-${BACKEND}}" +if [ ! -x "${BACKEND_DIR}/${RUN_BINARY}" ]; then + echo "ERROR: ${BACKEND_DIR}/${RUN_BINARY} not found after build; refusing to package a broken backend image (see issue #10267)." >&2 + exit 1 +fi + PLATFORMARCH="${PLATFORMARCH:-darwin/arm64}" IMAGE_NAME="${IMAGE_NAME:-localai/${BACKEND}-darwin}"