diff options
| -rw-r--r-- | src/rebar_prv_dialyzer.erl | 162 | ||||
| -rw-r--r-- | 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 \"<prefix>_<otp_release>_plt\".\n" -    "****The base PLT is a PLT containing the core applications often required " +    "**PLT files are named \"<prefix>_<otp_release>_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() -> | 
