Ver a proveniência

Fixing duplicate macro definition in umbrella edoc

When in umbrella mode (or through multiple profiles), users can specify
macros for EDocs based on either the {def, ...} or the {macros, ...}
arguments.

This patch replaces the prior options merging for umbrellas to use the
rebar3 tup_umerge utils to remove identical duplicates while preserving
correct ordering, and manually merges the {macros, ...} definitions
while ke eping the correct precedence rules since these appear (given
their behaviour) to be all individually extracted and passed as `{d,
...}` to  the compiler so that epp expands them. This compiler
function freaks out on any re-defined macros and explodes.

Do note that the macros with `{def, ...}` are edoc macros and do not
suffer from that issue, safely deduplicating multiple definitions.
pull/2133/head
Fred Hebert há 5 anos
ascendente
cometimento
28a50b8a36
2 ficheiros alterados com 81 adições e 2 eliminações
  1. +25
    -1
      src/rebar_prv_edoc.erl
  2. +56
    -1
      test/rebar_edoc_SUITE.erl

+ 25
- 1
src/rebar_prv_edoc.erl Ver ficheiro

@ -47,7 +47,7 @@ do(State) ->
AppDir = rebar_app_info:dir(AppInfo),
AppOpts = rebar_app_info:opts(AppInfo),
%% order of the merge is important to allow app opts overrides
AppEdocOpts = rebar_opts:get(AppOpts, edoc_opts, []) ++ EdocOptsAcc,
AppEdocOpts = merge_opts(rebar_opts:get(AppOpts, edoc_opts, []), EdocOptsAcc),
AppRes = (catch edoc:application(list_to_atom(AppName), AppDir, AppEdocOpts)),
rebar_hooks:run_all_hooks(Cwd, post, ?PROVIDER, Providers, AppInfo, State),
case {AppRes, ShouldAccPaths} of
@ -93,3 +93,27 @@ add_to_paths([{doc_path, Paths}|T], Path) ->
[{doc_path, [Path | Paths]} | T];
add_to_paths([H|T], Path) ->
[H | add_to_paths(T, Path)].
merge_opts(AppOpts, BaseOpts) ->
merge_epp_macros(rebar_utils:tup_umerge(AppOpts, BaseOpts)).
%% @private the `{macros, ...}' definitions for epp can't be
%% containing duplicate definitions even if multiple macro lists
%% are supported, so we need to manually remove duplicates
%% and merge the many lists into a single one.
merge_epp_macros([]) ->
[];
merge_epp_macros([{macros, M1}, {macros, M2} | Rest]) ->
NewMacros = dedupe_macros(lists:usort(M1), lists:usort(M2)),
merge_epp_macros( [{macros, NewMacros} | Rest]);
merge_epp_macros([H | T]) ->
[H | merge_epp_macros(T)].
dedupe_macros([], Bs) -> Bs;
dedupe_macros(As, []) -> As;
dedupe_macros([{K, V1} | As], [{K, _} | Bs]) ->
[{K, V1} | dedupe_macros(As, Bs)];
dedupe_macros([{KA, VA} | As], [{KB, VB} | Bs]) ->
if KA < KB -> [{KA, VA} | dedupe_macros(As, [{KB, VB} | Bs])];
KA > KB -> [{KB, VB} | dedupe_macros([{KA, VA} | As], Bs)]
end.

+ 56
- 1
test/rebar_edoc_SUITE.erl Ver ficheiro

@ -3,7 +3,7 @@
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
all() -> [multiapp, error_survival].
all() -> [multiapp, multiapp_macros, error_survival].
init_per_testcase(multiapp, Config) ->
application:load(rebar),
@ -17,6 +17,19 @@ init_per_testcase(multiapp, Config) ->
State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}
,{root_dir, AppsDir}]),
[{apps, AppsDir}, {state, State}, {name, Name} | Config];
init_per_testcase(multiapp_macros, Config) ->
application:load(rebar),
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
Name = rebar_test_utils:create_random_name("multiapp_macros"),
AppsDir = filename:join([PrivDir, rebar_test_utils:create_random_name(Name)]),
ec_file:copy(filename:join([DataDir, "foo"]), AppsDir, [recursive]),
ok = ec_file:remove(filename:join([AppsDir, "apps", "foo"]), [recursive]),
%Verbosity = rebar3:log_level(),
%rebar_log:init(command_line, Verbosity),
State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}
,{root_dir, AppsDir}]),
[{apps, AppsDir}, {state, State}, {name, Name} | Config];
init_per_testcase(error_survival, Config) ->
application:load(rebar),
DataDir = ?config(data_dir, Config),
@ -61,6 +74,48 @@ multiapp(Config) ->
)),
ok.
multiapp_macros(Config) ->
RebarConfig = [{edoc_opts, [
preprocess,
{macros, [{m1, x1}, {m2, x2}]},
{def, [{d1, "1"}, {d2, "1"}]}
]}],
AppConfig = {edoc_opts, [
{preprocess, true},
{macros, [{m2, f2}, {m3, f3}]},
{def, [{d2, "2"}, {d3, "2"}]}
]},
DebugModule = "
-module(debug).
-ifndef(m1). -define(m1,z1). -endif.
-ifndef(m2). -define(m2,z2). -endif.
-ifndef(m3). -define(m3,z3). -endif.
-export([?m1 /0, ?m2 /0, ?m3 /0]).
%% @doc
%% d1:{@d1}
%% d2:{@d2}
%% d3:{@d3}
%% @end
?m1 () -> ok.
?m2 () -> ok.
?m3 () -> ok.
",
AppsDir = ?config(apps, Config),
ct:pal("AppsDir: ~s", [AppsDir]),
ok = file:write_file(filename:join([AppsDir, "apps", "bar1", "rebar.config"]),
io_lib:format("~p.~n", [AppConfig])),
ok = file:write_file(filename:join([AppsDir, "apps", "bar1", "src", "debug.erl"]),
DebugModule),
rebar_test_utils:run_and_check(Config, RebarConfig, ["edoc"], {ok, []}),
DocFile = filename:join([AppsDir, "apps", "bar1", "doc", "debug.html"]),
?assert(file_content_matches(DocFile, "d1:1")), % config layered
?assert(file_content_matches(DocFile, "d2:2")),
?assert(file_content_matches(DocFile, "d3:2")),
?assert(file_content_matches(DocFile, "x1/0")), % elided in config drop
?assert(file_content_matches(DocFile, "f2/0")),
?assert(file_content_matches(DocFile, "f3/0")),
ok.
error_survival(Config) ->
RebarConfig = [],

Carregando…
Cancelar
Guardar