./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_opts() :: gen_mod:module_opts().
23 -type module_dep() :: {module(), module_opts(), hardness()}.
24 -type module_deps() :: [module_dep()].
25 -type deps() :: [module_dep() | {service, mongoose_service:service()}].
26 -type module_list() :: [{module(), module_opts()}].
27 -type module_map() :: #{module() => module_opts()}.
28
29 -export([add_deps/2, resolve_deps/2, sort_deps/2]).
30
31 -ignore_xref([add_deps/2]).
32
33 -export_type([hardness/0, module_list/0, module_map/0, module_deps/0, deps/0]).
34
35 %%--------------------------------------------------------------------
36 %% API
37 %%--------------------------------------------------------------------
38
39 %% @doc Adds deps into module list.
40 %% Side-effect free.
41 -spec add_deps(mongooseim:host_type(), module_map() | module_list()) -> module_list().
42 add_deps(HostType, Modules) ->
43
:-(
sort_deps(HostType, resolve_deps(HostType, Modules)).
44
45 %%--------------------------------------------------------------------
46 %% Helpers
47 %%--------------------------------------------------------------------
48
49 %% Resolving dependencies
50
51 %% @doc
52 %% Determines all modules to start, along with their options.
53 %%
54 %% NOTE: A dependency will not be discarded during resolving, e.g.
55 %% if the resolver processes dependencies in order:
56 %%
57 %% deps(mod_a, #{}) -> [{mod_b, #{}}, {mod_c, #{}}]
58 %% deps(mod_parent, #{}) -> [{mod_a, #{opt => val}}]
59 %%
60 %% then the dependency for mod_a will be reevaluated with new opts and might return:
61 %%
62 %% deps(mod_a, #{opt := val}) -> [{mod_c, #{}}]
63 %%
64 %% In this case, mod_b will still be started.
65 %% @end
66 -spec resolve_deps(mongooseim:host_type(), module_map() | module_list()) -> module_map().
67 resolve_deps(HostType, Modules) when is_map(Modules) ->
68 4801 resolve_deps(HostType, lists:sort(maps:to_list(Modules)));
69 resolve_deps(HostType, ModuleQueue) ->
70 4801 resolve_deps(HostType, ModuleQueue, #{}, #{}).
71
72 -spec resolve_deps(mongooseim:host_type(),
73 ModuleQueue :: [{module(), module_opts()} | module_dep()],
74 OptionalMods :: module_map(),
75 Acc :: module_map()) -> module_map().
76 resolve_deps(HostType, [], OptionalMods, KnownModules) ->
77 4830 KnownModNames = maps:keys(KnownModules),
78 4830 case maps:with(KnownModNames, OptionalMods) of
79 NewQueueMap when map_size(NewQueueMap) > 0 ->
80 29 resolve_deps(HostType, maps:to_list(NewQueueMap),
81 maps:without(KnownModNames, OptionalMods), KnownModules);
82 _Nothing ->
83 4801 KnownModules
84 end;
85 resolve_deps(HostType, [{Module, Opts, optional} | ModuleQueue], OptionalMods, KnownModules) ->
86 100 resolve_deps(HostType, ModuleQueue, maps:put(Module, Opts, OptionalMods), KnownModules);
87 resolve_deps(HostType, [{Module, Opts, _Hardness} | ModuleQueue], OptionalMods, KnownModules) ->
88 16418 resolve_deps(HostType, [{Module, Opts} | ModuleQueue], OptionalMods, KnownModules);
89 resolve_deps(HostType, [{Module, Opts} | ModuleQueue], OptionalMods, KnownModules)
90 when is_list(Opts); is_map(Opts) ->
91 58249 NewOpts =
92 case maps:find(Module, KnownModules) of
93 {ok, PreviousOpts} ->
94 1367 case merge_opts(Module, PreviousOpts, Opts) of
95 1367 PreviousOpts -> undefined;
96
:-(
MergedOpts -> MergedOpts
97 end;
98 error ->
99 56882 Opts
100 end,
101 58249 case NewOpts of
102 1367 undefined -> resolve_deps(HostType, ModuleQueue, OptionalMods, KnownModules);
103 _ ->
104 56882 Deps = gen_mod:get_deps(HostType, Module, NewOpts),
105 56882 UpdatedQueue = Deps ++ ModuleQueue,
106 56882 UpdatedKnownModules = maps:put(Module, NewOpts, KnownModules),
107 56882 resolve_deps(HostType, UpdatedQueue, OptionalMods, UpdatedKnownModules)
108 end.
109
110 %% @doc
111 %% Merges module opts prioritizing the new ones, and warns on overrides.
112 %% @end
113 -spec merge_opts(module(), module_opts(), module_opts()) -> module_opts().
114 merge_opts(Module, PreviousOpts, Opts) when is_map(PreviousOpts), is_map(Opts) ->
115 1367 case changed_opts(PreviousOpts, Opts) of
116 [] ->
117 1367 ok;
118 Changed ->
119
:-(
?LOG_WARNING(#{what => overriding_options, module => Module, options => Changed})
120 end,
121 1367 maps:merge(PreviousOpts, Opts).
122
123 -spec changed_opts(module_opts(), module_opts()) -> [map()].
124 changed_opts(PreviousOpts, Opts) ->
125 1367 lists:flatmap(
126 fun({Key, OldValue}) ->
127 8103 case maps:find(Key, Opts) of
128 29 error -> [];
129 8074 {ok, OldValue} -> [];
130
:-(
{ok, NewValue} -> [#{key => Key, old_value => OldValue, new_value => NewValue}]
131 end
132 end, maps:to_list(PreviousOpts)).
133
134 %% Sorting resolved dependencies
135
136 -spec sort_deps(mongooseim:host_type(), module_map()) -> module_list().
137 sort_deps(HostType, ModuleMap) ->
138 5084 DepsGraph = digraph:new([acyclic, private]),
139
140 5084 try
141 5084 maps:fold(
142 fun(Module, Opts, _) ->
143 59224 process_module_dep(HostType, Module, Opts, DepsGraph)
144 end,
145 undefined, ModuleMap),
146
147 5084 lists:filtermap(
148 fun(Module) ->
149 59295 case maps:find(Module, ModuleMap) of
150 71 error -> false;
151 59224 {ok, Opts} -> {true, {Module, Opts}}
152 end
153 end,
154 digraph_utils:topsort(DepsGraph))
155 after
156 5084 digraph:delete(DepsGraph)
157 end.
158
159 -spec process_module_dep(mongooseim:host_type(), module(), module_opts(), digraph:graph()) -> ok.
160 process_module_dep(HostType, Module, Opts, DepsGraph) ->
161 59224 digraph:add_vertex(DepsGraph, Module),
162 59224 lists:foreach(
163 16531 fun({DepModule, _, DepHardness}) -> process_dep(Module, DepModule, DepHardness, DepsGraph) end,
164 gen_mod:get_deps(HostType, Module, Opts)).
165
166 -spec process_dep(Module :: module(), DepModule :: module(),
167 DepHardness :: hardness(), Graph :: digraph:graph()) -> ok.
168 process_dep(Module, DepModule, DepHardness, Graph) ->
169 16531 digraph:add_vertex(Graph, DepModule),
170 16531 case {digraph:add_edge(Graph, DepModule, Module, DepHardness), DepHardness} of
171 {['$e' | _], _} ->
172 16531 ok;
173
174 {{error, {bad_edge, CyclePath}}, hard} ->
175
:-(
case find_soft_edge(Graph, CyclePath) of
176 false ->
177
:-(
?LOG_CRITICAL(#{what => resolving_dependencies_aborted,
178 text => <<"Module dependency cycle found">>,
179
:-(
cycle_path => CyclePath}),
180
:-(
error({dependency_cycle, CyclePath});
181
182 {EdgeId, B, A, _} ->
183
:-(
?LOG_INFO(#{what => soft_module_dependency_cycle_detected,
184 text => <<"Soft module dependency cycle detected. "
185 "Dropping edge">>, edge => {A, B},
186
:-(
cyclepath => CyclePath}),
187
188
:-(
digraph:del_edge(Graph, EdgeId),
189
:-(
['$e' | _] = digraph:add_edge(Graph, DepModule, Module, hard),
190
:-(
ok
191 end;
192
193 {{error, {bad_edge, CyclePath}}, _Soft} ->
194
:-(
?LOG_INFO(#{what => soft_module_dependency_cycle_detected,
195 text => <<"Soft module dependency cycle detected. "
196 "Dropping edge">>, edge => {Module, DepModule},
197
:-(
cyclepath => CyclePath}),
198
:-(
ok
199 end.
200
201 -spec find_soft_edge(digraph:graph(), [digraph:vertex()]) ->
202 {digraph:edge(), digraph:vertex(),
203 digraph:vertex(), digraph:label()} | false.
204 find_soft_edge(Graph, CyclePath) ->
205
:-(
VerticePairs = lists:zip(CyclePath, tl(CyclePath) ++ [hd(CyclePath)]),
206
:-(
Edges = lists:filtermap(
207 fun({A, B}) ->
208
:-(
case find_edge(Graph, A, B) of
209
:-(
false -> false;
210
:-(
Edge -> {true, digraph:edge(Graph, Edge)}
211 end
212 end,
213 VerticePairs),
214
215
:-(
case lists:keyfind(optional, 4, Edges) of
216
:-(
false -> lists:keyfind(soft, 4, Edges);
217
:-(
Edge -> Edge
218 end.
219
220 -spec find_edge(digraph:graph(), digraph:vertex(), digraph:vertex()) -> digraph:edge() | false.
221 find_edge(Graph, A, B) ->
222
:-(
OutEdges = ordsets:from_list(digraph:out_edges(Graph, A)),
223
:-(
InEdges = ordsets:from_list(digraph:in_edges(Graph, B)),
224
225
:-(
case ordsets:intersection(OutEdges, InEdges) of
226
:-(
[Edge] -> Edge;
227
:-(
[] -> false
228 end.
Line Hits Source