From f371d801e5ceeee5df2666b04b8daee9ce77c384 Mon Sep 17 00:00:00 2001 From: Maxim Fedorov Date: Fri, 13 Mar 2020 11:22:01 -0700 Subject: [PATCH] rebar3 dialyzer support: single-app success typing, project apps in PLT This patch enables two features: * in rebar.config, {diazlyer, [{plt_apps, all_apps}]} makes rebar3 to add all project files into PLT for subsequent analysis * rebar3 ct --app=myapp allows single application success typing This allows workflow suitable for large codebases: * "rebar3 ct dialyzer --succ-typings=false" to build PLT with all apps * "rebar3 ct dialyzer --app=myapp" to perform success typing of myapp --- src/rebar_prv_dialyzer.erl | 37 +++++++++++++++++++------- test/rebar_dialyzer_SUITE.erl | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/rebar_prv_dialyzer.erl b/src/rebar_prv_dialyzer.erl index 57bcbf309..9a59cb1ae 100644 --- a/src/rebar_prv_dialyzer.erl +++ b/src/rebar_prv_dialyzer.erl @@ -27,6 +27,7 @@ init(State) -> {base_plt_location, undefined, "base-plt-location", string, "The location of base PLT file, defaults to $HOME/.cache/rebar3"}, {plt_location, undefined, "plt-location", string, "The location of the PLT file, defaults to the profile's base directory"}, {plt_prefix, undefined, "plt-prefix", string, "The prefix to the PLT file, defaults to \"rebar3\"" }, + {app, $a, "app", string, "Perform success typing analysis of a single application"}, {base_plt_prefix, undefined, "base-plt-prefix", string, "The prefix to the base PLT file, defaults to \"rebar3\"" }, {statistics, undefined, "statistics", boolean, "Print information about the progress of execution. Default: false" }], State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER}, @@ -51,7 +52,8 @@ desc() -> "`get_warnings` - display warnings when altering a PLT file (boolean)\n" "`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" + "or `all_deps` to include all nested dependencies " + "or `all_apps` to include all project apps and nested dependencies*\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" @@ -210,20 +212,24 @@ proj_plt_files(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, [], []). + DepApps = lists:usort(proj_plt_apps(State) ++ PltApps), + get_files(State, DepApps, [], PltMods, [], []). proj_apps(State) -> [ec_cnv:to_atom(rebar_app_info:name(App)) || App <- rebar_state:project_apps(State)]. -proj_deps(State) -> +proj_plt_apps(State) -> Apps = rebar_state:project_apps(State), DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps), + ProjApps = proj_apps(State), case get_config(State, plt_apps, top_level_deps) of - top_level_deps -> DepApps; - all_deps -> collect_nested_dependent_apps(DepApps) + top_level_deps -> + DepApps -- ProjApps; + all_deps -> + collect_nested_dependent_apps(DepApps) -- ProjApps; + all_apps -> + proj_apps(State) ++ collect_nested_dependent_apps(DepApps) end. get_files(State, Apps, SkipApps, Mods, SkipMods, ExtraDirs) -> @@ -474,7 +480,7 @@ succ_typings(Args, State, Plt, Output) -> {0, State}; _ -> ?INFO("Doing success typing analysis...", []), - Files = proj_files(State), + Files = proj_files(proplists:get_value(app, Args), State), succ_typings_(State, Plt, Output, Files) end. @@ -490,8 +496,19 @@ succ_typings_(State, Plt, Output, Files) -> {init_plt, Plt}], run_dialyzer(State, Opts, Output). -proj_files(State) -> - Apps = proj_apps(State), +succ_typing_apps(undefined, ProjApps) -> + ProjApps; +succ_typing_apps(App, ProjApps) -> + try + true = lists:member(ec_cnv:to_atom(App), ProjApps), + [ec_cnv:to_atom(App)] + catch + error:_ -> + throw({unknown_application, App}) + end. + +proj_files(SingleApp, State) -> + Apps = succ_typing_apps(SingleApp, 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, []), diff --git a/test/rebar_dialyzer_SUITE.erl b/test/rebar_dialyzer_SUITE.erl index 4cfedff78..161bdc3dc 100644 --- a/test/rebar_dialyzer_SUITE.erl +++ b/test/rebar_dialyzer_SUITE.erl @@ -17,6 +17,7 @@ plt_apps_option/1, exclude_and_extra/1, cli_args/1, + single_app_succ_typing/1, extra_src_dirs/1]). -include_lib("common_test/include/ct.hrl"). @@ -61,6 +62,7 @@ all() -> groups() -> [{empty, [empty_base_plt, empty_app_plt, empty_app_succ_typings]}, {build_and_check, [cli_args, + single_app_succ_typing, build_release_plt, plt_apps_option, exclude_and_extra, @@ -352,6 +354,54 @@ cli_args(Config) -> {ok, PltFiles} = plt_files(Plt), ?assertEqual(ErtsFiles, PltFiles). +single_app_succ_typing(Config) -> + AppDir = ?config(apps, Config), + State = ?config(state, Config), + Plt = ?config(plt, Config), + RebarConfig = ?config(rebar_config, Config), + %% test workflow: + %% (a) build PLT containing all project apps and dependencies (no success typing yet)! + %% (b) perform success-typing for all apps (warnings expected) + %% (c) perform success-typing for app with and without warnings + Name = rebar_test_utils:create_random_name("app1_"), + Vsn = rebar_test_utils:create_random_vsn(), + %% contains warnings in tests + rebar_test_utils:create_eunit_app(AppDir, Name, Vsn, []), + %% second app, depending on first, should not produce warnings + App1 = ec_cnv:to_atom(Name), + %%% second app depends on first + Name2 = rebar_test_utils:create_random_name("app2_"), + Vsn2 = rebar_test_utils:create_random_vsn(), + rebar_test_utils:create_app(filename:join([AppDir,"apps",Name2]), Name2, Vsn2, + [App1]), % App2 depends on App1 + App2 = ec_cnv:to_atom(Name2), + + %% start with building all apps into PLT + RebarConfig2 = merge_config([{dialyzer, [{plt_apps, all_apps}]}], + RebarConfig), + {ok, _} = + rebar3:run(rebar_state:new(State, RebarConfig2, AppDir), ["dialyzer", "--succ-typings=false"]), + %% verify all project apps are in PLT + {ok, PltFiles} = plt_files(Plt), + ?assertEqual([App1, App2, erts], get_apps_from_beam_files(PltFiles)), + + %% warnings when apps are not specified + Command0 = ["as", "test", "dialyzer"], + % there are few warnings for generated test (see rebar_test_utils:erl_eunit_suite_file/1) + {error, {rebar_prv_dialyzer, {dialyzer_warnings, _}}} = + rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command0), + + %% warnings from App + Command1 = ["as", "test", "dialyzer", "--app=" ++ Name], + % there are few warnings for generated test (see rebar_test_utils:erl_eunit_suite_file/1) + {error, {rebar_prv_dialyzer, {dialyzer_warnings, _}}} = + rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command1), + + %% no warnings from App2 + Command2 = ["as", "test", "dialyzer", "--app=" ++ Name2], + {ok, _} = + rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command2). + extra_src_dirs(Config) -> AppDir = ?config(apps, Config), State = ?config(state, Config),