%%% @doc build a digraph of applications in order to figure out dependency
|
|
%%% and compile order.
|
|
-module(rebar_digraph).
|
|
|
|
-export([compile_order/1
|
|
,restore_graph/1
|
|
,subgraph/2
|
|
,format_error/1]).
|
|
|
|
-include("rebar.hrl").
|
|
|
|
%% @doc Sort apps with topological sort to get proper build order
|
|
-spec compile_order([rebar_app_info:t()]) ->
|
|
{ok, [rebar_app_info:t()]} | {error, no_sort | {cycles, [[binary(),...]]}}.
|
|
compile_order(Apps) ->
|
|
Graph = digraph:new(),
|
|
lists:foreach(fun(App) ->
|
|
Name = rebar_app_info:name(App),
|
|
Deps = all_apps_deps(App),
|
|
add(Graph, {Name, Deps})
|
|
end, Apps),
|
|
Order =
|
|
case digraph_utils:topsort(Graph) of
|
|
false ->
|
|
case digraph_utils:is_acyclic(Graph) of
|
|
true ->
|
|
{error, no_sort};
|
|
false ->
|
|
Cycles = lists:sort(
|
|
[lists:sort(Comp) || Comp <- digraph_utils:strong_components(Graph),
|
|
length(Comp)>1]),
|
|
{error, {cycles, Cycles}}
|
|
end;
|
|
V ->
|
|
{ok, names_to_apps(lists:reverse(V), Apps)}
|
|
end,
|
|
true = digraph:delete(Graph),
|
|
Order.
|
|
|
|
%% @private Add a package and its dependencies to an existing digraph
|
|
-spec add(digraph:graph(), {PkgName, [Dep]}) -> ok when
|
|
PkgName :: binary(),
|
|
Dep :: {Name, term()} | Name,
|
|
Name :: atom() | iodata().
|
|
add(Graph, {PkgName, Deps}) ->
|
|
case digraph:vertex(Graph, PkgName) of
|
|
false ->
|
|
V = digraph:add_vertex(Graph, PkgName);
|
|
{V, []} ->
|
|
V
|
|
end,
|
|
|
|
lists:foreach(fun(DepName) ->
|
|
Name1 = case DepName of
|
|
{Name, _Vsn} ->
|
|
rebar_utils:to_binary(Name);
|
|
Name ->
|
|
rebar_utils:to_binary(Name)
|
|
end,
|
|
V3 = case digraph:vertex(Graph, Name1) of
|
|
false ->
|
|
digraph:add_vertex(Graph, Name1);
|
|
{V2, []} ->
|
|
V2
|
|
end,
|
|
digraph:add_edge(Graph, V, V3)
|
|
end, Deps).
|
|
|
|
%% @doc based on a list of vertices and edges, build a digraph.
|
|
-spec restore_graph({[digraph:vertex()], [digraph:edge()]}) -> digraph:graph().
|
|
restore_graph({Vs, Es}) ->
|
|
Graph = digraph:new(),
|
|
lists:foreach(fun({V, LastUpdated}) ->
|
|
digraph:add_vertex(Graph, V, LastUpdated)
|
|
end, Vs),
|
|
lists:foreach(fun({V1, V2}) ->
|
|
digraph:add_edge(Graph, V1, V2)
|
|
end, Es),
|
|
Graph.
|
|
|
|
%% @doc convert a given exception's payload into an io description.
|
|
-spec format_error(any()) -> iolist().
|
|
format_error(no_solution) ->
|
|
io_lib:format("No solution for packages found.", []).
|
|
|
|
%%====================================================================
|
|
%% Internal Functions
|
|
%%====================================================================
|
|
|
|
%% @doc alias for `digraph_utils:subgraph/2'.
|
|
subgraph(Graph, Vertices) ->
|
|
digraph_utils:subgraph(Graph, Vertices).
|
|
|
|
%% @private from a list of app names, fetch the proper app info records
|
|
%% for them.
|
|
-spec names_to_apps([atom()], [rebar_app_info:t()]) -> [rebar_app_info:t()].
|
|
names_to_apps(Names, Apps) ->
|
|
[element(2, App) || App <- [find_app_by_name(Name, Apps) || Name <- Names], App =/= error].
|
|
|
|
%% @private fetch the proper app info record for a given app name.
|
|
-spec find_app_by_name(atom(), [rebar_app_info:t()]) -> {ok, rebar_app_info:t()} | error.
|
|
find_app_by_name(Name, Apps) ->
|
|
ec_lists:find(fun(App) ->
|
|
rebar_app_info:name(App) =:= Name
|
|
end, Apps).
|
|
|
|
%% @private The union of all entries in the applications list for an app and
|
|
%% the deps listed in its rebar.config is all deps that may be needed
|
|
%% for building the app.
|
|
-spec all_apps_deps(rebar_app_info:t()) -> [binary()].
|
|
all_apps_deps(App) ->
|
|
Applications = lists:usort([atom_to_binary(X, utf8) || X <- rebar_app_info:applications(App)]),
|
|
Deps = lists:usort(lists:map(fun({Name, _}) -> Name; (Name) -> Name end, rebar_app_info:deps(App))),
|
|
lists:umerge(Deps, Applications).
|