You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
4.5 KiB

  1. %%% @doc build a digraph of applications in order to figure out dependency
  2. %%% and compile order.
  3. -module(rebar_digraph).
  4. -export([compile_order/1
  5. ,restore_graph/1
  6. ,subgraph/2
  7. ,format_error/1]).
  8. -include("rebar.hrl").
  9. %% @doc Sort apps with topological sort to get proper build order
  10. -spec compile_order([rebar_app_info:t()]) ->
  11. {ok, [rebar_app_info:t()]} | {error, no_sort | {cycles, [[binary(),...]]}}.
  12. compile_order(Apps) ->
  13. Graph = digraph:new(),
  14. lists:foreach(fun(App) ->
  15. Name = rebar_app_info:name(App),
  16. Deps = all_apps_deps(App),
  17. add(Graph, {Name, Deps})
  18. end, Apps),
  19. Order =
  20. case digraph_utils:topsort(Graph) of
  21. false ->
  22. case digraph_utils:is_acyclic(Graph) of
  23. true ->
  24. {error, no_sort};
  25. false ->
  26. Cycles = lists:sort(
  27. [lists:sort(Comp) || Comp <- digraph_utils:strong_components(Graph),
  28. length(Comp)>1]),
  29. {error, {cycles, Cycles}}
  30. end;
  31. V ->
  32. {ok, names_to_apps(lists:reverse(V), Apps)}
  33. end,
  34. true = digraph:delete(Graph),
  35. Order.
  36. %% @private Add a package and its dependencies to an existing digraph
  37. -spec add(digraph:graph(), {PkgName, [Dep]}) -> ok when
  38. PkgName :: binary(),
  39. Dep :: {Name, term()} | Name,
  40. Name :: atom() | iodata().
  41. add(Graph, {PkgName, Deps}) ->
  42. case digraph:vertex(Graph, PkgName) of
  43. false ->
  44. V = digraph:add_vertex(Graph, PkgName);
  45. {V, []} ->
  46. V
  47. end,
  48. lists:foreach(fun(DepName) ->
  49. Name1 = case DepName of
  50. {Name, _Vsn} ->
  51. rebar_utils:to_binary(Name);
  52. Name ->
  53. rebar_utils:to_binary(Name)
  54. end,
  55. V3 = case digraph:vertex(Graph, Name1) of
  56. false ->
  57. digraph:add_vertex(Graph, Name1);
  58. {V2, []} ->
  59. V2
  60. end,
  61. digraph:add_edge(Graph, V, V3)
  62. end, Deps).
  63. %% @doc based on a list of vertices and edges, build a digraph.
  64. -spec restore_graph({[digraph:vertex()], [digraph:edge()]}) -> digraph:graph().
  65. restore_graph({Vs, Es}) ->
  66. Graph = digraph:new(),
  67. lists:foreach(fun({V, LastUpdated}) ->
  68. digraph:add_vertex(Graph, V, LastUpdated)
  69. end, Vs),
  70. lists:foreach(fun({V1, V2}) ->
  71. digraph:add_edge(Graph, V1, V2)
  72. end, Es),
  73. Graph.
  74. %% @doc convert a given exception's payload into an io description.
  75. -spec format_error(any()) -> iolist().
  76. format_error(no_solution) ->
  77. io_lib:format("No solution for packages found.", []).
  78. %%====================================================================
  79. %% Internal Functions
  80. %%====================================================================
  81. %% @doc alias for `digraph_utils:subgraph/2'.
  82. subgraph(Graph, Vertices) ->
  83. digraph_utils:subgraph(Graph, Vertices).
  84. %% @private from a list of app names, fetch the proper app info records
  85. %% for them.
  86. -spec names_to_apps([atom()], [rebar_app_info:t()]) -> [rebar_app_info:t()].
  87. names_to_apps(Names, Apps) ->
  88. [element(2, App) || App <- [find_app_by_name(Name, Apps) || Name <- Names], App =/= error].
  89. %% @private fetch the proper app info record for a given app name.
  90. -spec find_app_by_name(atom(), [rebar_app_info:t()]) -> {ok, rebar_app_info:t()} | error.
  91. find_app_by_name(Name, Apps) ->
  92. ec_lists:find(fun(App) ->
  93. rebar_app_info:name(App) =:= Name
  94. end, Apps).
  95. %% @private The union of all entries in the applications list for an app and
  96. %% the deps listed in its rebar.config is all deps that may be needed
  97. %% for building the app.
  98. -spec all_apps_deps(rebar_app_info:t()) -> [binary()].
  99. all_apps_deps(App) ->
  100. Applications = lists:usort([atom_to_binary(X, utf8) || X <- rebar_app_info:applications(App)]),
  101. Deps = lists:usort(lists:map(fun({Name, _}) -> Name; (Name) -> Name end, rebar_app_info:deps(App))),
  102. lists:umerge(Deps, Applications).