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

Warnings for the two ways a plugin's NIF silently misses the installed app
(MOB-281).

A plugin's `on_load` tolerates a missing NIF, so neither case fails a build
or a boot — the first signal is `{:nif_not_loaded, ...}` from the first NIF
call, with nothing pointing at the cause:

  * **Installed but not activated.** The plugin is in `mix.exs` deps but not
    in `config :mob, :plugins` in `mob.exs`. Activation is the deliberate
    second opt-in step (see [`MOB_PLUGINS.md`](https://github.com/GenericJam/mob/blob/master/MOB_PLUGINS.md)), so the native build compiles
    none of its NIFs. `mix mob.deploy --native` and `mix mob.doctor` name
    every such device-runtime dep (not `only: :dev` / `runtime: false`) that
    declares `nifs:` and print the exact `config` line.

  * **Activated after the last native build.** The plugin is activated, but
    only a BEAM-only `mix mob.deploy` / `mix mob.push` ran since, so the
    installed binary predates it. Every successful native build records, per
    platform, which activated plugins it compiled NIFs for
    (`mob_native_plugins.txt` under `Mix.Project.build_path/0`); a
    BEAM-only deploy or push compares the current activation against that
    record and names the plugins the installed app was built without.

The checks themselves are advisory: warnings, never errors, and a failure to
write the record is itself only a warning. (A `mob.exs` that fails to load
still raises, as every `mob.exs` reader does — MOB-280.) The record describes the last native
*build* on this machine for this `MIX_ENV`, not what a particular device has
installed — a device last installed from another checkout or before a
`mix clean` can still disagree with it. A missing record (a project built
before this check existed, or a wiped `_build`) is reported separately from
a known stale build.

Manifests are read through `MobDev.Plugin.Verify.load_verified/2`, like
every other build path (MOB-74): a dep whose manifest fails verification is
never evaluated, so its NIFs are unknown and it is not reported here.

# `build_record`

```elixir
@type build_record() :: %{optional(:android | :ios) =&gt; [String.t()]}
```

Per-platform plugin names compiled into the last native build.

# `dep_manifest`

```elixir
@type dep_manifest() :: {atom(), map() | nil}
```

`{dep_name, manifest}` — `nil` for a non-plugin dep or an unverifiable manifest.

# `drift`

```elixir
@type drift() :: {atom(), :android | :ios, :not_built | :no_record}
```

One stale-build finding: the plugin, the platform, and why.

# `activated`

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

The activated plugin names, non-atom entries dropped.

# `config_line`

```elixir
@spec config_line([atom()], [atom()]) :: String.t()
```

The `config :mob, :plugins, [...]` line that activates `inactive` on top of
the currently `activated` plugins — the whole list, so it replaces the
existing line verbatim.

# `dep_manifests`

```elixir
@spec dep_manifests() :: [dep_manifest()]
```

Every dep as `{name, manifest}`, manifests read through
`Verify.load_verified/2` (honouring `:acknowledge_unsafe_plugins`, like
`MobDev.Plugin.activated_with_verify/0`). Unverifiable manifests are `nil`.

# `drift`

```elixir
@spec drift(build_record(), build_record(), [:android | :ios]) :: [drift()]
```

Plugins activated now (`current`, from `nif_plugins_by_platform/2`) that the
recorded native build for each of `platforms` lacks. `:not_built` when that
platform has a record without the plugin; `:no_record` when that platform
was never recorded. Sorted by plugin, then platform.

# `drift_warning`

```elixir
@spec drift_warning([drift()]) :: String.t() | nil
```

The yellow warning block for `drift/3`'s result, or `nil` when empty.

# `inactive_nif_plugins`

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

`inactive_nif_plugins/3` for this project, against the activated plugins.

# `inactive_nif_plugins`

```elixir
@spec inactive_nif_plugins([dep_manifest()], [atom()], MapSet.t(String.t())) :: [
  atom()
]
```

Device-runtime deps that ship a manifest declaring at least one NIF but are
not in `activated`. `runtime` is the set of dep names that ship to the device
(`MobDev.HotPush.runtime_lib_names/0`): an `only: :dev` or `runtime: false`
dep never reaches the app, so activating it would fix nothing. Tier-0
plugins (no manifest), manifests without `nifs:`, and activated plugins are
never flagged. Sorted.

# `inactive_warning`

```elixir
@spec inactive_warning([atom()], [atom()]) :: String.t() | nil
```

The yellow warning block for `inactive_nif_plugins/3`'s result, or `nil`
when it is empty. `activated` is the current `config :mob, :plugins` list;
the printed `config` line is that list plus the inactive plugins, so it can
replace the existing line verbatim.

# `nif_plugins_by_platform`

```elixir
@spec nif_plugins_by_platform([dep_manifest()], [atom()]) :: build_record()
```

For each platform, the activated plugins whose manifest declares a NIF for
it (a NIF without `:platform` counts for both). Every platform is a key,
possibly with `[]`. Names are strings, the form the record stores.

# `node_platforms`

```elixir
@spec node_platforms([node()], atom() | String.t()) :: [:android | :ios]
```

The platforms of connected device nodes of the project app `app`, from the
node names `MobDev.Device.node_name/1` builds: `<app>_android[_<suffix>]@…`
and `<app>_ios[_<suffix>]@…`. The app prefix is stripped before reading the
platform, so an app named e.g. `my_ios_app` isn't mistaken for iOS. Nodes of
other apps are ignored.

# `parse_record`

```elixir
@spec parse_record(String.t()) :: build_record()
```

Parses the record file's text. One line per platform — the platform name
followed by the space-separated plugin names; `#` lines are comments.
Unknown platforms and malformed lines are ignored.

# `put_record`

```elixir
@spec put_record(build_record(), :android | :ios, [String.t()]) :: build_record()
```

`record` with `platform`'s entry replaced by `names`; other platforms kept.

# `read_record`

```elixir
@spec read_record(Path.t()) :: build_record()
```

Reads the record at `path`; a missing or unreadable file is `%{}`.

# `record_native_build`

```elixir
@spec record_native_build([:android | :ios]) :: :ok
```

Records that a native build for each of `platforms` just succeeded with the
currently activated NIF plugins. Other platforms' entries are kept.

# `record_native_build`

```elixir
@spec record_native_build([:android | :ios], build_record(), Path.t()) :: :ok
```

Merges `current`'s entries for `platforms` into the record at `path`. The
record is advisory: a write failure prints a warning and returns `:ok`
rather than failing the build that just succeeded.

# `record_path`

```elixir
@spec record_path() :: Path.t()
```

Where the native-build record lives: under `Mix.Project.build_path/0`.

# `render_record`

```elixir
@spec render_record(build_record()) :: String.t()
```

Renders a record as the text `parse_record/1` reads.

# `warn_inactive`

```elixir
@spec warn_inactive() :: :ok
```

Prints `inactive_warning/2` for this project, if any.

# `warn_stale_build`

```elixir
@spec warn_stale_build([:android | :ios]) :: :ok
```

Prints `drift_warning/1` for a BEAM-only deploy or push to `platforms`,
if any.

---

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