summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rebar_prv_dialyzer.erl162
-rw-r--r--test/rebar_dialyzer_SUITE.erl38
2 files changed, 141 insertions, 59 deletions
diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl
index fc13de1..44dc0d2 100644
--- a/src/rebar_prv_dialyzer.erl
+++ b/src/rebar_prv_dialyzer.erl
@@ -47,26 +47,33 @@ desc() ->
"`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"
- "`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 "
"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 "
- "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 "
- "$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 "
- "\"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"
"For example, to warn on unmatched returns: \n"
"{dialyzer, [{warnings, [unmatched_returns]}]}.\n"
"\n"
"*The direct dependent applications are listed in `applications` and "
"`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 "
"stored in `base_plt_location`. A base PLT is used to build project PLTs."
"\n".
@@ -90,6 +97,10 @@ do(State) ->
?PRV_ERROR({dialyzer_warnings, Warnings});
throw:{unknown_application, _} = Error ->
?PRV_ERROR(Error);
+ throw:{unknown_module, _} = Error ->
+ ?PRV_ERROR(Error);
+ throw:{duplicate_module, _, _, _} = Error ->
+ ?PRV_ERROR(Error);
throw:{output_file_error, _, _} = Error ->
?PRV_ERROR(Error)
after
@@ -110,6 +121,10 @@ format_error({dialyzer_warnings, Warnings}) ->
io_lib:format("Warnings occured running dialyzer: ~b", [Warnings]);
format_error({unknown_application, 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}) ->
Error1 = file:format_error(Error),
io_lib:format("Failed to write to ~s: ~s", [File, Error1]);
@@ -178,45 +193,45 @@ do_update_proj_plt(State, Plt, Output) ->
end.
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),
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...", []),
- 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;
-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 ->
- get_plt_files(DepApps, Apps, PltApps, Files);
+ apps_files(DepApps, SkipApps, Files);
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.
app_files(AppName) ->
@@ -244,9 +259,41 @@ check_ebin(EbinDir) ->
end.
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) ->
Vsn = dialyzer_version(),
@@ -355,9 +402,12 @@ get_base_plt(State) ->
end.
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) ->
case read_plt(State, BasePlt) of
@@ -394,9 +444,8 @@ succ_typings(State, Plt, Output) ->
false ->
{0, State};
_ ->
- Apps = rebar_state:project_apps(State),
?INFO("Doing success typing analysis...", []),
- Files = apps_to_files(Apps),
+ Files = proj_files(State),
succ_typings(State, Plt, Output, Files)
end.
@@ -412,14 +461,13 @@ succ_typings(State, Plt, Output, Files) ->
{init_plt, Plt}],
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) ->
%% dialyzer may return callgraph warnings when get_warnings is false
diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl
index e5d8c52..d0a3611 100644
--- a/test/rebar_dialyzer_SUITE.erl
+++ b/test/rebar_dialyzer_SUITE.erl
@@ -14,7 +14,8 @@
update_base_plt/1,
update_app_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("eunit/include/eunit.hrl").
@@ -57,7 +58,7 @@ all() ->
groups() ->
[{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]}].
empty_base_plt(Config) ->
@@ -275,6 +276,39 @@ plt_apps_option(Config) ->
{ok, PltFiles2} = plt_files(Plt),
?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
erts_files() ->