summaryrefslogtreecommitdiff
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
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]}
-rw-r--r--.gitignore1
-rw-r--r--Makefile41
-rw-r--r--dialyzer_reference3
-rw-r--r--ebin/rebar.app25
-rw-r--r--priv/shell-completion/bash/rebar6
-rw-r--r--priv/shell-completion/zsh/_rebar5
-rw-r--r--rebar.config12
-rw-r--r--rebar.config.sample16
-rw-r--r--src/rebar.erl10
-rw-r--r--src/rebar_dialyzer.erl234
10 files changed, 304 insertions, 49 deletions
diff --git a/.gitignore b/.gitignore
index 15668a7..671ac10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,6 @@
*.orig
.*.swp
/rt.work
-/dialyzer_warnings
/rebar.cmd
/.eunit
/deps
diff --git a/Makefile b/Makefile
index ed8dd48..9f8ea7a 100644
--- a/Makefile
+++ b/Makefile
@@ -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.