MobDev.StaticNifs (mob_dev v0.7.22)

Copy Markdown View Source

Schema, defaults, and C-source generation for the static NIF table.

The static NIF table lives in two C files inside an app's project:

priv/generated/driver_tab_ios.c
priv/generated/driver_tab_android.c

Both are linked before libbeam.a so they override BEAM's empty built-in erts_static_nif_tab[]. With these in place, load_nif/2 resolves to the in-binary init function instead of falling back to dlopen, which fails on iOS (App Store rejects bundled .dylibs) and on Android (RTLDLOCAL hides the parent's `enif*` symbols from child libraries).

Declaring NIFs

An app's mob.exs may add to or override the defaults via the :static_nifs key:

config :mob_dev,
  static_nifs: [
    %{module: :my_native, archs: [:all]}
  ]

Each entry is a map with these fields:

FieldTypeDefaultMeaning
:moduleatomrequiredErlang module name
:initstringderivedInit fn name. Defaults to <mod>_nif_init
:builtinbooleanfalseTrue for OTP-shipped libs
:archs[atom][:all]Where this NIF should appear
:guardstringnoneMacro naming a narrowed entry (see below)
:extra_static_libsmapnonePer-ABI archives to link with this NIF

Valid :archs values: :all, :ios, :android, :ios_sim, :ios_device, :android_arm64, :android_arm32.

When :archs is a strict subset of a target platform's archs (e.g. [:ios_device] for iOS), set :guard to a C-identifier macro name. The generated tables select a guarded project entry on exactly the targets its :archs cover, by the compiler's own target: the Zig table gates it on @import("builtin").target (the simulator ABI on iOS, the CPU on Android), and the C table wraps the forward declaration and row in #if <arch test> (TARGET_OS_SIMULATOR, __aarch64__, __arm__, or 1 when the entry covers the platform). No build option is involved, so it works with any app's build files (MOB-376). The guards of the built-in NIFs in default_nifs/0 are different: each is a feature switch that mob_new's build files set (build_options.<flag> for Zig, -D<guard> for C).

Defaults

See default_nifs/0 for the baked-in NIF set. It mirrors the hand-edited driver_tab_ios.c / driver_tab_android.c files mob shipped through v0.5.18 — regen/1 against an empty user list produces byte-equivalent output to those files.

Summary

Functions

Returns the baked-in NIF set used by every Mob app.

Generates the driver_tab source for one platform.

True when the entry carries a :guard key. A built-in NIF's guard (e.g. MOB_STATIC_EMLX_NIF) is a feature switch the build sets: #ifdef in C, a build_options flag in Zig. A project NIF's guard selects the entry on the targets its :archs cover, by the compiler's target, and is always on when the archs cover the whole platform.

Returns the init function name for an entry — either the explicit :init value or the conventional <module>_nif_init.

Returns true if the entry's archs are a strict subset of the platform's archs (i.e. it's present on this platform but not all of its arches). Such an entry needs a :guard, so the generated tables register it only on its archs.

Returns true if the entry should appear in the generated file for this platform (i.e. its archs intersect the platform's archs).

Combines the defaults with a user list (typically from Application.get_env(:mob_dev, :static_nifs, [])).

Validates a single entry, returning :ok or {:error, reason}.

Types

arch()

@type arch() ::
  :all
  | :ios
  | :android
  | :ios_sim
  | :ios_device
  | :android_arm64
  | :android_arm32

extra_static_lib_arch()

@type extra_static_lib_arch() ::
  :ios_sim | :ios_device | :android_arm64 | :android_arm32 | :android_x86_64

nif_entry()

@type nif_entry() :: %{
  :module => atom(),
  optional(:init) => String.t(),
  optional(:builtin) => boolean(),
  optional(:archs) => [arch()],
  optional(:guard) => String.t(),
  optional(:extra_static_libs) => %{
    optional(extra_static_lib_arch()) => Path.t()
  }
}

platform()

@type platform() :: :ios | :android | arch()

Functions

default_nifs()

@spec default_nifs() :: [nif_entry()]

Returns the baked-in NIF set used by every Mob app.

These match the hand-edited driver_tab_*.c files in mob ≤ 0.5.18. Users append to this list via :static_nifs in mob.exs.

generate(platform, nifs, opts \\ [])

@spec generate(platform(), [nif_entry()], keyword()) :: iodata()

Generates the driver_tab source for one platform.

Format is :c by default (produces driver_tab_<platform>.c matching the hand-edited reference files byte-for-byte). Pass format: :zig for the Phase 6a Zig output — same semantics, structured as comptime-friendly Zig (extern struct ABI types, export for the C-callable symbols, if (sqlite_static) ... else ... in place of #ifdef).

Pure function — given the same nif list it always produces the same bytes.

guarded?(nif)

@spec guarded?(nif_entry()) :: boolean()

True when the entry carries a :guard key. A built-in NIF's guard (e.g. MOB_STATIC_EMLX_NIF) is a feature switch the build sets: #ifdef in C, a build_options flag in Zig. A project NIF's guard selects the entry on the targets its :archs cover, by the compiler's target, and is always on when the archs cover the whole platform.

init_fn(map)

@spec init_fn(nif_entry()) :: String.t()

Returns the init function name for an entry — either the explicit :init value or the conventional <module>_nif_init.

needs_guard?(entry, platform)

@spec needs_guard?(nif_entry(), platform()) :: boolean()

Returns true if the entry's archs are a strict subset of the platform's archs (i.e. it's present on this platform but not all of its arches). Such an entry needs a :guard, so the generated tables register it only on its archs.

on_platform?(entry, platform)

@spec on_platform?(nif_entry(), platform()) :: boolean()

Returns true if the entry should appear in the generated file for this platform (i.e. its archs intersect the platform's archs).

resolve(user_nifs)

@spec resolve(user_nifs :: [nif_entry()]) :: [nif_entry()]

Combines the defaults with a user list (typically from Application.get_env(:mob_dev, :static_nifs, [])).

Later entries with the same :module override earlier ones. This lets users replace a default entry — e.g. drop :sqlite3_nif by setting archs: [] — without forking the default list.

validate_entry(entry)

@spec validate_entry(nif_entry()) :: :ok | {:error, String.t()}

Validates a single entry, returning :ok or {:error, reason}.