Build fails on Termux: cannot locate symbol "ggml_norm_affine" — fix: -DBUILD_SHARED_LIBS=OFF
Environment
- Platform: Android / Termux
- Architecture: aarch64
- Termux packages installed:
whisper-cli (or any package that ships its own libggml.so)
Symptom
Build reaches the test-linking stage and fails:
CANNOT LINK EXECUTABLE "build/bin/test-flash-attn-defaults": cannot locate symbol
"ggml_norm_affine" referenced by "build/bin/test-flash-attn-defaults"...
CMake Error at ...catch2.../CatchAddTests.cmake:81 (message):
Error running test executable '.../test-flash-attn-defaults':
Result: 1
Multiple test binaries fail with the same error (test-paraformer, test-flash-attn-defaults, etc.).
Root Cause
Termux installs system-wide packages (e.g. whisper-cli) that ship their own libggml.so
into /data/data/com.termux/files/usr/lib/. This system library is an older version that
does not export ggml_norm_affine.
CrispASR builds a newer libggml locally under build/ggml/src/, which does export the
symbol. However, the dynamic linker finds the system library first (it appears first in
RUNPATH) and loads that instead — so the symbol is missing at runtime.
The locally built library is correct; the wrong one is being loaded.
Fix
Add -DBUILD_SHARED_LIBS=OFF to your cmake invocation:
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DBUILD_SHARED_LIBS=OFF ..
make -j$(nproc)
make install
This switches from shared (.so) to static (.a) libraries. The linker copies all ggml
code directly into each executable at compile time. At runtime there is no .so to look
up, so the system library conflict cannot occur.
Trade-offs
The only cost is binary size. Each installed binary is self-contained and larger than
it would be in a shared build — roughly 2-5× bigger before stripping. For a full
CrispASR install this amounts to approximately 200-300 MB extra vs. a shared build.
You can recover roughly half of that by stripping debug symbols after install:
strip $PREFIX/bin/crispasr*
Note: the build configuration RelWithDebInfo (the default) includes debug symbols, so
unstripped binaries are significantly larger than they need to be in production.
Benefits on Termux:
- No
LD_LIBRARY_PATH manipulation needed
- No RPATH configuration needed
- Immune to future Termux package upgrades that bump the system
libggml version
- Binaries are portable to other aarch64 Android devices that have Termux installed
What does NOT work
Setting CMAKE_INSTALL_RPATH / CMAKE_BUILD_WITH_INSTALL_RPATH to point at the local
build directory does not reliably fix this on Termux, because Termux prepends its own
$PREFIX/lib to RUNPATH regardless, and that directory contains the conflicting system
library.
Build fails on Termux:
cannot locate symbol "ggml_norm_affine"— fix:-DBUILD_SHARED_LIBS=OFFEnvironment
whisper-cli(or any package that ships its ownlibggml.so)Symptom
Build reaches the test-linking stage and fails:
Multiple test binaries fail with the same error (
test-paraformer,test-flash-attn-defaults, etc.).Root Cause
Termux installs system-wide packages (e.g.
whisper-cli) that ship their ownlibggml.sointo
/data/data/com.termux/files/usr/lib/. This system library is an older version thatdoes not export
ggml_norm_affine.CrispASR builds a newer
libggmllocally underbuild/ggml/src/, which does export thesymbol. However, the dynamic linker finds the system library first (it appears first in
RUNPATH) and loads that instead — so the symbol is missing at runtime.The locally built library is correct; the wrong one is being loaded.
Fix
Add
-DBUILD_SHARED_LIBS=OFFto your cmake invocation:This switches from shared (
.so) to static (.a) libraries. The linker copies all ggmlcode directly into each executable at compile time. At runtime there is no
.soto lookup, so the system library conflict cannot occur.
Trade-offs
The only cost is binary size. Each installed binary is self-contained and larger than
it would be in a shared build — roughly 2-5× bigger before stripping. For a full
CrispASR install this amounts to approximately 200-300 MB extra vs. a shared build.
You can recover roughly half of that by stripping debug symbols after install:
Note: the build configuration
RelWithDebInfo(the default) includes debug symbols, sounstripped binaries are significantly larger than they need to be in production.
Benefits on Termux:
LD_LIBRARY_PATHmanipulation neededlibggmlversionWhat does NOT work
Setting
CMAKE_INSTALL_RPATH/CMAKE_BUILD_WITH_INSTALL_RPATHto point at the localbuild directory does not reliably fix this on Termux, because Termux prepends its own
$PREFIX/libtoRUNPATHregardless, and that directory contains the conflicting systemlibrary.