summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTuncer Ayaz <tuncer.ayaz@gmail.com>2015-02-27 23:17:24 +0100
committerTuncer Ayaz <tuncer.ayaz@gmail.com>2015-05-22 09:53:09 +0200
commit850a8a3dcedaf400a2e9d1afa67c80ebfd370a60 (patch)
tree6946f22c5f109b07d1be60aa07b44d80f28b0df4 /src
parent094b74e488291156cda8d12fffa6f62750d44f47 (diff)
downloadrebar-850a8a3dcedaf400a2e9d1afa67c80ebfd370a60.tar.gz
Add Dialyzer plugin
Build project-specific PLT $ rebar build-plt Check the PLT for consistency and rebuild it if it is not up-to-date $ rebar check-plt Analyze the code for discrepancies $ rebar dialyze Delete project-specific PLT $ rebar delete-plt Valid rebar.config options: %% Store PLT in ~/.rebar/plt (Default) {dialyzer_plt_location,shared} %% Store PLT locally inside the project in .rebar {dialyzer_plt_location,local} %% Store PLT in custom directory {dialyzer_plt_location,"custom_path"} {dialyzer_plt_extra_apps,[app1,app2]} {dialyzer_warnings,[unmatched_returns,error_handling]}
Diffstat (limited to 'src')
-rw-r--r--src/rebar.erl10
-rw-r--r--src/rebar_dialyzer.erl234
2 files changed, 244 insertions, 0 deletions
diff --git a/src/rebar.erl b/src/rebar.erl
index 2d356dd..9a5fc28 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -456,6 +456,12 @@ qc Test QuickCheck properties
xref Run cross reference analysis
+dialyze Analyze the code for discrepancies
+build-plt Build project-specific PLT
+check-plt Check the PLT for consistency and
+ rebuild it if it is not up-to-date
+delete-plt Delete project-specific PLT
+
shell Start a shell similar to
'erl -pa ebin -pa deps/*/ebin'
@@ -527,7 +533,9 @@ filter_flags(Config, [Item | Rest], Commands) ->
command_names() ->
[
+ "build-plt",
"check-deps",
+ "check-plt",
"clean",
"compile",
"create",
@@ -535,7 +543,9 @@ command_names() ->
"create-lib",
"create-node",
"ct",
+ "delete-plt",
"delete-deps",
+ "dialyze",
"doc",
"eunit",
"escriptize",
diff --git a/src/rebar_dialyzer.erl b/src/rebar_dialyzer.erl
new file mode 100644
index 0000000..5f0cb1e
--- /dev/null
+++ b/src/rebar_dialyzer.erl
@@ -0,0 +1,234 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2014-2015 Tuncer Ayaz
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+%% -------------------------------------------------------------------
+-module(rebar_dialyzer).
+
+-export([
+ dialyze/2,
+ 'build-plt'/2,
+ 'check-plt'/2,
+ 'delete-plt'/2
+ ]).
+
+%% for internal use only
+-export([info/2]).
+
+-include("rebar.hrl").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+dialyze(Config, AppFile) ->
+ {NewConfig, Plt} = plt(Config, AppFile),
+ ok = check_plt_existence(Plt),
+
+ Opts = [
+ {analysis_type, succ_typings},
+ %% http://erlang.org/pipermail/erlang-bugs/2015-February/004781.html
+ %% TODO: remove once the minimum required Erlang/OTP release
+ %% includes a Dialyzer version without the bug, or alternatively
+ %% add a config option to always check the PLT, as this may be
+ %% needed by some users.
+ {check_plt, false},
+ {init_plt, Plt},
+ {files_rec, ["ebin"]},
+ {warnings, warnings(Config)}
+ ],
+ ?DEBUG("dialyze opts:~n~p~n", [Opts]),
+ case run(Opts) of
+ [] ->
+ {ok, NewConfig};
+ Ws ->
+ print_warnings(Ws),
+ ?FAIL
+ end.
+
+'build-plt'(Config, AppFile) ->
+ {Config1, AppDirs} = app_dirs(Config, AppFile),
+ {NewConfig, Plt} = plt(Config1, AppFile),
+ Opts = [
+ {analysis_type, plt_build},
+ {output_plt, Plt},
+ {files_rec, AppDirs}
+ ],
+ ?DEBUG("build-plt opts:~n~p~n", [Opts]),
+ case run(Opts) of
+ [] ->
+ {ok, NewConfig};
+ Ws ->
+ %% As plt_build may raise warnings but still successfully
+ %% create the PLT, we cannot interpret this as failure,
+ %% and therefore all we can do is report warnings.
+ print_warnings(Ws)
+ end.
+
+'check-plt'(Config, AppFile) ->
+ {NewConfig, Plt} = plt(Config, AppFile),
+ ok = check_plt_existence(Plt),
+
+ Opts = [
+ {analysis_type, plt_check},
+ %% http://erlang.org/pipermail/erlang-bugs/2015-February/004781.html
+ %% Without this, the PLT will be checked twice.
+ %% TODO: remove once the minimum required Erlang/OTP release
+ %% includes a Dialyzer version without the bug.
+ {check_plt, false},
+ {init_plt, Plt}
+ ],
+ ?DEBUG("build-plt opts:~n~p~n", [Opts]),
+ case run(Opts) of
+ [] ->
+ {ok, NewConfig};
+ Ws ->
+ print_warnings(Ws),
+ ?FAIL
+ end.
+
+'delete-plt'(Config, AppFile) ->
+ {NewConfig, Plt} = plt(Config, AppFile),
+ ?DEBUG("Delete PLT '~s'~n", [Plt]),
+ ok = rebar_file_utils:delete_each([Plt]),
+ {ok, NewConfig}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, dialyze) ->
+ info_help("Analyze the code for discrepancies");
+info(help, 'build-plt') ->
+ info_help("Build project-specific PLT");
+info(help, 'check-plt') ->
+ info_help("Check the PLT for consistency and rebuild it if it"
+ " is not up-to-date");
+info(help, 'delete-plt') ->
+ info_help("Delete project-specific PLT").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ Description,
+ {dialyzer_plt_location, shared},
+ {dialyzer_plt_location, local},
+ {dialyzer_plt_location, "custom_path"},
+ {dialyzer_plt_extra_apps, [app1, app2]},
+ {dialyzer_warnings, [unmatched_returns, error_handling]}
+ ]).
+
+plt(Config, AppFile) ->
+ PltDir = plt_dir(Config),
+ {NewConfig, RawAppName} = rebar_app_utils:app_name(Config, AppFile),
+ AppName = atom_to_list(RawAppName),
+ OtpRel = rebar_utils:otp_release(),
+ Plt = filename:join([PltDir, AppName ++ "_" ++ OtpRel ++ "_plt"]),
+ ok = filelib:ensure_dir(Plt),
+ {NewConfig, Plt}.
+
+plt_dir(Config) ->
+ Location = rebar_config:get_local(Config, dialyzer_plt_location, shared),
+ plt_dir1(Config, Location).
+
+plt_dir1(_Config, Location) when is_list(Location) ->
+ case filelib:is_dir(Location) of
+ false ->
+ ?ABORT("PLT directory does not exist: ~s~n", [Location]);
+ true ->
+ Location
+ end;
+plt_dir1(_Config, shared) ->
+ {ok, Home} = init:get_argument(home),
+ filename:join([Home, ".rebar", "plt"]);
+plt_dir1(Config, local) ->
+ BaseDir = rebar_utils:base_dir(Config),
+ filename:join([BaseDir, ".rebar"]).
+
+check_plt_existence(Plt) ->
+ case filelib:is_regular(Plt) of
+ true ->
+ ok;
+ false ->
+ ?ABORT("PLT '~s' does not exist.~n"
+ "Please run 'rebar build-plt' first.~n", [Plt])
+ end.
+
+%% dialyzer:run/1 wrapper to gracefully fail in case of Dialyzer errors
+run(Opts) ->
+ try dialyzer:run(Opts) of
+ Ws -> Ws
+ catch
+ throw:{dialyzer_error, Reason} ->
+ ?ABORT("Dialyzer error:~n~s~n", [Reason])
+ end.
+
+warnings(Config) ->
+ rebar_config:get_local(Config, dialyzer_warnings, []).
+
+print_warnings(Ws) ->
+ lists:foreach(
+ fun(W) ->
+ ?CONSOLE("~s~n", [format_warning(W)])
+ end,
+ Ws).
+
+format_warning(W) ->
+ dialyzer:format_warning(W, fullpath).
+
+app_dirs(Config, AppFile) ->
+ {NewConfig, AppFileApps} = app_file_apps(Config, AppFile),
+ ?DEBUG("app file apps:~n~p~n", [AppFileApps]),
+ Deps = deps_apps(Config),
+ ?DEBUG("deps apps:~n~p~n", [Deps]),
+ ExtraApps = rebar_config:get_local(Config, dialyzer_plt_extra_apps, []),
+ ?DEBUG("extra apps:~n~p~n", [ExtraApps]),
+ %% erts is assumed, and has to be present unconditionally.
+ Erts = [erts],
+ Apps = ordsets:from_list(Erts ++ AppFileApps ++ Deps ++ ExtraApps),
+ AppDirs = [app_lib_dir(App) || App <- ordsets:to_list(Apps)],
+ ?DEBUG("app dirs:~n~p~n", [AppDirs]),
+ {NewConfig, AppDirs}.
+
+app_file_apps(Config, AppFile) ->
+ rebar_app_utils:app_applications(Config, AppFile).
+
+deps_apps(Config) ->
+ [element(1, Dep) || Dep <- rebar_config:get_local(Config, deps, [])].
+
+app_lib_dir(App) ->
+ case code:lib_dir(App, ebin) of
+ {error, _}=Err ->
+ ?ABORT("Failed to get ebin dir for app: ~p~n~p~n", [App, Err]);
+ Dir ->
+ Dir
+ end.