emqx/scripts/check-elixir-applications.exs

209 lines
5.7 KiB
Elixir
Executable File

#!/usr/bin/env elixir
defmodule CheckElixirApplications do
alias EMQXUmbrella.MixProject
@default_applications [:kernel, :stdlib, :sasl]
def main() do
{:ok, _} = Application.ensure_all_started(:mix)
File.cwd!()
|> Path.join("mix.exs")
|> Code.compile_file()
inputs = MixProject.check_profile!()
profile = Mix.env()
# produce `rebar.config.rendered` to consult
File.cwd!()
|> Path.join("rebar3")
|> System.cmd(["as", to_string(profile)],
env: [{"DEBUG", "1"}]
)
mix_apps = mix_applications(inputs.release_type, inputs.edition_type)
rebar_apps = rebar_applications(profile)
results = diff_apps(mix_apps, rebar_apps)
report_discrepancy(
results[:missing_apps],
"* There are missing applications in the Elixir release",
fn %{app: app, mode: mode, after: last_app} ->
IO.puts(" * #{app}: #{inspect(mode)} should be placed after #{inspect(last_app)}")
end
)
report_discrepancy(
results[:different_modes],
"* There are applications with different application modes in the Elixir release",
fn %{app: app, rebar_mode: rebar_mode, mix_mode: mix_mode} ->
IO.puts(
" * #{inspect(app)} should have mode #{inspect(rebar_mode)}, but it has mode #{inspect(mix_mode)}"
)
end
)
report_discrepancy(
results[:different_positions],
"* There are applications in the Elixir release in the wrong order",
fn %{app: app, mode: mode, after: last_app} ->
IO.puts(" * #{app}: #{inspect(mode)} should be placed after #{inspect(last_app)}")
end
)
success? =
results
|> Map.take([:missing_apps, :different_modes, :different_positions])
|> Map.values()
|> Enum.concat()
|> Enum.empty?()
if not success? do
System.halt(1)
else
IO.puts(
IO.ANSI.green() <>
"Mix and Rebar applications OK!" <>
IO.ANSI.reset()
)
end
end
defp mix_applications(release_type, edition_type) do
EMQXUmbrella.MixProject.applications(release_type, edition_type)
end
defp rebar_applications(profile) do
{:ok, props} =
File.cwd!()
|> Path.join("rebar.config.rendered")
|> :file.consult()
props[:profiles][profile][:relx]
|> Enum.find(&(elem(&1, 0) == :release))
|> elem(2)
|> Enum.map(fn
app when is_atom(app) ->
{app, :permanent}
{app, mode} ->
{app, mode}
end)
|> Enum.reject(fn {app, _mode} ->
# Elixir already includes those implicitly
app in @default_applications
end)
end
defp diff_apps(mix_apps, rebar_apps) do
app_names = Keyword.keys(rebar_apps)
mix_apps = Keyword.filter(mix_apps, fn {app, _mode} -> app in app_names end)
acc = %{
mix_apps: mix_apps,
missing_apps: [],
different_positions: [],
different_modes: [],
last_app: nil
}
Enum.reduce(
rebar_apps,
acc,
fn
{rebar_app, rebar_mode}, acc = %{mix_apps: [], last_app: last_app} ->
missing_app = %{
app: rebar_app,
mode: rebar_mode,
after: last_app
}
acc
|> Map.update!(:missing_apps, &[missing_app | &1])
|> Map.put(:last_app, rebar_app)
{rebar_app, rebar_mode},
acc = %{mix_apps: [{mix_app, mix_mode} | rest], last_app: last_app} ->
case {rebar_app, rebar_mode} do
{^mix_app, ^mix_mode} ->
acc
|> Map.put(:mix_apps, rest)
|> Map.put(:last_app, rebar_app)
{^mix_app, _mode} ->
different_mode = %{
app: rebar_app,
rebar_mode: rebar_mode,
mix_mode: mix_mode
}
acc
|> Map.put(:mix_apps, rest)
|> Map.update!(:different_modes, &[different_mode | &1])
|> Map.put(:last_app, rebar_app)
{_app, _mode} ->
case Keyword.pop(rest, rebar_app) do
{nil, _} ->
missing_app = %{
app: rebar_app,
mode: rebar_mode,
after: last_app
}
acc
|> Map.update!(:missing_apps, &[missing_app | &1])
|> Map.put(:last_app, rebar_app)
{^rebar_mode, rest} ->
different_position = %{
app: rebar_app,
mode: rebar_mode,
after: last_app
}
acc
|> Map.update!(:different_positions, &[different_position | &1])
|> Map.put(:last_app, rebar_app)
|> Map.put(:mix_apps, [{mix_app, mix_mode} | rest])
{mode, rest} ->
different_mode = %{
app: rebar_app,
rebar_mode: rebar_mode,
mix_mode: mode
}
different_position = %{
app: rebar_app,
mode: rebar_mode,
after: last_app
}
acc
|> Map.put(:mix_apps, [{mix_app, mix_mode} | rest])
|> Map.update!(:different_modes, &[different_mode | &1])
|> Map.update!(:different_positions, &[different_position | &1])
|> Map.put(:last_app, rebar_app)
end
end
end
)
end
defp report_discrepancy(diffs, header, line_fn) do
unless Enum.empty?(diffs) do
IO.puts(IO.ANSI.red() <> header)
diffs
|> Enum.reverse()
|> Enum.each(line_fn)
IO.puts(IO.ANSI.reset())
end
end
end
CheckElixirApplications.main()