ci: add dialyzer mix task

This commit is contained in:
Thales Macedo Garitezi 2024-07-09 10:04:38 -03:00
parent f3c6d10f76
commit 21313c766d
2 changed files with 171 additions and 1 deletions

View File

@ -0,0 +1,164 @@
defmodule Mix.Tasks.Emqx.Dialyzer do
use Mix.Task
alias EMQXUmbrella.MixProject, as: UMP
if UMP.new_mix_build?() do
Code.require_file("emqx.ct.ex", __DIR__)
end
alias Mix.Tasks.Emqx.Ct, as: ECt
@requirements ["compile", "loadpaths"]
@excluded_mods (
[
:emqx_exproto_v_1_connection_unary_handler_bhvr,
:emqx_exproto_v_1_connection_handler_client,
:emqx_exproto_v_1_connection_handler_bhvr,
:emqx_exproto_v_1_connection_adapter_client,
:emqx_exproto_v_1_connection_adapter_bhvr,
:emqx_exproto_v_1_connection_unary_handler_client,
:emqx_exhook_v_2_hook_provider_client,
:emqx_exhook_v_2_hook_provider_bhvr,
Mix.Tasks.Compile.Grpc,
Mix.Tasks.Compile.CopySrcs,
]
|> MapSet.new(&to_string/1)
)
@impl true
def run(_args) do
ECt.add_to_path_and_cache(:dialyzer)
%{
umbrella_apps: umbrella_apps,
dep_apps: dep_apps
} = resolve_apps()
umbrella_files = Enum.flat_map(umbrella_apps, & resolve_files/1)
dep_files = Enum.flat_map(dep_apps, & resolve_files/1)
files =
(umbrella_files ++ dep_files)
|> Enum.reject(fn path ->
name = Path.basename(path, ".beam")
MapSet.member?(@excluded_mods, name)
end)
|> Enum.map(&to_charlist/1)
warning_files =
umbrella_files
|> Enum.reject(fn path ->
name = Path.basename(path, ".beam")
MapSet.member?(@excluded_mods, name)
end)
|> Enum.map(&to_charlist/1)
warning_apps = Enum.sort(umbrella_apps)
try do
:dialyzer.run(
analysis_type: :incremental,
warnings: [
:unmatched_returns,
:error_handling
],
# plt_location: ~c".",
# plt_prefix: ~c"emqx_dialyzer",
warning_files: warning_files,
warning_files_rec: warning_files,
# apps: umbrella_apps ++ dep_apps,
# warning_apps: warning_apps,
get_warnings: false,
files: files,
files_rec: files
)
catch
{:dialyzer_error, msg} ->
{:dialyzer_error, to_string(msg)}
err ->
{:throw, err}
end
|> IO.inspect(limit: :infinity)
end
defp resolve_apps() do
base_apps = MapSet.new([:erts, :crypto])
# excluded_apps = MapSet.new([:elixir])
excluded_apps = MapSet.new()
acc = %{
umbrella_apps: [],
dep_apps: base_apps
}
Mix.Dep.Umbrella.loaded()
|> Enum.reduce(acc, fn dep, acc ->
# IO.inspect(dep)
props = dep.opts[:app_properties]
optional_apps = Keyword.get(props, :optional_applications, [])
apps = Keyword.get(props, :applications, [])
included_apps = Keyword.get(props, :included_applications, [])
dep_apps = MapSet.new(optional_apps ++ apps ++ included_apps)
acc
|> Map.update!(:umbrella_apps, & [dep.app | &1])
|> Map.update!(:dep_apps, & MapSet.union(&1, dep_apps))
end)
|> then(fn acc ->
dep_apps =
acc.dep_apps
|> MapSet.difference(MapSet.new(acc.umbrella_apps))
|> MapSet.difference(excluded_apps)
|> Enum.reduce(MapSet.new(), &find_nested_apps/2)
|> MapSet.difference(excluded_apps)
|> Enum.filter(&app_present?/1)
%{acc | dep_apps: dep_apps}
end)
end
defp app_present?(app) do
match?({:ok, _}, ebin_dir(app))
end
defp find_nested_apps(app, seen) do
if MapSet.member?(seen, app) do
seen
else
seen = MapSet.put(seen, app)
apps = case :application.get_key(app, :applications) do
{:ok, apps} -> apps
:undefined -> []
end
included_apps = case :application.get_key(app, :included_applications) do
{:ok, apps} -> apps
:undefined -> []
end
optional_apps = case :application.get_key(app, :optional_applications) do
{:ok, apps} -> apps
:undefined -> []
end
Enum.reduce(apps ++ included_apps, seen, &find_nested_apps/2)
end
end
defp resolve_files(app) do
with {:ok, dir} <- ebin_dir(app) do
Mix.Utils.extract_files([dir], [:beam])
else
_ -> []
end
end
defp ebin_dir(app) do
with dir when is_list(dir) <- :code.lib_dir(app, :ebin),
dir = to_string(dir),
true <- File.dir?(dir) || {:error, :not_a_dir} do
{:ok, to_string(dir)}
else
error ->
Mix.shell().info(IO.ANSI.format([
[:yellow,
"Unknown application: #{app}; error: #{inspect(error)}",
"; if this is is an optional application, ignore."
],
]))
:error
end
end
end

View File

@ -1297,7 +1297,8 @@ defmodule EMQXUmbrella.MixProject do
[
ct: &do_ct/1,
eunit: &do_eunit/1,
proper: &do_proper/1
proper: &do_proper/1,
dialyzer: &do_dialyzer/1
]
end
@ -1326,6 +1327,11 @@ defmodule EMQXUmbrella.MixProject do
Mix.Task.run("emqx.proper", args)
end
defp do_dialyzer(args) do
Code.require_file("lib/mix/tasks/emqx.dialyzer.ex")
Mix.Task.run("emqx.dialyzer", args)
end
defp ensure_test_mix_env!() do
Mix.env()
|> to_string()