Current section
Files
Jump to
Current section
Files
src/reltool_util.erl
%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
%%%
%%%------------------------------------------------------------------------
%%% @doc
%%% ==reltool Utility Functions==
%%% All the functions here are probably considered unorthodox, but
%%% are useful for runtime usage of applications and releases.
%%% @end
%%%
%%% MIT License
%%%
%%% Copyright (c) 2013-2023 Michael Truog <mjtruog at protonmail dot com>
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a
%%% copy of this software and associated documentation files (the "Software"),
%%% to deal in the Software without restriction, including without limitation
%%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%%% and/or sell copies of the Software, and to permit persons to whom the
%%% Software is furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%%% DEALINGS IN THE SOFTWARE.
%%%
%%% @author Michael Truog <mjtruog at protonmail dot com>
%%% @copyright 2013-2023 Michael Truog
%%% @version 2.0.7 {@date} {@time}
%%%------------------------------------------------------------------------
-module(reltool_util).
-author('mjtruog at protonmail dot com').
-export([application_env/1,
application_start/1,
application_start/2,
application_start/3,
application_start/4,
application_stop/1,
application_stop/2,
application_remove/1,
application_remove/2,
application_remove/3,
application_purged/1,
application_purged/2,
application_running/1,
application_running/2,
application_loaded/1,
application_modules/1,
application_modules/2,
applications_start/1,
applications_start/2,
applications_remove/3,
config_load/1,
ensure_application_loaded/1,
ensure_application_started/1,
ensure_application_stopped/1,
is_deprecated/3,
is_module_loaded/1,
is_module_loaded/2,
module_load/1,
module_loaded/1,
module_unload/1,
module_reload/1,
module_purged/1,
module_purged/2,
module_exports/1,
module_behaviours/1,
module_version/1,
modules_reload/1,
modules_purged/2,
script_start/1,
script_remove/1,
script_remove/2,
script_remove/3,
boot_start/1,
boot_remove/1,
boot_remove/2,
boot_remove/3]).
-define(IS_MODULE_LOADED_DELTA, 100).
-define(MODULES_PURGED_DELTA, 100).
-ifdef(OTP_RELEASE).
% able to use -if/-elif here
-define(ERLANG_OTP_VERSION_21_FEATURES, true).
-endif.
-compile({no_auto_import, [{module_loaded, 1}]}).
%%%------------------------------------------------------------------------
%%% External interface functions
%%%------------------------------------------------------------------------
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the application's env settings.===
%% Only get the env settings from the application's .app file.
%% @end
%%-------------------------------------------------------------------------
-spec application_env(Application :: atom()) ->
{ok, list({atom(), any()})} |
{error, any()}.
application_env(Application)
when is_atom(Application) ->
case code:where_is_file(erlang:atom_to_list(Application) ++ ".app") of
non_existing ->
{error, not_found};
Path ->
case file:consult(Path) of
{ok, [{application, Application, L}]} ->
case lists:keyfind(env, 1, L) of
{env, Env} ->
{ok, Env};
false ->
{ok, []}
end;
{ok, _} ->
{error, invalid_file};
{error, _} = Error ->
Error
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually.===
%% @end
%%-------------------------------------------------------------------------
-spec application_start(Application :: atom()) ->
ok |
{error, any()}.
application_start(Application)
when is_atom(Application) ->
application_start(Application, [], [], 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually with a specific configuration.===
%% @end
%%-------------------------------------------------------------------------
-spec application_start(Application :: atom(),
Env :: list({atom(), any()})) ->
ok |
{error, any()}.
application_start(Application, Env)
when is_atom(Application), is_list(Env) ->
application_start(Application, Env, [], 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually with a specific configuration and timeout.===
%% @end
%%-------------------------------------------------------------------------
-spec application_start(Application :: atom(),
Env :: list({atom(), any()}),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
application_start(Application, Env, Timeout)
when is_atom(Application), is_list(Env) ->
application_start(Application, Env, [], Timeout).
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually with a specific configuration, excluded dependencies and timeout.===
%% @end
%%-------------------------------------------------------------------------
-spec application_start(Application :: atom(),
Env :: list({atom(), any()}),
Excluded :: list(atom()),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
application_start(Application, Env, Excluded, Timeout)
when is_atom(Application), is_list(Env), is_list(Excluded) ->
case ensure_application_loaded(Application) of
ok ->
case application_start_set_env(Env, Application, Timeout) of
ok ->
case applications_dependencies(Application) of
{ok, As} ->
case application_start_dependencies(As, Excluded) of
ok ->
ensure_application_started(Application);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop an application and its dependencies.===
%% Only stop dependencies that are not required for other applications.
%% @end
%%-------------------------------------------------------------------------
-spec application_stop(Application :: atom()) ->
ok |
{error, any()}.
application_stop(Application) ->
application_stop(Application, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop an application and its dependencies with a list of applications to ignore.===
%% Only stop dependencies that are not required for other applications.
%% @end
%%-------------------------------------------------------------------------
-spec application_stop(Application :: atom(),
Ignore :: list(atom())) ->
ok |
{error, any()}.
application_stop(Application, Ignore)
when is_atom(Application), is_list(Ignore) ->
case application_stop_dependencies(Application, Ignore) of
{ok, _} ->
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop and purge the modules of an application and all of its dependencies.===
%% Only application dependencies that are not required for other
%% applications are removed.
%% @end
%%-------------------------------------------------------------------------
-spec application_remove(Application :: atom()) ->
ok |
{error, any()}.
application_remove(Application) ->
application_remove(Application, 5000, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop and purge the modules of an application and all of its dependencies with a timeout.===
%% Only application dependencies that are not required for other
%% applications are removed.
%% @end
%%-------------------------------------------------------------------------
-spec application_remove(Application :: atom(),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
application_remove(Application, Timeout) ->
application_remove(Application, Timeout, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop and purge the modules of an application and all of its dependencies with a timeout and a list of applications to ignore.===
%% Only application dependencies that are not required for other
%% applications are removed.
%% @end
%%-------------------------------------------------------------------------
-spec application_remove(Application :: atom(),
Timeout :: pos_integer() | infinity,
Ignore :: list(atom())) ->
ok |
{error, any()}.
application_remove(Application, infinity, Ignore)
when is_atom(Application), is_list(Ignore) ->
case application_stop_dependencies(Application, Ignore) of
{ok, Applications} ->
applications_purged(Applications, infinity);
{error, _} = Error ->
Error
end;
application_remove(Application, Timeout, Ignore)
when is_atom(Application), is_integer(Timeout), Timeout > 0,
is_list(Ignore) ->
case application_stop_dependencies(Application, Ignore) of
{ok, Applications} ->
TimeoutSlice = erlang:round(
0.5 + Timeout / erlang:length(Applications)),
applications_purged(Applications, TimeoutSlice);
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Purge a loaded application's modules and unload the application.===
%% The application is stopped if it is running, but its dependencies are
%% ignored.
%% @end
%%-------------------------------------------------------------------------
-spec application_purged(Application :: atom()) ->
ok |
{error, any()}.
application_purged(Application) ->
application_purged(Application, 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Purge a loaded application's modules and unload the application with a specific timeout.===
%% The application is stopped if it is running, but its dependencies are
%% ignored.
%% @end
%%-------------------------------------------------------------------------
-spec application_purged(Application :: atom(),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
application_purged(Application, Timeout)
when is_atom(Application) ->
case application_loaded(Application) of
{ok, _} ->
case ensure_application_stopped(Application) of
ok ->
case application_modules(Application) of
{ok, Modules} ->
case modules_purged(Modules, Timeout) of
ok ->
application:unload(Application);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Check if an application is currently running.===
%% @end
%%-------------------------------------------------------------------------
-spec application_running(Application :: atom()) ->
{ok, {atom(), string()}} |
{error, application_controller_timeout | {not_found, atom()}}.
application_running(Application)
when is_atom(Application) ->
application_running(Application, 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Check if an application is currently running with a timeout.===
%% @end
%%-------------------------------------------------------------------------
-spec application_running(Application :: atom(),
Timeout :: pos_integer() | infinity) ->
{ok, {atom(), string()}} |
{error, application_controller_timeout | {not_found, atom()}}.
application_running(Application, Timeout)
when is_atom(Application) ->
try application:which_applications(Timeout) of
Apps ->
case lists:keyfind(Application, 1, Apps) of
{Application, _, VSN} ->
{ok, {Application, VSN}};
false ->
{error, {not_found, Application}}
end
catch exit:{timeout, _} ->
{error, application_controller_timeout}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Check if an application is currently loaded.===
%% @end
%%-------------------------------------------------------------------------
-spec application_loaded(Application :: atom()) ->
{ok, {atom(), string()}} |
{error, {not_found, atom()}}.
application_loaded(Application)
when is_atom(Application) ->
Apps = application:loaded_applications(),
case lists:keyfind(Application, 1, Apps) of
{Application, _, VSN} ->
{ok, {Application, VSN}};
false ->
{error, {not_found, Application}}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Retrieve a list of application modules.===
%% @end
%%-------------------------------------------------------------------------
-spec application_modules(Application :: atom()) ->
{ok, list(atom())} |
{error, {modules_missing, atom()}}.
application_modules(Application) ->
application_modules(Application, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Retrieve a list of application modules with filter options.===
%% Options can contain {behavior, ModuleName} to list all the modules
%% that use a specific behaviour (the information will not be present if
%% the beam file was stripped).
%% @end
%%-------------------------------------------------------------------------
-spec application_modules(Application :: atom(),
Options :: list({atom(), any()})) ->
{ok, list(atom())} |
{error, {modules_missing, atom()}}.
application_modules(Application, Options)
when is_atom(Application), is_list(Options) ->
case application:get_key(Application, modules) of
{ok, Modules} ->
{ok, modules_filter(Options, Modules, false)};
undefined ->
{error, {modules_missing, Application}}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually.===
%% @end
%%-------------------------------------------------------------------------
-spec applications_start(Applications :: nonempty_list(atom() |
{atom(), list()})) ->
ok |
{error, any()}.
applications_start([_ | _] = Applications) ->
applications_start(Applications, 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Start all the dependent applications manually.===
%% @end
%%-------------------------------------------------------------------------
-spec applications_start(Applications :: nonempty_list(atom() |
{atom(), list()}),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
applications_start([_ | _] = Applications, Timeout) ->
applications_start_element(Applications, Timeout).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop and purge the modules of all applications and their dependencies with a timeout and a list of applications to ignore.===
%% Only application dependencies that are not required for other
%% applications are removed.
%% @end
%%-------------------------------------------------------------------------
-spec applications_remove(Applications :: list(atom()),
Timeout :: pos_integer() | infinity,
Ignore :: list(atom())) ->
ok |
{error, any()}.
applications_remove([], _, _) ->
ok;
applications_remove([Application | Applications], Timeout, Ignore) ->
case application_remove(Application, Timeout, Ignore) of
ok ->
applications_remove(Applications, Timeout, Ignore);
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Load a config file from a release.===
%% All applications with configuration values are loaded if they are not
%% already loaded.
%% @end
%%-------------------------------------------------------------------------
-spec config_load(FilePath :: string()) ->
{ok, list(atom())} |
{error, any()}.
config_load(FilePath)
when is_list(FilePath) ->
true = lists:suffix(".config", FilePath),
{ok, [ApplicationEnvs]} = file:consult(FilePath),
config_load_application(ApplicationEnvs, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure an application is loaded.===
%% @end
%%-------------------------------------------------------------------------
-spec ensure_application_loaded(Application :: atom()) ->
ok |
{error, any()}.
ensure_application_loaded(kernel) ->
ok;
ensure_application_loaded(stdlib) ->
ok;
ensure_application_loaded(Application) ->
Loaded = case application:load(Application) of
ok ->
ok;
{error, {already_loaded, Application}} ->
ok;
{error, _} = Error ->
Error
end,
if
Loaded =:= ok ->
case application:get_key(Application, modules) of
{ok, Modules} ->
% make sure all modules are loaded, even if the
% application information is already loaded, since
% loading the application data does not automatically
% load its modules
lists:foreach(fun(M) ->
% valid results, others cause a crash
case module_loaded(M) of
ok ->
ok;
{error, nofile} ->
error_logger:warning_msg("broken "
"application ~p "
"missing ~p file~n",
[Application, M]);
{error, Reason} ->
error_logger:error_msg("application ~p load "
"error ~p on ~p file~n",
[Application,
Reason, M])
end
end, Modules);
undefined ->
ok
end;
true ->
Loaded
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure an application is started.===
%% @end
%%-------------------------------------------------------------------------
-spec ensure_application_started(Application :: atom()) ->
ok |
{error, any()}.
ensure_application_started(Application) ->
case application:start(Application, temporary) of
ok ->
ok;
{error, {already_started, Application}} ->
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure an application is stopped.===
%% @end
%%-------------------------------------------------------------------------
-spec ensure_application_stopped(Application :: atom()) ->
ok |
{error, any()}.
ensure_application_stopped(Application) ->
case application:stop(Application) of
ok ->
ok;
{error, {not_started, Application}} ->
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Check if a module function is marked as deprecated.===
%% The value false will always be returned if the beam file was stripped.
%% @end
%%-------------------------------------------------------------------------
-spec is_deprecated(Module :: module(),
Function :: atom(),
Arity :: non_neg_integer()) ->
boolean().
is_deprecated(Module, Function, Arity) ->
Attributes = Module:module_info(attributes),
lists:any(fun(Attribute) ->
case Attribute of
{deprecated, [_ | _] = Deprecated} ->
lists:any(fun(Deprecate) ->
case Deprecate of
module ->
true;
{Function, FunctionArity}
when FunctionArity == Arity;
FunctionArity == '_' ->
true;
{Function, FunctionArity, _}
when FunctionArity == Arity;
FunctionArity == '_' ->
true;
_ ->
false
end
end, Deprecated);
_ ->
false
end
end, Attributes).
%%-------------------------------------------------------------------------
%% @doc
%% ===Wait to check if a module is loaded.===
%% @end
%%-------------------------------------------------------------------------
-spec is_module_loaded(Module :: atom()) ->
ok |
{error, timeout}.
is_module_loaded(Module)
when is_atom(Module) ->
case is_module_loaded(Module, 5000) of
{ok, _} ->
ok;
{error, timeout} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Wait to check if a module is loaded.===
%% Return a new timeout value with the elapsed time subtracted.
%% @end
%%-------------------------------------------------------------------------
-spec is_module_loaded(Module :: atom(),
Timeout :: non_neg_integer()) ->
{ok, non_neg_integer()} |
{error, timeout}.
is_module_loaded(Module, Timeout)
when is_atom(Module), is_integer(Timeout), Timeout >= 0 ->
case is_module_loaded_check(Module) of
true ->
{ok, Timeout};
false ->
case erlang:max(Timeout - ?IS_MODULE_LOADED_DELTA, 0) of
0 ->
{error, timeout};
NextTimeout ->
receive after ?IS_MODULE_LOADED_DELTA -> ok end,
is_module_loaded(Module, NextTimeout)
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Load a module.===
%% @end
%%-------------------------------------------------------------------------
-spec module_load(Module :: atom()) ->
ok |
{error, badfile | nofile | not_purged | on_load_failure | sticky_directory}.
module_load(Module)
when is_atom(Module) ->
case code:load_file(Module) of
{module, Module} ->
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure a module is loaded.===
%% If the module is not loaded, attempt to load it.
%% @end
%%-------------------------------------------------------------------------
-spec module_loaded(Module :: atom()) ->
ok |
{error, badfile | nofile | not_purged | on_load_failure | sticky_directory}.
module_loaded(Module)
when is_atom(Module) ->
case is_module_loaded_check(Module) of
false ->
module_load(Module);
true ->
ok
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Unload a module.===
%% @end
%%-------------------------------------------------------------------------
-spec module_unload(Module :: atom()) ->
ok |
{error, not_loaded | not_unloaded}.
module_unload(Module)
when is_atom(Module) ->
case code:delete(Module) of
true ->
ok;
false ->
case is_module_loaded_check(Module) of
true ->
code:purge(Module),
case code:delete(Module) of
true ->
ok;
false ->
{error, not_unloaded}
end;
false ->
{error, not_loaded}
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Reload a module.===
%% @end
%%-------------------------------------------------------------------------
-spec module_reload(Module :: atom()) ->
ok |
{error, badfile | nofile | not_purged | on_load_failure | sticky_directory}.
module_reload(Module)
when is_atom(Module) ->
code:purge(Module),
case code:load_file(Module) of
{module, Module} ->
code:soft_purge(Module),
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure a module is purged.===
%% If the module is not loaded, ignore it.
%% @end
%%-------------------------------------------------------------------------
-spec module_purged(Module :: atom()) ->
ok |
{error, timeout}.
module_purged(Module) ->
module_purged(Module, 5000).
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure a module is purged with a timeout.===
%% If the module is not loaded, ignore it.
%% @end
%%-------------------------------------------------------------------------
-spec module_purged(Module :: atom(),
Timeout :: non_neg_integer() | infinity) ->
ok |
{error, timeout}.
module_purged(Module, Timeout)
when is_atom(Module) ->
modules_purged([Module], Timeout).
%%-------------------------------------------------------------------------
%% @doc
%% ===List the exported functions of a module.===
%% @end
%%-------------------------------------------------------------------------
-spec module_exports(Module :: atom()) ->
list({atom(), pos_integer()}).
module_exports(Module)
when is_atom(Module) ->
{exports, L0} = lists:keyfind(exports, 1, Module:module_info()),
{value, _, L1} = lists:keytake(module_info, 1, L0),
{value, _, L2} = lists:keytake(module_info, 1, L1),
L2.
%%-------------------------------------------------------------------------
%% @doc
%% ===List the behaviours used by a module.===
%% The information will not be present if the beam file was stripped.
%% @end
%%-------------------------------------------------------------------------
-spec module_behaviours(Module :: module()) ->
list(module()).
module_behaviours(Module)
when is_atom(Module) ->
Behaviours = lists:flatmap(fun(Attribute) ->
case Attribute of
{behaviour, L} ->
L;
{behavior, L} ->
L;
_ ->
[]
end
end, Module:module_info(attributes)),
Behaviours.
%%-------------------------------------------------------------------------
%% @doc
%% ===Provide the current version of the module.===
%% A list is returned with an entry for each use of the -vsn attribute
%% in the order within the module file for the currently loaded version
%% (the result is consistent with beam_lib:version/1).
%% The information will not be present if the beam file was stripped.
%% @end
%%-------------------------------------------------------------------------
-spec module_version(Module :: atom()) ->
list(any()).
module_version(Module)
when is_atom(Module) ->
Version = lists:flatmap(fun(Attribute) ->
case Attribute of
{vsn, VSN} ->
VSN;
_ ->
[]
end
end, Module:module_info(attributes)),
Version.
%%-------------------------------------------------------------------------
%% @doc
%% ===Reload a list of modules.===
%% @end
%%-------------------------------------------------------------------------
-spec modules_reload(Modules :: nonempty_list(atom())) ->
ok |
{error, any()}.
modules_reload([Module | _] = Modules)
when is_atom(Module) ->
ok = modules_map(Modules, purge),
case code:atomic_load(Modules) of
ok ->
ok = modules_map(Modules, soft_purge),
ok;
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Make sure modules are purged with a timeout.===
%% If a module is not loaded, ignore it.
%% @end
%%-------------------------------------------------------------------------
-spec modules_purged(Modules :: list(atom()),
Timeout :: non_neg_integer() | infinity) ->
ok |
{error, timeout}.
modules_purged(Modules, infinity) ->
modules_purged(Modules, 5000);
modules_purged(Modules, Timeout)
when is_list(Modules), is_integer(Timeout), Timeout >= 0 ->
ok = modules_unload(Modules),
modules_purged(Modules, [], Timeout).
%%-------------------------------------------------------------------------
%% @doc
%% ===Start everything specified within a script file.===
%% A script file is the input used when creating a boot file, which is the
%% file used when first starting the Erlang VM. This function checks
%% all applications to determine if they are already running with the
%% expected versions. All modules are checked to make sure they have
%% been loaded, if they are expected to have been loaded. Normally,
%% the script is only used in the binary boot file format and only a single
%% boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The script file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec script_start(FilePath :: string()) ->
{ok, list(atom())} |
{error, any()}.
script_start(FilePath)
when is_list(FilePath) ->
true = lists:suffix(".script", FilePath),
case file:consult(FilePath) of
{ok, [{script, {_Name, _Vsn}, _Instructions} = Script]} ->
Dir = filename:dirname(FilePath),
script_start_data(Script, Dir);
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a script file.===
%% A script file is the input used when creating a boot file, which is the
%% file used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% the script is only used in the binary boot file format and only a single
%% boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The script file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec script_remove(FilePath :: string()) ->
ok |
{error, any()}.
script_remove(FilePath) ->
script_remove(FilePath, 5000, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a script file with a timeout.===
%% A script file is the input used when creating a boot file, which is the
%% file used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% the script is only used in the binary boot file format and only a single
%% boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The script file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec script_remove(FilePath :: string(),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
script_remove(FilePath, Timeout) ->
script_remove(FilePath, Timeout, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a script file with a timeout and a list of applications to ignore.===
%% A script file is the input used when creating a boot file, which is the
%% file used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% the script is only used in the binary boot file format and only a single
%% boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The script file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec script_remove(FilePath :: string(),
Timeout :: pos_integer() | infinity,
Ignore :: list(atom())) ->
ok |
{error, any()}.
script_remove(FilePath, Timeout, Ignore)
when is_list(FilePath),
((is_integer(Timeout) andalso (Timeout > 0)) orelse
(Timeout =:= infinity)), is_list(Ignore) ->
true = lists:suffix(".script", FilePath),
case file:consult(FilePath) of
{ok, [{script, {_Name, _Vsn}, _Instructions} = Script]} ->
Dir = filename:dirname(FilePath),
script_remove_data(Script, Dir, Timeout, Ignore);
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Start everything specified within a boot file.===
%% A boot file is used when first starting the Erlang VM. This function checks
%% all applications to determine if they are already running with the
%% expected versions. All modules are checked to make sure they have
%% been loaded, if they are expected to have been loaded. Normally,
%% only a single boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The boot file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec boot_start(FilePath :: string()) ->
{ok, list(atom())} |
{error, any()}.
boot_start(FilePath)
when is_list(FilePath) ->
true = lists:suffix(".boot", FilePath),
case file:read_file(FilePath) of
{ok, ScriptData} ->
Dir = filename:dirname(FilePath),
script_start_data(erlang:binary_to_term(ScriptData), Dir);
{error, _} = Error ->
Error
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a boot file.===
%% A boot file is used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% only a single boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The boot file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec boot_remove(FilePath :: string()) ->
ok |
{error, any()}.
boot_remove(FilePath) ->
boot_remove(FilePath, 5000, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a boot file with a timeout.===
%% A boot file is used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% only a single boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The boot file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec boot_remove(FilePath :: string(),
Timeout :: pos_integer() | infinity) ->
ok |
{error, any()}.
boot_remove(FilePath, Timeout) ->
boot_remove(FilePath, Timeout, []).
%%-------------------------------------------------------------------------
%% @doc
%% ===Stop everything specified within a boot file with a timeout and a list of applications to ignore.===
%% A boot file is used when first starting the Erlang VM. This function checks
%% all applications to determine applications which can be safely removed
%% (assuming the application dependencies are correct). The applications
%% will then be stopped and their modules will be purged. Normally,
%% only a single boot file is used during the lifetime of the Erlang VM
%% (so it is unclear if using this function is bad or just unorthodox).
%% The boot file is expected to be within a release directory created
%% by reltool.
%% @end
%%-------------------------------------------------------------------------
-spec boot_remove(FilePath :: string(),
Timeout :: pos_integer() | infinity,
Ignore :: list(atom())) ->
ok |
{error, any()}.
boot_remove(FilePath, Timeout, Ignore)
when is_list(FilePath),
((is_integer(Timeout) andalso (Timeout > 0)) orelse
(Timeout =:= infinity)), is_list(Ignore) ->
true = lists:suffix(".boot", FilePath),
case file:read_file(FilePath) of
{ok, ScriptData} ->
Dir = filename:dirname(FilePath),
script_remove_data(erlang:binary_to_term(ScriptData),
Dir, Timeout, Ignore);
{error, _} = Error ->
Error
end.
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
application_start_set_env([], _, _) ->
ok;
application_start_set_env([{K, V} | L], Application, Timeout) ->
try application:set_env(Application, K, V, Timeout) of
ok ->
application_start_set_env(L, Application, Timeout)
catch
exit:{timeout, _} ->
{error, application_controller_timeout}
end;
application_start_set_env(_, _, _) ->
{error, invalid_application_env}.
application_start_dependencies(As, Excluded) ->
application_start_dependencies(As -- Excluded).
application_start_dependencies([]) ->
ok;
application_start_dependencies([A | As]) ->
case ensure_application_started(A) of
ok ->
application_start_dependencies(As);
{error, _} = Error ->
Error
end.
applications_start_element([], _) ->
ok;
applications_start_element([Application | Applications], Timeout)
when is_atom(Application) ->
case application_start(Application, [], Timeout) of
ok ->
applications_start_element(Applications, Timeout);
{error, _} = Error ->
Error
end;
applications_start_element([{Application, Env} | Applications], Timeout)
when is_atom(Application), is_list(Env) ->
case application_start(Application, Env, Timeout) of
ok ->
applications_start_element(Applications, Timeout);
{error, _} = Error ->
Error
end.
application_stop_dependencies_ignore([], L) ->
L;
application_stop_dependencies_ignore([Application | Ignore], L) ->
application_stop_dependencies_ignore(Ignore, delete_all(Application, L)).
application_stop_external([], Apps) ->
{ok, Apps};
application_stop_external([A | As], Apps) ->
case lists:keytake(A, 1, Apps) of
false ->
{error, {not_started, A}};
{value, _, NextApps} ->
application_stop_external(As, NextApps)
end.
application_stop_ignore(L) ->
application_stop_ignore(sets:new(), L).
application_stop_ignore(Required, []) ->
sets:to_list(Required);
application_stop_ignore(Required, [{A, _, _} | L]) ->
case applications_dependencies(A) of
{ok, As} ->
application_stop_ignore(
sets:union(Required, sets:from_list(As)), L);
{error, _} = Error ->
Error
end.
application_stop_all([]) ->
ok;
application_stop_all([A | As]) ->
case ensure_application_stopped(A) of
ok ->
application_stop_all(As);
{error, _} = Error ->
Error
end.
application_stop_dependencies(Application, Ignore)
when is_atom(Application) ->
case applications_dependencies(Application) of
{ok, StopAs0} ->
case ensure_application_stopped(Application) of
ok ->
StopAs1 = application_stop_dependencies_ignore([kernel,
stdlib |
Ignore],
StopAs0),
Apps = application:loaded_applications(),
{value, _, OtherApps0} = lists:keytake(Application,
1, Apps),
% determine applications which are not dependencies
case application_stop_external(StopAs1, OtherApps0) of
{ok, OtherAppsN} ->
% check to see the required applications
% separate from the application dependencies
RequiredAs = application_stop_ignore(OtherAppsN),
% ignore all applications that are requirements
% of other applications
StopAsN = lists:reverse(lists:foldl(fun(A, As) ->
delete_all(A, As)
end, StopAs1, RequiredAs)),
% stop all the application dependencies
% that are no longer required
case application_stop_all(StopAsN) of
ok ->
{ok, [Application | StopAsN]};
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
applications_purged([], _) ->
ok;
applications_purged([Application | Applications], Timeout) ->
case application_purged(Application, Timeout) of
ok ->
applications_purged(Applications, Timeout);
{error, _} = Error ->
Error
end.
-ifdef(ERLANG_OTP_VERSION_21_FEATURES).
applications_dependencies(A) ->
Included = case application:get_key(A, included_applications) of
undefined ->
ok;
{ok, LoadAs} ->
applications_dependencies_load(LoadAs)
end,
if
Included =:= ok ->
case application:get_key(A, applications) of
undefined ->
{error, {undefined_dependencies, A}};
{ok, As} ->
applications_dependencies(As, As)
end;
true ->
Included
end.
-else.
applications_dependencies(A) ->
Included = case application:get_key(A, included_applications) of
undefined ->
ok;
{ok, LoadAs} ->
applications_dependencies_load(LoadAs)
end,
if
Included =:= ok ->
case application:get_key(A, applications) of
undefined ->
{error, {undefined_dependencies, A}};
{ok, As} when A =:= common_test ->
% XXX avoid error in Erlang/OTP until ct gets fixed
% (necessary for Erlang =< R16B02)
AsCT = case lists:member(sasl, As) of
true ->
As;
false ->
[sasl | As]
end,
applications_dependencies(AsCT, AsCT);
{ok, As} when A =:= elixir ->
% XXX avoid error in Elixir until its .app file gets fixed
% (necessary for Elixir =< 0.14.3)
AsElixir0 = case lists:member(crypto, As) of
true ->
As;
false ->
[crypto | As]
end,
AsElixir1 = case lists:member(compiler, As) of
true ->
AsElixir0;
false ->
[compiler | AsElixir0]
end,
AsElixirN = case lists:member(syntax_tools, As) of
true ->
AsElixir1;
false ->
[syntax_tools | AsElixir1]
end,
applications_dependencies(AsElixirN, AsElixirN);
{ok, As} ->
applications_dependencies(As, As)
end;
true ->
Included
end.
-endif.
applications_dependencies_load([]) ->
ok;
applications_dependencies_load([A | Rest]) ->
case ensure_application_loaded(A) of
ok ->
As1 = case application:get_key(A, included_applications) of
undefined ->
[];
{ok, As0} ->
As0
end,
LoadAs = case application:get_key(A, applications) of
undefined ->
As1;
{ok, As2} ->
As1 ++ As2
end,
case applications_dependencies_load(LoadAs) of
ok ->
applications_dependencies_load(Rest);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
applications_dependencies([], As) ->
{ok, As};
applications_dependencies([A | Rest], As) ->
case ensure_application_loaded(A) of
ok ->
Included = case application:get_key(A, included_applications) of
undefined ->
ok;
{ok, LoadAs} ->
applications_dependencies_load(LoadAs)
end,
if
Included =:= ok ->
case application:get_key(A, applications) of
undefined ->
{error, {undefined_dependencies, A}};
{ok, []} ->
applications_dependencies(Rest, As);
{ok, NextAs} ->
OtherAs = As -- NextAs,
case applications_dependencies(NextAs,
NextAs ++ OtherAs) of
{ok, NewAs} ->
applications_dependencies(Rest, NewAs);
{error, _} = Error ->
Error
end
end;
true ->
Included
end;
{error, _} = Error ->
Error
end.
applications_top_level(Applications) ->
true = erlang:length(lists:usort(Applications)) ==
erlang:length(Applications), % no duplicates
case applications_top_level(Applications, sets:new()) of
{ok, Dependencies} ->
TopLevelApplications = Applications -- sets:to_list(Dependencies),
{ok, TopLevelApplications};
{error, _} = Error ->
Error
end.
applications_top_level([], Dependencies) ->
{ok, Dependencies};
applications_top_level([Application | Applications], Dependencies) ->
case applications_dependencies(Application) of
{ok, As} ->
applications_top_level(Applications,
sets:union(sets:from_list(As),
Dependencies));
{error, _} = Error ->
Error
end.
script_start_data({script, {_Name, _Vsn}, Instructions}, Dir) ->
% system name and version are ignored
% expects the typical directory structure produced by reltool
DirNames = filename:split(Dir),
case erlang:length(DirNames) of
DirNamesLength when DirNamesLength > 2 ->
Root = lists:sublist(DirNames, DirNamesLength - 2),
case filelib:is_dir(filename:join(Root ++ ["lib"])) of
true ->
% on success, return the last application to be started
% (should be the main application since all the application
% dependencies are started first)
script_start_instructions(Instructions, Root);
false ->
{error, invalid_release_structure}
end;
_ ->
{error, invalid_release_directory}
end.
script_start_instructions(L, Root) ->
Apps = application:loaded_applications(),
script_start_instructions(L, preload, [], Root, Apps).
script_start_instructions([], started, Applications, _, _) ->
applications_top_level(Applications);
script_start_instructions([{progress, Progress} | L],
_, Applications, Root, Apps) ->
script_start_instructions(L, Progress, Applications, Root, Apps);
script_start_instructions([{preLoaded, _} | L],
preload, Applications, Root, Apps) ->
script_start_instructions(L, preload, Applications, Root, Apps);
script_start_instructions([{kernel_load_completed} | L],
preloaded, Applications, Root, Apps) ->
script_start_instructions(L, kernel_load_completed,
Applications, Root, Apps);
script_start_instructions([{path, Paths} | L],
preloaded, Applications, Root, Apps) ->
case ensure_code_paths(Paths, Apps) of
ok ->
script_start_instructions(L, preloaded, Applications, Root, Apps);
{error, _} = Error ->
Error
end;
script_start_instructions([{primLoad, Modules} | L],
preloaded, Applications, Root, Apps) ->
Loaded = lists:all(fun(M) ->
is_module_loaded_check(M) =:= true
end, Modules),
if
Loaded ->
script_start_instructions(L, preloaded, Applications, Root, Apps);
true ->
{error, modules_not_preloaded}
end;
script_start_instructions([{kernel_load_completed} | L],
kernel_load_completed, Applications, Root, Apps) ->
script_start_instructions(L, kernel_load_completed,
Applications, Root, Apps);
script_start_instructions([{primLoad, Modules} | L],
kernel_load_completed, Applications, Root, Apps) ->
case ensure_all_loaded(Modules) of
ok ->
script_start_instructions(L, kernel_load_completed,
Applications, Root, Apps);
{error, _} = Error ->
Error
end;
script_start_instructions([{path, Paths} | L],
kernel_load_completed, Applications, Root, Apps) ->
case load_all_paths(Paths, Root) of
ok ->
script_start_instructions(L, kernel_load_completed,
Applications, Root, Apps);
{error, _} = Error ->
Error
end;
script_start_instructions([{path, _} | L],
modules_loaded, Applications, Root, Apps) ->
script_start_instructions(L, modules_loaded, Applications, Root, Apps);
script_start_instructions([{kernelProcess, _, _} | L],
modules_loaded, Applications, Root, Apps) ->
script_start_instructions(L, modules_loaded, Applications, Root, Apps);
script_start_instructions([{apply, {application, load, [AppDescr]}} | L],
init_kernel_started, Applications, Root, Apps) ->
{application, A, [_ | _] = AppSpecKeys} = AppDescr,
case lists:keyfind(A, 1, Apps) of
{A, _, VSN} ->
{vsn, RequestedVSN} = lists:keyfind(vsn, 1, AppSpecKeys),
if
VSN == RequestedVSN ->
script_start_instructions(L, init_kernel_started,
[A | Applications], Root, Apps);
true ->
{error, {version_mismatch, A, RequestedVSN, VSN}}
end;
false ->
case application:load(AppDescr) of
ok ->
script_start_instructions(L, init_kernel_started,
[A | Applications], Root, Apps);
{error, _} = Error ->
Error
end
end;
script_start_instructions([{apply,
{application, start_boot, [A | _]}} | L],
applications_loaded, Applications, Root, Apps)
when A =:= kernel; A =:= stdlib ->
% if this code is being used, kernel and stdlib should have already
% been started with the boot file that was used to start the Erlang VM
script_start_instructions(L, applications_loaded, Applications, Root, Apps);
script_start_instructions([{apply,
{application, start_boot, [A | _]}} | L],
applications_loaded, Applications, Root, Apps) ->
case ensure_application_started(A) of
ok ->
% order loaded applications based on the start order
script_start_instructions(L, applications_loaded,
[A | lists:delete(A, Applications)],
Root, Apps);
{error, _} = Error ->
Error
end;
script_start_instructions([{apply, {c, erlangrc, _}} | L],
applications_loaded, Applications, Root, Apps) ->
script_start_instructions(L, applications_loaded,
Applications, Root, Apps).
script_remove_data({script, {_Name, _Vsn}, Instructions},
Dir, Timeout, Ignore) ->
% system name and version are ignored
% expects the typical directory structure produced by reltool
DirNames = filename:split(Dir),
case erlang:length(DirNames) of
DirNamesLength when DirNamesLength > 2 ->
Root = lists:sublist(DirNames, DirNamesLength - 2),
case filelib:is_dir(filename:join(Root ++ ["lib"])) of
true ->
case script_remove_instructions(Instructions) of
{ok, Applications} ->
NewTimeout = if
Timeout =:= infinity ->
Timeout;
is_integer(Timeout) ->
erlang:round(0.5 + Timeout /
erlang:length(Applications))
end,
applications_remove(Applications,
NewTimeout,
Ignore);
{error, _} = Error ->
Error
end;
false ->
{error, invalid_release_structure}
end;
_ ->
{error, invalid_release_directory}
end.
script_remove_instructions(L) ->
script_remove_instructions(L, preload, []).
script_remove_instructions([], started, Applications) ->
applications_top_level(Applications);
script_remove_instructions([{progress, Progress} | L], _, Applications) ->
script_remove_instructions(L, Progress, Applications);
script_remove_instructions([{preLoaded, _} | L], preload, Applications) ->
script_remove_instructions(L, preload, Applications);
script_remove_instructions([{kernel_load_completed} | L],
preloaded, Applications) ->
script_remove_instructions(L, kernel_load_completed, Applications);
script_remove_instructions([{path, _} | L],
preloaded, Applications) ->
script_remove_instructions(L, preloaded, Applications);
script_remove_instructions([{primLoad, _} | L],
preloaded, Applications) ->
script_remove_instructions(L, preloaded, Applications);
script_remove_instructions([{kernel_load_completed} | L],
kernel_load_completed, Applications) ->
script_remove_instructions(L, kernel_load_completed, Applications);
script_remove_instructions([{primLoad, _} | L],
kernel_load_completed, Applications) ->
script_remove_instructions(L, kernel_load_completed, Applications);
script_remove_instructions([{path, _} | L],
kernel_load_completed, Applications) ->
script_remove_instructions(L, kernel_load_completed, Applications);
script_remove_instructions([{path, _} | L],
modules_loaded, Applications) ->
script_remove_instructions(L, modules_loaded, Applications);
script_remove_instructions([{kernelProcess, _, _} | L],
modules_loaded, Applications) ->
script_remove_instructions(L, modules_loaded, Applications);
script_remove_instructions([{apply, {application, load, [AppDescr]}} | L],
init_kernel_started, Applications) ->
{application, A, [_ | _]} = AppDescr,
script_remove_instructions(L, init_kernel_started, [A | Applications]);
script_remove_instructions([{apply,
{application, start_boot, [A | _]}} | L],
applications_loaded, Applications) ->
% order loaded applications based on the start order
script_remove_instructions(L, applications_loaded,
[A | lists:delete(A, Applications)]);
script_remove_instructions([{apply, {c, erlangrc, _}} | L],
applications_loaded, Applications) ->
script_remove_instructions(L, applications_loaded, Applications).
ensure_all_loaded([]) ->
ok;
ensure_all_loaded([Module | Modules] = L) ->
case is_module_loaded_check(Module) of
true ->
Loaded = lists:all(fun(M) ->
is_module_loaded_check(M) =:= true
end, Modules),
if
Loaded ->
ok;
true ->
{error, modules_partially_loaded}
end;
false ->
NotLoaded = lists:all(fun(M) ->
is_module_loaded_check(M) =:= false
end, Modules),
if
NotLoaded ->
code:atomic_load(L);
true ->
{error, modules_partially_loaded}
end
end.
ensure_code_paths([], _) ->
ok;
ensure_code_paths([P | Paths], Apps) ->
["$ROOT", "lib", NameVSN, "ebin"] = filename:split(P),
{Name, VSN} = split_name_vsn(NameVSN),
Application = erlang:list_to_existing_atom(Name),
case lists:keyfind(Application, 1, Apps) of
{Application, _, VSN} ->
ensure_code_paths(Paths, Apps);
{Application, _, InvalidVSN} ->
{error, {version_mismatch, Application, VSN, InvalidVSN}};
false ->
{error, {not_loaded, Application, VSN}}
end.
config_load_application([], Applications) ->
{ok, lists:reverse(Applications)};
config_load_application([{Application, Env} | ApplicationEnvs], Applications) ->
case ensure_application_loaded(Application) of
ok ->
lists:foreach(fun({Key, Value}) ->
ok = application:set_env(Application, Key, Value)
end, Env),
config_load_application(ApplicationEnvs,
[Application | Applications]);
{error, _} = Error ->
Error
end.
is_module_loaded_check(Module) when is_atom(Module) ->
case code:is_loaded(Module) of
{file, _} ->
true;
false ->
false
end.
modules_unload([]) ->
ok;
modules_unload([Module | Modules]) ->
_ = module_unload(Module),
modules_unload(Modules).
modules_purged([], [], _) ->
ok;
modules_purged([], BusyModules, Timeout) ->
case erlang:max(Timeout - ?MODULES_PURGED_DELTA, 0) of
0 ->
% attempt to force the purge, killing any processes that remain
% executing the code
case lists:dropwhile(fun code:purge/1, BusyModules) of
[] ->
ok;
_ ->
{error, timeout}
end;
NextTimeout ->
receive after ?MODULES_PURGED_DELTA -> ok end,
modules_purged(lists:reverse(BusyModules), [], NextTimeout)
end;
modules_purged([Module | Modules], BusyModules, Timeout) ->
% purge the Module if no processes remain executing the code
case code:soft_purge(Module) of
true ->
modules_purged(Modules, BusyModules, Timeout);
false ->
modules_purged(Modules, [Module | BusyModules], Timeout)
end.
modules_filter([], Modules, _) ->
Modules;
modules_filter([{Behaviour, Name} | Options], Modules, Loaded)
when Behaviour =:= behaviour; Behaviour =:= behavior ->
NewModules = lists:filter(fun(Module) ->
Names = if
Loaded =:= true ->
module_behaviours(Module);
Loaded =:= false ->
case code:is_loaded(Module) of
{file, _} ->
module_behaviours(Module);
false ->
[]
end
end,
lists:member(Name, Names)
end, Modules),
modules_filter(Options, NewModules, true);
modules_filter([_ | _], _, _) ->
erlang:exit(badarg).
modules_map([], _) ->
ok;
modules_map([Module | Modules], Function) ->
_ = code:Function(Module),
modules_map(Modules, Function).
load_all_paths([], _) ->
ok;
load_all_paths([P | Paths], Root) ->
["$ROOT", "lib", NameVSN, "ebin"] = filename:split(P),
CodePath = filename:join(Root ++ ["lib", NameVSN, "ebin"]),
case code:add_pathz(CodePath) of
true ->
load_all_paths(Paths, Root);
{error, Reason} ->
{error, {Reason, CodePath}}
end.
split_name_vsn(NameVSN) ->
split_name_vsn([], [], NameVSN).
split_name_vsn([_ | _] = Name, VSN, []) ->
[$- | FinalVSN] = lists:reverse(VSN),
{Name, FinalVSN};
split_name_vsn(Name, NameSegment, [$- | L]) ->
split_name_vsn(lists:reverse(NameSegment) ++ Name, [$-], L);
split_name_vsn(Name, VSN, [C | L]) ->
split_name_vsn(Name, [C | VSN], L).
delete_all(Elem, List) when is_list(List) ->
delete_all(Elem, [], List).
delete_all(Elem, L, [Elem | T]) ->
delete_all(Elem, L, T);
delete_all(Elem, L, [H | T]) ->
delete_all(Elem, [H | L], T);
delete_all(_, L, []) ->
lists:reverse(L).