Refactor Dialyzer support to make it more usable

This commit is contained in:
Tuncer Ayaz 2010-10-25 22:07:32 +02:00
parent 7ddd4bdaad
commit 2d2aed627f
4 changed files with 95 additions and 35 deletions

View file

@ -8,8 +8,8 @@ _rebar()
prev="${COMP_WORDS[COMP_CWORD-1]}"
sopts="-h -c -v -V -f -j"
lopts=" --help --commands --verbose --force --jobs= --version"
cmdsnvars="analyze build_plt check_plt check-deps clean compile \
create create-app create-node ct doc delete-deps eunit \
cmdsnvars="build-plt check-plt check-deps clean compile \
create create-app create-node ct dialyze doc delete-deps eunit \
get-deps generate help list-templates version xref \
case= force=1 jobs= suite= verbose=1 appid= skip_deps=1 \
template= template_dir="

View file

@ -76,8 +76,11 @@
%% == Dialyzer ==
%% Options for running the dialyzer, right now only `plt' is supported
{dialyzer_opts, []}.
%% Options for running dialyzer
%% {plt, PltFile}
%% 'src': run Dialyzer on the source files as in 'dialyzer --src'
%% {warnings, [WarnOpts]}: turn on/off Dialyzer warnings
{warnings_opts, [{plt, PltFile}, {warnings, [WarnOpts]}, src]}.
%% == Cleanup ==

View file

@ -200,9 +200,9 @@ help() ->
%%
commands() ->
S = <<"
analyze Analyze with Dialyzer
build_plt Build Dialyzer PLT
check_plt Check Dialyzer PLT
dialyze Analyze with Dialyzer
build-plt Build Dialyzer PLT
check-plt Check Dialyzer PLT
clean Clean
compile Compile sources

View file

@ -27,9 +27,9 @@
%% @author Dave Smith <dizzyd@dizzyd.com>
%% @doc rebar_dialyzer supports the following commands:
%% <ul>
%% <li>analyze (essentially "dialyzer -r ebin")</li>
%% <li>build_plt (essentially "dialyzer --build_plt -r &lt;app_dirs&gt;")</li>
%% <li>check_plt (essentially "dialyzer --check_plt")</li>
%% <li>dialyze (essentially "dialyzer ebin" or "dialyzer --src src")</li>
%% <li>build-plt (essentially "dialyzer --build_plt -r &lt;app_dirs&gt;")</li>
%% <li>check-plt (essentially "dialyzer --check_plt")</li>
%% </ul>
%% A single option <code>plt</code> can be presented in the <code>dialyzer_opts</code>
%% options in <code>rebar.config</code>. If it is present, it is used as the PLT for the
@ -44,9 +44,9 @@
%% -------------------------------------------------------------------
-module(rebar_dialyzer).
-export([analyze/2,
build_plt/2,
check_plt/2]).
-export([dialyze/2,
'build-plt'/2,
'check-plt'/2]).
-include("rebar.hrl").
@ -57,21 +57,39 @@
%% ===================================================================
%% @doc Perform static analysis on the contents of the ebin directory.
%% @spec analyze(Config::#config{}, File::string()) -> ok
-spec(analyze(Config::#config{}, File::string()) -> ok).
analyze(Config, File) ->
Plt = plt_path(Config, File),
%% @spec dialyze(Config::#config{}, File::string()) -> ok
-spec dialyze(Config::#config{}, File::string()) -> ok.
dialyze(Config, File) ->
Plt = existing_plt_path(Config, File),
case dialyzer:plt_info(Plt) of
{ok, _} ->
try dialyzer:run([{files_rec, ["ebin"]}, {init_plt, Plt}]) of
FromSrc = proplists:get_bool(src, rebar_config:get(Config,
dialyzer_opts,
[])),
DialyzerOpts0 = case FromSrc of
true ->
[{files_rec, ["src"]}, {init_plt, Plt},
{from, src_code}];
false ->
[{files_rec, ["ebin"]}, {init_plt, Plt}]
end,
WarnOpts = warnings(Config),
DialyzerOpts = case WarnOpts of
[] -> DialyzerOpts0;
_ -> [{warnings, WarnOpts}|DialyzerOpts0]
end,
?DEBUG("DialyzerOpts: ~p~n", [DialyzerOpts]),
try dialyzer:run(DialyzerOpts) of
Warnings -> output_warnings(Warnings)
catch
throw:{dialyzer_error, Reason} ->
?ABORT("~s~n", [Reason])
end;
{error, no_such_file} ->
?ABORT("The PLT ~s does not exist. Please perform the build_plt command to ~n"
"produce the initial PLT. Be aware this operation may take several minutes.", [Plt]);
?ABORT("The PLT ~s does not exist. Please perform the build-plt "
"command to ~n"
"produce the initial PLT. Be aware that this operation may "
"take several minutes.~n", [Plt]);
{error, read_error} ->
?ABORT("Unable to read PLT ~n~n", [Plt]);
{error, not_valid} ->
@ -80,29 +98,30 @@ analyze(Config, File) ->
ok.
%% @doc Build the PLT.
%% @spec build_plt(Config::#config{}, File::string()) -> ok
-spec(build_plt(Config::#config{}, File::string()) -> ok).
build_plt(Config, File) ->
Plt = plt_path(Config, File),
%% @spec build-plt(Config::#config{}, File::string()) -> ok
-spec 'build-plt'(Config::#config{}, File::string()) -> ok.
'build-plt'(Config, File) ->
Plt = new_plt_path(Config, File),
Apps = rebar_app_utils:app_applications(File),
?DEBUG("Build PLT ~s including following apps:~n~p~n", [Plt, Apps]),
Warnings = dialyzer:run([{analysis_type, plt_build},
{files_rec, app_dirs(Apps)},
{output_plt, Plt}]),
case Warnings of
[] ->
?INFO("The built PLT can be found in ~s", [Plt]);
?INFO("The built PLT can be found in ~s~n", [Plt]);
_ ->
output_warnings(Warnings)
end,
ok.
%% @doc Check whether the PLT is up-to-date (rebuilding it if not).
%% @spec check_plt(Config::#config{}, File::string()) -> ok
-spec(check_plt(Config::#config{}, File::string()) -> ok).
check_plt(Config, File) ->
Plt = plt_path(Config, File),
%% @spec check-plt(Config::#config{}, File::string()) -> ok
-spec 'check-plt'(Config::#config{}, File::string()) -> ok.
'check-plt'(Config, File) ->
Plt = existing_plt_path(Config, File),
try dialyzer:run([{analysis_type, plt_check}, {init_plt, Plt}]) of
[] ->
?CONSOLE("The PLT ~s is up-to-date~n", [Plt]);
@ -121,14 +140,14 @@ check_plt(Config, File) ->
%% @doc Obtain the library paths for the supplied applications.
%% @spec app_dirs(Apps::[atom()]) -> [string()]
-spec(app_dirs(Apps::[atom()]) -> [string()]).
-spec app_dirs(Apps::[atom()]) -> [string()].
app_dirs(Apps) ->
[filename:join(Path, "ebin") ||
Path <- lists:map(fun(App) -> code:lib_dir(App) end, Apps), erlang:is_list(Path)].
%% @doc Render the warnings on the console.
%% @spec output_warnings(Warnings::[warning()]) -> 'ok'
-spec(output_warnings(Warnings::[warning()]) -> 'ok').
-spec output_warnings(Warnings::[warning()]) -> 'ok'.
output_warnings(Warnings) ->
lists:foreach(fun(Warning) ->
?CONSOLE("~s", [dialyzer:format_warning(Warning)])
@ -136,14 +155,52 @@ output_warnings(Warnings) ->
%% @doc If the plt option is present in rebar.config return its value, otherwise
%% return $HOME/.dialyzer_plt.
%% @spec plt_path(Config::#config{}, File::string()) -> string()
-spec(plt_path(Config::#config{}, File::string()) -> string()).
plt_path(Config, File) ->
%% @spec new_plt_path(Config::#config{}, File::string()) -> string()
-spec new_plt_path(Config::#config{}, File::string()) -> string().
new_plt_path(Config, File) ->
AppName = rebar_app_utils:app_name(File),
DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
case proplists:get_value(plt, DialyzerOpts) of
undefined ->
filename:join(os:getenv("HOME"), "." ++ atom_to_list(AppName) ++ "_dialyzer_plt");
filename:join(os:getenv("HOME"),
"." ++ atom_to_list(AppName) ++ "_dialyzer_plt");
Plt ->
Plt
end.
%% @doc If the plt option is present in rebar.config and the file exists
%% return its value or if ~/.AppName_dialyzer_plt exists return that.
%% Otherwise return ~/.dialyzer_plt if it exists or abort.
%% @spec existing_plt_path(Config::#config{}, File::string()) -> string()
-spec existing_plt_path(Config::#config{}, File::string()) -> string().
existing_plt_path(Config, File) ->
AppName = rebar_app_utils:app_name(File),
DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
Home = os:getenv("HOME"),
case proplists:get_value(plt, DialyzerOpts) of
undefined ->
AppPlt = filename:join(Home, "." ++ atom_to_list(AppName)
++ "_dialyzer_plt"),
case filelib:is_regular(AppPlt) of
true ->
AppPlt;
false ->
HomePlt = filename:join(Home, ".dialyzer_plt"),
case filelib:is_regular(HomePlt) of
true ->
HomePlt;
false ->
?ABORT("No PLT found~n", [])
end
end;
Plt ->
Plt
end.
%% @doc If the warnings option is present in rebar.config return its value,
%% otherwise return [].
%% @spec warnings(Config::#config{}) -> list().
-spec warnings(Config::#config{}) -> list().
warnings(Config) ->
DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
proplists:get_value(warnings, DialyzerOpts, []).