summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Fish <james@fishcakez.com>2014-11-19 18:14:51 +0000
committerJames Fish <james@fishcakez.com>2014-11-20 16:05:03 +0000
commit62e7cc27da361f6c6f63bef1e6588235e1e19609 (patch)
tree1db9f518b704ba5461d6514d398045909177c207
parent03b07c3d396d577c3a6d635c339900ed4d8c391c (diff)
Refactor dialyzer PLT use
* Only include `plt_apps` (defaults to deps) in a single PLT * Update PLT when `plt_apps` (or deps) are changed or updated * Print warnings for all dialyzer runs (not just `succ_typings`)
-rw-r--r--src/rebar_prv_dialyzer.erl209
1 files changed, 164 insertions, 45 deletions
diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl
index 75bc11e..7bbaf81 100644
--- a/src/rebar_prv_dialyzer.erl
+++ b/src/rebar_prv_dialyzer.erl
@@ -33,34 +33,15 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
?INFO("Dialyzer starting, this may take a while...", []),
- BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR),
- {ProjectPlt, DepPlt} = get_plt_location(BuildDir),
- Apps = rebar_state:project_apps(State),
+ State1 = set_plt_location(State),
+ Apps = rebar_state:project_apps(State1),
Deps = rebar_state:get(State, all_deps, []),
try
- ?INFO("Doing plt for dependencies...", []),
- update_dep_plt(State, DepPlt, Deps),
- ?INFO("Doing plt for project apps...", []),
- update_dep_plt(State, ProjectPlt, Apps),
- WarningTypes = rebar_state:get(State, dialyzer_warnings, default_warnings()),
- Paths = [filename:join(rebar_app_info:dir(App), "ebin") || App <- Apps],
- Opts = [{analysis_type, succ_typings},
- {from, byte_code},
- {files_rec, Paths},
- {warnings, WarningTypes},
- {plts, [ProjectPlt, DepPlt]}],
-
- case dialyzer:run(Opts) of
- [] ->
- {ok, State};
- Warnings ->
- [?CONSOLE(string:strip(dialyzer:format_warning(Warning), right, $\n), []) ||
- Warning <- Warnings],
- {ok, State}
- end
+ {ok, State2} = update_plt(State1, Apps, Deps),
+ succ_typings(State2, Apps)
catch
- _:{dialyzer_error, Error} ->
+ throw:{dialyzer_error, Error} ->
{error, {?MODULE, {error_processing_apps, Error, Apps}}}
end.
@@ -72,27 +53,165 @@ format_error(Reason) ->
%% Internal functions
-get_plt_location(BuildDir) ->
- {filename:join([BuildDir, ".project.plt"]),
- filename:join([BuildDir, ".deps.plt"])}.
-
-update_dep_plt(_State, DepPlt, AppList) ->
- Opts0 =
- case filelib:is_file(DepPlt) of
- true ->
- ?INFO("Plt is built, checking/updating...", []),
- [{analysis_type, plt_check},
- {plts, [DepPlt]}];
- false ->
- ?INFO("Building the plt, this will take a while...", []),
- [{analysis_type, plt_build},
- {output_plt, DepPlt}]
- end,
- Paths = [filename:join(rebar_app_info:dir(App), "ebin") || App <- AppList],
- Opts = [{files_rec, Paths},
- {from, byte_code}] ++ Opts0,
-
- dialyzer:run(Opts).
+set_plt_location(State) ->
+ BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR),
+ DefaultPlt = filename:join([BuildDir, ".deps.plt"]),
+ Plt = rebar_state:get(State, plt, DefaultPlt),
+ rebar_state:set(State, plt, Plt).
+
+update_plt(State, Apps, Deps) ->
+ ?INFO("Updating plt...", []),
+ Files = get_plt_files(State, Apps, Deps),
+ case read_plt(State) of
+ {ok, OldFiles} ->
+ check_plt(State, OldFiles, Files);
+ {error, no_such_file} ->
+ build_plt(State, Files)
+ end.
+
+get_plt_files(State, Apps, Deps) ->
+ case rebar_state:get(State, plt_apps) of
+ undefined ->
+ apps_to_files(Deps);
+ PltApps ->
+ app_names_to_files(PltApps, Apps ++ Deps)
+ end.
+
+apps_to_files(Apps) ->
+ lists:flatmap(fun app_to_files/1, Apps).
+
+app_to_files(App) ->
+ AppDetails = rebar_app_info:app_details(App),
+ Modules = proplists:get_value(modules, AppDetails, []),
+ EbinDir = rebar_app_info:ebin_dir(App),
+ modules_to_files(Modules, EbinDir).
+
+modules_to_files(Modules, EbinDir) ->
+ Ext = code:objfile_extension(),
+ Mod2File = fun(Module) -> module_to_file(Module, EbinDir, Ext) end,
+ rebar_utils:filtermap(Mod2File, Modules).
+
+module_to_file(Module, EbinDir, Ext) ->
+ File = filename:join(EbinDir, atom_to_list(Module) ++ Ext),
+ case filelib:is_file(File) of
+ true ->
+ {true, File};
+ false ->
+ ?CONSOLE("Unknown module ~s", [Module]),
+ false
+ end.
+
+app_names_to_files(AppNames, Apps) ->
+ lists:flatmap(fun(AppName) -> app_name_to_files(AppName, Apps) end,
+ AppNames).
+
+app_name_to_files(AppName, Apps) ->
+ case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of
+ {ok, App} ->
+ app_to_files(App);
+ error ->
+ app_name_to_files(AppName)
+ end.
+
+app_name_to_files(AppName) ->
+ case code:lib_dir(AppName) of
+ {error, _} ->
+ ?CONSOLE("Unknown application ~s", [AppName]),
+ [];
+ AppDir ->
+ app_dir_to_files(AppDir, AppName)
+ end.
+
+app_dir_to_files(AppDir, AppName) ->
+ EbinDir = filename:join(AppDir, "ebin"),
+ AppFile = filename:join(EbinDir, atom_to_list(AppName) ++ ".app"),
+ case file:consult(AppFile) of
+ {ok, [{application, AppName, AppDetails}]} ->
+ Modules = proplists:get_value(modules, AppDetails, []),
+ modules_to_files(Modules, EbinDir);
+ _ ->
+ Error = io_lib:format("Could not parse ~p", [AppFile]),
+ throw({dialyzer_error, Error})
+ end.
+
+read_plt(State) ->
+ Plt = rebar_state:get(State, plt),
+ case dialyzer:plt_info(Plt) of
+ {ok, Info} ->
+ Files = proplists:get_value(files, Info, []),
+ {ok, Files};
+ {error, no_such_file} = Error ->
+ Error;
+ {error, read_error} ->
+ Error = io_lib:format("Could not read the PLT file ~p", [Plt]),
+ throw({dialyzer_error, Error})
+ end.
+
+check_plt(State, OldList, FilesList) ->
+ Old = sets:from_list(OldList),
+ Files = sets:from_list(FilesList),
+ Remove = sets:subtract(Old, Files),
+ {ok, State1} = remove_plt(State, sets:to_list(Remove)),
+ Check = sets:intersection(Files, Old),
+ {ok, State2} = check_plt(State1, sets:to_list(Check)),
+ Add = sets:subtract(Files, Old),
+ add_plt(State2, sets:to_list(Add)).
+
+remove_plt(State, []) ->
+ {ok, State};
+remove_plt(State, Files) ->
+ ?INFO("Removing ~b files from plt...", [length(Files)]),
+ run_plt(State, plt_remove, Files).
+
+check_plt(State, []) ->
+ {ok, State};
+check_plt(State, Files) ->
+ ?INFO("Checking ~b files in plt...", [length(Files)]),
+ run_plt(State, plt_check, Files).
+
+add_plt(State, []) ->
+ {ok, State};
+add_plt(State, Files) ->
+ ?INFO("Adding ~b files to plt...", [length(Files)]),
+ run_plt(State, plt_add, Files).
+
+run_plt(State, Analysis, Files) ->
+ Plt = rebar_state:get(State, plt),
+ Opts = [{analysis_type, Analysis},
+ {init_plt, Plt},
+ {from, byte_code},
+ {files, Files}],
+ run_dialyzer(State, Opts).
+
+build_plt(State, Files) ->
+ Plt = rebar_state:get(State, plt),
+ ?INFO("Building PLT with ~b files...", [length(Files)]),
+ Opts = [{analysis_type, plt_build},
+ {output_plt, Plt},
+ {files, Files}],
+ run_dialyzer(State, Opts).
+
+succ_typings(State, Apps) ->
+ ?INFO("Doing success typing analysis...", []),
+ Files = apps_to_files(Apps),
+ Plt = rebar_state:get(State, plt),
+ Opts = [{analysis_type, succ_typings},
+ {from, byte_code},
+ {files, Files},
+ {plts, [Plt]}],
+ run_dialyzer(State, Opts).
+
+run_dialyzer(State, Opts) ->
+ Warnings = rebar_state:get(State, dialyzer_warnings, default_warnings()),
+ Opts2 = [{get_warnings, true},
+ {warnings, Warnings} |
+ Opts],
+ _ = [?CONSOLE(format_warning(Warning), [])
+ || Warning <- dialyzer:run(Opts2)],
+ {ok, State}.
+
+format_warning(Warning) ->
+ string:strip(dialyzer:format_warning(Warning), right, $\n).
default_warnings() ->
[error_handling,