# `MobDev.Plugin.CppArchive`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/plugin/cpp_archive.ex#L1)

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 `.so` inherits `RTLD_LOCAL`, hiding
    the BEAM's `enif_*` symbols → `on_load` fails. Static-linking lets the
    BEAM resolve the init symbol via `dlsym(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.

# `archive_name`

```elixir
@spec archive_name(atom()) :: String.t()
```

Archive filename for a NIF module — `lib<module>.a`.

# `build`

```elixir
@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-target `erts-VSN/include/` dir (carries `erl_nif.h`).
    **Required.**
  * `:deps_path` — for resolving `{:dep, …}` include tokens (defaults to
    `Mix.Project.deps_path/0`).
  * `:ndk_root` — Android NDK root (Android targets; default derived).

# `check_symbol_present`

```elixir
@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.

# `cxxflags`

```elixir
@spec cxxflags(map(), atom(), [Path.t()]) :: [String.t()]
```

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_deps`

```elixir
@spec resolve_deps([Path.t() | {:dep, atom(), String.t()}], Path.t()) :: [Path.t()]
```

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.

# `targets`

```elixir
@spec targets() :: [atom()]
```

All target ABIs a cpp_archive can be built for.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
