Packages
mob_dev
0.6.21
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob.verify_strip.ex
defmodule Mix.Tasks.Mob.VerifyStrip do
@shortdoc "Eager-load every shipped .beam on a connected device, report failures"
@moduledoc """
Boot-safety verification for stripped Mob app builds.
After deploying a slim build (where `mix mob.release` has dropped OTP
libs to shrink the IPA), this task connects to the running app's BEAM
and asks it to force-load every `.beam` shipped in the bundle. Any
module that fails to load — typically because a stripped lib was
needed by a transitive dep — shows up here.
## Usage
# First connect to the device (sets up tunnels + verifies node):
mix mob.connect --no-iex
# Then run the verifier:
mix mob.verify_strip
Or pass `--node` explicitly to skip auto-discovery.
## What it checks
1. **Eager load sweep** — `Code.ensure_loaded/1` on every `.beam`
under `<files_dir>/otp/`. Catches "module X depends on stripped
module Y" at load time.
2. **Harness exercise** — runs `MobDev.OtpTrace.Harness.all/0` on
the device and reports any exception. Catches issues with the
common Elixir surface even if individual modules loaded fine.
## What it does NOT check
App-specific code paths. If your app calls `:public_key.something`
when the user opens a particular screen, this verifier won't find it
unless the screen is opened. Run your own integration tests for that.
## Output
Plain mode prints a summary + per-failure detail. Exit code is 0 if
everything loaded + harness passed, 1 otherwise.
"""
use Mix.Task
@impl Mix.Task
def run(args) do
{opts, _, _} = OptionParser.parse(args, strict: [node: :string])
Mix.Task.run("loadpaths")
Mix.Task.run("compile")
node = resolve_node(opts[:node])
Mix.shell().info("Verifying strip safety on #{node}…\n")
case :rpc.call(node, Mob.Diag, :verify_loaded_modules, []) do
{:badrpc, reason} ->
Mix.shell().error("RPC failed: #{inspect(reason)}")
Mix.shell().error("""
Is `Mob.Diag` deployed to the device? It ships with the `mob`
runtime library — push the latest BEAMs first:
mix mob.push
And confirm the device's mob version is recent enough.
""")
exit({:shutdown, 1})
report ->
print_load_report(report)
if report.failed != [], do: exit({:shutdown, 1})
end
end
defp resolve_node(nil) do
case Node.list() do
[] ->
Mix.raise("""
No connected nodes found. Run `mix mob.connect --no-iex` first
in a separate terminal so the device's BEAM is reachable.
Or pass --node <name@host> explicitly.
""")
[single] ->
single
multiple ->
Mix.raise("""
Multiple nodes connected — specify with --node:
#{Enum.map_join(multiple, "\n", &" #{&1}")}
""")
end
end
defp resolve_node(node_str), do: String.to_atom(node_str)
defp print_load_report(report) do
h1 = IO.ANSI.bright()
green = IO.ANSI.green()
red = IO.ANSI.red()
reset = IO.ANSI.reset()
Mix.shell().info("#{h1}=== Eager-load sweep ==={reset}")
Mix.shell().info(" OTP root: #{report.otp_root || "(not detected)"}")
Mix.shell().info(" Total .beam files: #{report.total}")
Mix.shell().info(" #{green}Loaded: #{report.loaded}#{reset}")
case report.failed do
[] ->
Mix.shell().info(
" #{green}✓ all modules loaded — strip is safe at the load-time level#{reset}"
)
failures ->
Mix.shell().error(" #{red}✗ #{length(failures)} module(s) failed to load:#{reset}")
for %{module: mod, reason: reason} <- Enum.take(failures, 20) do
Mix.shell().error(" #{inspect(mod)} — #{inspect(reason)}")
end
if length(failures) > 20 do
Mix.shell().error(" … and #{length(failures) - 20} more")
end
end
Mix.shell().info(" Elapsed: #{MobDev.Duration.format_us(report.elapsed_us)}")
end
end