summaryrefslogtreecommitdiff
path: root/lib/sasl
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@dashbit.co>2020-06-29 11:43:19 +0200
committerSverker Eriksson <sverker@erlang.org>2021-01-19 11:59:03 +0100
commit791467116fdfabacb80d33cb458f22982e4f32ae (patch)
tree769b97d3d9b1f2dcac7d3b697ba3556ca4b837d9 /lib/sasl
parent08b1e1f8239518541e2855fd921fc6cff1ff84a7 (diff)
downloaderlang-791467116fdfabacb80d33cb458f22982e4f32ae.tar.gz
Add optional_applications to .app resource files
Both Mix and Rebar allow some applications to be absent at runtime - sometimes also known as optional dependencies. However, given optional applications are not stored in .app resource files, releases do not consider optional applications in its boot order, leaving it up to chance if an optional app will be started before its parent. Users can try to explicitly list optional applications on their release definition files, but given the order is not enforced, this manual specification may be reordered when new apps are added, leaving developers with broken releases. This PR introduces the "optional_applications" field to .app resource files. If an application is listed on both "applications" and "optional_applications", it will be attempted to be started before its parent but the parent won't fail to start in case it is missing: If application "b" is an optional application for application "a", and application "b" is missing, "application:start(a)" will still succeed. If application "b" is an optional application for application "a", and application "b" is available, "application:ensure_all_started(a)" will automatically start application "b" before "a". systools and reltool have also been modified to consider optional_applications.
Diffstat (limited to 'lib/sasl')
-rw-r--r--lib/sasl/src/systools.hrl2
-rw-r--r--lib/sasl/src/systools_make.erl28
-rw-r--r--lib/sasl/test/systools_SUITE.erl51
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/ebin/app1.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/src/myapp1.erl1
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/ebin/app2.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/src/myapp2.erl1
7 files changed, 87 insertions, 11 deletions
diff --git a/lib/sasl/src/systools.hrl b/lib/sasl/src/systools.hrl
index 6b2a597427..01118e320c 100644
--- a/lib/sasl/src/systools.hrl
+++ b/lib/sasl/src/systools.hrl
@@ -48,6 +48,8 @@
%% Module = atom(), Vsn = string().
uses = [], %% [Application] list of applications required
%% by the application, Application = atom().
+ optional = [], %% [Application] list of applications in uses
+ %% that are optional, Application = atom().
includes = [], %% [Application] list of applications included
%% by the application, Application = atom().
regs = [], %% [RegNames] a list of registered process
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index c7a14df28e..7b86bf58d0 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -642,10 +642,10 @@ read_application(_Name, _, [], _, _, FirstError) ->
parse_application({application, Name, Dict}, File, Vsn, Incls)
when is_atom(Name),
is_list(Dict) ->
- Items = [vsn,id,description,modules,registered,
- applications,included_applications,mod,start_phases,env,maxT,maxP],
+ Items = [vsn,id,description,modules,registered,applications,
+ optional_applications,included_applications,mod,start_phases,env,maxT,maxP],
case catch get_items(Items, Dict) of
- [Vsn,Id,Desc,Mods,Regs,Apps,Incs0,Mod,Phases,Env,MaxT,MaxP] ->
+ [Vsn,Id,Desc,Mods,Regs,Apps,Opts,Incs0,Mod,Phases,Env,MaxT,MaxP] ->
case override_include(Name, Incs0, Incls) of
{ok, Incs} ->
{ok, #application{name=Name,
@@ -654,6 +654,7 @@ parse_application({application, Name, Dict}, File, Vsn, Incls)
description=Desc,
modules=Mods,
uses=Apps,
+ optional=Opts,
includes=Incs,
regs=Regs,
mod=Mod,
@@ -665,7 +666,7 @@ parse_application({application, Name, Dict}, File, Vsn, Incls)
{error, IncApps} ->
{error, {override_include, IncApps}}
end;
- [OtherVsn,_,_,_,_,_,_,_,_,_,_,_] ->
+ [OtherVsn,_,_,_,_,_,_,_,_,_,_,_,_] ->
{error, {no_valid_version, {Vsn, OtherVsn}}};
Err ->
{error, {Err, {application, Name, Dict}}}
@@ -729,6 +730,11 @@ check_item({_,{applications,Apps}},I) ->
true -> Apps;
_ -> throw({bad_param, I})
end;
+check_item({_,{optional_applications,Apps}},I) ->
+ case a_list_p(Apps) of
+ true -> Apps;
+ _ -> throw({bad_param, I})
+ end;
check_item({_,{included_applications,Apps}},I) ->
case a_list_p(Apps) of
true -> Apps;
@@ -768,6 +774,8 @@ check_item({_,{maxP,MaxP}},I) ->
infinity -> infinity;
_ -> throw({bad_param, I})
end;
+check_item(false, optional_applications) -> % optional !
+ [];
check_item(false, included_applications) -> % optional !
[];
check_item(false, mod) -> % mod is optional !
@@ -905,7 +913,8 @@ find_top_app(App, InclApps) ->
undefined_applications(Appls) ->
Uses = append(map(fun({_,A}) ->
- A#application.uses ++ A#application.includes
+ (A#application.uses -- A#application.optional) ++
+ A#application.includes
end, Appls)),
Defined = map(fun({{X,_},_}) -> X end, Appls),
filter(fun(X) -> not member(X, Defined) end, Uses).
@@ -958,7 +967,9 @@ find_pos([], _OrderedAppls) ->
find_pos(N, Name, [{Name,_Vsn,_Type}|_OrderedAppls]) ->
{N, Name};
find_pos(N, Name, [_OtherAppl|OrderedAppls]) ->
- find_pos(N+1, Name, OrderedAppls).
+ find_pos(N+1, Name, OrderedAppls);
+find_pos(_N, Name, []) ->
+ {optional, Name}.
%%______________________________________________________________________
%% check_modules(Appls, Path, TestP) ->
@@ -1299,7 +1310,7 @@ sort_appls([{N, A}|T], Missing, Circular, Visited) ->
T, Visited, [], []),
{Incs, T2, NotFnd2} = find_all(Name, lists:reverse(A#application.includes),
T1, Visited, [], []),
- Missing1 = NotFnd1 ++ NotFnd2 ++ Missing,
+ Missing1 = (NotFnd1 -- A#application.optional) ++ NotFnd2 ++ Missing,
case Uses ++ Incs of
[] ->
%% No more app that must be started before this one is
@@ -1470,7 +1481,7 @@ load_commands(Mods, Path) ->
%% Pack an application to an application term.
pack_app(#application{name=Name,vsn=V,id=Id,description=D,modules=M,
- uses=App,includes=Incs,regs=Regs,mod=Mod,start_phases=SF,
+ uses=App,optional=Opts,includes=Incs,regs=Regs,mod=Mod,start_phases=SF,
env=Env,maxT=MaxT,maxP=MaxP}) ->
{application, Name,
[{description,D},
@@ -1479,6 +1490,7 @@ pack_app(#application{name=Name,vsn=V,id=Id,description=D,modules=M,
{modules, M},
{registered, Regs},
{applications, App},
+ {optional_applications, Opts},
{included_applications, Incs},
{env, Env},
{maxT, MaxT},
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
index 53ce272b17..b9d8ff2e02 100644
--- a/lib/sasl/test/systools_SUITE.erl
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -60,7 +60,7 @@ groups() ->
[script_options, normal_script, start_script, unicode_script, no_mod_vsn_script,
wildcard_script, variable_script, abnormal_script,
no_sasl_script, no_dot_erlang_script,
- src_tests_script, crazy_script,
+ src_tests_script, crazy_script, optional_apps_script,
included_script, included_override_script,
included_fail_script, included_bug_script, exref_script,
duplicate_modules_script,
@@ -320,6 +320,46 @@ unicode_script(cleanup,Config) ->
file:delete(fname(?privdir, "unicode_app.tgz")),
ok.
+%% make_script: Check that script handles optional apps.
+optional_apps_script(Config) when is_list(Config) ->
+ {ok, OldDir} = file:get_cwd(),
+ PSAVE = code:get_path(), % Save path
+
+ DataDir = filename:absname(?copydir),
+ LibDir = fname([DataDir, d_opt_apps, lib]),
+ P1 = fname([LibDir, 'app1-1.0', ebin]),
+ P2 = fname([LibDir, 'app2-1.0', ebin]),
+ true = code:add_patha(P1),
+ true = code:add_patha(P2),
+
+ %% First assemble a release without the optional app
+ {OptDir, OptName} = create_script(optional_apps_missing,Config),
+ ok = file:set_cwd(OptDir),
+ ok = systools:make_script(filename:basename(OptName), [{script_name, "start"}]),
+ {ok, [{script,_,OptCommands}]} = read_script_file("start"),
+
+ %% Check optional_applications is part of the generated script
+ [[app2]] =
+ [proplists:get_value(optional_applications, Properties) ||
+ {apply,{application,load,[{application,app1,Properties}]}} <- OptCommands],
+
+ %% And there is no app2
+ [] =
+ [ok || {apply,{application,load,[{application,app2,_}]}} <- OptCommands],
+
+ %% Now let's include the optional app
+ {AllDir, AllName} = create_script(optional_apps_all,Config),
+ ok = file:set_cwd(AllDir),
+ ok = systools:make_script(filename:basename(AllName), [{script_name, "start"}]),
+ {ok, [{script,_,AllCommands}]} = read_script_file("start"),
+
+ %% Check boot order is still correct
+ BootOrder = [App || {apply,{application,start_boot,[App,permanent]}} <- AllCommands],
+ [kernel, stdlib, sasl, app2, app1] = BootOrder,
+
+ ok = file:set_cwd(OldDir),
+ code:set_path(PSAVE), % Restore path
+ ok.
%% make_script:
%% Modules specified without version in .app file (db-3.1).
@@ -2544,8 +2584,13 @@ create_script(replace_app0,Config) ->
do_create_script(repace_app0,Config,current,Apps);
create_script(replace_app1,Config) ->
Apps = core_apps(current) ++ [{db,"1.0"},{fe,"2.1"}],
- do_create_script(repace_app1,Config,current,Apps).
-
+ do_create_script(repace_app1,Config,current,Apps);
+create_script(optional_apps_missing,Config) ->
+ Apps = core_apps(current) ++ [{app1,"1.0"}],
+ do_create_script(optional_apps_missing,Config,current,Apps);
+create_script(optional_apps_all,Config) ->
+ Apps = core_apps(current) ++ [{app1,"1.0"},{app2,"1.0"}],
+ do_create_script(optional_apps_all,Config,current,Apps).
do_create_script(Id,Config,ErtsVsn,AppVsns) ->
do_create_script(Id,string:to_upper(atom_to_list(Id)),Config,ErtsVsn,AppVsns).
diff --git a/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/ebin/app1.app b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/ebin/app1.app
new file mode 100644
index 0000000000..be01854653
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/ebin/app1.app
@@ -0,0 +1,8 @@
+{application, app1,
+ [{description, "Application 1"},
+ {vsn, "1.0"},
+ {modules, [myapp1]},
+ {registered, []},
+ {applications, [app2]},
+ {optional_applications, [app2]},
+ {env, []}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/src/myapp1.erl b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/src/myapp1.erl
new file mode 100644
index 0000000000..03e3583c3d
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app1-1.0/src/myapp1.erl
@@ -0,0 +1 @@
+-module(myapp1).
diff --git a/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/ebin/app2.app b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/ebin/app2.app
new file mode 100644
index 0000000000..c432fa5136
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/ebin/app2.app
@@ -0,0 +1,7 @@
+{application, app2,
+ [{description, "Application 2"},
+ {vsn, "1.0"},
+ {modules, [myapp2]},
+ {registered, []},
+ {applications, []},
+ {env, []}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/src/myapp2.erl b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/src/myapp2.erl
new file mode 100644
index 0000000000..f8a4c1e139
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_opt_apps/lib/app2-1.0/src/myapp2.erl
@@ -0,0 +1 @@
+-module(myapp2).