Packages
mob_dev
0.2.2
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.install.ex
defmodule Mix.Tasks.Mob.Install do
use Mix.Task
@shortdoc "First-run setup for a new Mob project"
@moduledoc """
Runs first-time setup for a Mob project generated by `mix mob.new`.
Must be run from inside the project directory (the one containing `mix.exs`).
mix mob.install [--no-icon] [--icon PATH]
## What it does
1. Generates app icons (random robot avatar, or a provided source image)
## Options
* `--no-icon` — skip icon generation
* `--icon PATH` — use an existing image instead of generating a random robot
## Icon output
- `android/app/src/main/res/mipmap-*/ic_launcher.png`
- `ios/Assets.xcassets/AppIcon.appiconset/icon_*.png` + `Contents.json`
- `icon_source.png` (1024×1024 master, when generating)
Run at any time to regenerate icons:
mix mob.install --no-icon # skip icons, re-run other setup steps
mix mob.icon # icon only, any time after install
"""
@switches [no_icon: :boolean, icon: :string]
@impl Mix.Task
def run(argv) do
{opts, _args, _} = OptionParser.parse(argv, strict: @switches)
project_dir = File.cwd!()
unless File.exists?(Path.join(project_dir, "mix.exs")) do
Mix.raise("No mix.exs found. Run mix mob.install from your project root.")
end
configure_paths(project_dir)
download_otp()
unless opts[:no_icon] do
generate_icons(project_dir, opts[:icon])
end
Mix.shell().info("""
Mob project ready!
mix mob.deploy --native # first deploy: build APK + iOS app, install + push BEAMs
mix mob.deploy # fast push + restart (day-to-day)
mix mob.watch # auto-push changes while developing
mix mob.connect # connect IEx to running device nodes
""")
end
# ── OTP download ─────────────────────────────────────────────────────────────
defp download_otp do
Mix.shell().info("Ensuring OTP releases are cached...")
case MobDev.OtpDownloader.ensure_android() do
{:ok, path} -> Mix.shell().info([:green, "* Android OTP: #{path}", :reset])
{:error, reason} -> Mix.shell().error("Warning: Android OTP download failed: #{reason}")
end
if match?({:unix, :darwin}, :os.type()) do
case MobDev.OtpDownloader.ensure_ios_sim() do
{:ok, path} -> Mix.shell().info([:green, "* iOS OTP: #{path}", :reset])
{:error, reason} -> Mix.shell().error("Warning: iOS OTP download failed: #{reason}")
end
end
end
# ── Path configuration ───────────────────────────────────────────────────────
@required_keys [:mob_dir, :elixir_lib]
defp configure_paths(project_dir) do
mob_exs = Path.join(project_dir, "mob.exs")
cfg =
if File.exists?(mob_exs) do
Config.Reader.read!(mob_exs) |> Keyword.get(:mob_dev, [])
else
[]
end
missing = Enum.filter(@required_keys, fn key ->
val = cfg[key]
is_nil(val) or String.contains?(val, "/path/to/")
end)
if missing != [] do
defaults = detect_defaults(project_dir)
Mix.shell().info("""
Configure your local build paths in mob.exs.
These are machine-specific and gitignored.
Press Enter to accept a detected value [ ], or type a new path.
""")
updates =
Enum.map(missing, fn key ->
{key, prompt_path(key, defaults[key])}
end)
new_cfg = Keyword.merge(cfg, updates)
write_mob_exs(mob_exs, new_cfg)
write_local_properties(project_dir, new_cfg)
Mix.shell().info([:green, "* mob.exs configured", :reset])
end
end
defp detect_defaults(project_dir) do
%{
elixir_lib: detect_elixir_lib(),
mob_dir: detect_mob_dir(project_dir)
}
end
# We're running inside Elixir — :code.lib_dir(:elixir) is always available.
defp detect_elixir_lib do
:code.lib_dir(:elixir) |> to_string() |> Path.dirname()
end
# Read the {:mob, path: "..."} dep from mix.exs and resolve it.
defp detect_mob_dir(project_dir) do
mix_exs = Path.join(project_dir, "mix.exs")
with {:ok, content} <- File.read(mix_exs),
[_, rel] <- Regex.run(~r/\{:mob,\s+path:\s+"([^"]+)"/, content) do
Path.expand(rel, project_dir)
else
_ -> nil
end
end
defp prompt_path(key, detected) do
label = prompt_label(key)
if detected do
input = Mix.shell().prompt(" #{label} [#{detected}]:") |> String.trim()
if input == "", do: detected, else: input
else
Mix.shell().prompt(" #{label}:") |> String.trim()
end
end
defp prompt_label(:mob_dir), do: "mob library path"
defp prompt_label(:elixir_lib), do: "Elixir lib path"
defp write_mob_exs(path, cfg) do
content = """
import Config
config :mob_dev,
mob_dir: #{inspect(cfg[:mob_dir])},
elixir_lib: #{inspect(cfg[:elixir_lib])}
"""
File.write!(path, content)
end
defp write_local_properties(project_dir, cfg) do
props = Path.join(project_dir, "android/local.properties")
if File.exists?(props) do
content = File.read!(props)
if String.contains?(content, "/path/to/") do
otp_dir = MobDev.OtpDownloader.android_otp_dir()
new_content =
content
|> replace_prop("mob.otp_release", otp_dir)
|> replace_prop("mob.mob_dir", cfg[:mob_dir])
File.write!(props, new_content)
Mix.shell().info([:green, "* android/local.properties configured", :reset])
end
end
end
defp replace_prop(content, key, value) when not is_nil(value) do
String.replace(content, ~r/^#{Regex.escape(key)}=.*$/m, "#{key}=#{value}")
end
defp replace_prop(content, _key, nil), do: content
# ── Icon generation ───────────────────────────────────────────────────────────
defp generate_icons(project_dir, nil) do
Mix.shell().info("Generating app icon (random robot)...")
MobDev.IconGenerator.generate_random(project_dir)
Mix.shell().info([:green, "* icons written", :reset])
end
defp generate_icons(project_dir, source) do
unless File.exists?(source) do
Mix.raise("Source file not found: #{source}")
end
Mix.shell().info("Generating app icon from #{source}...")
MobDev.IconGenerator.generate_from_source(source, project_dir)
Mix.shell().info([:green, "* icons written", :reset])
end
end