./ct_report/coverage/gen_mod_deps.COVER.html

1 %%==============================================================================
2 %% Copyright 2016 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16
17 -module(gen_mod_deps).
18
19 -include("mongoose.hrl").
20
21 -type hardness() :: soft | hard | optional.
22 -type module_deps_list() :: [{module(), gen_mod_params(), hardness()}].
23 -type gen_mod_params() :: proplists:proplist().
24 -type gen_mod_list() :: [{module(), gen_mod_params()}].
25 -type gen_mod_map() :: #{module() => gen_mod_params()}.
26
27 -export([add_deps/2, resolve_deps/2, sort_deps/2]).
28
29 -ignore_xref([add_deps/2]).
30
31 -export_type([hardness/0, gen_mod_params/0, gen_mod_list/0, gen_mod_map/0]).
32
33 %%--------------------------------------------------------------------
34 %% API
35 %%--------------------------------------------------------------------
36
37 %% @doc Adds deps into module list.
38 %% Side-effect free.
39 -spec add_deps(Host :: jid:server(), Modules :: gen_mod_map() | gen_mod_list()) -> gen_mod_list().
40 add_deps(Host, Modules) ->
41
:-(
sort_deps(Host, resolve_deps(Host, Modules)).
42
43 %%--------------------------------------------------------------------
44 %% Helpers
45 %%--------------------------------------------------------------------
46
47 %% Resolving dependencies
48
49 %% @doc
50 %% Determines all modules to start, along with their parameters.
51 %%
52 %% NOTE: A dependency will not be discarded during resolving, e.g.
53 %% if the resolver processes dependencies in order:
54 %%
55 %% deps(mod_a, []) -> [{mod_b, []}, {mod_c, []}]
56 %% deps(mod_parent, []) -> [{mod_a, [param]}]
57 %%
58 %% then the dependency for mod_a will be reevaluated with new parameters and
59 %% might return:
60 %%
61 %% deps(mod_a, [param]) -> [{mod_c, []}]
62 %%
63 %% In this case, mod_b will still be started.
64 %% @end
65 -spec resolve_deps(Host :: jid:server(), Modules :: gen_mod_map() | gen_mod_list()) -> gen_mod_map().
66 resolve_deps(Host, Modules) when is_map(Modules) ->
67 1131 resolve_deps(Host, maps:to_list(Modules));
68 resolve_deps(Host, ModuleQueue) ->
69 1131 resolve_deps(Host, ModuleQueue, #{}, #{}).
70
71 -spec resolve_deps(Host :: jid:server(),
72 Modules :: [{module(), gen_mod_params()} | {module(), gen_mod_params(), hardness()}],
73 OptionalQueue :: #{module() => gen_mod_params()},
74 Acc :: gen_mod_map()) -> gen_mod_map().
75 resolve_deps(Host, [], OptionalMods, KnownModules) ->
76 1164 KnownModNames = maps:keys(KnownModules),
77 1164 case maps:with(KnownModNames, OptionalMods) of
78 NewQueueMap when map_size(NewQueueMap) > 0 ->
79 33 resolve_deps(Host, maps:to_list(NewQueueMap),
80 maps:without(KnownModNames, OptionalMods), KnownModules);
81 _Nothing ->
82 1131 KnownModules
83 end;
84 resolve_deps(Host, [{Module, Args, optional} | ModuleQueue], OptionalMods, KnownModules) ->
85 93 resolve_deps(Host, ModuleQueue, maps:put(Module, Args, OptionalMods), KnownModules);
86 resolve_deps(Host, [{Module, Args, _Hardness} | ModuleQueue], OptionalMods, KnownModules) ->
87 547 resolve_deps(Host, [{Module, Args} | ModuleQueue], OptionalMods, KnownModules);
88 resolve_deps(Host, [{Module, Args} | ModuleQueue], OptionalMods, KnownModules) ->
89 12147 NewArgs =
90 case maps:find(Module, KnownModules) of
91 {ok, PreviousArgs} ->
92 302 case merge_args(Module, PreviousArgs, Args) of
93 226 PreviousArgs -> undefined;
94 76 ChangedArgs -> ChangedArgs
95 end;
96
97 error ->
98 11845 Args
99 end,
100
101 12147 case NewArgs of
102 226 undefined -> resolve_deps(Host, ModuleQueue, OptionalMods, KnownModules);
103 _ ->
104 11921 Deps = get_deps(Host, Module, NewArgs),
105 11921 UpdatedQueue = Deps ++ ModuleQueue,
106 11921 UpdatedKnownModules = maps:put(Module, NewArgs, KnownModules),
107 11921 resolve_deps(Host, UpdatedQueue, OptionalMods, UpdatedKnownModules)
108 end.
109
110 %% @doc
111 %% Merges proplists prioritizing the new list, and warns on overrides.
112 %% @end
113 -spec merge_args(Module :: module(), PreviousArgs :: gen_mod_params(),
114 Args :: gen_mod_params()) -> gen_mod_params().
115 merge_args(Module, PreviousArgs, Args) ->
116 302 lists:foldl(
117 fun(Property, OldProplist) ->
118 1755 [{Key, Value}] = proplists:unfold([Property]),
119 1755 case proplists:lookup(Key, OldProplist) of
120 none ->
121 321 [Property | OldProplist];
122
123 {_, Value} ->
124 1349 OldProplist;
125
126 {_, OldValue} ->
127 85 ?LOG_WARNING(#{what => overriding_argument, module => Module,
128 old_value => {Key, OldValue},
129
:-(
new_value => {Key, Value}}),
130
131 85 [Property | proplists:delete(Key, OldProplist)]
132 end
133 end, PreviousArgs, Args).
134
135 %% Sorting resolved dependencies
136
137 -spec sort_deps(Host :: jid:server(), ModuleMap :: gen_mod_map()) ->
138 gen_mod_list().
139 sort_deps(Host, ModuleMap) ->
140 1540 DepsGraph = digraph:new([acyclic, private]),
141
142 1540 try
143 1540 maps:fold(
144 fun(Module, Args, _) ->
145 15770 process_module_dep(Host, Module, Args, DepsGraph)
146 end,
147 undefined, ModuleMap),
148
149 1540 lists:filtermap(
150 fun(Module) ->
151 15830 case maps:find(Module, ModuleMap) of
152 60 error -> false;
153 15770 {ok, Opts} -> {true, {Module, Opts}}
154 end
155 end,
156 digraph_utils:topsort(DepsGraph))
157 after
158 1540 digraph:delete(DepsGraph)
159 end.
160
161
162 -spec process_module_dep(Host :: jid:server(), Module :: module(),
163 Args :: gen_mod_params(),
164 DepsGraph :: digraph:graph()) -> ok.
165 process_module_dep(Host, Module, Args, DepsGraph) ->
166 15770 digraph:add_vertex(DepsGraph, Module),
167 15770 lists:foreach(
168 639 fun({DepModule, _, DepHardness}) -> process_dep(Module, DepModule, DepHardness, DepsGraph) end,
169 get_deps(Host, Module, Args)).
170
171
172 -spec process_dep(Module :: module(), DepModule :: module(),
173 DepHardness :: hardness(), Graph :: digraph:graph()) -> ok.
174 process_dep(Module, DepModule, DepHardness, Graph) ->
175 639 digraph:add_vertex(Graph, DepModule),
176 639 case {digraph:add_edge(Graph, DepModule, Module, DepHardness), DepHardness} of
177 {['$e' | _], _} ->
178 639 ok;
179
180 {{error, {bad_edge, CyclePath}}, hard} ->
181
:-(
case find_soft_edge(Graph, CyclePath) of
182 false ->
183
:-(
?LOG_CRITICAL(#{what => resolving_dependencies_aborted,
184 text => <<"Module dependency cycle found">>,
185
:-(
cycle_path => CyclePath}),
186
:-(
error({dependency_cycle, CyclePath});
187
188 {EdgeId, B, A, _} ->
189
:-(
?LOG_INFO(#{what => soft_module_dependency_cycle_detected,
190 text => <<"Soft module dependency cycle detected. "
191 "Dropping edge">>, edge => {A, B},
192
:-(
cyclepath => CyclePath}),
193
194
:-(
digraph:del_edge(Graph, EdgeId),
195
:-(
['$e' | _] = digraph:add_edge(Graph, DepModule, Module, hard),
196
:-(
ok
197 end;
198
199 {{error, {bad_edge, CyclePath}}, _Soft} ->
200
:-(
?LOG_INFO(#{what => soft_module_dependency_cycle_detected,
201 text => <<"Soft module dependency cycle detected. "
202 "Dropping edge">>, edge => {Module, DepModule},
203
:-(
cyclepath => CyclePath}),
204
:-(
ok
205 end.
206
207
208 -spec find_soft_edge(digraph:graph(), [digraph:vertex()]) ->
209 {digraph:edge(), digraph:vertex(),
210 digraph:vertex(), digraph:label()} | false.
211 find_soft_edge(Graph, CyclePath) ->
212
:-(
VerticePairs = lists:zip(CyclePath, tl(CyclePath) ++ [hd(CyclePath)]),
213
:-(
Edges = lists:filtermap(
214 fun({A, B}) ->
215
:-(
case find_edge(Graph, A, B) of
216
:-(
false -> false;
217
:-(
Edge -> {true, digraph:edge(Graph, Edge)}
218 end
219 end,
220 VerticePairs),
221
222
:-(
case lists:keyfind(optional, 4, Edges) of
223
:-(
false -> lists:keyfind(soft, 4, Edges);
224
:-(
Edge -> Edge
225 end.
226
227
228 -spec find_edge(digraph:graph(), digraph:vertex(), digraph:vertex()) ->
229 digraph:edge() | false.
230 find_edge(Graph, A, B) ->
231
:-(
OutEdges = ordsets:from_list(digraph:out_edges(Graph, A)),
232
:-(
InEdges = ordsets:from_list(digraph:in_edges(Graph, B)),
233
234
:-(
case ordsets:intersection(OutEdges, InEdges) of
235
:-(
[Edge] -> Edge;
236
:-(
[] -> false
237 end.
238
239
240 -spec get_deps(Host :: jid:server(), module(), gen_mod_params()) -> module_deps_list().
241 get_deps(Host, Module, Args) ->
242 27691 lists:map(
243 fun
244 377 ({Mod, Hardness}) -> {Mod, [], Hardness};
245 902 ({Mod, ModArgs, Hardness}) -> {Mod, ModArgs, Hardness}
246 end,
247 gen_mod:get_deps(Host, Module, Args)).
Line Hits Source