Cross-compiles the nx_eigen C++ NIF (Eigen-backed Nx backend) for one
Android or iOS target ABI and archives the result as libnx_eigen.a.
The archive gets static-linked into the user app's main native binary
alongside crypto.a, libemlx.a, and any other static NIFs.
Why static-link this NIF?
Same constraints as every other NIF mob ships on phones:
- Android.
dlopen'd children inheritRTLD_LOCAL, hiding the parent'senif_*symbols from a separately-loadedlibnx_eigen.so.on_loadthen fails with "cannot locate symbol". Static linking sidesteps that — BEAM findsnx_eigen_nif_initviadlsym(RTLD_DEFAULT)against the main app binary. - iOS. App Store forbids loading unsigned dylibs /
dlopen; every NIF must already be present in the signed binary.
Per-target deltas
All four targets compile the same two source files (@sources) with the
shared base CXXFLAGS list (@base_cxxflags). The deltas are:
| Target | Arch dir | Toolchain | Extra CXXFLAGS | nm symbol |
|---|---|---|---|---|
| android_arm64 | aarch64-unknown-linux-android | NDK clang++/llvm-ar | Android hardening: branch-protect, stack-clash, _GNU_SOURCE | nx_eigen_nif_init |
| android_arm32 | arm-unknown-linux-androideabi | NDK clang++/llvm-ar | Android hardening + -march=armv7-a -mfloat-abi=softfp -mthumb | nx_eigen_nif_init |
| ios_sim | aarch64-apple-iossimulator | xcrun (sim SDK) | iOS minimal — no Android hardening | _nx_eigen_nif_init |
| ios_device | aarch64-apple-ios | xcrun (device SDK) | iOS minimal — no Android hardening | _nx_eigen_nif_init |
STATIC_ERLANG_NIF_LIBNAME
We pass -DSTATIC_ERLANG_NIF_LIBNAME=nx_eigen rather than plain
-DSTATIC_ERLANG_NIF. The reason: nx_eigen_nif.cpp uses Fine's
FINE_INIT(...) macro, which expands to ERL_NIF_INIT_DECL(NAME) —
passing the literal token NAME as MODNAME. With STATIC_ERLANG_NIF
alone the emitted symbol would be the unhelpful NAME_nif_init.
Setting LIBNAME overrides MODNAME entirely and forces the symbol to
nx_eigen_nif_init, which is what mob's driver_tab references.
FFT — Eigen's built-in kissfft backend
We don't use NxEigen's bundled FFT variants. Instead, mob_dev ships
its own priv/cpp_nif/nx_eigen_fft_eigen.cpp bridge that calls
Eigen's unsupported/Eigen/FFT module (kissfft underneath — header
only, embedded in the Eigen tarball). This gives Nx.fft/3 and
Nx.ifft/3 working on-device with no additional cross-compile.
Kissfft is roughly 2x slower than FFTW for large transforms but microseconds for audio-sized buffers; switch to a FFTW variant later if a real workload measures a bottleneck.
Phases
Each build/2 call:
- Precheck — nx_eigen source dir + Fine + Eigen headers + erts include all present; Android/iOS toolchain reachable.
- Compile — for each of @sources, run
<cxx> <cxxflags> -c -o obj src. - Archive —
<ar> rcs libnx_eigen.a obj1 obj2then<ranlib> .... - Verify —
<nm> libnx_eigen.a, scan for the expected symbol. Symbol missing is a:precondition_failed(means our compile didn't actually producenx_eigen_nif_init— usually because Fine or NxEigen's source moved out from under us, or LIBNAME wasn't picked up).
Summary
Functions
Base CXXFLAGS shared across all targets. Public for testing.
Compile + archive + verify libnx_eigen.a for one target. Returns
{:ok, info} naming the produced archive, or a tagged error.
Parse nm output and confirm the expected nx_eigen_nif_init symbol
is exported (T flag in nm's output). Returns :ok or a tagged
precondition_failed.
Assemble the full CXXFLAGS list for a target plus the include path list. Pure function for testability — silent flag drops are the exact regression class this module exists to prevent.
Source files compiled for every target — list of {root, basename}.
Public so tests can pin the surface.
Per-target spec. Public so tests can lock down the surface (especially
the extra_cxxflags lists — silent drops there would silently weaken
released binaries).
All known NxEigen targets.
Functions
@spec base_cxxflags() :: [String.t()]
Base CXXFLAGS shared across all targets. Public for testing.
@spec build( atom(), keyword() ) :: {:ok, map()} | MobDev.Release.Errors.t()
Compile + archive + verify libnx_eigen.a for one target. Returns
{:ok, info} naming the produced archive, or a tagged error.
Options:
:nx_eigen_dir— path to the nx_eigen Hex dep (the dir containingc_src/and the Eigen download). Required.:fine_dir— path to the fine Hex dep (containingc_include/). Required.:erts_include— path to the per-targeterts-VSN/include/dir (carrieserl_nif.h, etc.). Required.:eigen_dir— Eigen header root (defaults to<nx_eigen_dir>/eigen-3.4.0).:bridge_dir— directory holding the Eigen-FFT bridge source we ship (defaults to:code.priv_dir(:mob_dev)/cpp_nif).:out_dir— directory the archive + per-arch obj subdir get written to. Required.:ndk_root— Android NDK root (Android targets only; defaults to~/Library/Android/sdk/ndk/<NdkVersion.effective()>).
@spec check_symbol_present(binary(), String.t(), Path.t()) :: :ok | MobDev.Release.Errors.t()
Parse nm output and confirm the expected nx_eigen_nif_init symbol
is exported (T flag in nm's output). Returns :ok or a tagged
precondition_failed.
Mirror of MobDev.Release.OpenSSL.CryptoNif.check_symbol_present/3 —
the parsing logic is identical, just a different expected symbol.
Missing symbol on an otherwise-successful build almost always means
STATIC_ERLANG_NIF_LIBNAME=nx_eigen got dropped from the flags or
Fine's FINE_INIT macro changed shape.
@spec cxxflags(MobDev.NxEigenNif.Target.t(), [Path.t()]) :: [String.t()]
Assemble the full CXXFLAGS list for a target plus the include path list. Pure function for testability — silent flag drops are the exact regression class this module exists to prevent.
includes is a list of absolute directory paths to be -I-prefixed.
Order is preserved.
@spec sources() :: [{:nx_eigen | :bridge, String.t()}]
Source files compiled for every target — list of {root, basename}.
Public so tests can pin the surface.
@spec target_spec(atom()) :: MobDev.NxEigenNif.Target.t()
Per-target spec. Public so tests can lock down the surface (especially
the extra_cxxflags lists — silent drops there would silently weaken
released binaries).
@spec targets() :: [atom()]
All known NxEigen targets.