Packages
mob_dev
0.2.0
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/mob_dev/hot_push.ex
defmodule MobDev.HotPush do
@moduledoc """
Connects to already-running device nodes and hot-pushes BEAM modules via RPC.
Unlike `MobDev.Deployer`, this does NOT restart apps — modules are loaded
into the running BEAM in place, just like `nl/1` in IEx.
Requires apps to already be running (start with `mix mob.connect` or
`mix mob.deploy` first).
"""
alias MobDev.{Tunnel}
alias MobDev.Discovery.{Android, IOS}
@cookie :mob_secret
@doc """
Sets up adb tunnels (idempotent) and connects to all running device nodes.
Returns list of connected node atoms.
"""
def connect(opts \\ []) do
cookie = Keyword.get(opts, :cookie, @cookie)
nodes =
(Android.list_devices() ++ IOS.list_simulators())
|> Enum.with_index()
|> Enum.flat_map(fn {device, idx} ->
case Tunnel.setup(device, idx) do
{:ok, d} -> [d]
_ -> []
end
end)
|> Enum.flat_map(fn device ->
ensure_local_dist(cookie)
Node.set_cookie(device.node, cookie)
case Node.connect(device.node) do
true -> [device.node]
_ -> []
end
end)
nodes
end
@doc """
Pushes all compiled BEAM files from `_build/dev/lib/*/ebin/` to `nodes`.
Returns `{pushed_count, failed_list}`.
"""
def push_all(nodes) do
beams = Path.wildcard("_build/dev/lib/*/ebin/*.beam")
push_beams(nodes, beams)
end
@doc """
Takes a snapshot of current BEAM mtimes. Pass the result to `push_changed/2`
before and after compiling to get only the modules that actually changed.
"""
def snapshot_beams do
Path.wildcard("_build/dev/lib/*/ebin/*.beam")
|> Map.new(fn path ->
mtime = case File.stat(path, time: :posix) do
{:ok, %{mtime: t}} -> t
_ -> 0
end
{path, mtime}
end)
end
@doc """
Pushes BEAM files that changed since `snapshot` (from `snapshot_beams/0`).
Returns `{pushed_count, failed_list}` — pushed_count is 0 if nothing changed.
"""
def push_changed(nodes, snapshot) do
beams =
Path.wildcard("_build/dev/lib/*/ebin/*.beam")
|> Enum.filter(fn path ->
current_mtime = case File.stat(path, time: :posix) do
{:ok, %{mtime: t}} -> t
_ -> 0
end
current_mtime != Map.get(snapshot, path, 0)
end)
push_beams(nodes, beams)
end
# ── Private ─────────────────────────────────────────────────────────────────
defp push_beams(_nodes, []), do: {0, []}
defp push_beams(nodes, beam_files) do
results = Enum.map(beam_files, fn path ->
module = beam_path_to_module(path)
case File.read(path) do
{:ok, binary} -> load_on_nodes(nodes, module, path, binary)
{:error, reason} -> {:error, {module, reason}}
end
end)
pushed = Enum.count(results, &match?(:ok, &1))
failed = for {:error, pair} <- results, do: pair
{pushed, failed}
end
defp load_on_nodes(nodes, module, path, binary) do
fname = String.to_charlist(path)
errors = Enum.flat_map(nodes, fn node ->
case :rpc.call(node, :code, :load_binary, [module, fname, binary]) do
{:module, ^module} -> []
{:badrpc, reason} -> [{node, reason}]
{:error, reason} -> [{node, reason}]
end
end)
if errors == [], do: :ok, else: {:error, {module, errors}}
end
defp beam_path_to_module(path) do
path |> Path.basename(".beam") |> String.to_atom()
end
defp ensure_local_dist(cookie) do
unless Node.alive?() do
Node.start(:"mob_dev@127.0.0.1", :longnames)
Node.set_cookie(cookie)
end
end
end