Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rebar3 dialyzer support: single-app success typing, project apps in PLT #2249

Merged
merged 1 commit into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions src/rebar_prv_dialyzer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ init(State) ->
{base_plt_location, undefined, "base-plt-location", string, "The location of base PLT file, defaults to $HOME/.cache/rebar3"},
{plt_location, undefined, "plt-location", string, "The location of the PLT file, defaults to the profile's base directory"},
{plt_prefix, undefined, "plt-prefix", string, "The prefix to the PLT file, defaults to \"rebar3\"" },
{app, $a, "app", string, "Perform success typing analysis of a single application"},
{base_plt_prefix, undefined, "base-plt-prefix", string, "The prefix to the base PLT file, defaults to \"rebar3\"" },
{statistics, undefined, "statistics", boolean, "Print information about the progress of execution. Default: false" }],
State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
Expand All @@ -51,7 +52,8 @@ desc() ->
"`get_warnings` - display warnings when altering a PLT file (boolean)\n"
"`plt_apps` - the strategy for determining the applications which included "
"in the PLT file, `top_level_deps` to include just the direct dependencies "
"or `all_deps` to include all nested dependencies*\n"
"or `all_deps` to include all nested dependencies "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would probably keep the first asterisk there too

"or `all_apps` to include all project apps and nested dependencies*\n"
"`plt_extra_apps` - a list of extra applications to include in the PLT "
"file\n"
"`plt_extra_mods` - a list of extra modules to includes in the PLT file\n"
Expand Down Expand Up @@ -210,20 +212,24 @@ proj_plt_files(State) ->
PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps,
BasePltMods = get_config(State, base_plt_mods, []),
PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods,
Apps = proj_apps(State),
DepApps = proj_deps(State),
get_files(State, DepApps ++ PltApps, Apps -- PltApps, PltMods, [], []).
DepApps = lists:usort(proj_plt_apps(State) ++ PltApps),
get_files(State, DepApps, [], PltMods, [], []).

proj_apps(State) ->
[ec_cnv:to_atom(rebar_app_info:name(App)) ||
App <- rebar_state:project_apps(State)].

proj_deps(State) ->
proj_plt_apps(State) ->
Apps = rebar_state:project_apps(State),
DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps),
ProjApps = proj_apps(State),
case get_config(State, plt_apps, top_level_deps) of
top_level_deps -> DepApps;
all_deps -> collect_nested_dependent_apps(DepApps)
top_level_deps ->
DepApps -- ProjApps;
all_deps ->
collect_nested_dependent_apps(DepApps) -- ProjApps;
all_apps ->
proj_apps(State) ++ collect_nested_dependent_apps(DepApps)
end.

get_files(State, Apps, SkipApps, Mods, SkipMods, ExtraDirs) ->
Expand Down Expand Up @@ -474,7 +480,7 @@ succ_typings(Args, State, Plt, Output) ->
{0, State};
_ ->
?INFO("Doing success typing analysis...", []),
Files = proj_files(State),
Files = proj_files(proplists:get_value(app, Args), State),
succ_typings_(State, Plt, Output, Files)
end.

Expand All @@ -490,8 +496,19 @@ succ_typings_(State, Plt, Output, Files) ->
{init_plt, Plt}],
run_dialyzer(State, Opts, Output).

proj_files(State) ->
Apps = proj_apps(State),
succ_typing_apps(undefined, ProjApps) ->
ProjApps;
succ_typing_apps(App, ProjApps) ->
try
true = lists:member(ec_cnv:to_atom(App), ProjApps),
[ec_cnv:to_atom(App)]
catch
error:_ ->
throw({unknown_application, App})
end.

proj_files(SingleApp, State) ->
Apps = succ_typing_apps(SingleApp, proj_apps(State)),
BasePltApps = get_config(State, base_plt_apps, []),
PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps,
BasePltMods = get_config(State, base_plt_mods, []),
Expand Down
50 changes: 50 additions & 0 deletions test/rebar_dialyzer_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
plt_apps_option/1,
exclude_and_extra/1,
cli_args/1,
single_app_succ_typing/1,
extra_src_dirs/1]).

-include_lib("common_test/include/ct.hrl").
Expand Down Expand Up @@ -61,6 +62,7 @@ all() ->
groups() ->
[{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]},
{build_and_check, [cli_args,
single_app_succ_typing,
build_release_plt,
plt_apps_option,
exclude_and_extra,
Expand Down Expand Up @@ -352,6 +354,54 @@ cli_args(Config) ->
{ok, PltFiles} = plt_files(Plt),
?assertEqual(ErtsFiles, PltFiles).

single_app_succ_typing(Config) ->
AppDir = ?config(apps, Config),
State = ?config(state, Config),
Plt = ?config(plt, Config),
RebarConfig = ?config(rebar_config, Config),
%% test workflow:
%% (a) build PLT containing all project apps and dependencies (no success typing yet)!
%% (b) perform success-typing for all apps (warnings expected)
%% (c) perform success-typing for app with and without warnings
Name = rebar_test_utils:create_random_name("app1_"),
Vsn = rebar_test_utils:create_random_vsn(),
%% contains warnings in tests
rebar_test_utils:create_eunit_app(AppDir, Name, Vsn, []),
%% second app, depending on first, should not produce warnings
App1 = ec_cnv:to_atom(Name),
%%% second app depends on first
Name2 = rebar_test_utils:create_random_name("app2_"),
Vsn2 = rebar_test_utils:create_random_vsn(),
rebar_test_utils:create_app(filename:join([AppDir,"apps",Name2]), Name2, Vsn2,
[App1]), % App2 depends on App1
App2 = ec_cnv:to_atom(Name2),

%% start with building all apps into PLT
RebarConfig2 = merge_config([{dialyzer, [{plt_apps, all_apps}]}],
RebarConfig),
{ok, _} =
rebar3:run(rebar_state:new(State, RebarConfig2, AppDir), ["dialyzer", "--succ-typings=false"]),
%% verify all project apps are in PLT
{ok, PltFiles} = plt_files(Plt),
?assertEqual([App1, App2, erts], get_apps_from_beam_files(PltFiles)),

%% warnings when apps are not specified
Command0 = ["as", "test", "dialyzer"],
% there are few warnings for generated test (see rebar_test_utils:erl_eunit_suite_file/1)
{error, {rebar_prv_dialyzer, {dialyzer_warnings, _}}} =
rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command0),

%% warnings from App
Command1 = ["as", "test", "dialyzer", "--app=" ++ Name],
% there are few warnings for generated test (see rebar_test_utils:erl_eunit_suite_file/1)
{error, {rebar_prv_dialyzer, {dialyzer_warnings, _}}} =
rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command1),

%% no warnings from App2
Command2 = ["as", "test", "dialyzer", "--app=" ++ Name2],
{ok, _} =
rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command2).

extra_src_dirs(Config) ->
AppDir = ?config(apps, Config),
State = ?config(state, Config),
Expand Down