summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFred Hebert <mononcqc@ferd.ca>2015-03-22 21:39:36 -0400
committerFred Hebert <mononcqc@ferd.ca>2015-03-22 21:39:36 -0400
commit2d430bf3e2766ad98e9a859a0de246abfd4eee3e (patch)
treea9ce610d685fd630a1916d54f8c2171544e7f62e
parentcafd62e10185e2fc05490c387484dfdb2b615803 (diff)
Preserve profile order on mergeable tuple/opts
Rather than using the stdlib lists:umerge, we expand it to allow fuzzy matching on tuples vs. vals (`key` vs. `{key,val}`) with an overriden sort order so that two tuples or values comparing equal get a priority on the newest profile. This is a partial fix for #287 -- this current patch should be followed by a relx update to take options in order (as if they were a proplist) to complete it.
-rw-r--r--src/rebar_state.erl58
-rw-r--r--test/rebar_profiles_SUITE.erl11
2 files changed, 66 insertions, 3 deletions
diff --git a/src/rebar_state.erl b/src/rebar_state.erl
index 008f202..eced383 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -240,7 +240,7 @@ merge_opts(NewOpts, OldOpts) ->
true ->
NewValue;
false ->
- lists:umerge(lists:sort(NewValue), lists:sort(OldValue))
+ tup_umerge(lists:sort(NewValue), lists:sort(OldValue))
end;
(_Key, NewValue, _OldValue) ->
NewValue
@@ -362,3 +362,59 @@ add_hook(pre, {PreHooks, PostHooks}, Hook) ->
{[Hook | PreHooks], PostHooks};
add_hook(post, {PreHooks, PostHooks}, Hook) ->
{PreHooks, [Hook | PostHooks]}.
+
+%% Custom merge functions. The objective is to behave like lists:umerge/2,
+%% except that we also compare the merge elements based on the key if they're a
+%% tuple, such that `{key, val1}' is always prioritized over `{key, val0}' if
+%% the former is from the 'new' list.
+%%
+%% This lets us apply proper overrides to list of elements according to profile
+%% priority.
+tup_umerge([], Olds) ->
+ Olds;
+tup_umerge(News, Olds) ->
+ [ENew|ENews] = expand(News),
+ EOlds = expand(Olds),
+ unexpand(lists:reverse(umerge(ENews, EOlds, [], ENew))).
+
+%% Expand values, so they `key' is now `{key, key}', and so that
+%% `{key, val}' is now `{key, {key, val}'. This allows us to compare
+%% possibly only on the total key or the value itself.
+expand([]) -> [];
+expand([Tup|T]) when is_tuple(Tup) -> [{element(1, Tup), Tup} | expand(T)];
+expand([H|T]) -> [{H,H} | expand(T)].
+
+%% Go back to unexpanded form.
+unexpand(List) -> [element(2, X) || X <- List].
+
+%% This is equivalent to umerge2_2 in the stdlib, except we use the expanded
+%% value/key only to compare
+umerge(News, [{KOld,_}=Old|Olds], Merged, {KCmp, _} = Cmp) when KCmp =< KOld ->
+ umerge(News, Olds, [Cmp | Merged], Cmp, Old);
+umerge(News, [Old|Olds], Merged, Cmp) ->
+ umerge(News, Olds, [Old | Merged], Cmp);
+umerge(News, [], Merged, Cmp) ->
+ lists:reverse(News, [Cmp | Merged]).
+
+%% Similar to stdlib's umerge2_1 in the stdlib, except that when the expanded
+%% value/keys compare equal, we check if the element is a full dupe to clear it
+%% (like the stdlib function does) or otherwise keep the duplicate around in
+%% an order that prioritizes 'New' elements.
+umerge([{KNew,_}=New|News], Olds, Merged, _CmpMerged, {KCmp,_}=Cmp) when KNew =< KCmp ->
+ umerge(News, Olds, [New | Merged], New, Cmp);
+umerge([{KNew,_}=New|News], Olds, Merged, {KCmp,_}=CmpMerged, Cmp) when KNew == KCmp ->
+ if New == CmpMerged ->
+ umerge(News, Olds, Merged, New);
+ New =/= CmpMerged -> % this is where we depart from the stdlib!
+ umerge(News, Olds, [New | Merged], New, Cmp)
+ end;
+umerge([New|News], Olds, Merged, _CmpMerged, Cmp) -> % >
+ umerge(News, Olds, [Cmp | Merged], New);
+umerge([], Olds, Merged, {KCmpM,_}=CmpMerged, {KCmp,_}=Cmp) when KCmpM =:= KCmp ->
+ if CmpMerged == Cmp ->
+ lists:reverse(Olds, Merged);
+ CmpMerged =/= Cmp -> % We depart from stdlib here too!
+ lists:reverse(Olds, [Cmp | Merged])
+ end;
+umerge([], Olds, Merged, _CmpMerged, Cmp) ->
+ lists:reverse(Olds, [Cmp | Merged]).
diff --git a/test/rebar_profiles_SUITE.erl b/test/rebar_profiles_SUITE.erl
index a4f926e..ab37255 100644
--- a/test/rebar_profiles_SUITE.erl
+++ b/test/rebar_profiles_SUITE.erl
@@ -100,12 +100,17 @@ profile_merges(_Config) ->
{test2, "hello"},
{test3, [key3]},
{test4, "oldvalue"},
+ {test5, [{key5, true}]},
+ {test6, [{key6, false}]},
{profiles,
[{profile1,
[{test1, [{key3, 5}, key1]}]},
{profile2, [{test2, "goodbye"},
{test3, []},
- {test4, []}]}]}],
+ {test4, []},
+ {test5, [{key5, false}]},
+ {test6, [{key6, true}]}
+ ]}]}],
State = rebar_state:new(RebarConfig),
State1 = rebar_state:apply_profiles(State, [profile1, profile2]),
@@ -118,7 +123,9 @@ profile_merges(_Config) ->
%% Check that a newvalue of []/"" doesn't override non-string oldvalues
[key3] = rebar_state:get(State1, test3),
- [] = rebar_state:get(State1, test4).
+ [] = rebar_state:get(State1, test4),
+ [{key5, false}, {key5, true}] = rebar_state:get(State1, test5),
+ [{key6, true}, {key6, false}] = rebar_state:get(State1, test6).
add_to_profile(_Config) ->
RebarConfig = [{foo, true}, {bar, false}],