summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFred Hebert <mononcqc@ferd.ca>2019-07-30 16:28:50 -0400
committerFred Hebert <mononcqc@ferd.ca>2019-07-30 16:28:50 -0400
commit28a50b8a3632b4f9eafb3bc8a36cc72023b13576 (patch)
treea487a0d19df2b52a4a318b44f3c770507f921981
parentdab233d6edeef2e15b8fcd4616ac01fbaea7e664 (diff)
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.
-rw-r--r--src/rebar_prv_edoc.erl26
-rw-r--r--test/rebar_edoc_SUITE.erl57
2 files changed, 81 insertions, 2 deletions
diff --git a/src/rebar_prv_edoc.erl b/src/rebar_prv_edoc.erl
index 5e563ab..0c03f7b 100644
--- a/src/rebar_prv_edoc.erl
+++ b/src/rebar_prv_edoc.erl
@@ -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.
diff --git a/test/rebar_edoc_SUITE.erl b/test/rebar_edoc_SUITE.erl
index 2f0fad0..108df76 100644
--- a/test/rebar_edoc_SUITE.erl
+++ b/test/rebar_edoc_SUITE.erl
@@ -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 = [],