From 03b07c3d396d577c3a6d635c339900ed4d8c391c Mon Sep 17 00:00:00 2001 From: James Fish Date: Wed, 19 Nov 2014 18:10:41 +0000 Subject: Fix default dialyzer warnings * Nolonger supress standard warnings * Nolonger include race_conditions by default[1] [1] http://erlang.org/pipermail/erlang-bugs/2014-July/004518.html --- src/rebar_prv_dialyzer.erl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 20a58f3..75bc11e 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -95,14 +95,6 @@ update_dep_plt(_State, DepPlt, AppList) -> dialyzer:run(Opts). default_warnings() -> - [no_return, - no_unused, - no_improper_lists, - no_fun_app, - no_match, - no_opaque, - no_fail_call, - error_handling, - race_conditions, + [error_handling, unmatched_returns, underspecs]. -- cgit v1.1 From 62e7cc27da361f6c6f63bef1e6588235e1e19609 Mon Sep 17 00:00:00 2001 From: James Fish Date: Wed, 19 Nov 2014 18:14:51 +0000 Subject: Refactor dialyzer PLT use * Only include `plt_apps` (defaults to deps) in a single PLT * Update PLT when `plt_apps` (or deps) are changed or updated * Print warnings for all dialyzer runs (not just `succ_typings`) --- src/rebar_prv_dialyzer.erl | 209 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 164 insertions(+), 45 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 75bc11e..7bbaf81 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -33,34 +33,15 @@ init(State) -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Dialyzer starting, this may take a while...", []), - BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), - {ProjectPlt, DepPlt} = get_plt_location(BuildDir), - Apps = rebar_state:project_apps(State), + State1 = set_plt_location(State), + Apps = rebar_state:project_apps(State1), Deps = rebar_state:get(State, all_deps, []), try - ?INFO("Doing plt for dependencies...", []), - update_dep_plt(State, DepPlt, Deps), - ?INFO("Doing plt for project apps...", []), - update_dep_plt(State, ProjectPlt, Apps), - WarningTypes = rebar_state:get(State, dialyzer_warnings, default_warnings()), - Paths = [filename:join(rebar_app_info:dir(App), "ebin") || App <- Apps], - Opts = [{analysis_type, succ_typings}, - {from, byte_code}, - {files_rec, Paths}, - {warnings, WarningTypes}, - {plts, [ProjectPlt, DepPlt]}], - - case dialyzer:run(Opts) of - [] -> - {ok, State}; - Warnings -> - [?CONSOLE(string:strip(dialyzer:format_warning(Warning), right, $\n), []) || - Warning <- Warnings], - {ok, State} - end + {ok, State2} = update_plt(State1, Apps, Deps), + succ_typings(State2, Apps) catch - _:{dialyzer_error, Error} -> + throw:{dialyzer_error, Error} -> {error, {?MODULE, {error_processing_apps, Error, Apps}}} end. @@ -72,27 +53,165 @@ format_error(Reason) -> %% Internal functions -get_plt_location(BuildDir) -> - {filename:join([BuildDir, ".project.plt"]), - filename:join([BuildDir, ".deps.plt"])}. - -update_dep_plt(_State, DepPlt, AppList) -> - Opts0 = - case filelib:is_file(DepPlt) of - true -> - ?INFO("Plt is built, checking/updating...", []), - [{analysis_type, plt_check}, - {plts, [DepPlt]}]; - false -> - ?INFO("Building the plt, this will take a while...", []), - [{analysis_type, plt_build}, - {output_plt, DepPlt}] - end, - Paths = [filename:join(rebar_app_info:dir(App), "ebin") || App <- AppList], - Opts = [{files_rec, Paths}, - {from, byte_code}] ++ Opts0, - - dialyzer:run(Opts). +set_plt_location(State) -> + BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), + DefaultPlt = filename:join([BuildDir, ".deps.plt"]), + Plt = rebar_state:get(State, plt, DefaultPlt), + rebar_state:set(State, plt, Plt). + +update_plt(State, Apps, Deps) -> + ?INFO("Updating plt...", []), + Files = get_plt_files(State, Apps, Deps), + case read_plt(State) of + {ok, OldFiles} -> + check_plt(State, OldFiles, Files); + {error, no_such_file} -> + build_plt(State, Files) + end. + +get_plt_files(State, Apps, Deps) -> + case rebar_state:get(State, plt_apps) of + undefined -> + apps_to_files(Deps); + PltApps -> + app_names_to_files(PltApps, Apps ++ Deps) + end. + +apps_to_files(Apps) -> + lists:flatmap(fun app_to_files/1, Apps). + +app_to_files(App) -> + AppDetails = rebar_app_info:app_details(App), + Modules = proplists:get_value(modules, AppDetails, []), + EbinDir = rebar_app_info:ebin_dir(App), + modules_to_files(Modules, EbinDir). + +modules_to_files(Modules, EbinDir) -> + Ext = code:objfile_extension(), + Mod2File = fun(Module) -> module_to_file(Module, EbinDir, Ext) end, + rebar_utils:filtermap(Mod2File, Modules). + +module_to_file(Module, EbinDir, Ext) -> + File = filename:join(EbinDir, atom_to_list(Module) ++ Ext), + case filelib:is_file(File) of + true -> + {true, File}; + false -> + ?CONSOLE("Unknown module ~s", [Module]), + false + end. + +app_names_to_files(AppNames, Apps) -> + lists:flatmap(fun(AppName) -> app_name_to_files(AppName, Apps) end, + AppNames). + +app_name_to_files(AppName, Apps) -> + case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of + {ok, App} -> + app_to_files(App); + error -> + app_name_to_files(AppName) + end. + +app_name_to_files(AppName) -> + case code:lib_dir(AppName) of + {error, _} -> + ?CONSOLE("Unknown application ~s", [AppName]), + []; + AppDir -> + app_dir_to_files(AppDir, AppName) + end. + +app_dir_to_files(AppDir, AppName) -> + EbinDir = filename:join(AppDir, "ebin"), + AppFile = filename:join(EbinDir, atom_to_list(AppName) ++ ".app"), + case file:consult(AppFile) of + {ok, [{application, AppName, AppDetails}]} -> + Modules = proplists:get_value(modules, AppDetails, []), + modules_to_files(Modules, EbinDir); + _ -> + Error = io_lib:format("Could not parse ~p", [AppFile]), + throw({dialyzer_error, Error}) + end. + +read_plt(State) -> + Plt = rebar_state:get(State, plt), + case dialyzer:plt_info(Plt) of + {ok, Info} -> + Files = proplists:get_value(files, Info, []), + {ok, Files}; + {error, no_such_file} = Error -> + Error; + {error, read_error} -> + Error = io_lib:format("Could not read the PLT file ~p", [Plt]), + throw({dialyzer_error, Error}) + end. + +check_plt(State, OldList, FilesList) -> + Old = sets:from_list(OldList), + Files = sets:from_list(FilesList), + Remove = sets:subtract(Old, Files), + {ok, State1} = remove_plt(State, sets:to_list(Remove)), + Check = sets:intersection(Files, Old), + {ok, State2} = check_plt(State1, sets:to_list(Check)), + Add = sets:subtract(Files, Old), + add_plt(State2, sets:to_list(Add)). + +remove_plt(State, []) -> + {ok, State}; +remove_plt(State, Files) -> + ?INFO("Removing ~b files from plt...", [length(Files)]), + run_plt(State, plt_remove, Files). + +check_plt(State, []) -> + {ok, State}; +check_plt(State, Files) -> + ?INFO("Checking ~b files in plt...", [length(Files)]), + run_plt(State, plt_check, Files). + +add_plt(State, []) -> + {ok, State}; +add_plt(State, Files) -> + ?INFO("Adding ~b files to plt...", [length(Files)]), + run_plt(State, plt_add, Files). + +run_plt(State, Analysis, Files) -> + Plt = rebar_state:get(State, plt), + Opts = [{analysis_type, Analysis}, + {init_plt, Plt}, + {from, byte_code}, + {files, Files}], + run_dialyzer(State, Opts). + +build_plt(State, Files) -> + Plt = rebar_state:get(State, plt), + ?INFO("Building PLT with ~b files...", [length(Files)]), + Opts = [{analysis_type, plt_build}, + {output_plt, Plt}, + {files, Files}], + run_dialyzer(State, Opts). + +succ_typings(State, Apps) -> + ?INFO("Doing success typing analysis...", []), + Files = apps_to_files(Apps), + Plt = rebar_state:get(State, plt), + Opts = [{analysis_type, succ_typings}, + {from, byte_code}, + {files, Files}, + {plts, [Plt]}], + run_dialyzer(State, Opts). + +run_dialyzer(State, Opts) -> + Warnings = rebar_state:get(State, dialyzer_warnings, default_warnings()), + Opts2 = [{get_warnings, true}, + {warnings, Warnings} | + Opts], + _ = [?CONSOLE(format_warning(Warning), []) + || Warning <- dialyzer:run(Opts2)], + {ok, State}. + +format_warning(Warning) -> + string:strip(dialyzer:format_warning(Warning), right, $\n). default_warnings() -> [error_handling, -- cgit v1.1 From 16b2dae066484f44e28f707cb58326da0d217bb5 Mon Sep 17 00:00:00 2001 From: James Fish Date: Wed, 19 Nov 2014 18:17:17 +0000 Subject: Improve formatting of dialyzer errors Strip ":0: " from unknown type/function/behaviour warnings --- src/rebar_prv_dialyzer.erl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 7bbaf81..f5d840d 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -211,7 +211,15 @@ run_dialyzer(State, Opts) -> {ok, State}. format_warning(Warning) -> - string:strip(dialyzer:format_warning(Warning), right, $\n). + string:strip(dialyzer_format_warning(Warning), right, $\n). + +dialyzer_format_warning(Warning) -> + case dialyzer:format_warning(Warning) of + ":0: " ++ Warning2 -> + Warning2; + Warning2 -> + Warning2 + end. default_warnings() -> [error_handling, -- cgit v1.1 From a96fddfcde4ab3a71aea01914bd3e7a836db46c0 Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 20 Nov 2014 15:46:54 +0000 Subject: Improve default dialyzer PLT by trying to guess dependencies Try to automatically detect all application dependencies when `plt_apps` is not included in `rebar.config`. Note that this will not follow `runtime_dependencies` in OTP applications. This can be resolved by adding any missing `runtime_dependencies` to `applications` in the .app.src file or including `plt_apps` in `rebar.config`. --- src/rebar_prv_dialyzer.erl | 62 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index f5d840d..7c13417 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -72,11 +72,42 @@ update_plt(State, Apps, Deps) -> get_plt_files(State, Apps, Deps) -> case rebar_state:get(State, plt_apps) of undefined -> - apps_to_files(Deps); + default_plt_files(Apps, Deps); PltApps -> app_names_to_files(PltApps, Apps ++ Deps) end. +default_plt_files(Apps, Deps) -> + DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), + default_plt_files(default_plt_apps() ++ DepApps, Apps, Deps, [], []). + +default_plt_apps() -> + [erts, + kernel, + stdlib]. + +default_plt_files([], _, _, _, Files) -> + Files; +default_plt_files([AppName | DepApps], Apps, Deps, PltApps, Files) -> + case lists:member(AppName, PltApps) of + true -> + default_plt_files(DepApps, Apps, Deps, PltApps, Files); + false -> + {DepApps2, Files2} = app_name_to_info(AppName, Apps, Deps), + DepApps3 = DepApps2 ++ DepApps, + Files3 = Files2 ++ Files, + default_plt_files(DepApps3, Apps, Deps, [AppName | PltApps], Files3) + end. + +app_name_to_info(AppName, Apps, Deps) -> + case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of + {ok, App} -> + % Don't include project app files in plt + {rebar_app_info:applications(App), []}; + error -> + app_name_to_info(AppName, Deps) + end. + apps_to_files(Apps) -> lists:flatmap(fun app_to_files/1, Apps). @@ -102,33 +133,42 @@ module_to_file(Module, EbinDir, Ext) -> end. app_names_to_files(AppNames, Apps) -> - lists:flatmap(fun(AppName) -> app_name_to_files(AppName, Apps) end, - AppNames). + ToFiles = fun(AppName) -> + {_, Files} = app_name_to_info(AppName, Apps), + Files + end, + lists:flatmap(ToFiles, AppNames). -app_name_to_files(AppName, Apps) -> +app_name_to_info(AppName, Apps) -> case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of {ok, App} -> - app_to_files(App); + DepApps = rebar_app_info:applications(App), + Files = app_to_files(App), + {DepApps, Files}; error -> - app_name_to_files(AppName) + app_name_to_info(AppName) end. -app_name_to_files(AppName) -> +app_name_to_info(AppName) -> case code:lib_dir(AppName) of {error, _} -> ?CONSOLE("Unknown application ~s", [AppName]), - []; + {[], []}; AppDir -> - app_dir_to_files(AppDir, AppName) + app_dir_to_info(AppDir, AppName) end. -app_dir_to_files(AppDir, AppName) -> +app_dir_to_info(AppDir, AppName) -> EbinDir = filename:join(AppDir, "ebin"), AppFile = filename:join(EbinDir, atom_to_list(AppName) ++ ".app"), case file:consult(AppFile) of {ok, [{application, AppName, AppDetails}]} -> + DepApps = proplists:get_value(applications, AppDetails, []), + IncApps = proplists:get_value(included_applications, AppDetails, + []), Modules = proplists:get_value(modules, AppDetails, []), - modules_to_files(Modules, EbinDir); + Files = modules_to_files(Modules, EbinDir), + {IncApps ++ DepApps, Files}; _ -> Error = io_lib:format("Could not parse ~p", [AppFile]), throw({dialyzer_error, Error}) -- cgit v1.1 From 16da6d2630d11a8ac9d9c75e3c344415050efac7 Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 20 Nov 2014 15:57:07 +0000 Subject: Add options to skip updating plt or success typing * --update-plt=true|false (false skips updating plt) * --succ-typings=true|false (false skips success typing analysis) --- src/rebar_prv_dialyzer.erl | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 7c13417..e21b2da 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -20,6 +20,8 @@ -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. init(State) -> + Opts = [{update_plt, $u, "update-plt", boolean, "Enable updating the PLT. Default: true"}, + {succ_typings, $s, "succ-typings", boolean, "Enable success typing analysis. Default: true"}], State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, {bare, false}, @@ -27,7 +29,7 @@ init(State) -> {example, "rebar dialyzer"}, {short_desc, "Run the Dialyzer analyzer on the project."}, {desc, ""}, - {opts, []}])), + {opts, Opts}])), {ok, State1}. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. @@ -60,6 +62,15 @@ set_plt_location(State) -> rebar_state:set(State, plt, Plt). update_plt(State, Apps, Deps) -> + {Args, _} = rebar_state:command_parsed_args(State), + case proplists:get_value(update_plt, Args) of + false -> + {ok, State}; + _ -> + do_update_plt(State, Apps, Deps) + end. + +do_update_plt(State, Apps, Deps) -> ?INFO("Updating plt...", []), Files = get_plt_files(State, Apps, Deps), case read_plt(State) of @@ -232,6 +243,15 @@ build_plt(State, Files) -> run_dialyzer(State, Opts). succ_typings(State, Apps) -> + {Args, _} = rebar_state:command_parsed_args(State), + case proplists:get_value(succ_typings, Args) of + false -> + {ok, State}; + _ -> + do_succ_typings(State, Apps) + end. + +do_succ_typings(State, Apps) -> ?INFO("Doing success typing analysis...", []), Files = apps_to_files(Apps), Plt = rebar_state:get(State, plt), @@ -260,7 +280,6 @@ dialyzer_format_warning(Warning) -> Warning2 -> Warning2 end. - default_warnings() -> [error_handling, unmatched_returns, -- cgit v1.1 From 642a71e4cea50e5fb980a374de41f029e347d79d Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 20 Nov 2014 17:25:35 +0000 Subject: Prepare dialyzer command for public use * Add description * Make configuration constitent (prefix with dialyzer_) * Make printed info consistent --- src/rebar_prv_dialyzer.erl | 47 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index e21b2da..c99e9a5 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -27,11 +27,36 @@ init(State) -> {bare, false}, {deps, ?DEPS}, {example, "rebar dialyzer"}, - {short_desc, "Run the Dialyzer analyzer on the project."}, - {desc, ""}, + {short_desc, short_desc()}, + {desc, desc()}, {opts, Opts}])), {ok, State1}. +desc() -> + short_desc() ++ "\n" + "\n" + "This command will build, and keep up-to-date, a suitable PLT and will use " + "it to carry out success typing analysis on the current project.\n" + "\n" + "The following (optional) configurations can be added to a rebar.config:\n" + "`dialyzer_warnings` - a list of dialyzer warnings\n" + "`dialyzer_plt` - the PLT file to use\n" + "`dialyzer_plt_apps` - a list of applications to include in the PLT file*\n" + "\n" + "*If this configuration is not present a selection of applications will be " + "used based on the `applications` and `included_applications` fields in " + "the relevant .app files.\n" + "\n" + "Note that it may take a long time to build the initial PLT file. Once a " + "PLT file (defaults to `.rebar.plt`) has been created for a project it can " + "safely be copied and reused in another project. If a PLT file has been " + "copied from another project this command will do the minimial alterations " + "to the PLT file for use in the new project. This will likely be faster " + "than building a new PLT file from scratch.". + +short_desc() -> + "Run the Dialyzer analyzer on the project.". + -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Dialyzer starting, this may take a while...", []), @@ -57,9 +82,9 @@ format_error(Reason) -> set_plt_location(State) -> BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), - DefaultPlt = filename:join([BuildDir, ".deps.plt"]), - Plt = rebar_state:get(State, plt, DefaultPlt), - rebar_state:set(State, plt, Plt). + DefaultPlt = filename:join([BuildDir, ".rebar.plt"]), + Plt = rebar_state:get(State, dialyzer_plt, DefaultPlt), + rebar_state:set(State, dialyzer_plt, Plt). update_plt(State, Apps, Deps) -> {Args, _} = rebar_state:command_parsed_args(State), @@ -81,7 +106,7 @@ do_update_plt(State, Apps, Deps) -> end. get_plt_files(State, Apps, Deps) -> - case rebar_state:get(State, plt_apps) of + case rebar_state:get(State, dialyzer_plt_apps) of undefined -> default_plt_files(Apps, Deps); PltApps -> @@ -186,7 +211,7 @@ app_dir_to_info(AppDir, AppName) -> end. read_plt(State) -> - Plt = rebar_state:get(State, plt), + Plt = rebar_state:get(State, dialyzer_plt), case dialyzer:plt_info(Plt) of {ok, Info} -> Files = proplists:get_value(files, Info, []), @@ -227,7 +252,7 @@ add_plt(State, Files) -> run_plt(State, plt_add, Files). run_plt(State, Analysis, Files) -> - Plt = rebar_state:get(State, plt), + Plt = rebar_state:get(State, dialyzer_plt), Opts = [{analysis_type, Analysis}, {init_plt, Plt}, {from, byte_code}, @@ -235,8 +260,8 @@ run_plt(State, Analysis, Files) -> run_dialyzer(State, Opts). build_plt(State, Files) -> - Plt = rebar_state:get(State, plt), - ?INFO("Building PLT with ~b files...", [length(Files)]), + Plt = rebar_state:get(State, dialyzer_plt), + ?INFO("Adding ~b files to plt...", [length(Files)]), Opts = [{analysis_type, plt_build}, {output_plt, Plt}, {files, Files}], @@ -254,7 +279,7 @@ succ_typings(State, Apps) -> do_succ_typings(State, Apps) -> ?INFO("Doing success typing analysis...", []), Files = apps_to_files(Apps), - Plt = rebar_state:get(State, plt), + Plt = rebar_state:get(State, dialyzer_plt), Opts = [{analysis_type, succ_typings}, {from, byte_code}, {files, Files}, -- cgit v1.1 From 08698c9403f2dee266ff5d4910a7ee92869b55cd Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 20 Nov 2014 17:28:47 +0000 Subject: Remove inets from escript --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 692ee7d..177037f 100644 --- a/rebar.config +++ b/rebar.config @@ -6,7 +6,7 @@ {escript_incl_extra, [{"priv/templates/*", "."}, {"rebar/include/*", "."}]}. {escript_incl_apps, - [inets, getopt, erlydtl, erlware_commons, relx, providers, rebar]}. + [getopt, erlydtl, erlware_commons, relx, providers, rebar]}. {escript_top_level_app, rebar}. {escript_name, rebar3}. -- cgit v1.1 From 37a88be6629a5e63b7771a8ab6967ced64843814 Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 20 Nov 2014 23:21:47 +0000 Subject: Introduce dialyzer otp versioned plts and base plts Adds a base plt per OTP version that is stored in $HOME/.rebar3/. The base plt is copied to new projects that don't have a plt for the active OTP version. Modules are added/removed to the project's copy based on the project's .app files. --- src/rebar_prv_dialyzer.erl | 208 ++++++++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 88 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index c99e9a5..916d745 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -42,17 +42,18 @@ desc() -> "`dialyzer_warnings` - a list of dialyzer warnings\n" "`dialyzer_plt` - the PLT file to use\n" "`dialyzer_plt_apps` - a list of applications to include in the PLT file*\n" + "`dialyzer_base_plt` - the base PLT file to use**\n" + "`dialyzer_base_plt_dir` - the base PLT directory**\n" + "`dialyzer_base_plt_apps` - a list of applications to include in the base " + "PLT file**\n" "\n" "*If this configuration is not present a selection of applications will be " "used based on the `applications` and `included_applications` fields in " "the relevant .app files.\n" - "\n" - "Note that it may take a long time to build the initial PLT file. Once a " - "PLT file (defaults to `.rebar.plt`) has been created for a project it can " - "safely be copied and reused in another project. If a PLT file has been " - "copied from another project this command will do the minimial alterations " - "to the PLT file for use in the new project. This will likely be faster " - "than building a new PLT file from scratch.". + "**The base PLT is a PLT containing the core OTP applications often " + "required for a project's PLT. One base PLT is created per OTP version and " + "stored in `dialyzer_base_plt_dir` (defaults to $HOME/.rebar3/). A base " + "PLT is used to create a project's initial PLT.". short_desc() -> "Run the Dialyzer analyzer on the project.". @@ -60,13 +61,12 @@ short_desc() -> -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. do(State) -> ?INFO("Dialyzer starting, this may take a while...", []), - State1 = set_plt_location(State), - Apps = rebar_state:project_apps(State1), - Deps = rebar_state:get(State, all_deps, []), + Plt = get_plt_location(State), + Apps = rebar_state:project_apps(State), try - {ok, State2} = update_plt(State1, Apps, Deps), - succ_typings(State2, Apps) + {ok, State1} = update_proj_plt(State, Plt, Apps), + succ_typings(State1, Plt, Apps) catch throw:{dialyzer_error, Error} -> {error, {?MODULE, {error_processing_apps, Error, Apps}}} @@ -80,78 +80,87 @@ format_error(Reason) -> %% Internal functions -set_plt_location(State) -> +get_plt_location(State) -> BuildDir = rebar_state:get(State, base_dir, ?DEFAULT_BASE_DIR), - DefaultPlt = filename:join([BuildDir, ".rebar.plt"]), - Plt = rebar_state:get(State, dialyzer_plt, DefaultPlt), - rebar_state:set(State, dialyzer_plt, Plt). + DefaultPlt = filename:join([BuildDir, default_plt()]), + rebar_state:get(State, dialyzer_plt, DefaultPlt). + +default_plt() -> + ".rebar3.otp-" ++ otp_version() ++ ".plt". + +otp_version() -> + Release = erlang:system_info(otp_release), + try otp_version(Release) of + Vsn -> + Vsn + catch + error:_ -> + Release + end. -update_plt(State, Apps, Deps) -> +otp_version(Release) -> + File = filename:join([code:root_dir(), "releases", Release, "OTP_VERSION"]), + {ok, Contents} = file:read_file(File), + [Vsn] = binary:split(Contents, [<<$\n>>], [global, trim]), + [_ | _] = unicode:characters_to_list(Vsn). + +update_proj_plt(State, Plt, Apps) -> {Args, _} = rebar_state:command_parsed_args(State), case proplists:get_value(update_plt, Args) of false -> {ok, State}; _ -> - do_update_plt(State, Apps, Deps) + do_update_proj_plt(State, Plt, Apps) end. -do_update_plt(State, Apps, Deps) -> +do_update_proj_plt(State, Plt, Apps) -> ?INFO("Updating plt...", []), - Files = get_plt_files(State, Apps, Deps), - case read_plt(State) of + Files = get_plt_files(State, Apps), + case read_plt(State, Plt) of {ok, OldFiles} -> - check_plt(State, OldFiles, Files); + check_plt(State, Plt, OldFiles, Files); {error, no_such_file} -> - build_plt(State, Files) - end. - -get_plt_files(State, Apps, Deps) -> - case rebar_state:get(State, dialyzer_plt_apps) of - undefined -> - default_plt_files(Apps, Deps); - PltApps -> - app_names_to_files(PltApps, Apps ++ Deps) + build_proj_plt(State, Plt, Files) end. -default_plt_files(Apps, Deps) -> +get_plt_files(State, Apps) -> + PltApps = rebar_state:get(State, dialyzer_plt_apps, []), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), - default_plt_files(default_plt_apps() ++ DepApps, Apps, Deps, [], []). + get_plt_files([erts] ++ PltApps ++ DepApps, Apps, [], []). default_plt_apps() -> [erts, kernel, stdlib]. -default_plt_files([], _, _, _, Files) -> +get_plt_files([], _, _, Files) -> Files; -default_plt_files([AppName | DepApps], Apps, Deps, PltApps, Files) -> - case lists:member(AppName, PltApps) of +get_plt_files([AppName | DepApps], Apps, PltApps, Files) -> + case lists:member(AppName, PltApps) orelse app_member(AppName, Apps) of true -> - default_plt_files(DepApps, Apps, Deps, PltApps, Files); + get_plt_files(DepApps, Apps, PltApps, Files); false -> - {DepApps2, Files2} = app_name_to_info(AppName, Apps, Deps), + {DepApps2, Files2} = app_name_to_info(AppName), DepApps3 = DepApps2 ++ DepApps, Files3 = Files2 ++ Files, - default_plt_files(DepApps3, Apps, Deps, [AppName | PltApps], Files3) + get_plt_files(DepApps3, Apps, [AppName | PltApps], Files3) end. -app_name_to_info(AppName, Apps, Deps) -> +app_member(AppName, Apps) -> case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of - {ok, App} -> - % Don't include project app files in plt - {rebar_app_info:applications(App), []}; + {ok, _App} -> + true; error -> - app_name_to_info(AppName, Deps) + false end. apps_to_files(Apps) -> lists:flatmap(fun app_to_files/1, Apps). app_to_files(App) -> - AppDetails = rebar_app_info:app_details(App), - Modules = proplists:get_value(modules, AppDetails, []), - EbinDir = rebar_app_info:ebin_dir(App), - modules_to_files(Modules, EbinDir). + AppName = ec_cnv:to_atom(rebar_app_info:name(App)), + {_, Files} = app_name_to_info(AppName), + Files. modules_to_files(Modules, EbinDir) -> Ext = code:objfile_extension(), @@ -168,23 +177,13 @@ module_to_file(Module, EbinDir, Ext) -> false end. -app_names_to_files(AppNames, Apps) -> +app_names_to_files(AppNames) -> ToFiles = fun(AppName) -> - {_, Files} = app_name_to_info(AppName, Apps), + {_, Files} = app_name_to_info(AppName), Files end, lists:flatmap(ToFiles, AppNames). -app_name_to_info(AppName, Apps) -> - case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of - {ok, App} -> - DepApps = rebar_app_info:applications(App), - Files = app_to_files(App), - {DepApps, Files}; - error -> - app_name_to_info(AppName) - end. - app_name_to_info(AppName) -> case code:lib_dir(AppName) of {error, _} -> @@ -210,8 +209,7 @@ app_dir_to_info(AppDir, AppName) -> throw({dialyzer_error, Error}) end. -read_plt(State) -> - Plt = rebar_state:get(State, dialyzer_plt), +read_plt(_State, Plt) -> case dialyzer:plt_info(Plt) of {ok, Info} -> Files = proplists:get_value(files, Info, []), @@ -223,67 +221,101 @@ read_plt(State) -> throw({dialyzer_error, Error}) end. -check_plt(State, OldList, FilesList) -> +check_plt(State, Plt, OldList, FilesList) -> Old = sets:from_list(OldList), Files = sets:from_list(FilesList), Remove = sets:subtract(Old, Files), - {ok, State1} = remove_plt(State, sets:to_list(Remove)), + {ok, State1} = remove_plt(State, Plt, sets:to_list(Remove)), Check = sets:intersection(Files, Old), - {ok, State2} = check_plt(State1, sets:to_list(Check)), + {ok, State2} = check_plt(State1, Plt, sets:to_list(Check)), Add = sets:subtract(Files, Old), - add_plt(State2, sets:to_list(Add)). + add_plt(State2, Plt, sets:to_list(Add)). -remove_plt(State, []) -> +remove_plt(State, _Plt, []) -> {ok, State}; -remove_plt(State, Files) -> - ?INFO("Removing ~b files from plt...", [length(Files)]), - run_plt(State, plt_remove, Files). +remove_plt(State, Plt, Files) -> + ?INFO("Removing ~b files from ~p...", [length(Files), Plt]), + run_plt(State, Plt, plt_remove, Files). -check_plt(State, []) -> +check_plt(State, _Plt, []) -> {ok, State}; -check_plt(State, Files) -> - ?INFO("Checking ~b files in plt...", [length(Files)]), - run_plt(State, plt_check, Files). +check_plt(State, Plt, Files) -> + ?INFO("Checking ~b files in ~p...", [length(Files), Plt]), + run_plt(State, Plt, plt_check, Files). -add_plt(State, []) -> +add_plt(State, _Plt, []) -> {ok, State}; -add_plt(State, Files) -> - ?INFO("Adding ~b files to plt...", [length(Files)]), - run_plt(State, plt_add, Files). +add_plt(State, Plt, Files) -> + ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), + run_plt(State, Plt, plt_add, Files). -run_plt(State, Analysis, Files) -> - Plt = rebar_state:get(State, dialyzer_plt), +run_plt(State, Plt, Analysis, Files) -> Opts = [{analysis_type, Analysis}, {init_plt, Plt}, {from, byte_code}, {files, Files}], run_dialyzer(State, Opts). -build_plt(State, Files) -> - Plt = rebar_state:get(State, dialyzer_plt), - ?INFO("Adding ~b files to plt...", [length(Files)]), +build_plt(State, Plt, Files) -> + ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), Opts = [{analysis_type, plt_build}, {output_plt, Plt}, {files, Files}], run_dialyzer(State, Opts). -succ_typings(State, Apps) -> +build_proj_plt(State, Plt, Files) -> + BasePlt = get_base_plt_location(State), + BaseFiles = get_base_plt_files(State), + {ok, State1} = update_base_plt(State, BasePlt, BaseFiles), + ?INFO("Copying ~p to ~p...", [BasePlt, Plt]), + case file:copy(BasePlt, Plt) of + {ok, _} -> + check_plt(State1, Plt, BaseFiles, Files); + {error, Reason} -> + Error = io_lib:format("Could not copy PLT from ~p to ~p: ~p", + [BasePlt, Plt, file:format_error(Reason)]), + throw({dialyzer_error, Error}) + end. + +get_base_plt_location(State) -> + Home = rebar_utils:home_dir(), + GlobalConfigDir = filename:join(Home, ?CONFIG_DIR), + BaseDir = rebar_state:get(State, dialyzer_base_plt_dir, GlobalConfigDir), + BasePlt = rebar_state:get(State, dialyzer_base_plt, default_plt()), + filename:join(BaseDir, BasePlt). + +get_base_plt_files(State) -> + BasePltApps = rebar_state:get(State, dialyzer_base_plt_apps, + default_plt_apps()), + app_names_to_files(BasePltApps). + +update_base_plt(State, BasePlt, BaseFiles) -> + ?INFO("Updating base plt...", []), + case read_plt(State, BasePlt) of + {ok, OldBaseFiles} -> + check_plt(State, BasePlt, OldBaseFiles, BaseFiles); + {error, no_such_file} -> + _ = filelib:ensure_dir(BasePlt), + build_plt(State, BasePlt, BaseFiles) + end. + +succ_typings(State, Plt, Apps) -> {Args, _} = rebar_state:command_parsed_args(State), case proplists:get_value(succ_typings, Args) of false -> {ok, State}; _ -> - do_succ_typings(State, Apps) + do_succ_typings(State, Plt, Apps) end. -do_succ_typings(State, Apps) -> +do_succ_typings(State, Plt, Apps) -> ?INFO("Doing success typing analysis...", []), Files = apps_to_files(Apps), - Plt = rebar_state:get(State, dialyzer_plt), + ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]), Opts = [{analysis_type, succ_typings}, {from, byte_code}, {files, Files}, - {plts, [Plt]}], + {init_plt, Plt}], run_dialyzer(State, Opts). run_dialyzer(State, Opts) -> -- cgit v1.1 From 3dff706e14e5240b68d4f961e6e5804191b9f1da Mon Sep 17 00:00:00 2001 From: James Fish Date: Fri, 21 Nov 2014 21:54:07 +0000 Subject: Clarify how apps are selected for the plt files --- src/rebar_prv_dialyzer.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 916d745..907a6f4 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -47,9 +47,9 @@ desc() -> "`dialyzer_base_plt_apps` - a list of applications to include in the base " "PLT file**\n" "\n" - "*If this configuration is not present a selection of applications will be " - "used based on the `applications` and `included_applications` fields in " - "the relevant .app files.\n" + "*The applications in `dialyzer_base_plt_apps` and any `applications` and " + "`included_applications` listed in their .app files will be added to the " + "list.\n" "**The base PLT is a PLT containing the core OTP applications often " "required for a project's PLT. One base PLT is created per OTP version and " "stored in `dialyzer_base_plt_dir` (defaults to $HOME/.rebar3/). A base " @@ -124,9 +124,11 @@ do_update_proj_plt(State, Plt, Apps) -> end. get_plt_files(State, Apps) -> + BasePltApps = rebar_state:get(State, dialyzer_base_plt_apps, + default_plt_apps()), PltApps = rebar_state:get(State, dialyzer_plt_apps, []), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), - get_plt_files([erts] ++ PltApps ++ DepApps, Apps, [], []). + get_plt_files(BasePltApps ++ PltApps ++ DepApps, Apps, [], []). default_plt_apps() -> [erts, -- cgit v1.1 From c948769512e495105d2d748d3876fb0f28964590 Mon Sep 17 00:00:00 2001 From: James Fish Date: Fri, 21 Nov 2014 21:54:51 +0000 Subject: Add crypto to base plt by default --- src/rebar_prv_dialyzer.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 907a6f4..6b6464c 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -132,6 +132,7 @@ get_plt_files(State, Apps) -> default_plt_apps() -> [erts, + crypto, kernel, stdlib]. -- cgit v1.1 From 16c9a232120524515c4db088a3ba03f6478f189e Mon Sep 17 00:00:00 2001 From: James Fish Date: Fri, 21 Nov 2014 21:55:35 +0000 Subject: Cleanup dialyzer provider --- src/rebar_prv_dialyzer.erl | 75 +++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 6b6464c..93893cc 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -157,36 +157,6 @@ app_member(AppName, Apps) -> false end. -apps_to_files(Apps) -> - lists:flatmap(fun app_to_files/1, Apps). - -app_to_files(App) -> - AppName = ec_cnv:to_atom(rebar_app_info:name(App)), - {_, Files} = app_name_to_info(AppName), - Files. - -modules_to_files(Modules, EbinDir) -> - Ext = code:objfile_extension(), - Mod2File = fun(Module) -> module_to_file(Module, EbinDir, Ext) end, - rebar_utils:filtermap(Mod2File, Modules). - -module_to_file(Module, EbinDir, Ext) -> - File = filename:join(EbinDir, atom_to_list(Module) ++ Ext), - case filelib:is_file(File) of - true -> - {true, File}; - false -> - ?CONSOLE("Unknown module ~s", [Module]), - false - end. - -app_names_to_files(AppNames) -> - ToFiles = fun(AppName) -> - {_, Files} = app_name_to_info(AppName), - Files - end, - lists:flatmap(ToFiles, AppNames). - app_name_to_info(AppName) -> case code:lib_dir(AppName) of {error, _} -> @@ -212,6 +182,21 @@ app_dir_to_info(AppDir, AppName) -> throw({dialyzer_error, Error}) end. +modules_to_files(Modules, EbinDir) -> + Ext = code:objfile_extension(), + Mod2File = fun(Module) -> module_to_file(Module, EbinDir, Ext) end, + rebar_utils:filtermap(Mod2File, Modules). + +module_to_file(Module, EbinDir, Ext) -> + File = filename:join(EbinDir, atom_to_list(Module) ++ Ext), + case filelib:is_file(File) of + true -> + {true, File}; + false -> + ?CONSOLE("Unknown module ~s", [Module]), + false + end. + read_plt(_State, Plt) -> case dialyzer:plt_info(Plt) of {ok, Info} -> @@ -259,13 +244,6 @@ run_plt(State, Plt, Analysis, Files) -> {files, Files}], run_dialyzer(State, Opts). -build_plt(State, Plt, Files) -> - ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), - Opts = [{analysis_type, plt_build}, - {output_plt, Plt}, - {files, Files}], - run_dialyzer(State, Opts). - build_proj_plt(State, Plt, Files) -> BasePlt = get_base_plt_location(State), BaseFiles = get_base_plt_files(State), @@ -292,6 +270,13 @@ get_base_plt_files(State) -> default_plt_apps()), app_names_to_files(BasePltApps). +app_names_to_files(AppNames) -> + ToFiles = fun(AppName) -> + {_, Files} = app_name_to_info(AppName), + Files + end, + lists:flatmap(ToFiles, AppNames). + update_base_plt(State, BasePlt, BaseFiles) -> ?INFO("Updating base plt...", []), case read_plt(State, BasePlt) of @@ -302,6 +287,13 @@ update_base_plt(State, BasePlt, BaseFiles) -> build_plt(State, BasePlt, BaseFiles) end. +build_plt(State, Plt, Files) -> + ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), + Opts = [{analysis_type, plt_build}, + {output_plt, Plt}, + {files, Files}], + run_dialyzer(State, Opts). + succ_typings(State, Plt, Apps) -> {Args, _} = rebar_state:command_parsed_args(State), case proplists:get_value(succ_typings, Args) of @@ -321,6 +313,14 @@ do_succ_typings(State, Plt, Apps) -> {init_plt, Plt}], run_dialyzer(State, Opts). +apps_to_files(Apps) -> + lists:flatmap(fun app_to_files/1, Apps). + +app_to_files(App) -> + AppName = ec_cnv:to_atom(rebar_app_info:name(App)), + {_, Files} = app_name_to_info(AppName), + Files. + run_dialyzer(State, Opts) -> Warnings = rebar_state:get(State, dialyzer_warnings, default_warnings()), Opts2 = [{get_warnings, true}, @@ -340,6 +340,7 @@ dialyzer_format_warning(Warning) -> Warning2 -> Warning2 end. + default_warnings() -> [error_handling, unmatched_returns, -- cgit v1.1 From 4ee0157b9653a395880445e72180df1d03e90e15 Mon Sep 17 00:00:00 2001 From: James Fish Date: Fri, 21 Nov 2014 22:04:35 +0000 Subject: Add configuration to hide warnings when updating plt --- src/rebar_prv_dialyzer.erl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 93893cc..61b25e1 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -42,6 +42,8 @@ desc() -> "`dialyzer_warnings` - a list of dialyzer warnings\n" "`dialyzer_plt` - the PLT file to use\n" "`dialyzer_plt_apps` - a list of applications to include in the PLT file*\n" + "`dialyzer_plt_warnings` - display warnings when updating a PLT file " + "(boolean)\n" "`dialyzer_base_plt` - the base PLT file to use**\n" "`dialyzer_base_plt_dir` - the base PLT directory**\n" "`dialyzer_base_plt_apps` - a list of applications to include in the base " @@ -238,7 +240,9 @@ add_plt(State, Plt, Files) -> run_plt(State, Plt, plt_add, Files). run_plt(State, Plt, Analysis, Files) -> + GetWarnings = rebar_state:get(State, dialyzer_plt_warnings, false), Opts = [{analysis_type, Analysis}, + {get_warnings, GetWarnings}, {init_plt, Plt}, {from, byte_code}, {files, Files}], @@ -289,7 +293,9 @@ update_base_plt(State, BasePlt, BaseFiles) -> build_plt(State, Plt, Files) -> ?INFO("Adding ~b files to ~p...", [length(Files), Plt]), + GetWarnings = rebar_state:get(State, dialyzer_plt_warnings, false), Opts = [{analysis_type, plt_build}, + {get_warnings, GetWarnings}, {output_plt, Plt}, {files, Files}], run_dialyzer(State, Opts). @@ -308,6 +314,7 @@ do_succ_typings(State, Plt, Apps) -> Files = apps_to_files(Apps), ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]), Opts = [{analysis_type, succ_typings}, + {get_warnings, true}, {from, byte_code}, {files, Files}, {init_plt, Plt}], @@ -323,9 +330,7 @@ app_to_files(App) -> run_dialyzer(State, Opts) -> Warnings = rebar_state:get(State, dialyzer_warnings, default_warnings()), - Opts2 = [{get_warnings, true}, - {warnings, Warnings} | - Opts], + Opts2 = [{warnings, Warnings} | Opts], _ = [?CONSOLE(format_warning(Warning), []) || Warning <- dialyzer:run(Opts2)], {ok, State}. -- cgit v1.1