Quellcode durchsuchen

Add exclude_apps/mods, plt_extra_mods, base_plt_mods config

* exclude_apps - never use applications for PLT/analysis
* base_plt_mods - add modules to base PLT (overrules exclude_apps)
* plt_extra_mods - add modules to PLT (overrules exclude_apps)
* exclude_mods - never use modules for PLT/analysis (overrules all)
pull/1370/head
James Fish vor 8 Jahren
Ursprung
Commit
bcfd8d6f80
2 geänderte Dateien mit 141 neuen und 59 gelöschten Zeilen
  1. +105
    -57
      src/rebar_prv_dialyzer.erl
  2. +36
    -2
      test/rebar_dialyzer_SUITE.erl

+ 105
- 57
src/rebar_prv_dialyzer.erl Datei anzeigen

@ -47,26 +47,33 @@ desc() ->
"`plt_apps` - the strategy for determining the applications which included " "`plt_apps` - the strategy for determining the applications which included "
"in the PLT file, `top_level_deps` to include just the direct dependencies " "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*\n"
"`plt_extra_apps` - a list of applications to include in the PLT file**\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"
"`plt_location` - the location of the PLT file, `local` to store in the " "`plt_location` - the location of the PLT file, `local` to store in the "
"profile's base directory (default) or a custom directory.\n" "profile's base directory (default) or a custom directory.\n"
"`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"***\n"
"`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"**\n"
"`base_plt_apps` - a list of applications to include in the base " "`base_plt_apps` - a list of applications to include in the base "
"PLT file****\n"
"PLT file***\n"
"`base_plt_mods` - a list of modules to include in the base "
"PLT file***\n"
"`base_plt_location` - the location of base PLT file, `global` to store in " "`base_plt_location` - the location of base PLT file, `global` to store in "
"$HOME/.cache/rebar3 (default) or a custom directory****\n"
"$HOME/.cache/rebar3 (default) or a custom directory***\n"
"`base_plt_prefix` - the prefix to the base PLT file, defaults to " "`base_plt_prefix` - the prefix to the base PLT file, defaults to "
"\"rebar3\"*** ****\n"
"\"rebar3\"** ***\n"
"`exclude_apps` - a list of applications to exclude from PLT files and "
"success typing analysis, `plt_extra_mods` and `base_plt_mods` can add "
"modules from excluded applications\n"
"`exclude_mods` - a list of modules to exclude from PLT files and "
"success typing analysis\n"
"\n" "\n"
"For example, to warn on unmatched returns: \n" "For example, to warn on unmatched returns: \n"
"{dialyzer, [{warnings, [unmatched_returns]}]}.\n" "{dialyzer, [{warnings, [unmatched_returns]}]}.\n"
"\n" "\n"
"*The direct dependent applications are listed in `applications` and " "*The direct dependent applications are listed in `applications` and "
"`included_applications` of their .app files.\n" "`included_applications` of their .app files.\n"
"**The applications in `base_plt_apps` will be added to the "
"list. \n"
"***PLT files are named \"<prefix>_<otp_release>_plt\".\n"
"****The base PLT is a PLT containing the core applications often required "
"**PLT files are named \"<prefix>_<otp_release>_plt\".\n"
"***The base PLT is a PLT containing the core applications often required "
"for a project's PLT. One base PLT is created per OTP version and " "for a project's PLT. One base PLT is created per OTP version and "
"stored in `base_plt_location`. A base PLT is used to build project PLTs." "stored in `base_plt_location`. A base PLT is used to build project PLTs."
"\n". "\n".
@ -90,6 +97,10 @@ do(State) ->
?PRV_ERROR({dialyzer_warnings, Warnings}); ?PRV_ERROR({dialyzer_warnings, Warnings});
throw:{unknown_application, _} = Error -> throw:{unknown_application, _} = Error ->
?PRV_ERROR(Error); ?PRV_ERROR(Error);
throw:{unknown_module, _} = Error ->
?PRV_ERROR(Error);
throw:{duplicate_module, _, _, _} = Error ->
?PRV_ERROR(Error);
throw:{output_file_error, _, _} = Error -> throw:{output_file_error, _, _} = Error ->
?PRV_ERROR(Error) ?PRV_ERROR(Error)
after after
@ -110,6 +121,10 @@ format_error({dialyzer_warnings, Warnings}) ->
io_lib:format("Warnings occured running dialyzer: ~b", [Warnings]); io_lib:format("Warnings occured running dialyzer: ~b", [Warnings]);
format_error({unknown_application, App}) -> format_error({unknown_application, App}) ->
io_lib:format("Could not find application: ~s", [App]); io_lib:format("Could not find application: ~s", [App]);
format_error({unknown_module, Mod}) ->
io_lib:format("Could not find module: ~s", [Mod]);
format_error({duplicate_module, Mod, File1, File2}) ->
io_lib:format("Duplicates of module ~s: ~s ~s", [Mod, File1, File2]);
format_error({output_file_error, File, Error}) -> format_error({output_file_error, File, Error}) ->
Error1 = file:format_error(Error), Error1 = file:format_error(Error),
io_lib:format("Failed to write to ~s: ~s", [File, Error1]); io_lib:format("Failed to write to ~s: ~s", [File, Error1]);
@ -178,45 +193,45 @@ do_update_proj_plt(State, Plt, Output) ->
end. end.
proj_plt_files(State) -> proj_plt_files(State) ->
BasePltApps = get_config(State, base_plt_apps, default_plt_apps()),
PltApps = get_config(State, plt_extra_apps, []),
BasePltApps = base_plt_apps(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, []).
proj_apps(State) ->
[ec_cnv:to_atom(rebar_app_info:name(App)) ||
App <- rebar_state:project_apps(State)].
proj_deps(State) ->
Apps = rebar_state:project_apps(State), Apps = rebar_state:project_apps(State),
DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps),
DepApps1 =
case get_config(State, plt_apps, top_level_deps) of
top_level_deps -> DepApps;
all_deps -> collect_nested_dependent_apps(DepApps)
end,
get_plt_files(BasePltApps ++ PltApps ++ DepApps1, Apps).
default_plt_apps() ->
[erts,
crypto,
kernel,
stdlib].
get_plt_files(DepApps, Apps) ->
case get_config(State, plt_apps, top_level_deps) of
top_level_deps -> DepApps;
all_deps -> collect_nested_dependent_apps(DepApps)
end.
get_files(State, Apps, SkipApps, Mods, SkipMods) ->
?INFO("Resolving files...", []), ?INFO("Resolving files...", []),
get_plt_files(DepApps, Apps, [], []).
ExcludeApps = get_config(State, exclude_apps, []),
Files = apps_files(Apps, ExcludeApps ++ SkipApps, dict:new()),
ExcludeMods = get_config(State, exclude_mods, []),
Files2 = mods_files(Mods, ExcludeMods ++ SkipMods, Files),
dict:fold(fun(_, File, Acc) -> [File | Acc] end, [], Files2).
get_plt_files([], _, _, Files) ->
apps_files([], _, Files) ->
Files; Files;
get_plt_files([AppName | DepApps], Apps, PltApps, Files) ->
case lists:member(AppName, PltApps) orelse app_member(AppName, Apps) of
apps_files([AppName | DepApps], SkipApps, Files) ->
case lists:member(AppName, SkipApps) of
true -> true ->
get_plt_files(DepApps, Apps, PltApps, Files);
apps_files(DepApps, SkipApps, Files);
false -> false ->
Files2 = app_files(AppName),
?DEBUG("~s files: ~p", [AppName, Files2]),
get_plt_files(DepApps, Apps, [AppName | PltApps], Files2 ++ Files)
end.
app_member(AppName, Apps) ->
case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of
{ok, _App} ->
true;
error ->
false
AppFiles = app_files(AppName),
?DEBUG("~s modules: ~p", [AppName, dict:fetch_keys(AppFiles)]),
Files2 = merge_files(Files, AppFiles),
apps_files(DepApps, [AppName | SkipApps], Files2)
end. end.
app_files(AppName) -> app_files(AppName) ->
@ -244,9 +259,41 @@ check_ebin(EbinDir) ->
end. end.
ebin_files(EbinDir) -> ebin_files(EbinDir) ->
Wildcard = "*" ++ code:objfile_extension(),
[filename:join(EbinDir, File) ||
File <- filelib:wildcard(Wildcard, EbinDir)].
Ext = code:objfile_extension(),
Wildcard = "*" ++ Ext,
Files = filelib:wildcard(Wildcard, EbinDir),
Store = fun(File, Mods) ->
Mod = list_to_atom(filename:basename(File, Ext)),
Absname = filename:join(EbinDir, File),
dict:store(Mod, Absname, Mods)
end,
lists:foldl(Store, dict:new(), Files).
merge_files(Files1, Files2) ->
Duplicate = fun(Mod, File1, File2) ->
throw({duplicate_module, Mod, File1, File2})
end,
dict:merge(Duplicate, Files1, Files2).
mods_files(Mods, SkipMods, Files) ->
Keep = fun(File) -> File end,
Ensure = fun(Mod, Acc) ->
case lists:member(Mod, SkipMods) of
true ->
Acc;
false ->
dict:update(Mod, Keep, mod_file(Mod), Acc)
end
end,
Files2 = lists:foldl(Ensure, Files, Mods),
lists:foldl(fun dict:erase/2, Files2, SkipMods).
mod_file(Mod) ->
File = atom_to_list(Mod) ++ code:objfile_extension(),
case code:where_is_file(File) of
non_existing -> throw({unknown_module, Mod});
Absname -> Absname
end.
read_plt(_State, Plt) -> read_plt(_State, Plt) ->
Vsn = dialyzer_version(), Vsn = dialyzer_version(),
@ -355,9 +402,12 @@ get_base_plt(State) ->
end. end.
base_plt_files(State) -> base_plt_files(State) ->
BasePltApps = get_config(State, base_plt_apps, default_plt_apps()),
Apps = rebar_state:project_apps(State),
get_plt_files(BasePltApps, Apps).
BasePltApps = base_plt_apps(State),
BasePltMods = get_config(State, base_plt_mods, []),
get_files(State, BasePltApps, [], BasePltMods, []).
base_plt_apps(State) ->
get_config(State, base_plt_apps, [erts, crypto, kernel, stdlib]).
update_base_plt(State, BasePlt, Output, BaseFiles) -> update_base_plt(State, BasePlt, Output, BaseFiles) ->
case read_plt(State, BasePlt) of case read_plt(State, BasePlt) of
@ -394,9 +444,8 @@ succ_typings(State, Plt, Output) ->
false -> false ->
{0, State}; {0, State};
_ -> _ ->
Apps = rebar_state:project_apps(State),
?INFO("Doing success typing analysis...", []), ?INFO("Doing success typing analysis...", []),
Files = apps_to_files(Apps),
Files = proj_files(State),
succ_typings(State, Plt, Output, Files) succ_typings(State, Plt, Output, Files)
end. end.
@ -412,14 +461,13 @@ succ_typings(State, Plt, Output, Files) ->
{init_plt, Plt}], {init_plt, Plt}],
run_dialyzer(State, Opts, Output). run_dialyzer(State, Opts, Output).
apps_to_files(Apps) ->
?INFO("Resolving files...", []),
[File || App <- Apps,
File <- app_to_files(App)].
app_to_files(App) ->
AppName = ec_cnv:to_atom(rebar_app_info:name(App)),
app_files(AppName).
proj_files(State) ->
Apps = 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, []),
PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods,
get_files(State, Apps, PltApps, [], PltMods).
run_dialyzer(State, Opts, Output) -> run_dialyzer(State, Opts, Output) ->
%% dialyzer may return callgraph warnings when get_warnings is false %% dialyzer may return callgraph warnings when get_warnings is false

+ 36
- 2
test/rebar_dialyzer_SUITE.erl Datei anzeigen

@ -14,7 +14,8 @@
update_base_plt/1, update_base_plt/1,
update_app_plt/1, update_app_plt/1,
build_release_plt/1, build_release_plt/1,
plt_apps_option/1]).
plt_apps_option/1,
exclude_and_extra/1]).
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -57,7 +58,7 @@ all() ->
groups() -> groups() ->
[{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]}, [{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]},
{build_and_check, [build_release_plt, plt_apps_option]},
{build_and_check, [build_release_plt, plt_apps_option, exclude_and_extra]},
{update, [update_base_plt, update_app_plt]}]. {update, [update_base_plt, update_app_plt]}].
empty_base_plt(Config) -> empty_base_plt(Config) ->
@ -275,6 +276,39 @@ plt_apps_option(Config) ->
{ok, PltFiles2} = plt_files(Plt), {ok, PltFiles2} = plt_files(Plt),
?assertEqual([App1, App2, erts], get_apps_from_beam_files(PltFiles2)). ?assertEqual([App1, App2, erts], get_apps_from_beam_files(PltFiles2)).
exclude_and_extra(Config) ->
AppDir = ?config(apps, Config),
RebarConfig = ?config(rebar_config, Config),
BasePlt = ?config(base_plt, Config),
Plt = ?config(plt, Config),
{value, {dialyzer, Opts}, Rest} = lists:keytake(dialyzer, 1, RebarConfig),
% Remove erts => []
% Add erlang+zlib => [erlang, zlib],
% Add erl_prim_loader+init => [erl_prim_loader, init, erlang, zlib]
% Remove zlib+init => [erl_prim_loader, erlang]
Opts2 = [{exclude_apps, [erts]},
{base_plt_mods, [erlang, zlib]},
{plt_extra_mods, [erl_prim_loader, init]},
{exclude_mods, [zlib, init]} |
Opts],
RebarConfig2 = [{dialyzer, Opts2} | Rest],
Name = rebar_test_utils:create_random_name("app1_"),
Vsn = rebar_test_utils:create_random_vsn(),
rebar_test_utils:create_app(AppDir, Name, Vsn, [erts]),
rebar_test_utils:run_and_check(Config, RebarConfig2, ["dialyzer"],
{ok, [{app, Name}]}),
Erlang = code:where_is_file("erlang.beam"),
{ok, BasePltFiles} = plt_files(BasePlt),
?assertEqual([Erlang], BasePltFiles),
Pair = lists:sort([Erlang, code:where_is_file("erl_prim_loader.beam")]),
{ok, PltFiles} = plt_files(Plt),
?assertEqual(Pair, PltFiles).
%% Helpers %% Helpers
erts_files() -> erts_files() ->

Laden…
Abbrechen
Speichern