From 6a092aeb6953c9e302011bfaa31dfb788047d708 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Tue, 30 Jan 2024 16:23:27 +0300 Subject: [PATCH] chore(emqx_machine): refactor injecting runtime deps Co-authored-by: Thales Macedo Garitezi --- apps/emqx_machine/src/emqx_machine_boot.erl | 57 ++++++------------- .../src/emqx_machine_boot_runtime_deps.erl | 53 +++++++++++++++++ 2 files changed, 70 insertions(+), 40 deletions(-) create mode 100644 apps/emqx_machine/src/emqx_machine_boot_runtime_deps.erl diff --git a/apps/emqx_machine/src/emqx_machine_boot.erl b/apps/emqx_machine/src/emqx_machine_boot.erl index 8783dd10a..ee6e02986 100644 --- a/apps/emqx_machine/src/emqx_machine_boot.erl +++ b/apps/emqx_machine/src/emqx_machine_boot.erl @@ -166,9 +166,8 @@ is_app(Name) -> sorted_reboot_apps() -> RebootApps = reboot_apps(), Apps0 = [{App, app_deps(App, RebootApps)} || App <- RebootApps], - Apps1 = inject_bridge_deps(Apps0), - Apps2 = inject_dashboard_deps(Apps1), - sorted_reboot_apps(Apps2). + Apps = emqx_machine_boot_runtime_deps:inject(Apps0, runtime_deps()), + sorted_reboot_apps(Apps). app_deps(App, RebootApps) -> case application:get_key(App, applications) of @@ -176,43 +175,21 @@ app_deps(App, RebootApps) -> {ok, List} -> lists:filter(fun(A) -> lists:member(A, RebootApps) end, List) end. -%% `emqx_bridge' is special in that it needs all the bridges apps to -%% be started before it, so that, when it loads the bridges from -%% configuration, the bridge app and its dependencies need to be up. -%% -%% `emqx_connector' also needs to start all connector dependencies for the same reason. -%% Since standalone apps like `emqx_mongodb' are already dependencies of `emqx_bridge_*' -%% apps, we may apply the same tactic for `emqx_connector' and inject individual bridges -%% as its dependencies. -inject_bridge_deps(RebootAppDeps) -> - BridgeApps = [ - App - || {App, _Deps} <- RebootAppDeps, - lists:prefix("emqx_bridge_", atom_to_list(App)) - ], - lists:map( - fun - ({emqx_bridge, Deps0}) when is_list(Deps0) -> - {emqx_bridge, Deps0 ++ BridgeApps}; - ({emqx_connector, Deps0}) when is_list(Deps0) -> - {emqx_connector, Deps0 ++ BridgeApps}; - (App) -> - App - end, - RebootAppDeps - ). -inject_dashboard_deps(Reboots) -> - Apps = [emqx_license], - Deps = lists:filter(fun(App) -> lists:keymember(App, 1, Reboots) end, Apps), - lists:map( - fun - ({emqx_dashboard, Deps0}) when is_list(Deps0) -> - {emqx_dashboard, Deps0 ++ Deps}; - (App) -> - App - end, - Reboots - ). +runtime_deps() -> + [ + %% `emqx_bridge' is special in that it needs all the bridges apps to + %% be started before it, so that, when it loads the bridges from + %% configuration, the bridge app and its dependencies need to be up. + {emqx_bridge, fun(App) -> lists:prefix("emqx_bridge_", atom_to_list(App)) end}, + %% `emqx_connector' also needs to start all connector dependencies for the same reason. + %% Since standalone apps like `emqx_mongodb' are already dependencies of `emqx_bridge_*' + %% apps, we may apply the same tactic for `emqx_connector' and inject individual bridges + %% as its dependencies. + {emqx_connector, fun(App) -> lists:prefix("emqx_bridge_", atom_to_list(App)) end}, + %% emqx_fdb is an EE app + {emqx_durable_storage, emqx_fdb}, + {emqx_dashboard, emqx_license} + ]. sorted_reboot_apps(Apps) -> G = digraph:new(), diff --git a/apps/emqx_machine/src/emqx_machine_boot_runtime_deps.erl b/apps/emqx_machine/src/emqx_machine_boot_runtime_deps.erl new file mode 100644 index 000000000..81f66839a --- /dev/null +++ b/apps/emqx_machine/src/emqx_machine_boot_runtime_deps.erl @@ -0,0 +1,53 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_machine_boot_runtime_deps). + +-export([inject/2]). + +-type app_name() :: atom(). +-type app_deps() :: {app_name(), [app_name()]}. +-type app_selector() :: app_name() | fun((app_name()) -> boolean()). +-type runtime_dep() :: {_WhatDepends :: app_name(), _OnWhat :: app_selector()}. + +-spec inject([app_deps()], [runtime_dep()]) -> [app_deps()]. +inject(AppDepList, DepSpecs) -> + AppDep0 = maps:from_list(AppDepList), + AppDep1 = lists:foldl( + fun(DepSpec, AppDepAcc) -> + inject_one_dep(AppDepAcc, DepSpec) + end, + AppDep0, + DepSpecs + ), + maps:to_list(AppDep1). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +inject_one_dep(AppDep, {WhatDepends, OnWhatSelector}) -> + OnWhat = select_apps(OnWhatSelector, maps:keys(AppDep)), + case AppDep of + #{WhatDepends := Deps} when is_list(Deps) -> + AppDep#{WhatDepends => lists:usort(Deps ++ OnWhat)}; + _ -> + AppDep + end. + +select_apps(AppName, AppNames) when is_atom(AppName) -> + lists:filter(fun(App) -> App =:= AppName end, AppNames); +select_apps(AppSelector, AppNames) when is_function(AppSelector, 1) -> + lists:filter(AppSelector, AppNames).