From bcfd8d6f80f551e1b2a0e23eaa6b5ff2c7da5b15 Mon Sep 17 00:00:00 2001 From: James Fish Date: Thu, 3 Nov 2016 15:45:41 +0000 Subject: Add exclude_apps/mods, plt_extra_mods, base_plt_mods config * exclude_apps - never use applications for PLT/analysis * base_plt_mods - add modules to base PLT (overrules exclude_apps) * plt_extra_mods - add modules to PLT (overrules exclude_apps) * exclude_mods - never use modules for PLT/analysis (overrules all) --- src/rebar_prv_dialyzer.erl | 162 +++++++++++++++++++++++++++--------------- test/rebar_dialyzer_SUITE.erl | 38 +++++++++- 2 files changed, 141 insertions(+), 59 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index fc13de1..44dc0d2 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -47,26 +47,33 @@ desc() -> "`plt_apps` - the strategy for determining the applications which included " "in the PLT file, `top_level_deps` to include just the direct dependencies " "or `all_deps` to include all nested dependencies*\n" - "`plt_extra_apps` - a list of applications to include in the PLT file**\n" + "`plt_extra_apps` - a list of extra applications to include in the PLT " + "file\n" + "`plt_extra_mods` - a list of extra modules to includes in the PLT file\n" "`plt_location` - the location of the PLT file, `local` to store in the " "profile's base directory (default) or a custom directory.\n" - "`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"***\n" + "`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"**\n" "`base_plt_apps` - a list of applications to include in the base " - "PLT file****\n" + "PLT file***\n" + "`base_plt_mods` - a list of modules to include in the base " + "PLT file***\n" "`base_plt_location` - the location of base PLT file, `global` to store in " - "$HOME/.cache/rebar3 (default) or a custom directory****\n" + "$HOME/.cache/rebar3 (default) or a custom directory***\n" "`base_plt_prefix` - the prefix to the base PLT file, defaults to " - "\"rebar3\"*** ****\n" + "\"rebar3\"** ***\n" + "`exclude_apps` - a list of applications to exclude from PLT files and " + "success typing analysis, `plt_extra_mods` and `base_plt_mods` can add " + "modules from excluded applications\n" + "`exclude_mods` - a list of modules to exclude from PLT files and " + "success typing analysis\n" "\n" "For example, to warn on unmatched returns: \n" "{dialyzer, [{warnings, [unmatched_returns]}]}.\n" "\n" "*The direct dependent applications are listed in `applications` and " "`included_applications` of their .app files.\n" - "**The applications in `base_plt_apps` will be added to the " - "list. \n" - "***PLT files are named \"__plt\".\n" - "****The base PLT is a PLT containing the core applications often required " + "**PLT files are named \"__plt\".\n" + "***The base PLT is a PLT containing the core applications often required " "for a project's PLT. One base PLT is created per OTP version and " "stored in `base_plt_location`. A base PLT is used to build project PLTs." "\n". @@ -90,6 +97,10 @@ do(State) -> ?PRV_ERROR({dialyzer_warnings, Warnings}); throw:{unknown_application, _} = Error -> ?PRV_ERROR(Error); + throw:{unknown_module, _} = Error -> + ?PRV_ERROR(Error); + throw:{duplicate_module, _, _, _} = Error -> + ?PRV_ERROR(Error); throw:{output_file_error, _, _} = Error -> ?PRV_ERROR(Error) after @@ -110,6 +121,10 @@ format_error({dialyzer_warnings, Warnings}) -> io_lib:format("Warnings occured running dialyzer: ~b", [Warnings]); format_error({unknown_application, App}) -> io_lib:format("Could not find application: ~s", [App]); +format_error({unknown_module, Mod}) -> + io_lib:format("Could not find module: ~s", [Mod]); +format_error({duplicate_module, Mod, File1, File2}) -> + io_lib:format("Duplicates of module ~s: ~s ~s", [Mod, File1, File2]); format_error({output_file_error, File, Error}) -> Error1 = file:format_error(Error), io_lib:format("Failed to write to ~s: ~s", [File, Error1]); @@ -178,45 +193,45 @@ do_update_proj_plt(State, Plt, Output) -> end. proj_plt_files(State) -> - BasePltApps = get_config(State, base_plt_apps, default_plt_apps()), - PltApps = get_config(State, plt_extra_apps, []), + BasePltApps = base_plt_apps(State), + PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps, + BasePltMods = get_config(State, base_plt_mods, []), + PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods, + Apps = proj_apps(State), + DepApps = proj_deps(State), + get_files(State, DepApps ++ PltApps, Apps -- PltApps, PltMods, []). + +proj_apps(State) -> + [ec_cnv:to_atom(rebar_app_info:name(App)) || + App <- rebar_state:project_apps(State)]. + +proj_deps(State) -> Apps = rebar_state:project_apps(State), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), - DepApps1 = - case get_config(State, plt_apps, top_level_deps) of - top_level_deps -> DepApps; - all_deps -> collect_nested_dependent_apps(DepApps) - end, - get_plt_files(BasePltApps ++ PltApps ++ DepApps1, Apps). - -default_plt_apps() -> - [erts, - crypto, - kernel, - stdlib]. - -get_plt_files(DepApps, Apps) -> + case get_config(State, plt_apps, top_level_deps) of + top_level_deps -> DepApps; + all_deps -> collect_nested_dependent_apps(DepApps) + end. + +get_files(State, Apps, SkipApps, Mods, SkipMods) -> ?INFO("Resolving files...", []), - get_plt_files(DepApps, Apps, [], []). + ExcludeApps = get_config(State, exclude_apps, []), + Files = apps_files(Apps, ExcludeApps ++ SkipApps, dict:new()), + ExcludeMods = get_config(State, exclude_mods, []), + Files2 = mods_files(Mods, ExcludeMods ++ SkipMods, Files), + dict:fold(fun(_, File, Acc) -> [File | Acc] end, [], Files2). -get_plt_files([], _, _, Files) -> +apps_files([], _, Files) -> Files; -get_plt_files([AppName | DepApps], Apps, PltApps, Files) -> - case lists:member(AppName, PltApps) orelse app_member(AppName, Apps) of +apps_files([AppName | DepApps], SkipApps, Files) -> + case lists:member(AppName, SkipApps) of true -> - get_plt_files(DepApps, Apps, PltApps, Files); + apps_files(DepApps, SkipApps, Files); false -> - Files2 = app_files(AppName), - ?DEBUG("~s files: ~p", [AppName, Files2]), - get_plt_files(DepApps, Apps, [AppName | PltApps], Files2 ++ Files) - end. - -app_member(AppName, Apps) -> - case rebar_app_utils:find(ec_cnv:to_binary(AppName), Apps) of - {ok, _App} -> - true; - error -> - false + AppFiles = app_files(AppName), + ?DEBUG("~s modules: ~p", [AppName, dict:fetch_keys(AppFiles)]), + Files2 = merge_files(Files, AppFiles), + apps_files(DepApps, [AppName | SkipApps], Files2) end. app_files(AppName) -> @@ -244,9 +259,41 @@ check_ebin(EbinDir) -> end. ebin_files(EbinDir) -> - Wildcard = "*" ++ code:objfile_extension(), - [filename:join(EbinDir, File) || - File <- filelib:wildcard(Wildcard, EbinDir)]. + Ext = code:objfile_extension(), + Wildcard = "*" ++ Ext, + Files = filelib:wildcard(Wildcard, EbinDir), + Store = fun(File, Mods) -> + Mod = list_to_atom(filename:basename(File, Ext)), + Absname = filename:join(EbinDir, File), + dict:store(Mod, Absname, Mods) + end, + lists:foldl(Store, dict:new(), Files). + +merge_files(Files1, Files2) -> + Duplicate = fun(Mod, File1, File2) -> + throw({duplicate_module, Mod, File1, File2}) + end, + dict:merge(Duplicate, Files1, Files2). + +mods_files(Mods, SkipMods, Files) -> + Keep = fun(File) -> File end, + Ensure = fun(Mod, Acc) -> + case lists:member(Mod, SkipMods) of + true -> + Acc; + false -> + dict:update(Mod, Keep, mod_file(Mod), Acc) + end + end, + Files2 = lists:foldl(Ensure, Files, Mods), + lists:foldl(fun dict:erase/2, Files2, SkipMods). + +mod_file(Mod) -> + File = atom_to_list(Mod) ++ code:objfile_extension(), + case code:where_is_file(File) of + non_existing -> throw({unknown_module, Mod}); + Absname -> Absname + end. read_plt(_State, Plt) -> Vsn = dialyzer_version(), @@ -355,9 +402,12 @@ get_base_plt(State) -> end. base_plt_files(State) -> - BasePltApps = get_config(State, base_plt_apps, default_plt_apps()), - Apps = rebar_state:project_apps(State), - get_plt_files(BasePltApps, Apps). + BasePltApps = base_plt_apps(State), + BasePltMods = get_config(State, base_plt_mods, []), + get_files(State, BasePltApps, [], BasePltMods, []). + +base_plt_apps(State) -> + get_config(State, base_plt_apps, [erts, crypto, kernel, stdlib]). update_base_plt(State, BasePlt, Output, BaseFiles) -> case read_plt(State, BasePlt) of @@ -394,9 +444,8 @@ succ_typings(State, Plt, Output) -> false -> {0, State}; _ -> - Apps = rebar_state:project_apps(State), ?INFO("Doing success typing analysis...", []), - Files = apps_to_files(Apps), + Files = proj_files(State), succ_typings(State, Plt, Output, Files) end. @@ -412,14 +461,13 @@ succ_typings(State, Plt, Output, Files) -> {init_plt, Plt}], run_dialyzer(State, Opts, Output). -apps_to_files(Apps) -> - ?INFO("Resolving files...", []), - [File || App <- Apps, - File <- app_to_files(App)]. - -app_to_files(App) -> - AppName = ec_cnv:to_atom(rebar_app_info:name(App)), - app_files(AppName). +proj_files(State) -> + Apps = proj_apps(State), + BasePltApps = get_config(State, base_plt_apps, []), + PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps, + BasePltMods = get_config(State, base_plt_mods, []), + PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods, + get_files(State, Apps, PltApps, [], PltMods). run_dialyzer(State, Opts, Output) -> %% dialyzer may return callgraph warnings when get_warnings is false diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl index e5d8c52..d0a3611 100644 --- a/test/rebar_dialyzer_SUITE.erl +++ b/test/rebar_dialyzer_SUITE.erl @@ -14,7 +14,8 @@ update_base_plt/1, update_app_plt/1, build_release_plt/1, - plt_apps_option/1]). + plt_apps_option/1, + exclude_and_extra/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -57,7 +58,7 @@ all() -> groups() -> [{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]}, - {build_and_check, [build_release_plt, plt_apps_option]}, + {build_and_check, [build_release_plt, plt_apps_option, exclude_and_extra]}, {update, [update_base_plt, update_app_plt]}]. empty_base_plt(Config) -> @@ -275,6 +276,39 @@ plt_apps_option(Config) -> {ok, PltFiles2} = plt_files(Plt), ?assertEqual([App1, App2, erts], get_apps_from_beam_files(PltFiles2)). +exclude_and_extra(Config) -> + AppDir = ?config(apps, Config), + RebarConfig = ?config(rebar_config, Config), + BasePlt = ?config(base_plt, Config), + Plt = ?config(plt, Config), + + {value, {dialyzer, Opts}, Rest} = lists:keytake(dialyzer, 1, RebarConfig), + % Remove erts => [] + % Add erlang+zlib => [erlang, zlib], + % Add erl_prim_loader+init => [erl_prim_loader, init, erlang, zlib] + % Remove zlib+init => [erl_prim_loader, erlang] + Opts2 = [{exclude_apps, [erts]}, + {base_plt_mods, [erlang, zlib]}, + {plt_extra_mods, [erl_prim_loader, init]}, + {exclude_mods, [zlib, init]} | + Opts], + RebarConfig2 = [{dialyzer, Opts2} | Rest], + + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(AppDir, Name, Vsn, [erts]), + + rebar_test_utils:run_and_check(Config, RebarConfig2, ["dialyzer"], + {ok, [{app, Name}]}), + + Erlang = code:where_is_file("erlang.beam"), + {ok, BasePltFiles} = plt_files(BasePlt), + ?assertEqual([Erlang], BasePltFiles), + + Pair = lists:sort([Erlang, code:where_is_file("erl_prim_loader.beam")]), + {ok, PltFiles} = plt_files(Plt), + ?assertEqual(Pair, PltFiles). + %% Helpers erts_files() -> -- cgit v1.1