MobDev.StaticNifs (mob_dev v0.5.2)

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
:guardstringnonePreprocessor macro that gates the entry

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 preprocessor macro that the build defines only on those archs. The generated C file wraps both the forward declaration and the table row in #ifdef <guard>.

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. The guard is the user's explicit opt-in (e.g. MOB_STATIC_EMLX_NIF) and gets emitted as a preprocessor #ifdef in C output or a comptime const in Zig output, independent of whether the entry's archs narrow the 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). When true, the generated entry must be wrapped in #ifdef <guard>.

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

nif_entry()

@type nif_entry() :: %{
  :module => atom(),
  optional(:init) => String.t(),
  optional(:builtin) => boolean(),
  optional(:archs) => [arch()],
  optional(:guard) => String.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. The guard is the user's explicit opt-in (e.g. MOB_STATIC_EMLX_NIF) and gets emitted as a preprocessor #ifdef in C output or a comptime const in Zig output, independent of whether the entry's archs narrow the 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). When true, the generated entry must be wrapped in #ifdef <guard>.

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}.