diff options
author | Tuncer Ayaz <tuncer.ayaz@gmail.com> | 2015-02-27 23:17:24 +0100 |
---|---|---|
committer | Tuncer Ayaz <tuncer.ayaz@gmail.com> | 2015-05-22 09:53:09 +0200 |
commit | 850a8a3dcedaf400a2e9d1afa67c80ebfd370a60 (patch) | |
tree | 6946f22c5f109b07d1be60aa07b44d80f28b0df4 | |
parent | 094b74e488291156cda8d12fffa6f62750d44f47 (diff) | |
download | rebar-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]}
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 41 | ||||
-rw-r--r-- | dialyzer_reference | 3 | ||||
-rw-r--r-- | ebin/rebar.app | 25 | ||||
-rw-r--r-- | priv/shell-completion/bash/rebar | 6 | ||||
-rw-r--r-- | priv/shell-completion/zsh/_rebar | 5 | ||||
-rw-r--r-- | rebar.config | 12 | ||||
-rw-r--r-- | rebar.config.sample | 16 | ||||
-rw-r--r-- | src/rebar.erl | 10 | ||||
-rw-r--r-- | src/rebar_dialyzer.erl | 234 |
10 files changed, 304 insertions, 49 deletions
@@ -4,7 +4,6 @@ *.orig .*.swp /rt.work -/dialyzer_warnings /rebar.cmd /.eunit /deps @@ -1,10 +1,7 @@ -.PHONY: clean dialyzer_warnings xref_warnings deps test test_eunit test_inttest +.PHONY: clean xref_warnings deps test test_eunit test_inttest REBAR=$(PWD)/rebar RETEST=$(PWD)/deps/retest/retest -OTPVSNCMD='io:fwrite("~s",[rebar_utils:otp_release()]), halt().' -OTPVSN=$(shell erl -pa ebin/ -noshell -eval $(OTPVSNCMD)) -PLT_FILENAME=~/.dialyzer_rebar_$(OTPVSN)_plt LOG_LEVEL?=debug RT_TARGETS?=inttest @@ -12,11 +9,9 @@ all: ./bootstrap clean: - @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .eunit - @rm -f .rebarinfo + @rm -rf rebar .rebar ebin/*.beam inttest/rt.work rt.work .eunit distclean: clean - @rm -f dialyzer_warnings @rm -rf deps debug: @@ -28,36 +23,10 @@ xref: @./rebar xref build_plt: - -dialyzer --build_plt --output_plt $(PLT_FILENAME) --apps \ - erts \ - kernel \ - stdlib \ - crypto \ - compiler \ - asn1 \ - eunit \ - tools \ - ssl \ - edoc \ - reltool \ - snmp \ - sasl - -dialyzer --add_to_plt --plt $(PLT_FILENAME) \ - --output_plt $(PLT_FILENAME) \ - --apps diameter + @./rebar build-plt -dialyzer: dialyzer_warnings - @diff -U0 dialyzer_reference dialyzer_warnings - -dialyzer_warnings: - -@dialyzer --plt $(PLT_FILENAME) -q -nn -n ebin \ - -Wunmatched_returns \ - -Werror_handling \ - -Wrace_conditions \ - > dialyzer_warnings - -typer: - typer -r --plt $(PLT_FILENAME) ./src -I ./include +dialyzer: + @./rebar dialyze binary: VSN = $(shell ./rebar -V) binary: clean all diff --git a/dialyzer_reference b/dialyzer_reference deleted file mode 100644 index 41b1cff..0000000 --- a/dialyzer_reference +++ /dev/null @@ -1,3 +0,0 @@ - -rebar_eunit.erl:471: Call to missing or unexported function eunit_test:function_wrapper/2 -rebar_utils.erl:222: Call to missing or unexported function escript:foldl/3 diff --git a/ebin/rebar.app b/ebin/rebar.app index 1f3a895..9ebc83c 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -17,6 +17,7 @@ rebar_cover_utils, rebar_ct, rebar_deps, + rebar_dialyzer, rebar_edoc, rebar_erlc_compiler, rebar_erlydtl_compiler, @@ -46,13 +47,22 @@ rebar_getopt, rebar_mustache ]}, {registered, []}, - {applications, [kernel, - stdlib, - sasl, - compiler, - crypto, - syntax_tools, - tools]}, + {applications, + [ + kernel, + stdlib, + sasl, + compiler, + crypto, + syntax_tools, + tools, + eunit, + reltool, + dialyzer, + asn1, + snmp, + edoc + ]}, {env, [ %% Default log level {log_level, warn}, @@ -76,6 +86,7 @@ rebar_neotoma_compiler, rebar_asn1_compiler, rebar_dia_compiler, + rebar_dialyzer, rebar_erlc_compiler, rebar_lfe_compiler, rebar_erlydtl_compiler, diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar index 0baf389..c017d20 100644 --- a/priv/shell-completion/bash/rebar +++ b/priv/shell-completion/bash/rebar @@ -17,14 +17,18 @@ _rebar() --keep-going \ --recursive \ --version" - cmdsnvars="check-deps \ + cmdsnvars=" \ + build-plt \ + check-deps \ clean \ compile \ + check-plt \ create \ create-app \ create-lib \ create-node \ ct \ + dialyze \ doc \ delete-deps \ escriptize \ diff --git a/priv/shell-completion/zsh/_rebar b/priv/shell-completion/zsh/_rebar index 0390881..2ba7cdc 100644 --- a/priv/shell-completion/zsh/_rebar +++ b/priv/shell-completion/zsh/_rebar @@ -28,13 +28,16 @@ _rebar () { case $state in cmd_and_var) _values -S = 'variables' \ + '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]' \ 'clean[Clean]' \ 'compile[Compile sources]' \ 'create[Create skel based on template and vars]' \ 'create-app[Create simple app skel]' \ 'create-lib[Create simple lib skel]' \ 'create-node[Create simple node skel]' \ - 'list-template[List avaiavle templates]' \ + 'list-template[List available templates]' \ 'doc[Generate Erlang program documentation]' \ 'check-deps[Display to be fetched dependencies]' \ 'prepare-deps[Fetch and build dependencies]' \ diff --git a/rebar.config b/rebar.config index 33d2aea..370bc8f 100644 --- a/rebar.config +++ b/rebar.config @@ -31,3 +31,15 @@ - (\"diameter_dict_util\":\"format_error\"/\"1\") - (\"diameter_dict_util\":\"parse\"/\"2\"))", []}]}. + +{dialyzer_plt_extra_apps, + [ + diameter + ]}. + +{dialyzer_warnings, + [ + unmatched_returns, + error_handling, + race_conditions + ]}. diff --git a/rebar.config.sample b/rebar.config.sample index b650bc9..41a96a4 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -266,3 +266,19 @@ {xref_queries, [{"(XC - UC) || (XU - X - B" " - (\"mod\":\".*foo\"/\"4\"))",[]}]}. + +%% == Dialyzer == + +%% 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"}. + +%% Extra apps to include in the PLT +{dialyzer_plt_extra_apps, [app1, app2]}. + +{dialyzer_warnings, [unmatched_returns, error_handling]}. 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. |