Cross-compiles a plugin's lang: :cpp_archive NIF — a set of C++ sources —
into lib<module>.a for one target ABI, and verifies the NIF-init symbol is
present. The archive is then static-linked into the app's single signed native
binary (same slot as crypto.a / libnx_eigen.a).
This is the generic, manifest-driven generalization of MobDev.NxEigenNif:
the sources, include dirs, CXXFLAGS, and expected symbol all come from the
plugin's Merge.static_archives/2 spec rather than being hardcoded. It exists
because the single-source :c/:zig plugin NIF path (compiled inline by
build.zig) can't express a C++ build with external headers (Eigen/Fine),
RTTI/exceptions, a per-arch hardening flag set, and an archive output.
Why static-link (same as every on-device NIF)
- Android. A separately-
dlopen'd.soinheritsRTLD_LOCAL, hiding the BEAM'senif_*symbols →on_loadfails. Static-linking lets the BEAM resolve the init symbol viadlsym(RTLD_DEFAULT)on the app binary. - iOS. The App Store forbids loading unsigned dylibs /
dlopen; the NIF must already be in the signed binary.
What the builder forces vs. what the plugin controls
The builder forces only -fPIC (a static lib linked into a shared object must
be position-independent) and the compile/output flags (-c -o). Everything
else — C++ standard, optimization, visibility, exceptions, the
-DSTATIC_ERLANG_NIF_LIBNAME=… that fixes the emitted init symbol, and any
Android hardening — is the plugin's via :cxxflags / :cxxflags_android /
:cxxflags_ios, so a plugin author keeps full control of its own ABI.
Summary
Functions
Archive filename for a NIF module — lib<module>.a.
Compile + archive + verify a cpp_archive spec (from Merge.static_archives/2)
for one target_id. Returns {:ok, %{module:, archive:, objects:}} or a
tagged error.
Parse nm output and confirm the expected init symbol is exported (T).
Mirrors MobDev.NxEigenNif.check_symbol_present/3. Pure.
Assemble the full CXXFLAGS for one target: forced -fPIC + the target's
intrinsic ABI flags (e.g. armv7 flags for android_arm32), then the plugin's
base :cxxflags, then the target's platform-specific flags
(:cxxflags_android / :cxxflags_ios), then -I for each resolved include
dir (order preserved). Pure — silent flag drops are the regression class this
whole module guards against, so it's directly testable.
Resolve a spec's :sources/:includes (a mix of absolute strings and
{:dep, name, subpath} tokens left by Merge.static_archives/2) to absolute
paths, resolving dep tokens against deps_path. Pure.
All target ABIs a cpp_archive can be built for.
Functions
Archive filename for a NIF module — lib<module>.a.
@spec build(map(), atom(), keyword()) :: {:ok, map()} | MobDev.Release.Errors.t()
Compile + archive + verify a cpp_archive spec (from Merge.static_archives/2)
for one target_id. Returns {:ok, %{module:, archive:, objects:}} or a
tagged error.
Options:
:out_dir— where the archive + per-arch objs land. Required.:erts_include— per-targeterts-VSN/include/dir (carrieserl_nif.h). Required.:deps_path— for resolving{:dep, …}include tokens (defaults toMix.Project.deps_path/0).:ndk_root— Android NDK root (Android targets; default derived).
@spec check_symbol_present(binary(), String.t(), Path.t()) :: :ok | MobDev.Release.Errors.t()
Parse nm output and confirm the expected init symbol is exported (T).
Mirrors MobDev.NxEigenNif.check_symbol_present/3. Pure.
Assemble the full CXXFLAGS for one target: forced -fPIC + the target's
intrinsic ABI flags (e.g. armv7 flags for android_arm32), then the plugin's
base :cxxflags, then the target's platform-specific flags
(:cxxflags_android / :cxxflags_ios), then -I for each resolved include
dir (order preserved). Pure — silent flag drops are the regression class this
whole module guards against, so it's directly testable.
Resolve a spec's :sources/:includes (a mix of absolute strings and
{:dep, name, subpath} tokens left by Merge.static_archives/2) to absolute
paths, resolving dep tokens against deps_path. Pure.
@spec targets() :: [atom()]
All target ABIs a cpp_archive can be built for.