diff options
author | José Valim <jose.valim@dashbit.co> | 2020-06-29 11:43:19 +0200 |
---|---|---|
committer | Sverker Eriksson <sverker@erlang.org> | 2021-01-19 11:59:03 +0100 |
commit | 791467116fdfabacb80d33cb458f22982e4f32ae (patch) | |
tree | 769b97d3d9b1f2dcac7d3b697ba3556ca4b837d9 /lib/sasl | |
parent | 08b1e1f8239518541e2855fd921fc6cff1ff84a7 (diff) | |
download | erlang-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')
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). |