MobDev.Plugin.NifActivation (mob_dev v0.7.8)

Copy Markdown View Source

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), 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.

Summary

Types

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

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

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

Functions

The activated plugin names, non-atom entries dropped.

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.

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.

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.

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

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

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.

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.

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.

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.

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.

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

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

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

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.

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

Renders a record as the text parse_record/1 reads.

Prints inactive_warning/2 for this project, if any.

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

Types

build_record()

@type build_record() :: %{optional(:android | :ios) => [String.t()]}

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

dep_manifest()

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

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

drift()

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

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

Functions

activated()

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

The activated plugin names, non-atom entries dropped.

config_line(activated, inactive)

@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()

@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(current, recorded, platforms)

@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(drift)

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

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

inactive_nif_plugins(activated)

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

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

inactive_nif_plugins(dep_manifests, activated, runtime)

@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(inactive, activated)

@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(dep_manifests, activated)

@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(nodes, app)

@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(text)

@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(record, platform, names)

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

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

read_record(path)

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

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

record_native_build(platforms)

@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(platforms, current, path)

@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()

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

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

render_record(record)

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

Renders a record as the text parse_record/1 reads.

warn_inactive()

@spec warn_inactive() :: :ok

Prints inactive_warning/2 for this project, if any.

warn_stale_build(platforms)

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

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