diff --git a/ebin/rebar.app b/ebin/rebar.app index cfd62d0..02a2094 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -8,6 +8,7 @@ rebar_core, rebar_ct, rebar_deps, + rebar_dialyzer, rebar_erlc_compiler, rebar_escripter, rebar_eunit, @@ -52,6 +53,7 @@ rebar_otp_app, rebar_ct, rebar_eunit, + rebar_dialyzer, rebar_escripter ]}, diff --git a/include/rebar.hrl b/include/rebar.hrl index 4ec3778..ad3e2e0 100644 --- a/include/rebar.hrl +++ b/include/rebar.hrl @@ -1,3 +1,5 @@ +-record(config, { dir, + opts }). -record(global_state, { working_dir }). diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 1ccf6af..8528d5f 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -35,9 +35,6 @@ -include("rebar.hrl"). --record(config, { dir, - opts }). - %% =================================================================== %% Public API diff --git a/src/rebar_dialyzer.erl b/src/rebar_dialyzer.erl new file mode 100644 index 0000000..c725e48 --- /dev/null +++ b/src/rebar_dialyzer.erl @@ -0,0 +1,145 @@ +%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2010 Dave Smith (dizzyd@dizzyd.com) +%% +%% 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. +%% ------------------------------------------------------------------- +%% @author Dave Smith +%% @doc rebar_dialyzer supports the following commands: +%% +%% A single option plt can be presented in the dialyzer_opts +%% options in rebar.config. If it is present, it is used as the PLT for the +%% supported commands. Should it not be present, then the default is $HOME/.dialyzer_plt. +%% @reference Experience from developing the Dialyzer: +%% A static analysis tool detecting defects in Erlang applications +%% @reference A Language for Specifying Type +%% Contracts in Erlang and its Interaction with Success Typings +%% @reference Gradual Typing of Erlang +%% Programs: A Wrangler Experience +%% @copyright 2010 Dave Smith +%% ------------------------------------------------------------------- +-module(rebar_dialyzer). + +-export([analyze/2, + build_plt/2, + check_plt/2]). + +-include("rebar.hrl"). + +-type(warning() :: {atom(), {string(), integer()}, any()}). + +%% =================================================================== +%% Public API +%% =================================================================== + +%% @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), + case dialyzer:plt_info(Plt) of + {ok, _} -> + try dialyzer:run([{files_rec, ["ebin"]}, {init_plt, Plt}]) 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]); + {error, not_valid} -> + ?ABORT("The PLT ~s is not valid.~n", [Plt]); + {error, _Reason} -> + ?ABORT("Unable to determine the validity of the PLT ~s~n", [Plt]) + end, + 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), + + %% This is the recommended minimal PLT for OTP + %% (see http://www.erlang.org/doc/apps/dialyzer/dialyzer_chapter.html#id2256857). + Warnings = dialyzer:run([{analysis_type, plt_build}, + {files_rec, [ + filename:join(code:lib_dir(stdlib), "ebin"), + filename:join(code:lib_dir(kernel), "ebin"), + filename:join(code:lib_dir(mnesia), "ebin") + ]}, + {output_plt, Plt}]), + case Warnings of + [] -> + ?INFO("The built PLT can be found in ~s", [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), + try dialyzer:run([{analysis_type, plt_check}, {init_plt, Plt}]) of + [] -> + ?CONSOLE("The PLT ~s is up-to-date~n", [Plt]); + _ -> + %% @todo Determine whether this is the correct summary. + ?CONSOLE("The PLT ~s is not up-to-date~n", [Plt]) + catch + throw:{dialyzer_error, _Reason} -> + ?CONSOLE("The PLT ~s is not valid.~n", [Plt]) + end, + ok. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +%% @doc Render the warnings on the console. +%% @spec output_warnings(Warnings::[warning()]) -> void() +-spec(output_warnings(Warnings::[warning()]) -> none()). +output_warnings(Warnings) -> + lists:foreach(fun(Warning) -> + ?CONSOLE("~s", [dialyzer:format_warning(Warning)]) + end, Warnings). + +%% @doc If the plt option is present in rebar.config return its value, otherwise +%% return $HOME/.dialyzer_plt. +%% @spec plt_path(Config::#config{}) -> string() +-spec(plt_path(Config::#config{}) -> string()). +plt_path(Config) -> + DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []), + case proplists:get_value(plt, DialyzerOpts) of + undefined -> + filename:join(os:getenv("HOME"), ".dialyzer_plt"); + Plt -> + Plt + end.