Packages
mob_dev
0.6.22
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.plugins.ex
defmodule Mix.Tasks.Mob.Plugins do
use Mix.Task
@shortdoc "List installed Mob plugins and their activation status"
@moduledoc """
Lists the Mob plugins this project depends on, with tier, hot-push status,
and whether each is activated.
mix mob.plugins
A dependency is shown as a plugin if it ships a `priv/mob_plugin.exs`
manifest, or if it is named in `config :mob, :plugins` in `mob.exs`.
Tier-0 plugins (pure Elixir, no manifest) are indistinguishable from
ordinary libraries until activated, so they appear only once listed in
`config :mob, :plugins`.
Activation is two-step by design (see `MOB_PLUGINS.md`): adding a plugin to
`deps` makes it *installed*; adding it to `config :mob, :plugins` makes it
*activated* — only then are its contributions merged into the build.
"""
alias MobDev.Plugin.{Manifest, Report, Validator}
@impl Mix.Task
def run(_args) do
Mix.Task.run("loadpaths")
deps = load_all_manifests()
activated = activated_plugins()
dep_dirs = Mix.Project.deps_paths()
deps
|> Report.rows(activated)
|> Report.with_vetting(dep_dirs)
|> Report.render()
|> then(&IO.puts("\n" <> &1 <> "\n"))
report_conflicts(deps, activated)
end
defp report_conflicts(deps, activated) do
activated_manifests = for {name, manifest} <- deps, name in activated, do: {name, manifest}
case Validator.cross_validate(activated_manifests) do
%{errors: []} ->
:ok
%{errors: errors} ->
Mix.shell().error("Plugin activation conflicts:")
Enum.each(errors, &Mix.shell().error(" ✗ #{&1}"))
IO.puts("")
end
end
defp load_all_manifests do
Mix.Project.deps_paths()
|> Enum.map(fn {app, path} ->
case Manifest.load(path) do
{:ok, manifest} ->
{app, manifest}
{:error, reason} ->
Mix.shell().info([:yellow, "[mob.plugins] skipping #{app}: #{reason}", :reset])
{app, nil}
end
end)
end
# Reads `config :mob, :plugins` from mob.exs (the build config). Falls back
# to the loaded Application env, then an empty list.
defp activated_plugins do
config_file = Path.join(File.cwd!(), "mob.exs")
raw =
if File.exists?(config_file) do
config_file
|> Config.Reader.read!()
|> Keyword.get(:mob, [])
|> Keyword.get(:plugins, [])
else
Application.get_env(:mob, :plugins, [])
end
normalize_activated(raw)
rescue
_ -> normalize_activated(Application.get_env(:mob, :plugins, []))
end
@doc false
# Pure kernel: coerces a `config :mob, :plugins` value into a clean list of
# atom plugin names. A misconfigured value (a non-list, or a list carrying
# non-atom entries like a stray string typo `"mob_haptic"`) must not crash
# `mix mob.plugins` — `name in activated` raises Protocol.UndefinedError on a
# non-list, and downstream `name in activated` silently mismatches string
# entries. Non-list → `[]`; lists are filtered down to their atom entries.
@spec normalize_activated(term()) :: [atom()]
def normalize_activated(plugins) when is_list(plugins),
do: Enum.filter(plugins, &is_atom/1)
def normalize_activated(_other), do: []
end