Browse Source

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

This patch enables two features:
 * in rebar.config, {diazlyer, [{plt_apps, all_apps}]} makes rebar3 to
   add all project files into PLT for subsequent analysis
 * rebar3 ct --app=myapp allows single application success typing

This allows workflow suitable for large codebases:
 * "rebar3 ct dialyzer --succ-typings=false" to build PLT with all apps
 * "rebar3 ct dialyzer --app=myapp" to perform success typing of myapp
pull/2249/head
Maxim Fedorov 5 years ago
parent
commit
f371d801e5
2 changed files with 77 additions and 10 deletions
  1. +27
    -10
      src/rebar_prv_dialyzer.erl
  2. +50
    -0
      test/rebar_dialyzer_SUITE.erl

+ 27
- 10
src/rebar_prv_dialyzer.erl View File

@ -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},
@ -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 "
"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"
@ -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) ->
@ -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.
@ -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, []),

+ 50
- 0
test/rebar_dialyzer_SUITE.erl View File

@ -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").
@ -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,
@ -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),

Loading…
Cancel
Save