summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTristan Sloughter <t@crashfast.com>2015-04-10 22:31:01 -0500
committerTristan Sloughter <t@crashfast.com>2015-04-11 11:50:16 -0500
commitd75ba02671236634906989f70eadc785658b4959 (patch)
tree187d17775bb5617dcc37b81cdfe307379b84c28e /src
parenta447067888deb47cad7b009d65f76bed61fda7a3 (diff)
support single atoms for pkg deps, fetch highest version available
Diffstat (limited to 'src')
-rw-r--r--src/rebar_app_discover.erl9
-rw-r--r--src/rebar_config.erl40
-rw-r--r--src/rebar_digraph.erl12
-rw-r--r--src/rebar_packages.erl55
-rw-r--r--src/rebar_prv_install_deps.erl55
-rw-r--r--src/rebar_prv_packages.erl33
-rw-r--r--src/rebar_prv_update.erl48
-rw-r--r--src/rebar_state.erl77
-rw-r--r--src/rebar_utils.erl66
9 files changed, 225 insertions, 170 deletions
diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl
index 0799313..fe930bd 100644
--- a/src/rebar_app_discover.erl
+++ b/src/rebar_app_discover.erl
@@ -48,9 +48,9 @@ merge_deps(AppInfo, State) ->
State1 = lists:foldl(fun(Profile, StateAcc) ->
AppProfDeps = rebar_state:get(AppState, {deps, Profile}, []),
TopLevelProfDeps = rebar_state:get(StateAcc, {deps, Profile}, []),
- ProfDeps2 = dedup(lists:keymerge(1,
- lists:keysort(1, TopLevelProfDeps),
- lists:keysort(1, AppProfDeps))),
+ ProfDeps2 = dedup(rebar_utils:tup_umerge(
+ rebar_utils:tup_sort(TopLevelProfDeps)
+ ,rebar_utils:tup_sort(AppProfDeps))),
rebar_state:set(StateAcc, {deps, Profile}, ProfDeps2)
end, State, lists:reverse(CurrentProfiles)),
@@ -164,7 +164,8 @@ create_app_info(AppDir, AppFile) ->
AppInfo1 = rebar_app_info:applications(
rebar_app_info:app_details(AppInfo, AppDetails),
IncludedApplications++Applications),
- rebar_app_info:dir(AppInfo1, AppDir);
+ Valid = rebar_app_utils:validate_application_info(AppInfo1),
+ rebar_app_info:dir(rebar_app_info:valid(AppInfo1, Valid), AppDir);
_ ->
error
end.
diff --git a/src/rebar_config.erl b/src/rebar_config.erl
index c1c4381..97aea91 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -106,21 +106,25 @@ bs(Vars) ->
%% Find deps that have been added to the config after the lock was created
find_newly_added(ConfigDeps, LockedDeps) ->
- [Dep || Dep <- ConfigDeps,
- begin
- NewDep = ec_cnv:to_binary(element(1, Dep)),
- case lists:keyfind(NewDep, 1, LockedDeps) of
- false ->
- true;
- Match ->
- case element(3, Match) of
- 0 ->
- true;
- _ ->
- ?WARN("Newly added dep ~s is locked at a lower level. "
- "If you really want to unlock it, use 'rebar3 upgrade ~s'",
- [NewDep, NewDep]),
- false
- end
- end
- end].
+ rebar_utils:filtermap(fun(Dep) when is_tuple(Dep) ->
+ check_dep(element(1, Dep), LockedDeps);
+ (Dep) ->
+ check_dep(Dep, LockedDeps)
+ end, ConfigDeps).
+
+check_dep(Dep, LockedDeps) ->
+ NewDep = ec_cnv:to_binary(Dep),
+ case lists:keyfind(NewDep, 1, LockedDeps) of
+ false ->
+ true;
+ Match ->
+ case element(3, Match) of
+ 0 ->
+ true;
+ _ ->
+ ?WARN("Newly added dep ~s is locked at a lower level. "
+ "If you really want to unlock it, use 'rebar3 upgrade ~s'",
+ [NewDep, NewDep]),
+ false
+ end
+ end.
diff --git a/src/rebar_digraph.erl b/src/rebar_digraph.erl
index 129ea35..35fd7e7 100644
--- a/src/rebar_digraph.erl
+++ b/src/rebar_digraph.erl
@@ -72,10 +72,12 @@ cull_deps(Graph, Vertices) ->
cull_deps(Graph,
Vertices,
1,
- lists:foldl(fun({Key, _}, Levels) -> dict:store(Key, 0, Levels) end,
- dict:new(), Vertices),
- lists:foldl(fun({Key, _}=N, Solution) -> dict:store(Key, N, Solution) end,
- dict:new(), Vertices),
+ lists:foldl(fun({Key, _}, Levels) ->
+ dict:store(Key, 0, Levels)
+ end, dict:new(), Vertices),
+ lists:foldl(fun({Key, _}=N, Solution) ->
+ dict:store(Key, N, Solution)
+ end, dict:new(), Vertices),
[]).
cull_deps(_Graph, [], _Level, Levels, Solution, Discarded) ->
@@ -99,7 +101,7 @@ cull_deps(Graph, Vertices, Level, Levels, Solution, Discarded) ->
DiscardedAcc1}
end
end, {NewVertices, SolutionAcc, LevelsAcc, DiscardedAcc}, OutNeighbors)
- end, {[], Solution, Levels, Discarded}, lists:keysort(1, Vertices)),
+ end, {[], Solution, Levels, Discarded}, lists:sort(Vertices)),
cull_deps(Graph, NV, Level+1, LS, NS, DS).
subgraph(Graph, Vertices) ->
diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl
index 5c67600..3890510 100644
--- a/src/rebar_packages.erl
+++ b/src/rebar_packages.erl
@@ -1,6 +1,8 @@
-module(rebar_packages).
--export([get_packages/1]).
+-export([get_packages/1
+ ,registry/1
+ ,find_highest_matching/3]).
-export_type([package/0]).
@@ -37,3 +39,54 @@ get_packages(State) ->
?ERROR("Bad packages index, try to fix with `rebar3 update`", []),
{dict:new(), digraph:new()}
end.
+
+registry(State) ->
+ Dir = rebar_dir:global_cache_dir(State),
+ RegistryDir = filename:join(Dir, "packages"),
+ HexFile = filename:join(RegistryDir, "registry2"),
+ case ets:file2tab(HexFile) of
+ {ok, T} ->
+ {ok, T};
+ {error, Reason} ->
+ ?DEBUG("Error loading registry: ~p", [Reason]),
+ error
+ end.
+
+
+%% Hex supports use of ~> to specify the version required for a dependency.
+%% Since rebar3 requires exact versions to choose from we find the highest
+%% available version of the dep that passes the constraint.
+
+%% `~>` will never include pre-release versions of its upper bound.
+%% It can also be used to set an upper bound on only the major
+%% version part. See the table below for `~>` requirements and
+%% their corresponding translation.
+%% `~>` | Translation
+%% :------------- | :---------------------
+%% `~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
+%% `~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
+%% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
+%% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
+%% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
+find_highest_matching(Dep, Constraint, T) ->
+ case ets:lookup(T, Dep) of
+ [{Dep, [[Vsn]]}] ->
+ case ec_semver:pes(Vsn, Constraint) of
+ true ->
+ Vsn;
+ false ->
+ ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
+ "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]),
+ Vsn
+ end;
+ [{Dep, [[HeadVsn | VsnTail]]}] ->
+ lists:foldl(fun(Version, Highest) ->
+ case ec_semver:pes(Version, Constraint) andalso
+ ec_semver:gt(Version, Highest) of
+ true ->
+ Version;
+ false ->
+ Highest
+ end
+ end, HeadVsn, VsnTail)
+ end.
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index db2b036..57cd05f 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -97,6 +97,8 @@ do(State) ->
end.
-spec format_error(any()) -> iolist().
+format_error({load_registry_fail, Dep}) ->
+ io_lib:format("Error loading registry to resolve version of ~s. Try fixing by running 'rebar3 update'", [Dep]);
format_error({bad_constraint, Name, Constraint}) ->
io_lib:format("Unable to parse version for package ~s: ~s", [Name, Constraint]);
format_error({parse_dep, Dep}) ->
@@ -129,9 +131,10 @@ handle_deps(Profile, State, Deps, Locks) when is_list(Locks) ->
-> {ok, [rebar_app_info:t()], rebar_state:t()} | {error, string()}.
handle_deps(_Profile, State, [], _, _) ->
{ok, [], State};
-handle_deps(Profile, State, Deps, Upgrade, Locks) ->
+handle_deps(Profile, State0, Deps, Upgrade, Locks) ->
%% Read in package index and dep graph
- {Packages, Graph} = rebar_packages:get_packages(State),
+ {Packages, Graph} = rebar_state:packages(State0),
+ State = rebar_state:packages(State0, {Packages, Graph}),
%% Split source deps from pkg deps, needed to keep backwards compatibility
DepsDir = rebar_dir:deps_dir(State),
{SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, State, Locks, 0),
@@ -210,8 +213,8 @@ handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, State) -
AppInfo = package_to_app(DepsDir, Packages, Pkg),
Level = rebar_app_info:dep_level(AppInfo),
{NewSeen, NewState} = maybe_lock(Profile, AppInfo, Seen, State, Level),
- maybe_fetch(AppInfo, Upgrade, Seen, NewState),
- {[AppInfo | Fetched], NewSeen, NewState}.
+ {_, AppInfo1} = maybe_fetch(AppInfo, Upgrade, Seen, NewState),
+ {[AppInfo1 | Fetched], NewSeen, NewState}.
maybe_lock(Profile, AppInfo, Seen, State, Level) ->
case rebar_app_info:is_checkout(AppInfo) of
@@ -312,8 +315,8 @@ update_unseen_src_dep(AppInfo, Profile, Level, SrcDeps, PkgDeps, SrcApps, State,
handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps,
Level, State1, Locks);
_ ->
- maybe_fetch(AppInfo, false, Seen, State1),
- handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps,
+ {_, AppInfo1} = maybe_fetch(AppInfo, false, Seen, State1),
+ handle_dep(AppInfo1, SrcDeps, PkgDeps, SrcApps,
Level, State1, Locks)
end,
{NewSrcDeps, NewPkgDeps, NewSrcApps, State2, NewSeen, NewLocks}.
@@ -323,12 +326,12 @@ handle_upgrade(AppInfo, SrcDeps, PkgDeps, SrcApps, Level, State, Locks) ->
case lists:keyfind(Name, 1, Locks) of
false ->
case maybe_fetch(AppInfo, true, sets:new(), State) of
- true ->
- handle_dep(AppInfo, SrcDeps, PkgDeps, SrcApps,
+ {true, AppInfo1} ->
+ handle_dep(AppInfo1, SrcDeps, PkgDeps, SrcApps,
Level, State, Locks);
- false ->
- {[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
+ {false, AppInfo1} ->
+ {[AppInfo1|SrcDeps], PkgDeps, SrcApps, State, Locks}
end;
_StillLocked ->
{[AppInfo|SrcDeps], PkgDeps, SrcApps, State, Locks}
@@ -371,31 +374,37 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->
{AppInfo2, SrcDeps, PkgDeps, Locks++NewLocks, State1}.
-spec maybe_fetch(rebar_app_info:t(), boolean() | {true, binary(), integer()},
- sets:set(binary()), rebar_state:t()) -> boolean().
+ sets:set(binary()), rebar_state:t()) -> {boolean(), rebar_app_info:t()}.
maybe_fetch(AppInfo, Upgrade, Seen, State) ->
AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
%% Don't fetch dep if it exists in the _checkouts dir
case rebar_app_info:is_checkout(AppInfo) of
true ->
- false;
+ {false, AppInfo};
false ->
case rebar_app_discover:find_app(AppDir, all) of
false ->
case in_default(AppInfo, State) of
false ->
- fetch_app(AppInfo, AppDir, State);
+ {fetch_app(AppInfo, AppDir, State), AppInfo};
{true, FoundApp} ->
+ %% Preserve the state we created with overrides
+ AppState = rebar_app_info:state(AppInfo),
+ FoundApp = rebar_app_info:state(FoundApp, AppState),
?INFO("Linking ~s to ~s", [rebar_app_info:dir(FoundApp), AppDir]),
filelib:ensure_dir(AppDir),
rebar_file_utils:symlink_or_copy(rebar_app_info:dir(FoundApp), AppDir),
- true
+ {true, AppInfo}
end;
- {true, _} ->
+ {true, AppInfo1} ->
+ %% Preserve the state we created with overrides
+ AppState = rebar_app_info:state(AppInfo),
+ AppInfo2 = rebar_app_info:state(AppInfo1, AppState),
case sets:is_element(rebar_app_info:name(AppInfo), Seen) of
true ->
- false;
+ {false, AppInfo2};
false ->
- maybe_upgrade(AppInfo, AppDir, Upgrade, State)
+ {maybe_upgrade(AppInfo, AppDir, Upgrade, State), AppInfo2}
end
end
end.
@@ -439,13 +448,14 @@ parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_list(Vs
,ec_cnv:to_binary(Vsn)) | PkgDepsAcc]}
end;
parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_atom(Name) ->
+ {PkgName, PkgVsn} = get_package(ec_cnv:to_binary(Name), State),
CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),
case rebar_app_info:discover(CheckoutsDir) of
{ok, _App} ->
Dep = new_dep(DepsDir, Name, [], [], State),
{[Dep | SrcDepsAcc], PkgDepsAcc};
not_found ->
- {SrcDepsAcc, [ec_cnv:to_binary(Name) | PkgDepsAcc]}
+ {SrcDepsAcc, [{PkgName, PkgVsn} | PkgDepsAcc]}
end;
parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) ->
Dep = new_dep(DepsDir, Name, [], Source, State),
@@ -561,3 +571,12 @@ warn_skip_pkg({Name, Source}, State) ->
not_needs_compile(App) ->
not(rebar_app_info:is_checkout(App))
andalso rebar_app_info:valid(App).
+
+get_package(Dep, State) ->
+ case rebar_packages:registry(State) of
+ {ok, T} ->
+ HighestDepVsn = rebar_packages:find_highest_matching(Dep, "0", T),
+ {Dep, HighestDepVsn};
+ error ->
+ throw(?PRV_ERROR({load_registry_fail, Dep}))
+ end.
diff --git a/src/rebar_prv_packages.erl b/src/rebar_prv_packages.erl
index 8ba9e92..c7c0d50 100644
--- a/src/rebar_prv_packages.erl
+++ b/src/rebar_prv_packages.erl
@@ -7,6 +7,8 @@
format_error/1]).
-include("rebar.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include_lib("providers/include/providers.hrl").
-define(PROVIDER, pkgs).
-define(DEPS, []).
@@ -25,33 +27,26 @@ init(State) ->
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
- {Packages, _Graph} = rebar_packages:get_packages(State),
- print_packages(Packages),
- {ok, State}.
+ case rebar_packages:registry(State) of
+ {ok, Registry} ->
+ print_packages(Registry),
+ {ok, State};
+ error ->
+ ?PRV_ERROR(load_registry_fail)
+ end.
-spec format_error(any()) -> iolist().
-format_error(Reason) ->
- io_lib:format("~p", [Reason]).
+format_error(load_registry_fail) ->
+ "Failed to load package regsitry. Try running 'rebar3 update' to fix".
-print_packages(Packages) ->
- Keys = lists:keysort(1, dict:fetch_keys(Packages)),
- Pkgs = merge(Keys),
+print_packages(Table) ->
+ MS = ets:fun2ms(fun({Key, [Value]}) when is_binary(Key) -> {Key, Value} end),
+ Pkgs = ets:select(Table, MS),
lists:foreach(fun({Name, Vsns}) ->
VsnStr = join(Vsns, <<", ">>),
io:format("~s:~n Versions: ~s~n~n", [Name, VsnStr])
end, Pkgs).
--spec merge([{binary(), binary()}]) -> [{binary(), [binary()]}].
-merge(List) ->
- merge([], List).
-
-merge(List, []) ->
- List;
-merge([{Key, Values} | T], [{Key, Value} | Rest]) ->
- merge([{Key, [Value | Values]} | T], Rest);
-merge(List, [{Key, Value} | Rest]) ->
- merge([{Key, [Value]} | List], Rest).
-
-spec join([binary()], binary()) -> binary().
join([Bin], _Sep) ->
<<Bin/binary>>;
diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl
index be5cafd..6cdabeb 100644
--- a/src/rebar_prv_update.erl
+++ b/src/rebar_prv_update.erl
@@ -35,10 +35,14 @@ init(State) ->
do(State) ->
?INFO("Updating package index...", []),
try
- Url = rebar_state:get(State, rebar_packages_cdn, "https://s3.amazonaws.com/s3.hex.pm/registry.ets.gz"),
+ Dir = rebar_dir:global_cache_dir(State),
+ RegistryDir = filename:join(Dir, "packages"),
+ filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
+ HexFile = filename:join(RegistryDir, "registry"),
TmpDir = ec_file:insecure_mkdtemp(),
TmpFile = filename:join(TmpDir, "packages.gz"),
- HexFile = filename:join(TmpDir, "packages"),
+
+ Url = rebar_state:get(State, rebar_packages_cdn, "https://s3.amazonaws.com/s3.hex.pm/registry.ets.gz"),
{ok, _RequestId} = httpc:request(get, {Url, []},
[], [{stream, TmpFile}, {sync, true}]),
{ok, Data} = file:read_file(TmpFile),
@@ -90,7 +94,7 @@ update_graph(Pkg, PkgVsn, Deps, HexRegistry, Graph) ->
lists:foldl(fun([Dep, DepVsn, _, _], DepsListAcc) ->
case DepVsn of
<<"~> ", Vsn/binary>> ->
- HighestDepVsn = find_highest_matching(Dep, Vsn, HexRegistry),
+ HighestDepVsn = rebar_packages:find_highest_matching(Dep, Vsn, HexRegistry),
digraph:add_edge(Graph, {Pkg, PkgVsn}, {Dep, HighestDepVsn}),
[{Dep, DepVsn} | DepsListAcc];
Vsn ->
@@ -98,41 +102,3 @@ update_graph(Pkg, PkgVsn, Deps, HexRegistry, Graph) ->
[{Dep, Vsn} | DepsListAcc]
end
end, [], Deps).
-
-%% Hex supports use of ~> to specify the version required for a dependency.
-%% Since rebar3 requires exact versions to choose from we find the highest
-%% available version of the dep that passes the constraint.
-
-%% `~>` will never include pre-release versions of its upper bound.
-%% It can also be used to set an upper bound on only the major
-%% version part. See the table below for `~>` requirements and
-%% their corresponding translation.
-%% `~>` | Translation
-%% :------------- | :---------------------
-%% `~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
-%% `~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
-%% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
-%% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
-%% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
-find_highest_matching(Dep, Constraint, T) ->
- case ets:lookup(T, Dep) of
- [{Dep, [[Vsn]]}] ->
- case ec_semver:pes(Vsn, Constraint) of
- true ->
- Vsn;
- false ->
- ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
- "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]),
- Vsn
- end;
- [{Dep, [[HeadVsn | VsnTail]]}] ->
- lists:foldl(fun(Version, Highest) ->
- case ec_semver:pes(Version, Constraint) andalso
- ec_semver:gt(Version, Highest) of
- true ->
- Version;
- false ->
- Highest
- end
- end, HeadVsn, VsnTail)
- end.
diff --git a/src/rebar_state.erl b/src/rebar_state.erl
index 68c71a9..af875d7 100644
--- a/src/rebar_state.erl
+++ b/src/rebar_state.erl
@@ -30,6 +30,8 @@
overrides/1, overrides/2,
apply_overrides/2,
+ packages/1, packages/2,
+
resources/1, resources/2, add_resource/2,
providers/1, providers/2, add_provider/2]).
@@ -51,6 +53,8 @@
deps_to_build = [] :: [rebar_app_info:t()],
all_deps = [] :: [rebar_app_info:t()],
+ packages = undefined :: {rebar_dict(), rebar_digraph()} | undefined,
+
overrides = [],
resources = [],
providers = []}).
@@ -242,7 +246,8 @@ merge_opts(NewOpts, OldOpts) ->
true ->
NewValue;
false ->
- tup_umerge(tup_sort(NewValue), tup_sort(OldValue))
+ rebar_utils:tup_umerge(rebar_utils:tup_sort(NewValue)
+ ,rebar_utils:tup_sort(OldValue))
end;
(_Key, NewValue, _OldValue) ->
NewValue
@@ -298,6 +303,14 @@ namespace(#state_t{namespace=Namespace}) ->
namespace(State=#state_t{}, Namespace) ->
State#state_t{namespace=Namespace}.
+packages(State=#state_t{packages=undefined}) ->
+ rebar_packages:get_packages(State);
+packages(#state_t{packages=Packages}) ->
+ Packages.
+
+packages(State, Packages) ->
+ State#state_t{packages=Packages}.
+
-spec resources(t()) -> rebar_resource:resource().
resources(#state_t{resources=Resources}) ->
Resources.
@@ -340,65 +353,3 @@ create_logic_providers(ProviderModules, State0) ->
%% ===================================================================
%% Internal functions
%% ===================================================================
-
-%% Sort the list in proplist-order, meaning that `{a,b}' and `{a,c}'
-%% both compare as usual, and `a' and `b' do the same, but `a' and `{a,b}' will
-%% compare based on the first element of the key, and in order. So the following
-%% list will sort as:
-%% - `[native, {native,o3}, check]' -> `[check, native, {native, o3}]'
-%% - `[native, {native,o3}, {native, o2}, check]' -> `[check,native,{native,o3},{native,o2}]'
-%% Meaning that:
-%% a) no deduplication takes place
-%% b) the key of a tuple is what counts in being sorted, but atoms are seen as {atom}
-%% as far as comparison is concerned (departing from lists:ukeysort/2)
-%% c) order is preserved for similar keys and tuples no matter their size (sort is stable)
-%%
-%% These properties let us merge proplists fairly easily.
-tup_sort(List) ->
- lists:sort(fun(A, B) when is_tuple(A), is_tuple(B) -> element(1, A) =< element(1, B)
- ; (A, B) when is_tuple(A) -> element(1, A) =< B
- ; (A, B) when is_tuple(B) -> A =< element(1, B)
- ; (A, B) -> A =< B
- end, List).
-
-%% 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. This function depends on a stable proplist sort.
-tup_umerge([], Olds) ->
- Olds;
-tup_umerge([New|News], Olds) ->
- lists:reverse(umerge(News, Olds, [], New)).
-
-%% This is equivalent to umerge2_2 in the stdlib, except we use the expanded
-%% value/key only to compare
-umerge(News, [Old|Olds], Merged, Cmp) when element(1, Cmp) == element(1, Old);
- element(1, Cmp) == Old;
- Cmp == element(1, Old);
- Cmp =< Old ->
- 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([New|News], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
- umerge(News, Olds, Merged, New);
-umerge([New|News], Olds, Merged, _CmpMerged, Cmp) when element(1,New) == element(1, Cmp);
- element(1,New) == Cmp;
- New == element(1, Cmp);
- New =< Cmp ->
- umerge(News, Olds, [New | Merged], New, Cmp);
-umerge([New|News], Olds, Merged, _CmpMerged, Cmp) -> % >
- umerge(News, Olds, [Cmp | Merged], New);
-umerge([], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
- lists:reverse(Olds, Merged);
-umerge([], Olds, Merged, _CmpMerged, Cmp) ->
- lists:reverse(Olds, [Cmp | Merged]).
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index d92ab80..bc2e4ac 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -52,7 +52,9 @@
args_to_tasks/1,
expand_env_variable/3,
get_arch/0,
- wordsize/0]).
+ wordsize/0,
+ tup_umerge/2,
+ tup_sort/1]).
%% for internal use only
-export([otp_release/0]).
@@ -225,6 +227,68 @@ erl_opts(Config) ->
%% was enclosed in quotes and might have commas but should not be split.
args_to_tasks(Args) -> new_task(Args, []).
+%% Sort the list in proplist-order, meaning that `{a,b}' and `{a,c}'
+%% both compare as usual, and `a' and `b' do the same, but `a' and `{a,b}' will
+%% compare based on the first element of the key, and in order. So the following
+%% list will sort as:
+%% - `[native, {native,o3}, check]' -> `[check, native, {native, o3}]'
+%% - `[native, {native,o3}, {native, o2}, check]' -> `[check,native,{native,o3},{native,o2}]'
+%% Meaning that:
+%% a) no deduplication takes place
+%% b) the key of a tuple is what counts in being sorted, but atoms are seen as {atom}
+%% as far as comparison is concerned (departing from lists:ukeysort/2)
+%% c) order is preserved for similar keys and tuples no matter their size (sort is stable)
+%%
+%% These properties let us merge proplists fairly easily.
+tup_sort(List) ->
+ lists:sort(fun(A, B) when is_tuple(A), is_tuple(B) -> element(1, A) =< element(1, B)
+ ; (A, B) when is_tuple(A) -> element(1, A) =< B
+ ; (A, B) when is_tuple(B) -> A =< element(1, B)
+ ; (A, B) -> A =< B
+ end, List).
+
+%% 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. This function depends on a stable proplist sort.
+tup_umerge([], Olds) ->
+ Olds;
+tup_umerge([New|News], Olds) ->
+ lists:reverse(umerge(News, Olds, [], New)).
+
+%% This is equivalent to umerge2_2 in the stdlib, except we use the expanded
+%% value/key only to compare
+umerge(News, [Old|Olds], Merged, Cmp) when element(1, Cmp) == element(1, Old);
+ element(1, Cmp) == Old;
+ Cmp == element(1, Old);
+ Cmp =< Old ->
+ 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([New|News], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
+ umerge(News, Olds, Merged, New);
+umerge([New|News], Olds, Merged, _CmpMerged, Cmp) when element(1,New) == element(1, Cmp);
+ element(1,New) == Cmp;
+ New == element(1, Cmp);
+ New =< Cmp ->
+ umerge(News, Olds, [New | Merged], New, Cmp);
+umerge([New|News], Olds, Merged, _CmpMerged, Cmp) -> % >
+ umerge(News, Olds, [Cmp | Merged], New);
+umerge([], Olds, Merged, CmpMerged, Cmp) when CmpMerged == Cmp ->
+ lists:reverse(Olds, Merged);
+umerge([], Olds, Merged, _CmpMerged, Cmp) ->
+ lists:reverse(Olds, [Cmp | Merged]).
+
%% ====================================================================
%% Internal functions
%% ====================================================================