%%% @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).