diff options
Diffstat (limited to 'lib/reltool/src/reltool_server.erl')
-rw-r--r-- | lib/reltool/src/reltool_server.erl | 1678 |
1 files changed, 1678 insertions, 0 deletions
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl new file mode 100644 index 0000000000..8d4530131f --- /dev/null +++ b/lib/reltool/src/reltool_server.erl @@ -0,0 +1,1678 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +-module(reltool_server). + +%% Public +-export([ + start_link/0, start_link/1, + get_config/3, load_config/2, save_config/4, + get_rel/2, get_script/2, + reset_config/1, undo_config/1, + get_mod/2, + get_app/2, set_app/2, + get_apps/2, set_apps/2, + get_sys/1, set_sys/2, + get_status/1, + gen_rel_files/2, gen_target/2, gen_spec/1 + ]). + +%% Internal +-export([init/1, loop/1]). + +%% sys callback functions +-export([ + system_continue/3, + system_terminate/4, + system_code_change/4 + ]). + +-include("reltool.hrl"). + +-record(state, + {options, + parent_pid, + common, + sys, + old_sys, + status, + old_status}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Client + +start_link() -> + start_link([]). + +start_link(Options) -> + proc_lib:start_link(?MODULE, init, [[{parent, self()} | Options]], infinity, []). + +get_config(Pid, InclDefaults, InclDerivates) -> + reltool_utils:call(Pid, {get_config, InclDefaults, InclDerivates}). + +load_config(Pid, FilenameOrConfig) -> + reltool_utils:call(Pid, {load_config, FilenameOrConfig}). + +save_config(Pid, Filename, InclDefaults, InclDerivates) -> + reltool_utils:call(Pid, {save_config, Filename, InclDefaults, InclDerivates}). + +reset_config(Pid) -> + reltool_utils:call(Pid, reset_config). + +undo_config(Pid) -> + reltool_utils:call(Pid, undo_config). + +get_rel(Pid, RelName) -> + reltool_utils:call(Pid, {get_rel, RelName}). + +get_script(Pid, RelName) -> + reltool_utils:call(Pid, {get_script, RelName}). + +get_mod(Pid, ModName) -> + reltool_utils:call(Pid, {get_mod, ModName}). + +get_app(Pid, AppName) -> + reltool_utils:call(Pid, {get_app, AppName}). + +set_app(Pid, App) -> + reltool_utils:call(Pid, {set_app, App}). + +get_apps(Pid, Kind) -> + reltool_utils:call(Pid, {get_apps, Kind}). + +set_apps(Pid, Apps) -> + reltool_utils:call(Pid, {set_apps, Apps}). + +get_sys(Pid) -> + reltool_utils:call(Pid, get_sys). + +set_sys(Pid, Sys) -> + reltool_utils:call(Pid, {set_sys, Sys}). + +get_status(Pid) -> + reltool_utils:call(Pid, get_status). + +gen_rel_files(Pid, Dir) -> + reltool_utils:call(Pid, {gen_rel_files, Dir}). + +gen_target(Pid, Dir) -> + reltool_utils:call(Pid, {gen_target, Dir}). + +gen_spec(Pid) -> + reltool_utils:call(Pid, gen_spec). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Server + +init(Options) -> + try + do_init(Options) + catch + error:Reason -> + exit({Reason, erlang:get_stacktrace()}) + end. + +do_init(Options) -> + case parse_options(Options) of + {#state{parent_pid = ParentPid, common = C, sys = Sys} = S, Status} -> + %% process_flag(trap_exit, (S#state.common)#common.trap_exit), + proc_lib:init_ack(ParentPid, {ok, self(), C, Sys#sys{apps = undefined}}), + {S2, Status2} = refresh(S, true, Status), + {S3, Status3} = analyse(S2#state{old_sys = S2#state.sys}, Status2), + case Status3 of + {ok, _Warnings} -> + loop(S3#state{status = Status3, old_status = {ok, []}}); + {error, Reason} -> + exit(Reason) + end + end. + +parse_options(Opts) -> + AppTab = ets:new(reltool_apps, [public, ordered_set, {keypos, #app.name}]), + ModTab = ets:new(reltool_mods, [public, ordered_set, {keypos, #mod.name}]), + ModUsesTab = ets:new(reltool_mod_uses, [public, bag, {keypos, 1}]), + Sys = #sys{root_dir = reltool_utils:root_dir(), + lib_dirs = reltool_utils:erl_libs(), + escripts = [], + incl_cond = ?DEFAULT_INCL_COND, + mod_cond = ?DEFAULT_MOD_COND, + apps = ?DEFAULT_APPS, + boot_rel = ?DEFAULT_REL_NAME, + rels = reltool_utils:default_rels(), + emu_name = ?DEFAULT_EMU_NAME, + profile = ?DEFAULT_PROFILE, + incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters, ?DEFAULT_INCL_SYS_FILTERS, []), + excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters, ?DEFAULT_EXCL_SYS_FILTERS, []), + incl_app_filters = reltool_utils:decode_regexps(incl_app_filters, ?DEFAULT_INCL_APP_FILTERS, []), + excl_app_filters = reltool_utils:decode_regexps(excl_app_filters, ?DEFAULT_EXCL_APP_FILTERS, []), + relocatable = ?DEFAULT_RELOCATABLE, + app_type = ?DEFAULT_APP_TYPE, + app_file = ?DEFAULT_APP_FILE, + incl_archive_filters = reltool_utils:decode_regexps(incl_archive_filters, ?DEFAULT_INCL_ARCHIVE_FILTERS, []), + excl_archive_filters = reltool_utils:decode_regexps(excl_archive_filters, ?DEFAULT_EXCL_ARCHIVE_FILTERS, []), + archive_opts = ?DEFAULT_ARCHIVE_OPTS, + debug_info = ?DEFAULT_DEBUG_INFO}, + C2 = #common{sys_debug = [], + wx_debug = 0, + trap_exit = true, + app_tab = AppTab, + mod_tab = ModTab, + mod_used_by_tab = ModUsesTab}, + S = #state{options = Opts}, + parse_options(Opts, S, C2, Sys, {ok, []}). + +parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) -> + case Key of + parent -> + parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys, Status); + sys_debug -> + parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys, Status); + wx_debug -> + parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys, Status); + trap_exit -> + parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys, Status); + config -> + {Sys2, Status2} = read_config(Sys, Val, Status), + parse_options(KeyVals, S, C, Sys2, Status2); + sys -> + {Sys2, Status2} = read_config(Sys, {sys, Val}, Status), + parse_options(KeyVals, S, C, Sys2, Status2); + _ -> + Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), + Status2 = reltool_utils:return_first_error(Status, "Illegal option: " ++ Text), + parse_options(KeyVals, S, C, Sys, Status2) + end; +parse_options([], S, C, Sys, Status) -> + {S#state{common = C, sys = Sys}, Status}; +parse_options(KeyVals, S, C, Sys, Status) -> + Text = lists:flatten(io_lib:format("~p", [KeyVals])), + Status2 = reltool_utils:return_first_error(Status, "Illegal options: " ++ Text), + {S#state{common = C, sys = Sys}, Status2}. + +loop(#state{common = C, sys = Sys} = S) -> + receive + {system, From, Msg} -> + sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, C#common.sys_debug, S); + {call, ReplyTo, Ref, {get_config, InclDefaults, InclDerivates}} -> + Reply = do_get_config(S, InclDefaults, InclDerivates), + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {load_config, SysConfig}} -> + {S2, Reply} = do_load_config(S, SysConfig), + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S2); + {call, ReplyTo, Ref, {save_config, Filename, InclDefaults, InclDerivates}} -> + Reply = do_save_config(S, Filename, InclDefaults, InclDerivates), + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, reset_config} -> + {S2, Status} = parse_options(S#state.options), + S3 = shrink_sys(S2), + {S4, Status2} = refresh(S3, true, Status), + {S5, Status3} = analyse(S4#state{old_sys = S4#state.sys}, Status2), + S6 = + case Status3 of + {ok, _Warnings} -> + S5#state{status = Status3, old_status = S#state.status}; + {error, _} -> + S + end, + reltool_utils:reply(ReplyTo, Ref, Status3), + ?MODULE:loop(S6); + {call, ReplyTo, Ref, undo_config} -> + reltool_utils:reply(ReplyTo, Ref, ok), + S2 = S#state{sys = S#state.old_sys, + old_sys = S#state.sys, + status = S#state.old_status, + old_status = S#state.status}, + ?MODULE:loop(S2); + {call, ReplyTo, Ref, {get_rel, RelName}} -> + Sys = S#state.sys, + Reply = + case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of + {value, Rel} -> + {ok, reltool_target:gen_rel(Rel, Sys)}; + false -> + {error, "No such release"} + end, + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {get_script, RelName}} -> + Sys = S#state.sys, + Reply = + case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of + {value, Rel} -> + PathFlag = true, + Variables = [], + reltool_target:gen_script(Rel, Sys, PathFlag, Variables); + false -> + {error, "No such release"} + end, + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {get_mod, ModName}} -> + Reply = + case ets:lookup(C#common.mod_tab, ModName) of + [M] -> + {ok, M}; + [] -> + {ok, missing_mod(ModName, ?MISSING_APP)} + end, + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {get_app, AppName}} when is_atom(AppName) -> + Reply = + case lists:keysearch(AppName, #app.name, Sys#sys.apps) of + {value, App} -> + {ok, App}; + false -> + {error, enoent} + end, + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {set_app, App}} -> + {S2, Status} = do_set_app(S, App, {ok, []}), + {S3, Status2} = analyse(S2, Status), + case Status2 of + {ok, Warnings} -> + App2 = ?KEYSEARCH(App#app.name, + #app.name, + (S3#state.sys)#sys.apps), + reltool_utils:reply(ReplyTo, Ref, {ok, App2, Warnings}), + ?MODULE:loop(S3); + {error, Reason} -> + reltool_utils:reply(ReplyTo, Ref, {error, Reason}), + ?MODULE:loop(S) + end; + {call, ReplyTo, Ref, {get_apps, Kind}} -> + AppNames = + case Kind of + whitelist -> + [A || + A <- Sys#sys.apps, + A#app.is_pre_included =:= true]; + blacklist -> + [A || + A <- Sys#sys.apps, + A#app.is_pre_included =:= false]; + source -> + [A || + A <- Sys#sys.apps, + A#app.is_included =/= true, + A#app.is_pre_included =/= false]; + derived -> + [A || + A <- Sys#sys.apps, + A#app.is_included =:= true, + A#app.is_pre_included =/= true] + end, + reltool_utils:reply(ReplyTo, Ref, {ok, AppNames}), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {set_apps, Apps}} -> + {S2, Status} = lists:foldl(fun(A, {X, Y}) -> do_set_app(X, A, Y) end, + {S, {ok, []}}, + Apps), + {S3, Status2} = analyse(S2, Status), + reltool_utils:reply(ReplyTo, Ref, Status2), + ?MODULE:loop(S3); + {call, ReplyTo, Ref, get_sys} -> + reltool_utils:reply(ReplyTo, Ref, {ok, Sys#sys{apps = undefined}}), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {set_sys, Sys2}} -> + S2 = S#state{sys = Sys2#sys{apps = Sys#sys.apps}}, + Force = + (Sys2#sys.root_dir =/= Sys#sys.root_dir) orelse + (Sys2#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse + (Sys2#sys.escripts =/= Sys#sys.escripts), + {S3, Status} = refresh(S2, Force, {ok, []}), + {S4, Status2} = analyse(S3#state{old_sys = S#state.sys}, Status), + S6 = + case Status2 of + {ok, _Warnings} -> + S4#state{status = Status2, old_status = S#state.status}; + {error, _} -> + S + end, + reltool_utils:reply(ReplyTo, Ref, Status2), + ?MODULE:loop(S6); + {call, ReplyTo, Ref, get_status} -> + reltool_utils:reply(ReplyTo, Ref, S#state.status), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {gen_rel_files, Dir}} -> + Status = + case reltool_target:gen_rel_files(S#state.sys, Dir) of + ok -> + {ok, []}; + {error, Reason} -> + {error, Reason} + end, + reltool_utils:reply(ReplyTo, Ref, Status), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {gen_target, Dir}} -> + Reply = reltool_target:gen_target(S#state.sys, Dir), + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {call, ReplyTo, Ref, gen_spec} -> + Reply = reltool_target:gen_spec(S#state.sys), + reltool_utils:reply(ReplyTo, Ref, Reply), + ?MODULE:loop(S); + {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid -> + exit(Reason); + {call, ReplyTo, Ref, Msg} when is_pid(ReplyTo), is_reference(Ref) -> + error_logger:format("~p~p got unexpected call:\n\t~p\n", + [?MODULE, self(), Msg]), + reltool_utils:reply(ReplyTo, Ref, {error, {invalid_call, Msg}}), + ?MODULE:loop(S); + Msg -> + error_logger:format("~p~p got unexpected message:\n\t~p\n", + [?MODULE, self(), Msg]), + ?MODULE:loop(S) + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_set_app(#state{sys = Sys} = S, App, Status) -> + AppName = App#app.name, + {App2, Status2} = refresh_app(App, false, Status), + Apps = Sys#sys.apps, + Apps2 = lists:keystore(AppName, #app.name, Apps, App2), + Escripts = [A#app.active_dir || A <- Apps2, A#app.is_escript], + Sys2 = Sys#sys{apps = Apps2, escripts = Escripts}, + {S#state{sys = Sys2}, Status2}. + +analyse(#state{common = C, sys = #sys{apps = Apps0} = Sys} = S, Status) -> + Apps = lists:keydelete(?MISSING_APP, #app.name, Apps0), + ets:delete_all_objects(C#common.app_tab), + ets:delete_all_objects(C#common.mod_tab), + ets:delete_all_objects(C#common.mod_used_by_tab), + MissingApp = default_app(?MISSING_APP, "missing"), + ets:insert(C#common.app_tab, MissingApp), + + Apps2 = lists:map(fun(App) -> app_init_is_included(C, Sys, App) end, Apps), + Apps3 = + case app_propagate_is_included(C, Sys, Apps2, []) of + [] -> + Apps2; + MissingMods -> + %% io:format("Missing mods: ~p\n", [MissingMods]), + MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT, + info = missing_app_info(""), + mods = MissingMods, + status = missing, + uses_mods = []}, + [MissingApp2 | Apps2] + end, + app_propagate_is_used_by(C, Apps3), + Apps4 = read_apps(C, Sys, Apps3, []), + %% io:format("Missing app: ~p\n", [lists:keysearch(?MISSING_APP, #app.name, Apps4)]), + Sys2 = Sys#sys{apps = Apps4}, + try + Status2 = verify_config(Sys2, Status), + {S#state{sys = Sys2}, Status2} + catch + throw:{error, Status3} -> + {S, Status3} + end. + +app_init_is_included(C, Sys, #app{mods = Mods} = A) -> + AppCond = + case A#app.incl_cond of + undefined -> Sys#sys.incl_cond; + _ -> A#app.incl_cond + end, + ModCond = + case A#app.mod_cond of + undefined -> Sys#sys.mod_cond; + _ -> A#app.mod_cond + end, + IsIncl = + case AppCond of + include -> true; + exclude -> false; + derived -> undefined + end, + A2 = A#app{is_pre_included = IsIncl, is_included = IsIncl}, + ets:insert(C#common.app_tab, A2), + lists:foreach(fun(Mod) -> mod_init_is_included(C, Mod, ModCond, AppCond, undefined) end, Mods), + %%app_mod_init_is_included(C, AppName, Info, ModCond, AppCond), + A2. + +mod_init_is_included(C, M, ModCond, AppCond, Default) -> + %% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]), + IsIncl = + case AppCond of + include -> + case M#mod.incl_cond of + include -> + true; + exclude -> + false; + undefined -> + %% print(M#mod.name, hipe, "mod_cond -> ~p\n", [ModCond]), + case ModCond of + all -> true; + app -> false_to_undefined(M#mod.is_app_mod); + ebin -> false_to_undefined(M#mod.is_ebin_mod); + derived -> Default; + none -> false + end + end; + exclude -> + false; + derived -> + case M#mod.incl_cond of + include -> + true; + exclude -> + false; + undefined -> + Default + end + end, + M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl}, + %% print(M#mod.name, hipe, "~p -> ~p\n", [M2, IsIncl]), + ets:insert(C#common.mod_tab, M2). + +false_to_undefined(Bool) -> + case Bool of + false -> undefined; + _ -> Bool + end. + +app_propagate_is_included(C, Sys, [#app{mods = Mods} = A | Apps], Acc) -> + Acc2 = mod_propagate_is_included(C, Sys, A, Mods, Acc), + app_propagate_is_included(C, Sys, Apps, Acc2); +app_propagate_is_included(_C, _Sys, [], Acc) -> + Acc. + +mod_propagate_is_included(C, Sys, A, [#mod{name = ModName} | Mods], Acc) -> + [M2] = ets:lookup(C#common.mod_tab, ModName), + %% print(ModName, file, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]), + %% print(ModName, filename, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]), + Acc2 = + case M2#mod.is_included of + true -> + %% Propagate include mark + mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc); + false -> + Acc; + undefined -> + Acc + end, + mod_propagate_is_included(C, Sys, A, Mods, Acc2); +mod_propagate_is_included(_C, _Sys, _A, [], Acc) -> + Acc. + +mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> + Acc3 = + case ets:lookup(C#common.mod_tab, ModName) of + [M] -> + %% print(UsedByName, file, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]), + %% print(UsedByName, filename, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]), + case M#mod.is_included of + true -> + %% Already marked + Acc; + false -> + %% Already marked + Acc; + undefined -> + %% Mark and propagate + M2 = + case M#mod.incl_cond of + include -> + M#mod{is_pre_included = true, is_included = true}; + exclude -> + M#mod{is_pre_included = true, is_included = true}; + undefined -> + M#mod{is_included = true} + end, + ets:insert(C#common.mod_tab, M2), + %% io:format("Propagate mod: ~p -> ~p (~p)\n", [UsedByName, ModName, M#mod.incl_cond]), + [A] = ets:lookup(C#common.app_tab, M2#mod.app_name), + Acc2 = + case A#app.is_included of + true -> + Acc; + false -> + Acc; + undefined -> + ModCond = + case A#app.mod_cond of + undefined -> Sys#sys.mod_cond; + _ -> A#app.mod_cond + end, + Filter = + fun(M3) -> + case ModCond of + all -> true; + app -> M3#mod.is_app_mod; + ebin -> M3#mod.is_ebin_mod; + derived -> false; + none -> false + end + end, + Mods = lists:filter(Filter, A#app.mods), + %% io:format("Propagate app: ~p ~p -> ~p\n", + %% [UsedByName, A#app.name, [M3#mod.name || M3 <- Mods]]), + A2 = A#app{is_included = true}, + ets:insert(C#common.app_tab, A2), + mod_mark_is_included(C, Sys, ModName, [M3#mod.name || M3 <- Mods], Acc) + end, + mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc2) + end; + [] -> + M = missing_mod(ModName, ?MISSING_APP), + M2 = M#mod{is_included = true}, + ets:insert(C#common.mod_tab, M2), + ets:insert(C#common.mod_used_by_tab, {UsedByName, ModName}), + [M2 | Acc] + end, + mod_mark_is_included(C, Sys, UsedByName, ModNames, Acc3); +mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) -> + Acc. + +app_propagate_is_used_by(C, [#app{mods = Mods, name = Name} | Apps]) -> + case Name =:= ?MISSING_APP of + true -> ok; + false -> ok + end, + mod_propagate_is_used_by(C, Mods), + app_propagate_is_used_by(C, Apps); +app_propagate_is_used_by(_C, []) -> + ok. + +mod_propagate_is_used_by(C, [#mod{name = ModName} | Mods]) -> + [M] = ets:lookup(C#common.mod_tab, ModName), + case M#mod.is_included of + true -> + [ets:insert(C#common.mod_used_by_tab, {UsedModName, ModName}) || + UsedModName <- M#mod.uses_mods]; + false -> + ignore; + undefined -> + ignore + end, + mod_propagate_is_used_by(C, Mods); +mod_propagate_is_used_by(_C, []) -> + ok. + +read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) -> + {Mods2, IsIncl2} = read_apps(C, Sys, A, Mods, [], IsIncl), + %% reltool_utils:print(A#app.name, stdlib, "Mods2: ~p\n", [[M#mod.status || M <- Mods2]]), + Status = + case lists:keysearch(missing, #mod.status, Mods2) of + {value, _} -> missing; + false -> ok + end, + UsesMods = [M#mod.uses_mods || M <- Mods2, M#mod.is_included =:= true], + UsesMods2 = lists:usort(lists:flatten(UsesMods)), + UsesApps = [M#mod.app_name || ModName <- UsesMods2, M <- ets:lookup(C#common.mod_tab, ModName)], + UsesApps2 = lists:usort(UsesApps), + UsedByMods = [M#mod.used_by_mods || M <- Mods2, M#mod.is_included =:= true], + UsedByMods2 = lists:usort(lists:flatten(UsedByMods)), + UsedByApps = [M#mod.app_name || ModName <- UsedByMods2, M <- ets:lookup(C#common.mod_tab, ModName)], + UsedByApps2 = lists:usort(UsedByApps), + + A2 = A#app{mods = Mods2, + status = Status, + uses_mods = UsesMods2, + used_by_mods = UsedByMods2, + uses_apps = UsesApps2, + used_by_apps = UsedByApps2, + is_included = IsIncl2}, + read_apps(C, Sys, Apps, [A2 | Acc]); +read_apps(_C, _Sys, [], Acc) -> + lists:reverse(Acc). + +read_apps(C, Sys, A, [#mod{name = ModName} | Mods], Acc, IsIncl) -> + [M2] = ets:lookup(C#common.mod_tab, ModName), + Status = do_get_status(M2), + %% print(M2#mod.name, hipe, "status -> ~p\n", [Status]), + {IsIncl2, M3} = + case M2#mod.is_included of + true -> + UsedByMods = [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab, ModName)], + {true, M2#mod{status = Status, used_by_mods = UsedByMods}}; + _ -> + {IsIncl, M2#mod{status = Status, used_by_mods = []}} + end, + ets:insert(C#common.mod_tab, M3), + read_apps(C, Sys, A, Mods, [M3 | Acc], IsIncl2); +read_apps(_C, _Sys, _A, [], Acc, IsIncl) -> + {lists:reverse(Acc), IsIncl}. + +do_get_status(M) -> + if + M#mod.exists =:= false, M#mod.is_included =/= false -> + missing; + true -> + ok + end. + +shrink_sys(#state{sys = #sys{apps = Apps} = Sys} = S) -> + Apps2 = lists:zf(fun filter_app/1, Apps), + S#state{sys = Sys#sys{apps = Apps2}}. + +filter_app(A) -> + Mods = [M#mod{is_app_mod = undefined, + is_ebin_mod = undefined, + uses_mods = undefined, + exists = false, + is_pre_included = undefined, + is_included = undefined} || + M <- A#app.mods, + M#mod.incl_cond =/= undefined], + if + A#app.is_escript -> + {true, A#app{vsn = undefined, + label = undefined, + info = undefined, + mods = [], + uses_mods = undefined, + is_included = undefined}}; + Mods =:= [], + A#app.mod_cond =:= undefined, + A#app.incl_cond =:= undefined, + A#app.use_selected_vsn =:= undefined -> + false; + true -> + {Dir, Dirs} = + case A#app.use_selected_vsn of + undefined -> + {shrinked, []}; + false -> + {shrinked, []}; + true -> + {A#app.active_dir, [A#app.active_dir]}; + _ when A#app.is_escript -> + {A#app.active_dir, [A#app.active_dir]} + end, + OptVsn = + case A#app.use_selected_vsn of + undefined -> undefined; + false -> undefined; + true -> A#app.vsn + end, + {true, A#app{active_dir = Dir, + sorted_dirs = Dirs, + vsn = OptVsn, + label = undefined, + info = undefined, + mods = Mods, + uses_mods = undefined, + is_included = undefined}} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +refresh_app(#app{name = AppName, + is_escript = IsEscript, + active_dir = ActiveDir, + label = OptLabel, + mods = Mods} = App, + Force, + Status) -> + if + Force; OptLabel =:= undefined -> + {AppInfo, EbinMods, Status3} = + case IsEscript of + false -> + + %% Add info from .app file + Base = get_base(AppName, ActiveDir), + {_, DefaultVsn} = reltool_utils:split_app_name(Base), + Ebin = filename:join([ActiveDir, "ebin"]), + AppFile = filename:join([Ebin, atom_to_list(AppName) ++ ".app"]), + {AI, Status2} = read_app_info(AppFile, AppFile, AppName, DefaultVsn, Status), + {AI, read_ebin_mods(Ebin, AppName), Status2}; + true -> + {App#app.info, Mods, Status} + end, + + %% Add non-existing modules + AppModNames = + case AppInfo#app_info.mod of + {StartModName, _} -> + case lists:member(StartModName, AppInfo#app_info.modules) of + true -> AppInfo#app_info.modules; + false -> [StartModName | AppInfo#app_info.modules] + end; + undefined -> + AppInfo#app_info.modules + end, + MissingMods = add_missing_mods(AppName, EbinMods, AppModNames), + + %% Add optional user config for each module + Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods), + + %% Set app flag for each module in app file + Mods3 = set_mod_flags(Mods2, AppModNames), + AppVsn = AppInfo#app_info.vsn, + AppLabel = + case AppVsn of + "" -> atom_to_list(AppName); + _ -> atom_to_list(AppName) ++ "-" ++ AppVsn + end, + App2 = App#app{vsn = AppVsn, + label = AppLabel, + info = AppInfo, + mods = lists:keysort(#mod.name, Mods3)}, + {App2, Status3}; + true -> + {App, Status} + end. + +missing_app_info(Vsn) -> + #app_info{vsn = Vsn}. + +read_app_info(_AppFileOrBin, _AppFile, erts, DefaultVsn, Status) -> + {missing_app_info(DefaultVsn), Status}; +read_app_info(AppFileOrBin, AppFile, AppName, DefaultVsn, Status) -> + EnoentText = file:format_error(enoent), + case reltool_utils:prim_consult(AppFileOrBin) of + {ok, [{application, AppName, Info}]} -> + AI = #app_info{vsn = DefaultVsn}, + parse_app_info(AppFile, Info, AI, Status); + {ok, _BadApp} -> + Text = lists:concat([AppName, ": Illegal contents in app file ", AppFile]), + {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text)}; + {error, Text} when Text =:= EnoentText-> + {missing_app_info(DefaultVsn), Status}; + {error, Text} -> + Text2 = lists:concat([AppName, ": Cannot parse app file ", AppFile, " (", Text, ")."]), + {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text2)} + end. + +parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) -> + case Key of + description -> parse_app_info(File, KeyVals, AI#app_info{description = Val}, Status); + id -> parse_app_info(File, KeyVals, AI#app_info{id = Val}, Status); + vsn -> parse_app_info(File, KeyVals, AI#app_info{vsn = Val}, Status); + modules -> parse_app_info(File, KeyVals, AI#app_info{modules = Val}, Status); + maxP -> parse_app_info(File, KeyVals, AI#app_info{maxP = Val}, Status); + maxT -> parse_app_info(File, KeyVals, AI#app_info{maxT = Val}, Status); + registered -> parse_app_info(File, KeyVals, AI#app_info{registered = Val}, Status); + included_applications -> parse_app_info(File, KeyVals, AI#app_info{incl_apps = Val}, Status); + applications -> parse_app_info(File, KeyVals, AI#app_info{applications = Val}, Status); + env -> parse_app_info(File, KeyVals, AI#app_info{env = Val}, Status); + mod -> parse_app_info(File, KeyVals, AI#app_info{mod = Val}, Status); + start_phases -> parse_app_info(File, KeyVals, AI#app_info{start_phases = Val}, Status); + _ -> parse_app_info(File, KeyVals, AI, reltool_utils:add_warning(Status, lists:concat(["Unexpected item ", Key, "in app file ", File]))) + end; +parse_app_info(_, [], AI, Status) -> + {AI, Status}. + +read_ebin_mods(Ebin, AppName) -> + case erl_prim_loader:list_dir(Ebin) of + {ok, Files} -> + Ext = code:objfile_extension(), + InitMod = fun(F) -> + File = filename:join([Ebin, F]), + init_mod(AppName, File, File, Ext) + end, + Files2 = [F || F <- Files, filename:extension(F) =:= Ext], + pmap(InitMod, Files2); + error -> + [] + end. + +pmap(Fun, List) -> + lists:map(Fun, List). + %% N = erlang:system_info(schedulers) * 2, + %% pmap(Fun, List, 0, N, 0, [], []). + +%% -record(pmap_res, {count, ref, res}). +%% -record(pmap_wait, {count, ref, pid}). +%% +%% pmap(Fun, [H | T], N, Max, Count, WaitFor, Results) when N < Max -> +%% Ref = make_ref(), +%% Parent = self(), +%% Count2 = Count + 1, +%% Pid = spawn_link(fun() -> Parent ! #pmap_res{count = Count2, ref = Ref, res = Fun(H)}, unlink(Parent) end), +%% PW = #pmap_wait{count = Count2, pid = Pid, ref = Ref}, +%% pmap(Fun, T, N + 1, Max, Count2, [PW | WaitFor], Results); +%% pmap(_Fun, [], _N, _Max, _Count, [], Results) -> +%% %% Sort results and return them in the same orderas the original list +%% [PR#pmap_res.res || PR <- lists:keysort(#pmap_res.count, Results)]; +%% pmap(Fun, List, N, Max, Count, WaitFor, Results) -> +%% receive +%% #pmap_res{ref = Ref} = PR -> +%% WaitFor2 = lists:keydelete(Ref, #pmap_wait.ref, WaitFor), +%% pmap(Fun, List, N - 1, Max, Count, WaitFor2, [PR | Results]); +%% {'EXIT', Reason} -> +%% exit(Reason) +%% end. + +init_mod(AppName, File, FileOrBin, Ext) -> + UsesMods = xref_mod(FileOrBin), + Base = filename:basename(File, Ext), + ModName = list_to_atom(Base), + #mod{name = ModName, + app_name = AppName, + incl_cond = undefined, + is_ebin_mod = true, + uses_mods = UsesMods, + exists = true}. + +xref_mod({Base, Bin}) when is_binary(Bin) -> + Dir = filename:absname("reltool_server.tmp"), + ok = reltool_utils:recursive_delete(Dir), + ok = file:make_dir(Dir), + File = filename:join([Dir, Base]), + ok = file:write_file(File, Bin), + Res = xref_mod(File), + ok = reltool_utils:recursive_delete(Dir), + Res; +xref_mod(File) when is_list(File) -> + {ok, Pid} = xref:start([{xref_mode, modules}]), + link(Pid), + ok = xref:set_default(Pid, [{verbose,false}, {warnings, false}]), + ok = xref:set_library_path(Pid, []), + {ok, _} = xref:add_module(Pid, File, []), + {ok, UnknownMods} = xref:q(Pid, "UM", []), + %% {ok, ExportedFuns} = xref:q(Pid, "X", []), + %% io:format("Unres: ~p\n", [xref:variables(Pid, [predefined])]), + %% io:format("Q: ~p\n", [xref:q(Pid, "XU", [])]), + Ref = erlang:monitor(process, Pid), + unlink(Pid), + xref:stop(Pid), + wait_for_processto_die(Ref, Pid, File), + UnknownMods. + +wait_for_processto_die(Ref, Pid, File) -> + receive + {'DOWN', Ref, _Type, _Object, _Info} -> + ok + after timer:seconds(30) -> + error_logger:error_msg("~p(~p): Waiting for process ~p to die ~p\n", + [?MODULE, ?LINE, Pid, File]), + wait_for_processto_die(Ref, Pid, File) + end. + +add_missing_mods(AppName, EbinMods, AppModNames) -> + EbinModNames = [M#mod.name || M <- EbinMods], + MissingModNames = AppModNames -- EbinModNames, + [missing_mod(ModName, AppName) || ModName <- MissingModNames]. + +missing_mod(ModName, AppName) -> + %% io:format("Missing: ~p -> ~p\n", [AppName, ModName]), + #mod{name = ModName, + app_name = AppName, + incl_cond = undefined, + is_ebin_mod = false, + exists = false, + status = missing, + uses_mods = []}. + +add_mod_config(Mods, ModConfigs) -> + AddConfig = + fun(Config, Acc) -> + case lists:keysearch(Config#mod.name, #mod.name, Mods) of + {value, M} -> + M2 = M#mod{incl_cond = Config#mod.incl_cond}, + lists:keystore(Config#mod.name, #mod.name, Acc, M2); + false -> + Config2 = Config#mod{uses_mods = [], exists = false}, + [Config2 | Acc] + end + end, + lists:foldl(AddConfig, Mods, ModConfigs). + +set_mod_flags(Mods, AppModNames) -> + SetFlags = + fun(#mod{name = N} = M) -> + M#mod{is_app_mod = lists:member(N, AppModNames)} + end, + lists:map(SetFlags, Mods). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_get_config(S, InclDefaults, InclDerivates) -> + S2 = + case InclDerivates of + false -> shrink_sys(S); + true -> S + end, + {ok, reltool_target:gen_config(S2#state.sys, InclDefaults)}. + +do_save_config(S, Filename, InclDefaults, InclDerivates) -> + {ok, Config} = do_get_config(S, InclDefaults, InclDerivates), + IoList = io_lib:format("%% config generated at ~w ~w\n~p.\n\n", + [date(), time(), Config]), + file:write_file(Filename, IoList). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_load_config(S, SysConfig) -> + OldSys = S#state.sys, + S2 = shrink_sys(S), + ShrinkedSys = S2#state.sys, + {NewSys, Status} = read_config(ShrinkedSys#sys{apps = []}, SysConfig, {ok, []}), + case Status of + {ok, _Warnings} -> + Force = false, + {MergedSys, Status2} = merge_config(OldSys, NewSys, Force, Status), + {S3, Status3} = analyse(S2#state{sys = MergedSys, old_sys = OldSys}, Status2), + S4 = + case Status3 of + {ok, _Warnings2} -> + S3#state{status = Status3, old_status = S#state.status}; + {error, _} -> + S + end, + {S4, Status3}; + {error, _} -> + %% Keep old state + {S, Status} + end. + +read_config(OldSys, Filename, Status) when is_list(Filename) -> + case file:consult(Filename) of + {ok, [SysConfig | _]} -> + read_config(OldSys, SysConfig, Status); + {ok, Content} -> + Text = lists:flatten(io_lib:format("~p", [Content])), + {OldSys, reltool_utils:return_first_error(Status, "Illegal file content: " ++ Text)}; + {error, Reason} -> + Text = file:format_error(Reason), + {OldSys, reltool_utils:return_first_error(Status, "File access: " ++ Text)} + end; +read_config(OldSys, {sys, KeyVals}, Status) -> + {NewSys, Status2} = + try + decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status) + catch + throw:{error, Text} -> + {OldSys, reltool_utils:return_first_error(Status, Text)} + end, + Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps], + case NewSys#sys.rels of + [] -> Rels = reltool_utils:default_rels(); + Rels -> ok + end, + NewSys2 = NewSys#sys{apps = lists:sort(Apps), rels = lists:sort(Rels)}, + case lists:keysearch(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of + {value, _} -> + {NewSys2, Status2}; + false -> + Text2 = "Missing rel: " ++ NewSys2#sys.boot_rel, + {OldSys, reltool_utils:return_first_error(Status2, Text2)} + end; +read_config(OldSys, BadConfig, Status) -> + Text = lists:flatten(io_lib:format("~p", [BadConfig])), + {OldSys, reltool_utils:return_first_error(Status, "Illegal content: " ++ Text)}. + +decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals], Status) + when is_atom(Name), is_list(AppKeyVals) -> + App = default_app(Name), + {App2, Status2} = decode(App, AppKeyVals, Status), + decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2); +decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals], Status) + when is_atom(Name), is_list(AppKeyVals) -> + App = default_app(Name), + {App2, Status2} = decode(App, AppKeyVals, Status), + decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2); +decode(#sys{apps = Apps, escripts = Escripts} = Sys, [{escript, File, AppKeyVals} | SysKeyVals], Status) + when is_list(File), is_list(AppKeyVals) -> + {Name, Label} = split_escript_name(File), + App = default_app(Name, File), + App2 = App#app{is_escript = true, + label = Label, + info = missing_app_info(""), + active_dir = File, + sorted_dirs = [File]}, + {App3, Status2} = decode(App2, AppKeyVals, Status), + decode(Sys#sys{apps = [App3 | Apps], escripts = [File | Escripts]}, SysKeyVals, Status2); +decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals], Status) + when is_list(Name), is_list(Vsn), is_list(RelApps) -> + Rel = #rel{name = Name, vsn = Vsn, rel_apps = []}, + {Rel2, Status2} = decode(Rel, RelApps, Status), + decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals, Status2); +decode(#sys{} = Sys, [{Key, Val} | KeyVals], Status) -> + {Sys3, Status3} = + case Key of + root_dir when is_list(Val) -> + {Sys#sys{root_dir = Val}, Status}; + lib_dirs when is_list(Val) -> + {Sys#sys{lib_dirs = Val}, Status}; + mod_cond when Val =:= all; Val =:= app; + Val =:= ebin; Val =:= derived; + Val =:= none -> + {Sys#sys{mod_cond = Val}, Status}; + incl_cond when Val =:= include; Val =:= exclude; + Val =:= derived -> + {Sys#sys{incl_cond = Val}, Status}; + boot_rel when is_list(Val) -> + {Sys#sys{boot_rel = Val}, Status}; + emu_name when is_list(Val) -> + {Sys#sys{emu_name = Val}, Status}; + profile when Val =:= development -> + Val = ?DEFAULT_PROFILE, % assert, + {Sys#sys{profile = Val, + incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters, + ?DEFAULT_INCL_SYS_FILTERS, + Sys#sys.incl_sys_filters), + excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters, + ?DEFAULT_EXCL_SYS_FILTERS, + Sys#sys.excl_sys_filters), + incl_app_filters = reltool_utils:decode_regexps(incl_app_filters, + ?DEFAULT_INCL_APP_FILTERS, + Sys#sys.incl_app_filters), + excl_app_filters = reltool_utils:decode_regexps(excl_app_filters, + ?DEFAULT_EXCL_APP_FILTERS, + Sys#sys.excl_app_filters)}, + Status}; + profile when Val =:= embedded -> + {Sys#sys{profile = Val, + incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters, + ?EMBEDDED_INCL_SYS_FILTERS, + Sys#sys.incl_sys_filters), + excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters, + ?EMBEDDED_EXCL_SYS_FILTERS, + Sys#sys.excl_sys_filters), + incl_app_filters = reltool_utils:decode_regexps(incl_app_filters, + ?EMBEDDED_INCL_APP_FILTERS, + Sys#sys.incl_app_filters), + excl_app_filters = reltool_utils:decode_regexps(excl_app_filters, + ?EMBEDDED_EXCL_APP_FILTERS, + Sys#sys.excl_app_filters)}, + Status}; + profile when Val =:= standalone -> + {Sys#sys{profile = Val, + incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters, + ?STANDALONE_INCL_SYS_FILTERS, + Sys#sys.incl_sys_filters), + excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters, + ?STANDALONE_EXCL_SYS_FILTERS, + Sys#sys.excl_sys_filters), + incl_app_filters = reltool_utils:decode_regexps(incl_app_filters, + ?STANDALONE_INCL_APP_FILTERS, + Sys#sys.incl_app_filters), + excl_app_filters = reltool_utils:decode_regexps(excl_app_filters, + ?STANDALONE_EXCL_APP_FILTERS, + Sys#sys.excl_app_filters)}, + Status}; + incl_sys_filters -> + {Sys#sys{incl_sys_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_sys_filters)}, Status}; + excl_sys_filters -> + {Sys#sys{excl_sys_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_sys_filters)}, Status}; + incl_app_filters -> + {Sys#sys{incl_app_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_app_filters)}, Status}; + excl_app_filters -> + {Sys#sys{excl_app_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_app_filters)}, Status}; + incl_archive_filters -> + {Sys#sys{incl_archive_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_archive_filters)}, Status}; + excl_archive_filters -> + {Sys#sys{excl_archive_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_archive_filters)}, Status}; + archive_opts when is_list(Val) -> + {Sys#sys{archive_opts = Val}, Status}; + relocatable when Val =:= true; Val =:= false -> + {Sys#sys{relocatable = Val}, Status}; + app_type when Val =:= permanent; Val =:= transient; Val =:= temporary; + Val =:= load; Val =:= none -> + {Sys#sys{app_type = Val}, Status}; + app_file when Val =:= keep; Val =:= strip, Val =:= all -> + {Sys#sys{app_file = Val}, Status}; + debug_info when Val =:= keep; Val =:= strip -> + {Sys#sys{debug_info = Val}, Status}; + _ -> + Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), + {Sys, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)} + end, + decode(Sys3, KeyVals, Status3); +decode(#app{} = App, [{Key, Val} | KeyVals], Status) -> + {App2, Status2} = + case Key of + mod_cond when Val =:= all; Val =:= app; Val =:= ebin; Val =:= derived; Val =:= none -> + {App#app{mod_cond = Val}, Status}; + incl_cond when Val =:= include; Val =:= exclude; Val =:= derived -> + {App#app{incl_cond = Val}, Status}; + + debug_info when Val =:= keep; Val =:= strip -> + {App#app{debug_info = Val}, Status}; + app_file when Val =:= keep; Val =:= strip, Val =:= all -> + {App#app{app_file = Val}, Status}; + app_type when Val =:= permanent; Val =:= transient; Val =:= temporary; + Val =:= load; Val =:= none -> + {App#app{app_type = Val}, Status}; + incl_app_filters -> + {App#app{incl_app_filters = reltool_utils:decode_regexps(Key, Val, App#app.incl_app_filters)}, Status}; + excl_app_filters -> + {App#app{excl_app_filters = reltool_utils:decode_regexps(Key, Val, App#app.excl_app_filters)}, Status}; + incl_archive_filters -> + {App#app{incl_archive_filters = reltool_utils:decode_regexps(Key, Val, App#app.incl_archive_filters)}, Status}; + excl_archive_filters -> + {App#app{excl_archive_filters = reltool_utils:decode_regexps(Key, Val, App#app.excl_archive_filters)}, Status}; + archive_opts when is_list(Val) -> + {App#app{archive_opts = Val}, Status}; + vsn when is_list(Val) -> + {App#app{use_selected_vsn = true, vsn = Val}, Status}; + _ -> + Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), + {App, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)} + end, + decode(App2, KeyVals, Status2); +decode(#app{mods = Mods} = App, [{mod, Name, ModKeyVals} | AppKeyVals], Status) -> + {Mod, Status2} = decode(#mod{name = Name}, ModKeyVals, Status), + decode(App#app{mods = [Mod | Mods]}, AppKeyVals, Status2); +decode(#mod{} = Mod, [{Key, Val} | KeyVals], Status) -> + {Mod2, Status2} = + case Key of + incl_cond when Val =:= include; Val =:= exclude; Val =:= derived -> + {Mod#mod{incl_cond = Val}, Status}; + debug_info when Val =:= keep; Val =:= strip -> + {Mod#mod{debug_info = Val}, Status}; + _ -> + Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), + {Mod, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)} + end, + decode(Mod2, KeyVals, Status2); +decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals], Status) -> + RA = + case RelApp of + Name when is_atom(Name) -> + #rel_app{name = Name, app_type = undefined, incl_apps = []}; + {Name, Type} when is_atom(Name) -> + #rel_app{name = Name, app_type = Type, incl_apps = []}; + {Name, InclApps} when is_atom(Name), is_list(InclApps) -> + #rel_app{name = Name, app_type = undefined, incl_apps = InclApps}; + {Name, Type, InclApps} when is_atom(Name), is_list(InclApps) -> + #rel_app{name = Name, app_type = Type, incl_apps = InclApps}; + _ -> + #rel_app{incl_apps = []} + end, + IsType = is_type(RA#rel_app.app_type), + NonAtoms = [IA || IA <- RA#rel_app.incl_apps, not is_atom(IA)], + if + IsType, NonAtoms =:= [] -> + decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals, Status); + true -> + Text = lists:flatten(io_lib:format("~p", [RelApp])), + Status2 = reltool_utils:return_first_error(Status, "Illegal option: " ++ Text), + decode(Rel, KeyVals, Status2) + end; +decode(Acc, [], Status) -> + {Acc, Status}; +decode(Acc, KeyVal, Status) -> + Text = lists:flatten(io_lib:format("~p", [KeyVal])), + {Acc, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)}. + +is_type(Type) -> + case Type of + undefined -> true; + permanent -> true; + transient -> true; + temporary -> true; + load -> true; + none -> true; + _ -> false + end. + +split_escript_name(File) when is_list(File) -> + Label = filename:basename(File, ".escript"), + {list_to_atom("*escript* " ++ Label), Label}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +refresh(#state{sys = Sys} = S, Force, Status) -> + {Sys2, Status2} = merge_config(Sys, Sys#sys{apps = []}, Force, Status), + {S#state{sys = Sys2}, Status2}. + +merge_config(OldSys, NewSys, Force, Status) -> + RootDir = filename:absname(NewSys#sys.root_dir), + LibDirs = [filename:absname(D) || D <- NewSys#sys.lib_dirs], + Escripts = [filename:absname(E) || E <- NewSys#sys.escripts], + {SourceDirs, Status2} = + libs_to_dirs(RootDir, LibDirs, Status), + MergedApps = merge_app_dirs(SourceDirs, NewSys#sys.apps, OldSys#sys.apps), + {AllApps, Status3} = + escripts_to_apps(Escripts, MergedApps, OldSys#sys.apps, Status2), + {RefreshedApps, Status4} = + refresh_apps(OldSys#sys.apps, AllApps, [], Force, Status3), + {PatchedApps, Status5} = patch_erts_version(RootDir, RefreshedApps, Status4), + Escripts2 = [A#app.active_dir || A <- PatchedApps, A#app.is_escript], + NewSys2 = NewSys#sys{root_dir = RootDir, + lib_dirs = LibDirs, + escripts = Escripts2, + apps = PatchedApps}, + {NewSys2, Status5}. + +verify_config(Sys, Status) -> + case lists:keymember(Sys#sys.boot_rel, #rel.name, Sys#sys.rels) of + true -> + lists:foreach(fun(Rel)-> check_rel(Rel, Sys, Status) end, Sys#sys.rels), + Status; + false -> + Text = lists:concat([Sys#sys.boot_rel, ": release is mandatory"]), + Status2 = reltool_utils:return_first_error(Status, Text), + throw({error, Status2}) + end. + +check_rel(#rel{name = RelName, rel_apps = RelApps}, #sys{apps = Apps}, Status) -> + EnsureApp = + fun(AppName) -> + case lists:keymember(AppName, #rel_app.name, RelApps) of + true -> + ok; + false -> + Text = lists:concat([RelName, ": ", AppName, " is not included."]), + Status2 = reltool_utils:return_first_error(Status, Text), + throw({error, Status2}) + end + end, + EnsureApp(kernel), + EnsureApp(stdlib), + CheckRelApp = + fun(#rel_app{name = AppName}) -> + case lists:keysearch(AppName, #app.name, Apps) of + {value, App} when App#app.is_pre_included -> + ok; + {value, App} when App#app.is_included -> + ok; + _ -> + Text = lists:concat([RelName, ": uses application ", + AppName, " that not is included."]), + Status2 = reltool_utils:return_first_error(Status, Text), + %% throw BUGBUG: add throw + ({error, Status2}) + end + end, + lists:foreach(CheckRelApp, RelApps). + +patch_erts_version(RootDir, Apps, Status) -> + AppName = erts, + case lists:keysearch(AppName, #app.name, Apps) of + {value, Erts} -> + LocalRoot = code:root_dir(), + Vsn = Erts#app.vsn, + if + LocalRoot =:= RootDir, Vsn =:= "" -> + Vsn2 = erlang:system_info(version), + Erts2 = Erts#app{vsn = Vsn2, label = "erts-" ++ Vsn2}, + Apps2 = lists:keystore(AppName, #app.name, Apps, Erts2), + {Apps2, Status}; + Vsn =:= "" -> + {Apps, reltool_utils:add_warning(Status, "erts has no version")}; + true -> + {Apps, Status} + end; + false -> + Text = "erts cannnot be found in the root directory " ++ RootDir, + Status2 = reltool_utils:return_first_error(Status, Text), + {Apps, Status2} + end. + +libs_to_dirs(RootDir, LibDirs, Status) -> + case file:list_dir(RootDir) of + {ok, RootFiles} -> + RootLibDir = filename:join([RootDir, "lib"]), + AllLibDirs = [RootLibDir | LibDirs], + case AllLibDirs -- lists:usort(AllLibDirs) of + [] -> + Fun = fun(Base) -> + AppDir = filename:join([RootLibDir, Base]), + case filelib:is_dir(filename:join([AppDir, "ebin"]), erl_prim_loader) of + true -> + AppDir; + false -> + filename:join([RootDir, Base, "preloaded"]) + end + end, + ErtsFiles = [{erts, Fun(F)} || F <- RootFiles, lists:prefix("erts", F)], + app_dirs2(AllLibDirs, [ErtsFiles], Status); + [Duplicate | _] -> + {[], reltool_utils:return_first_error(Status, "Duplicate library: " ++ Duplicate)} + end; + {error, Reason} -> + Text = file:format_error(Reason), + {[], reltool_utils:return_first_error(Status, "Missing root library " ++ RootDir ++ ": " ++ Text)} + end. + +app_dirs2([Lib | Libs], Acc, Status) -> + case file:list_dir(Lib) of + {ok, Files} -> + Filter = + fun(Base) -> + AppDir = filename:join([Lib, Base]), + EbinDir = filename:join([AppDir, "ebin"]), + case filelib:is_dir(EbinDir, erl_prim_loader) of + true -> + {Name, _Vsn} = reltool_utils:split_app_name(Base), + case Name of + erts -> false; + _ -> {true, {Name, AppDir}} + end; + false -> + false + end + end, + Files2 = lists:zf(Filter, Files), + app_dirs2(Libs, [Files2 | Acc], Status); + {error, Reason} -> + Text = file:format_error(Reason), + {[], reltool_utils:return_first_error(Status, "Illegal library " ++ Lib ++ ": " ++ Text)} + end; +app_dirs2([], Acc, Status) -> + {lists:sort(lists:append(Acc)), Status}. + +escripts_to_apps([Escript | Escripts], Apps, OldApps, Status) -> + {EscriptAppName, _Label} = split_escript_name(Escript), + Ext = code:objfile_extension(), + Fun = fun(FullName, _GetInfo, GetBin, {FileAcc, StatusAcc}) -> + Components = filename:split(FullName), + case Components of + [AppLabel, "ebin", File] -> + case filename:extension(File) of + ".app" -> + {AppName, DefaultVsn} = reltool_utils:split_app_name(AppLabel), + AppFileName = filename:join([Escript, FullName]), + {Info, StatusAcc2} = + read_app_info(GetBin(), AppFileName, AppName, DefaultVsn, Status), + Dir = filename:join([Escript, AppName]), + {[{AppName, app, Dir, Info} | FileAcc], StatusAcc2}; + E when E =:= Ext -> + {AppName, _} = reltool_utils:split_app_name(AppLabel), + Mod = init_mod(AppName, File, {File, GetBin()}, Ext), + Dir = filename:join([Escript, AppName]), + {[{AppName, mod, Dir, Mod} | FileAcc], StatusAcc}; + _ -> + {FileAcc, StatusAcc} + end; + ["."] -> + Bin = GetBin(), + {ok, {ModName, _}} = beam_lib:version(Bin), + ModStr = atom_to_list(ModName) ++ Ext, + Mod = init_mod(EscriptAppName, ModStr, {ModStr, GetBin()}, Ext), + {[{EscriptAppName, mod, Escript, Mod} | FileAcc], StatusAcc}; + [File] -> + case filename:extension(File) of + E when E =:= Ext -> + Mod = init_mod(EscriptAppName, File, {File, GetBin()}, Ext), + {[{EscriptAppName, mod, File, Mod} | FileAcc], StatusAcc}; + _ -> + {FileAcc, StatusAcc} + end; + _ -> + {FileAcc, StatusAcc} + end + end, + try + case escript:foldl(Fun, {[], Status}, Escript) of + {ok, {Files, Status2}} -> + {Apps2, Status3} = files_to_apps(Escript, lists:sort(Files), Apps, Apps, OldApps, Status2), + escripts_to_apps(Escripts, Apps2, OldApps, Status3); + {error, Reason} -> + Text = lists:flatten(io_lib:format("~p", [Reason])), + {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Text)} + end + catch + throw:Reason2 when is_list(Reason2) -> + {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Reason2)} + end; +escripts_to_apps([], Apps, _OldApps, Status) -> + {Apps, Status}. + +%% Assume that all files for an app are in consecutive order +%% Assume the app info is before the mods +files_to_apps(Escript, [{AppName, Type, Dir, ModOrInfo} | Files] = AllFiles, Acc, Apps, OldApps, Status) -> + case Type of + mod -> + case Acc of + [] -> + Info = missing_app_info(""), + {NewApp, Status2} = merge_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, OldApps, Status), + files_to_apps(Escript, AllFiles, [NewApp | Acc], Apps, OldApps, Status2); + [App | Acc2] when App#app.name =:= ModOrInfo#mod.app_name -> + App2 = App#app{mods = [ModOrInfo | App#app.mods]}, + files_to_apps(Escript, Files, [App2 | Acc2], Apps, OldApps, Status); + [App | Acc2] -> + PrevApp = App#app{mods = lists:keysort(#mod.name, App#app.mods)}, + Info = missing_app_info(""), + {NewApp, Status2} = merge_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, OldApps, Status), + files_to_apps(Escript, Files, [NewApp, PrevApp | Acc2], Apps, OldApps, Status2) + end; + app -> + {App, Status2} = merge_escript_app(AppName, Dir, ModOrInfo, [], Apps, OldApps, Status), + files_to_apps(Escript, Files, [App | Acc], Apps, OldApps, Status2) + end; +files_to_apps(_Escript, [], Acc, _Apps, _OldApps, Status) -> + {lists:keysort(#app.name, Acc), Status}. + +merge_escript_app(AppName, Dir, Info, Mods, Apps, OldApps, Status) -> + case lists:keysearch(AppName, #app.name, OldApps) of + {value, App} -> + ok; + false -> + App = default_app(AppName, Dir) + end, + App2 = App#app{is_escript = true, + label = filename:basename(Dir, ".escript"), + info = Info, + mods = Mods, + active_dir = Dir, + sorted_dirs = [Dir]}, + case lists:keysearch(AppName, #app.name, Apps) of + {value, _} -> + Error = lists:concat([AppName, ": Application name clash. ", + "Escript ", Dir," contains application ", AppName, "."]), + {App2, reltool_utils:return_first_error(Status, Error)}; + false -> + {App2, Status} + end. + +merge_app_dirs([{Name, Dir} | Rest], [App | Apps], OldApps) + when App#app.name =:= Name -> + %% Add new dir to app + App2 = App#app{sorted_dirs = [Dir | App#app.sorted_dirs]}, + merge_app_dirs(Rest, [App2 | Apps], OldApps); +merge_app_dirs([{Name, Dir} | Rest], Apps, OldApps) -> + %% Initate app + Apps2 = sort_app_dirs(Apps), + Apps4 = + case lists:keysearch(Name, #app.name, Apps) of + false -> + case lists:keysearch(Name, #app.name, OldApps) of + {value, OldApp} when OldApp#app.active_dir =:= Dir -> + [OldApp | Apps2]; + {value, OldApp} -> + App = + case filter_app(OldApp) of + {true, NewApp} -> + NewApp#app{active_dir = Dir, sorted_dirs = [Dir]}; + false -> + default_app(Name, Dir) + end, + [App | Apps2]; + false -> + App = default_app(Name, Dir), + [App | Apps2] + end; + {value, OldApp} -> + Apps3 = lists:keydelete(Name, #app.name, Apps2), + App = OldApp#app{sorted_dirs = [Dir | OldApp#app.sorted_dirs]}, + [App | Apps3] + end, + merge_app_dirs(Rest, Apps4, OldApps); +merge_app_dirs([], Apps, _OldApps) -> + Apps2 = sort_app_dirs(Apps), + lists:reverse(Apps2). + +sort_app_dirs([#app{sorted_dirs = Dirs} = App | Acc]) -> + SortedDirs = lists:sort(fun reltool_utils:app_dir_test/2, Dirs), + case SortedDirs of + [ActiveDir | _] -> ok; + [] -> ActiveDir = undefined + end, + [App#app{active_dir = ActiveDir, sorted_dirs = SortedDirs} | Acc]; +sort_app_dirs([]) -> + []. + +default_app(Name, Dir) -> + App = default_app(Name), + App#app{active_dir = Dir, + sorted_dirs = [Dir]}. + +default_app(Name) -> + #app{name = Name, + is_escript = false, + use_selected_vsn = undefined, + active_dir = undefined, + sorted_dirs = [], + vsn = undefined, + label = undefined, + info = undefined, + mods = [], + + mod_cond = undefined, + incl_cond = undefined, + + status = missing, + uses_mods = undefined, + is_pre_included = undefined, + is_included = undefined}. + +%% Assume that the application are sorted +refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name =:= Old#app.name -> + {Info, ActiveDir, Status2} = ensure_app_info(New, Status), + OptLabel = + case Info#app_info.vsn =:= New#app.vsn of + true -> New#app.label; + false -> undefined % Cause refresh + end, + {Refreshed, Status3} = + refresh_app(New#app{label = OptLabel, + active_dir = ActiveDir, + vsn = Info#app_info.vsn, + info = Info}, + Force, + Status2), + refresh_apps(OldApps, NewApps, [Refreshed | Acc], Force, Status3); +refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name < Old#app.name -> + %% No old app version exists. Use new as is. + %% BUGBUG: Issue warning if the active_dir is not defined + {New2, Status2} = refresh_app(New, Force, Status), + refresh_apps([Old | OldApps], NewApps, [New2 | Acc], Force, Status2); +refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name > Old#app.name -> + %% No new version. Remove the old. + Status2 = + case Old#app.name =:= ?MISSING_APP of + true -> + Status; + false -> + Warning = lists:concat([Old#app.name, ": The source dirs does not contain the application anymore."]), + reltool_utils:add_warning(Status, Warning) + end, + refresh_apps(OldApps, [New | NewApps], Acc, Force, Status2); +refresh_apps([], [New | NewApps], Acc, Force, Status) -> + %% No old app version exists. Use new as is. + {New2, Status2} = refresh_app(New, Force, Status), + refresh_apps([], NewApps, [New2 | Acc], Force, Status2); +refresh_apps([Old | OldApps], [], Acc, Force, Status) -> + %% No new version. Remove the old. + Status2 = + case Old#app.name =:= ?MISSING_APP of + true -> + Status; + false -> + Warning = lists:concat([Old#app.name, ": The source dirs ", + "does not contain the application anymore."]), + reltool_utils:add_warning(Status, Warning) + end, + refresh_apps(OldApps, [], Acc, Force, Status2); +refresh_apps([], [], Acc, _Force, Status) -> + {lists:reverse(Acc), Status}. + +ensure_app_info(#app{is_escript = true, active_dir = Dir, info = Info}, Status) -> + {Info, Dir, Status}; +ensure_app_info(#app{name = Name, sorted_dirs = []}, Status) -> + Error = lists:concat([Name, ": Missing application directory."]), + Status2 = reltool_utils:return_first_error(Status, Error), + {missing_app_info(""), undefined, Status2}; +ensure_app_info(#app{name = Name, vsn = Vsn, sorted_dirs = Dirs, info = undefined}, Status) -> + ReadInfo = + fun(Dir, StatusAcc) -> + Base = get_base(Name, Dir), + Ebin = filename:join([Dir, "ebin"]), + {_, DefaultVsn} = reltool_utils:split_app_name(Base), + AppFile = filename:join([Ebin, atom_to_list(Name) ++ ".app"]), + read_app_info(AppFile, AppFile, Name, DefaultVsn, StatusAcc) + end, + {AllInfo, Status2} = lists:mapfoldl(ReadInfo, Status, Dirs), + AllVsns = [I#app_info.vsn || I <- AllInfo], + Status3 = + case AllVsns -- lists:usort(AllVsns) of + [] -> + %% No redundant info + Status2; + [BadVsn | _] -> + Error2 = lists:concat([Name, ": Application version clash. ", + "Multiple directories contains version \"", BadVsn, "\"."]), + reltool_utils:return_first_error(Status2, Error2) + end, + FirstInfo = hd(AllInfo), + FirstDir = hd(Dirs), + if + Vsn =:= undefined -> + {FirstInfo, FirstDir, Status3}; + Vsn =:= FirstInfo#app_info.vsn -> + {FirstInfo, FirstDir, Status3}; + true -> + case find_vsn(Vsn, AllInfo, Dirs) of + {Info, VsnDir} -> + {Info, VsnDir, Status3}; + false -> + Error3 = lists:concat([Name, ": No application directory contains selected version \"", Vsn, "\"."]), + Status4 = reltool_utils:return_first_error(Status3, Error3), + {FirstInfo, FirstDir, Status4} + end + end; +ensure_app_info(#app{active_dir = Dir, info = Info}, Status) -> + {Info, Dir, Status}. + +find_vsn(Vsn, [#app_info{vsn = Vsn} = Info | _], [Dir | _]) -> + {Info, Dir}; +find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) -> + find_vsn(Vsn, MoreInfo, MoreDirs); +find_vsn(_, [], []) -> + false. + +get_base(Name, Dir) -> + case Name of + erts -> + case filename:basename(Dir) of + "preloaded" -> + filename:basename(filename:dirname(Dir)); + TmpBase -> + TmpBase + end; + _ -> + filename:basename(Dir) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% sys callbacks + +system_continue(_Parent, _Debug, S) -> + ?MODULE:loop(S). + +system_terminate(Reason, _Parent, _Debug, _S) -> + exit(Reason). + +system_code_change(S,_Module,_OldVsn,_Extra) -> + {ok, S}. |