diff options
Diffstat (limited to 'lib/reltool')
-rw-r--r-- | lib/reltool/src/reltool_server.erl | 1177 | ||||
-rw-r--r-- | lib/reltool/test/reltool_server_SUITE.erl | 37 |
2 files changed, 613 insertions, 601 deletions
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl index fcd1ad393e..b6ffb9b134 100644 --- a/lib/reltool/src/reltool_server.erl +++ b/lib/reltool/src/reltool_server.erl @@ -138,12 +138,13 @@ do_init(Options) -> %% 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), + {S2, Apps, Status2} = refresh(S, true, Status), + Status3 = analyse(S2, Apps, Status2), case Status3 of {ok, _Warnings} -> % BUGBUG: handle warnings - loop(S3#state{status = Status3, old_status = {ok, []}}); + loop(S2#state{old_sys = S2#state.sys, + status = Status3, + old_status = {ok, []}}); {error, Reason} -> exit(Reason) end. @@ -152,40 +153,7 @@ 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 = dec_re(incl_sys_filters, - ?DEFAULT_INCL_SYS_FILTERS, - []), - excl_sys_filters = dec_re(excl_sys_filters, - ?DEFAULT_EXCL_SYS_FILTERS, - []), - incl_app_filters = dec_re(incl_app_filters, - ?DEFAULT_INCL_APP_FILTERS, - []), - excl_app_filters = dec_re(excl_app_filters, - ?DEFAULT_EXCL_APP_FILTERS, - []), - relocatable = ?DEFAULT_RELOCATABLE, - rel_app_type = ?DEFAULT_REL_APP_TYPE, - embedded_app_type = ?DEFAULT_EMBEDDED_APP_TYPE, - app_file = ?DEFAULT_APP_FILE, - incl_archive_filters = dec_re(incl_archive_filters, - ?DEFAULT_INCL_ARCHIVE_FILTERS, - []), - excl_archive_filters = dec_re(excl_archive_filters, - ?DEFAULT_EXCL_ARCHIVE_FILTERS, - []), - archive_opts = ?DEFAULT_ARCHIVE_OPTS, - debug_info = ?DEFAULT_DEBUG_INFO}, + Sys = default_sys(), C2 = #common{sys_debug = [], wx_debug = 0, trap_exit = true, @@ -195,6 +163,42 @@ parse_options(Opts) -> S = #state{options = Opts}, parse_options(Opts, S, C2, Sys, {ok, []}). +default_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 = dec_re(incl_sys_filters, + ?DEFAULT_INCL_SYS_FILTERS, + []), + excl_sys_filters = dec_re(excl_sys_filters, + ?DEFAULT_EXCL_SYS_FILTERS, + []), + incl_app_filters = dec_re(incl_app_filters, + ?DEFAULT_INCL_APP_FILTERS, + []), + excl_app_filters = dec_re(excl_app_filters, + ?DEFAULT_EXCL_APP_FILTERS, + []), + relocatable = ?DEFAULT_RELOCATABLE, + rel_app_type = ?DEFAULT_REL_APP_TYPE, + embedded_app_type = ?DEFAULT_EMBEDDED_APP_TYPE, + app_file = ?DEFAULT_APP_FILE, + incl_archive_filters = dec_re(incl_archive_filters, + ?DEFAULT_INCL_ARCHIVE_FILTERS, + []), + excl_archive_filters = dec_re(excl_archive_filters, + ?DEFAULT_EXCL_ARCHIVE_FILTERS, + []), + archive_opts = ?DEFAULT_ARCHIVE_OPTS, + debug_info = ?DEFAULT_DEBUG_INFO}. + dec_re(Key, Regexps, Old) -> reltool_utils:decode_regexps(Key, Regexps, Old). @@ -252,19 +256,20 @@ loop(#state{common = C, sys = Sys} = S) -> ?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 = S#state.sys}, Status2), - S6 = + {S4, Apps, Status2} = refresh(S2, true, Status), + Status3 = analyse(S4, Apps, Status2), + S5 = case Status3 of {ok, _Warnings} -> - S5#state{status = Status3, old_status = S#state.status}; + S4#state{old_sys = Sys, + status = Status3, + old_status = S#state.status}; {error, _} -> %% Keep old state S end, reltool_utils:reply(ReplyTo, Ref, Status3), - ?MODULE:loop(S6); + ?MODULE:loop(S5); {call, ReplyTo, Ref, undo_config} -> OldSys = S#state.old_sys, S2 = S#state{sys = OldSys, @@ -273,29 +278,29 @@ loop(#state{common = C, sys = Sys} = S) -> %%! If so, consider if it is correct to use Force or not - %%! since warnings from refresh_app will not re-appear here %%! in undo if Force==false. - Force = - (OldSys#sys.root_dir =/= Sys#sys.root_dir) orelse - (OldSys#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse - (OldSys#sys.escripts =/= Sys#sys.escripts), - - {S3, Status} = refresh(S2, Force, S#state.old_status), - {S4, Status2} = analyse(S3, Status), - S5 = + Force = true, +% (OldSys#sys.root_dir =/= Sys#sys.root_dir) orelse +% (OldSys#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse +% (OldSys#sys.escripts =/= Sys#sys.escripts), + + {S3, Apps, Status} = refresh(S2, Force, S#state.old_status), + Status2 = analyse(S3, Apps, Status), + S4 = case Status2 of {ok, _Warnings} -> % BUGBUG: handle warnings - S4#state{status = Status2, old_status = S#state.status}; + S3#state{status = Status2, old_status = S#state.status}; {error, _} -> %% Keep old state S end, reltool_utils:reply(ReplyTo, Ref, Status2), - ?MODULE:loop(S5); + ?MODULE:loop(S4); {call, ReplyTo, Ref, {get_rel, RelName}} -> Sys = S#state.sys, Reply = case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of {value, Rel} -> - reltool_target:gen_rel(Rel, Sys); + reltool_target:gen_rel(Rel, sys_all_apps(C,Sys)); false -> {error, "No such release: " ++ RelName} end, @@ -308,7 +313,8 @@ loop(#state{common = C, sys = Sys} = S) -> {value, Rel} -> PathFlag = true, Vars = [], - reltool_target:gen_script(Rel, Sys, PathFlag, Vars); + reltool_target:gen_script(Rel, sys_all_apps(C,Sys), + PathFlag, Vars); false -> {error, "No such release: " ++ RelName} end, @@ -326,99 +332,110 @@ loop(#state{common = C, sys = Sys} = S) -> ?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} -> + case ets:lookup(C#common.app_tab,AppName) of + [App] -> {ok, App}; - false -> + [] -> {error, "No such application: " ++ atom_to_list(AppName)} 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#state{old_sys=S#state.sys}, Status), - {S4, Reply} = - case Status2 of + {S2, Status} = do_set_apps(S, [App], {ok, []}), + {S3, Reply} = + case Status of {ok, Warnings} -> - App2 = ?KEYSEARCH(App#app.name, - #app.name, - (S3#state.sys)#sys.apps), - {S3#state{status=Status2, old_status=S#state.status}, + [App2] = ets:lookup(C#common.app_tab,App#app.name), + {S2#state{old_sys=Sys, + status=Status, + old_status=S#state.status}, {ok, App2, Warnings}}; {error, _} -> %% Keep old state - {S, Status2} + {S, Status} end, reltool_utils:reply(ReplyTo, Ref, Reply), - ?MODULE:loop(S4); + ?MODULE:loop(S3); {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]; + %% Pre-included + ets:select(C#common.app_tab, + [{#app{is_pre_included=true,_='_'}, + [], + ['$_']}]); + blacklist -> + %% Pre-excluded + ets:select(C#common.app_tab, + [{#app{is_pre_included=false,_='_'}, + [], + ['$_']}]); + source -> + %% Not included and not pre-excluded + ets:select(C#common.app_tab, + [{#app{is_included='$1', + is_pre_included='$2', + _='_'}, + [{'=/=','$1',true}, + {'=/=','$2',false}], + ['$_']}]); derived -> - [A || - A <- Sys#sys.apps, - A#app.is_included =:= true, - A#app.is_pre_included =/= true] + %% Included, but not pre-included + ets:select(C#common.app_tab, + [{#app{is_included='$1', + is_pre_included='$2', + _='_'}, + [{'=:=','$1',true}, + {'=/=','$2',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#state{old_sys = S#state.sys}, Status), - S4 = - case Status2 of + {S2, Status} = do_set_apps(S, Apps, {ok, []}), + S3 = + case Status of {ok, _Warnings} -> - S3#state{status=Status2, old_status=S#state.status}; + S2#state{old_sys = Sys, + status=Status, + old_status=S#state.status}; {error, _} -> %% Keep old state S end, - reltool_utils:reply(ReplyTo, Ref, Status2), - ?MODULE:loop(S4); + reltool_utils:reply(ReplyTo, Ref, Status), + ?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), - S5 = + Force = true, +% (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, Apps, Status} = refresh(S2, Force, {ok, []}), + Status2 = analyse(S3, Apps, Status), + S4 = case Status2 of {ok, _Warnings} -> % BUGBUG: handle warnings - S4#state{status = Status2, old_status = S#state.status}; + S3#state{old_sys = Sys, + status = Status2, + old_status = S#state.status}; {error, _} -> %% Keep old state S end, reltool_utils:reply(ReplyTo, Ref, Status2), - ?MODULE:loop(S5); + ?MODULE:loop(S4); {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 + case reltool_target:gen_rel_files(sys_all_apps(C,Sys), Dir) of ok -> {ok, []}; {error, Reason} -> @@ -427,11 +444,11 @@ loop(#state{common = C, sys = Sys} = S) -> reltool_utils:reply(ReplyTo, Ref, Status), ?MODULE:loop(S); {call, ReplyTo, Ref, {gen_target, Dir}} -> - Reply = reltool_target:gen_target(S#state.sys, Dir), + Reply = reltool_target:gen_target(sys_all_apps(C,Sys), Dir), reltool_utils:reply(ReplyTo, Ref, Reply), ?MODULE:loop(S); {call, ReplyTo, Ref, gen_spec} -> - Reply = reltool_target:gen_spec(S#state.sys), + Reply = reltool_target:gen_spec(sys_all_apps(C,Sys)), reltool_utils:reply(ReplyTo, Ref, Reply), ?MODULE:loop(S); {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid -> @@ -448,67 +465,141 @@ loop(#state{common = C, sys = Sys} = S) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_set_apps(#state{sys = Sys} = S, ChangedApps, Status) -> + + %% Create new list of configured applications + Sys2 = Sys#sys{apps = app_update_config(ChangedApps, Sys#sys.apps)}, + + %% Refresh and analyse + {S2, Apps, Status2} = refresh(S#state{sys = Sys2}, true, Status), + Status3 = analyse(S2, Apps, Status2), + + {S2, Status3}. + +%% Re-create the #sys.apps list by +%% 1) taking configurable fields from the changed #app records and +%% create new default records +%% 2) removing #app records if no configurable fields are set +%% 3) keeping #app records that are not changed +app_update_config([Config|Configs],SysApps) -> + NewSysApps = + case app_set_config_only(Config) of + {delete,Name} -> + lists:keydelete(Name,#app.name,SysApps); + New -> + lists:ukeymerge(#app.name,[New],SysApps) + end, + app_update_config(Configs,NewSysApps); +app_update_config([],SysApps) -> + SysApps. + +app_set_config_only(#app{mods=ConfigMods} = Config) -> + app_set_config_only(mod_set_config_only(ConfigMods),Config). + +app_set_config_only([],#app{name = Name, + incl_cond = undefined, + mod_cond = undefined, + use_selected_vsn = undefined, + debug_info = undefined, + app_file = undefined, + app_type = undefined, + incl_app_filters = undefined, + excl_app_filters = undefined, + incl_archive_filters = undefined, + excl_archive_filters = undefined, + archive_opts = undefined}) -> + {delete,Name}; +app_set_config_only(Mods,#app{name = Name, + incl_cond = InclCond, + mod_cond = ModCond, + use_selected_vsn = UseSelectedVsn, + active_dir = ActiveDir, + debug_info = DebugInfo, + app_file = AppFile, + app_type = AppType, + incl_app_filters = InclAppFilters, + excl_app_filters = ExclAppFilters, + incl_archive_filters = InclArchiveFilters, + excl_archive_filters = ExclArchiveFilters, + archive_opts = ArchiveOpts, + vsn = Vsn}) -> + (default_app(Name))#app{incl_cond = InclCond, + mod_cond = ModCond, + use_selected_vsn = UseSelectedVsn, + active_dir = ActiveDir, + debug_info = DebugInfo, + app_file = AppFile, + app_type = AppType, + incl_app_filters = InclAppFilters, + excl_app_filters = ExclAppFilters, + incl_archive_filters = InclArchiveFilters, + excl_archive_filters = ExclArchiveFilters, + archive_opts = ArchiveOpts, + vsn = Vsn, + mods = Mods}. + +mod_set_config_only(ConfigMods) -> + [#mod{name = Name, + incl_cond = InclCond, + debug_info = DebugInfo} || + #mod{name = Name, + incl_cond = InclCond, + debug_info = DebugInfo} <- ConfigMods, + (InclCond =/= undefined) orelse (DebugInfo =/= undefined)]. -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, rels = Rels} = Sys} = S, - Status) -> - Apps = lists:keydelete(?MISSING_APP_NAME, #app.name, Apps0), + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +analyse(#state{common=C, sys=Sys}, Apps, Status) -> 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_NAME, "missing"), - ets:insert(C#common.app_tab, MissingApp), - - {RevRelApps, Status2} = apps_in_rels(Rels, Apps, Status), - RelApps2 = lists:reverse(RevRelApps), - {Apps2, Status3} = - lists:mapfoldl(fun(App, Acc) -> - app_init_is_included(C, Sys, App, RelApps2, Acc) - end, - Status2, - 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,Status4} = app_recap_dependencies(C, Sys, Apps3, [], Status3), - %% io:format("Missing app: ~p\n", - %% [lists:keysearch(?MISSING_APP_NAME, #app.name, Apps4)]), - Sys2 = Sys#sys{apps = Apps4}, - - case verify_config(RelApps2, Sys2, Status4) of - {ok, _Warnings} = Status5 -> - {S#state{sys = Sys2}, Status5}; - {error, _} = Status5 -> - {S, Status5} - end. + + %% Create a list of {RelName,AppName}, one element for each + %% AppName that needs to be included for the given release. + {RelApps, Status2} = apps_in_rels(Sys#sys.rels, Apps, Status), + + %% Initiate is_pre_included and is_included for all applications + %% based on #sys.incl_cond, #app.incl_cond and if the application + %% is included in a release (rel spec - see apps_in_rels above). + %% Then initiate the same for each module, and check that there + %% are no duplicated module names (in different applications) + %% where we can not decide which one to use. + %% Write all #app to app_tab and all #mod to mod_tab. + Status3 = apps_init_is_included(C, Sys, Apps, RelApps, Status2), + + %% For each module that has #mod.is_included==true, propagate + %% is_included to the modules it uses. + propagate_is_included(C, Sys), + + %% Insert reverse dependencies - i.e. for each + %% #mod{name=Mod, uses_mods=[UsedMod]}, + %% insert an entry {UsedMod,Mod} in mod_used_by_tab. + propagate_is_used_by(C), + + %% Set the above reverse dependencies in #mod records + %% (used_by_mods) and accumulate in #app records. + %% Make sure #app.is_included is always true if some + %% #mod.is_included==true for at least one module in the app. + %% Set status=missing|ok for #app and #mod - indicates if module + %% (.beam file) is missing in file system. + Status4 = app_recap_dependencies(C, Status3), + + %% Check that the boot_rel exists. + %% Check that all applications that are listed in a 'rel' spec are + %% also really included in the target release. + %% Check that all mandatory applications are included in all rels. + verify_config(C, Sys, RelApps, Status4). apps_in_rels(Rels, Apps, Status) -> - lists:foldl(fun(Rel, {RelApps, S}) -> - {MoreRelApps, S2} = apps_in_rel(Rel, Apps, S), - {MoreRelApps ++ RelApps, S2} - end, - {[], Status}, - Rels). + {AllRelApps, Status2} = + lists:foldl(fun(Rel, {RelApps, S}) -> + {MoreRelApps, S2} = apps_in_rel(Rel, Apps, S), + {MoreRelApps ++ RelApps, S2} + end, + {[], Status}, + Rels), + {lists:reverse(AllRelApps), Status2}. apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps, Status) -> Mandatory = [{RelName, kernel}, {RelName, stdlib}], @@ -540,6 +631,14 @@ more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc, Status) -> more_apps_in_rels([], _Apps, Acc, Status) -> {Acc, Status}. + +apps_init_is_included(C, Sys, Apps, RelApps, Status) -> + lists:foldl(fun(App, AccStatus) -> + app_init_is_included(C, Sys, App, RelApps, AccStatus) + end, + Status, + Apps). + app_init_is_included(C, Sys, #app{name = AppName, mods = Mods} = A, @@ -587,8 +686,8 @@ app_init_is_included(C, is_pre_included = IsPreIncl, is_included = IsIncl, rels = Rels}, - ets:insert(C#common.app_tab, A2), - {A2, Status3}. + ets:insert(C#common.app_tab, A2), %%! Set mods to only mod names here???? + Status3. mod_init_is_included(C, M, ModCond, AppCond, Default, Status) -> %% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]), @@ -676,55 +775,42 @@ false_to_undefined(Bool) -> _ -> 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) -> - Acc2 = - case ets:lookup(C#common.mod_tab, ModName) of - [M2] when M2#mod.app_name=:=A#app.name -> - %% 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]), - 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; - [_] -> - %% This module is currently used from a different application - %% Ignore - Acc - end, - mod_propagate_is_included(C, Sys, A, Mods, Acc2); -mod_propagate_is_included(_C, _Sys, _A, [], Acc) -> - Acc. +%% Return the list for {ModName, UsesModNames} for all modules where +%% #mod.is_included==true. +get_all_mods_and_dependencies(C) -> + ets:select(C#common.mod_tab, [{#mod{name='$1', + uses_mods='$2', + is_included=true, + _='_'}, + [], + [{{'$1','$2'}}]}]). + +propagate_is_included(C, Sys) -> + case lists:flatmap( + fun({ModName,UsesModNames}) -> + mod_mark_is_included(C,Sys,ModName,UsesModNames,[]) + end, + get_all_mods_and_dependencies(C)) of + [] -> + ok; + MissingMods -> + MissingApp = default_app(?MISSING_APP_NAME, "missing"), + MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT, + info = missing_app_info(""), + mods = MissingMods, + status = missing, + uses_mods = []}, + ets:insert(C#common.app_tab, MissingApp2), + ok + end. 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 + %% Not yet marked => mark and propagate M2 = case M#mod.incl_cond of include -> @@ -737,15 +823,9 @@ mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> 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 @@ -763,9 +843,6 @@ mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> 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, @@ -773,53 +850,52 @@ mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> ModName, [M3#mod.name || M3 <- Mods], - Acc) + Acc); + _ -> + %% Already marked true or false + Acc end, mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, - Acc2) + Acc2); + _ -> + %% Already marked true or false + Acc end; [] -> M = missing_mod(ModName, ?MISSING_APP_NAME), 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_NAME 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. - -app_recap_dependencies(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc, Status) -> +propagate_is_used_by(C) -> + lists:foreach( + fun({Mod,UsesMods}) -> + lists:foreach( + fun(UsedMod) -> + ets:insert(C#common.mod_used_by_tab,{UsedMod,Mod}) + end, + UsesMods) + end, + get_all_mods_and_dependencies(C)). + + +app_recap_dependencies(C, Status0) -> + ets:foldl(fun(App,Status) -> + app_recap_dependencies(C,App,Status) + end, + Status0, + C#common.app_tab). + +app_recap_dependencies(C, #app{mods = Mods, is_included = IsIncl} = A, Status) -> {Mods2, IsIncl2, Status2} = - mod_recap_dependencies(C, Sys, A, Mods, [], IsIncl, Status), + mod_recap_dependencies(C, A, Mods, [], IsIncl, Status), AppStatus = case lists:keymember(missing, #mod.status, Mods2) of true -> missing; @@ -844,11 +920,9 @@ app_recap_dependencies(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Ap used_by_apps = UsedByApps2, is_included = IsIncl2}, ets:insert(C#common.app_tab,A2), - app_recap_dependencies(C, Sys, Apps, [A2 | Acc], Status2); -app_recap_dependencies(_C, _Sys, [], Acc, Status) -> - {lists:reverse(Acc), Status}. + Status2. -mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, Status) -> +mod_recap_dependencies(C, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, Status) -> case ets:lookup(C#common.mod_tab, ModName) of [M2] when M2#mod.app_name=:=A#app.name -> ModStatus = do_get_status(M2), @@ -864,12 +938,12 @@ mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, {IsIncl, M2#mod{status = ModStatus, used_by_mods = []}} end, ets:insert(C#common.mod_tab, M3), - mod_recap_dependencies(C, Sys, A, Mods, [M3 | Acc], IsIncl2, Status); - [_] when A#app.is_included==false; M1#mod.incl_cond==exclude -> + mod_recap_dependencies(C, A, Mods, [M3 | Acc], IsIncl2, Status); + [_] when A#app.is_included==false; M1#mod.incl_cond==exclude -> %!!! incl_cond could be read from #sys.app.mods %% App is explicitely excluded so it is ok that the module %% record does not exist for this module in this %% application. - mod_recap_dependencies(C, Sys, A, Mods, [M1 | Acc], IsIncl, Status); + mod_recap_dependencies(C, A, Mods, [M1 | Acc], IsIncl, Status); [M2] -> %% A module is potensially included by multiple %% applications. This is not allowed! @@ -879,9 +953,9 @@ mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, " potentially included by two different applications: ", A#app.name, " and ", M2#mod.app_name, "."]), Status2 = reltool_utils:return_first_error(Status,Error), - mod_recap_dependencies(C, Sys, A, Mods, [M1 | Acc], IsIncl, Status2) + mod_recap_dependencies(C, A, Mods, [M1 | Acc], IsIncl, Status2) end; -mod_recap_dependencies(_C, _Sys, _A, [], Acc, IsIncl, Status) -> +mod_recap_dependencies(_C, _A, [], Acc, IsIncl, Status) -> {lists:reverse(Acc), IsIncl, Status}. do_get_status(M) -> @@ -892,48 +966,53 @@ do_get_status(M) -> 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} || - 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}}; - Mods =:= [], - A#app.mod_cond =:= undefined, - A#app.incl_cond =:= undefined, - A#app.use_selected_vsn =:= undefined -> - false; +verify_config(C, #sys{boot_rel = BootRel, rels = Rels}, RelApps, Status) -> + case lists:keymember(BootRel, #rel.name, Rels) of true -> - {Dir, Dirs, OptVsn} = - case A#app.use_selected_vsn of - undefined -> - {shrinked, [], undefined}; - false -> - {shrinked, [], undefined}; - true -> - {A#app.active_dir, [A#app.active_dir], A#app.vsn} - end, - {true, A#app{active_dir = Dir, - sorted_dirs = Dirs, - vsn = OptVsn, - label = undefined, - info = undefined, - mods = Mods, - uses_mods = undefined}} + Status2 = lists:foldl(fun(RA, Acc) -> + check_app(C, RA, Acc) end, + Status, + RelApps), + lists:foldl(fun(#rel{name = RelName}, Acc)-> + check_rel(RelName, RelApps, Acc) + end, + Status2, + Rels); + false -> + Text = lists:concat(["Release ", BootRel, + " is mandatory (used as boot_rel)"]), + reltool_utils:return_first_error(Status, Text) end. +check_app(C, {RelName, AppName}, Status) -> + case ets:lookup(C#common.app_tab, AppName) of + [#app{is_pre_included=IsPreIncl, is_included=IsIncl}] + when IsPreIncl; IsIncl -> + Status; + _ -> + Text = lists:concat(["Release ", RelName, + " uses non included application ", + AppName]), + reltool_utils:return_first_error(Status, Text) + end. + +check_rel(RelName, RelApps, Status) -> + EnsureApp = + fun(AppName, Acc) -> + case lists:member({RelName, AppName}, RelApps) of + true -> + Acc; + false -> + Text = lists:concat(["Mandatory application ", + AppName, + " is not included in release ", + RelName]), + reltool_utils:return_first_error(Acc, Text) + end + end, + Mandatory = [kernel, stdlib], + lists:foldl(EnsureApp, Status, Mandatory). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% refresh_app(#app{name = AppName, @@ -962,12 +1041,17 @@ refresh_app(#app{name = AppName, AppName, DefaultVsn, Status), + + %% And read all modules from ebin and create + %% #mod record with dependencies (uses_mods). {AI, read_ebin_mods(Ebin, AppName), Status2}; true -> {App#app.info, Mods, Status} end, - %% Add non-existing modules + %% Add non-existing modules - i.e. create default #mod + %% records for all modules that are listed in .app file + %% but do not exist in ebin. AppInfoMods = AppInfo#app_info.modules, AppModNames = case AppInfo#app_info.mod of @@ -981,11 +1065,19 @@ refresh_app(#app{name = AppName, end, MissingMods = add_missing_mods(AppName, EbinMods, AppModNames), - %% Add optional user config for each module + %% Add optional user config for each module. + %% The #mod records that are already in the #app record at + %% this point do only contain user defined configuration + %% (set by parse_options/1). So here we merge with the + %% default records from above. Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods), - %% Set app flag for each module in app file + %% Set app flag for each module in app file, i.e. the flag + %% which indicates if the module is listed in the .app + %% file or not. The start module also get the flag set to true. Mods3 = set_mod_flags(Mods2, AppModNames), + + %% Finally, set label and update the #app record AppVsn = AppInfo#app_info.vsn, AppLabel = case AppVsn of @@ -1196,12 +1288,54 @@ set_mod_flags(Mods, AppModNames) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% do_get_config(S, InclDef, InclDeriv) -> - S2 = + AppTab = (S#state.common)#common.app_tab, + Sys = case InclDeriv of - false -> shrink_sys(S); - true -> S + false -> + %% Only the apps that exist in #sys.apps shall be + %% included,and they shall be minimized + Apps = [shrink_app(App) || + #app{name=Name} <- (S#state.sys)#sys.apps, + App <- ets:lookup(AppTab,Name)], + (S#state.sys)#sys{apps=Apps}; + true -> + sys_all_apps(S#state.common,S#state.sys) end, - reltool_target:gen_config(S2#state.sys, InclDef). + reltool_target:gen_config(Sys, InclDef). + +shrink_app(A) -> + Mods = [M#mod{is_app_mod = undefined, + is_ebin_mod = undefined, + uses_mods = undefined, + exists = false} || + M <- A#app.mods, + M#mod.incl_cond =/= undefined], + if + A#app.is_escript -> + A#app{vsn = undefined, + label = undefined, + info = undefined, + mods = [], + uses_mods = undefined}; + true -> + {Dir, Dirs, OptVsn} = + case A#app.use_selected_vsn of + undefined -> + {shrinked, [], undefined}; + false -> + {shrinked, [], undefined}; + true -> + {A#app.active_dir, [A#app.active_dir], A#app.vsn} + end, + A#app{active_dir = Dir, + sorted_dirs = Dirs, + vsn = OptVsn, + label = undefined, + info = undefined, + mods = Mods, + uses_mods = undefined} + end. + do_save_config(S, Filename, InclDef, InclDeriv) -> {ok, Config} = do_get_config(S, InclDef, InclDeriv), @@ -1212,26 +1346,22 @@ do_save_config(S, Filename, InclDef, InclDeriv) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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, []}), + {NewSys, Status} = read_config(default_sys(), 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 = + {S2, Apps, Status2} = refresh(S#state{sys=NewSys}, true, Status), + Status3 = analyse(S2, Apps, Status2), + S3 = case Status3 of {ok, _Warnings2} -> - S3#state{status = Status3, old_status = S#state.status}; + S2#state{old_sys = S#state.sys, + status = Status3, + old_status = S#state.status}; {error, _} -> %% Keep old state S end, - {S4, Status3}; + {S3, Status3}; {error, _} -> %% Keep old state {S, Status} @@ -1298,17 +1428,12 @@ decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals], Status) {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]}, + [{escript, File0, AppKeyVals} | SysKeyVals], Status) + when is_list(File0), is_list(AppKeyVals) -> + File = filename:absname(File0), + App = default_escript_app(File), + {App2, Status2} = decode(App, AppKeyVals, Status), + decode(Sys#sys{apps = [App2 | Apps], escripts = [File | Escripts]}, SysKeyVals, Status2); decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals], @@ -1554,79 +1679,50 @@ split_escript_name(File) when is_list(File) -> Label = filename:basename(File, ".escript"), {list_to_atom("*escript* " ++ Label), Label}. +default_escript_app(File) -> + {Name, Label} = split_escript_name(File), + App = default_app(Name, File), + App#app{is_escript = true, + label = Label, + info = missing_app_info("")}. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -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}. +%% Apps is a list of #app records - sorted on #app.name - containing +%% only the apps that have specific configuration (e.g. in the config +%% file) +refresh(#state{sys=Sys} = S, Force, Status) -> + RootDir = filename:absname(Sys#sys.root_dir), + LibDirs = [filename:absname(D) || D <- Sys#sys.lib_dirs], + Escripts = [filename:absname(E) || E <- Sys#sys.escripts], -verify_config(RelApps, #sys{boot_rel = BootRel, rels = Rels, apps = Apps}, Status) -> - case lists:keymember(BootRel, #rel.name, Rels) of - true -> - Status2 = lists:foldl(fun(RA, Acc) -> - check_app(RA, Apps, Acc) end, - Status, - RelApps), - lists:foldl(fun(#rel{name = RelName}, Acc)-> - check_rel(RelName, RelApps, Acc) - end, - Status2, - Rels); - false -> - Text = lists:concat(["Release ", BootRel, - " is mandatory (used as boot_rel)"]), - reltool_utils:return_first_error(Status, Text) - end. + %% Read all lib dirs and return sorted [{AppName,Dir}] + {SourceDirs, Status2} = libs_to_dirs(RootDir, LibDirs, Status), -check_app({RelName, AppName}, Apps, Status) -> - case lists:keysearch(AppName, #app.name, Apps) of - {value, App} when App#app.is_pre_included -> - Status; - {value, App} when App#app.is_included -> - Status; - _ -> - Text = lists:concat(["Release ", RelName, - " uses non included application ", - AppName]), - reltool_utils:return_first_error(Status, Text) - end. + %% Create #app records for all apps in SourceDirs, and merge with + %% list of apps from config. + MergedApps = merge_app_dirs(SourceDirs, Sys#sys.apps), -check_rel(RelName, RelApps, Status) -> - EnsureApp = - fun(AppName, Acc) -> - case lists:member({RelName, AppName}, RelApps) of - true -> - Acc; - false -> - Text = lists:concat(["Mandatory application ", - AppName, - " is not included in release ", - RelName]), - reltool_utils:return_first_error(Acc, Text) - end - end, - Mandatory = [kernel, stdlib], - lists:foldl(EnsureApp, Status, Mandatory). + %% For each escript, find all related files and convert to #app + %% and #mod records + {AllApps, Status3} = escripts_to_apps(Escripts, MergedApps, Status2), + + %% Make sure correct version of each application is used according + %% to the user configuration. + %% Then find all modules and their dependencies and set user + %% configuration per module if it exists. + {RefreshedApps, Status4} = refresh_apps(Sys#sys.apps, AllApps, [], + Force, Status3), + + %% Make sure erts exists in app list and has a version (or warn) + {PatchedApps, Status5} = patch_erts_version(RootDir, RefreshedApps, Status4), + + %% Update #sys and return + Escripts2 = [A#app.active_dir || A <- PatchedApps, A#app.is_escript], + Sys2 = Sys#sys{root_dir = RootDir, + lib_dirs = LibDirs, + escripts = Escripts2}, + {S#state{sys=Sys2}, PatchedApps, Status5}. patch_erts_version(RootDir, Apps, Status) -> AppName = erts, @@ -1717,7 +1813,7 @@ app_dirs2([Lib | Libs], Acc, Status) -> app_dirs2([], Acc, Status) -> {lists:sort(lists:append(Acc)), Status}. -escripts_to_apps([Escript | Escripts], Apps, OldApps, Status) -> +escripts_to_apps([Escript | Escripts], Apps, Status) -> {EscriptAppName, _Label} = split_escript_name(Escript), Ext = code:objfile_extension(), Fun = fun(FullName, _GetInfo, GetBin, {FileAcc, StatusAcc}) -> @@ -1780,92 +1876,67 @@ escripts_to_apps([Escript | Escripts], Apps, OldApps, Status) -> end, case reltool_utils:escript_foldl(Fun, {[], Status}, Escript) of {ok, {Files, Status2}} -> + EscriptApp = + case lists:keyfind(EscriptAppName,#app.name,Apps) of + false -> default_escript_app(Escript); + EA -> EA + end, {Apps2, Status3} = - files_to_apps(Escript, - lists:sort(Files), - Apps, - Apps, - OldApps, - Status2), - escripts_to_apps(Escripts, Apps2, OldApps, Status3); + escript_files_to_apps(Escript, + lists:sort(Files), + [EscriptApp], + Apps, + Status2), + escripts_to_apps(Escripts, Apps2, Status3); {error, Reason} -> Text = lists:flatten(io_lib:format("~p", [Reason])), {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Text)} end; -escripts_to_apps([], Apps, _OldApps, Status) -> +escripts_to_apps([], Apps, 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) -> - App1 = case lists:keyfind(AppName, #app.name, OldApps) of - #app{} = App -> - App; - false -> - default_app(AppName, Dir) - end, +escript_files_to_apps(Escript, + [{AppName, Type, Dir, ModOrInfo} | Files], + Acc, + Apps, + Status) -> + {NewAcc,Status3} = + case Type of + mod -> + case Acc of + [App | Acc2] when App#app.name =:= ModOrInfo#mod.app_name -> + Mods = lists:ukeymerge(#mod.name, + [ModOrInfo], + App#app.mods), + {[App#app{mods = Mods} | Acc2], Status}; + Acc -> + {NewApp, Status2} = init_escript_app(AppName, + Dir, + missing_app_info(""), + [ModOrInfo], + Apps, + Status), + {[NewApp | Acc], Status2} + end; + app -> + {App, Status2} = init_escript_app(AppName, + Dir, + ModOrInfo, + [], + Apps, + Status), + {[App | Acc], Status2} + end, + escript_files_to_apps(Escript, Files, NewAcc, Apps, Status3); +escript_files_to_apps(_Escript, [], Acc, Apps, Status) -> + {lists:ukeymerge(#app.name, Acc, Apps), Status}. + +init_escript_app(AppName, Dir, Info, Mods, Apps, Status) -> + App1 = default_app(AppName, Dir), App2 = App1#app{is_escript = true, label = filename:basename(Dir, ".escript"), info = Info, @@ -1882,54 +1953,30 @@ merge_escript_app(AppName, Dir, Info, Mods, Apps, OldApps, Status) -> {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 = +merge_app_dirs([{Name, Dir} | Rest], Apps) -> + App = case lists:keyfind(Name, #app.name, Apps) of false -> - case lists:keyfind(Name, #app.name, OldApps) of - false -> - App = default_app(Name, Dir), - [App | Apps2]; - #app{active_dir = Dir} = OldApp -> - [OldApp | Apps2]; - 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] - end; + default_app(Name, Dir); OldApp -> - Apps3 = lists:keydelete(Name, #app.name, Apps2), - App = OldApp#app{sorted_dirs = [Dir | OldApp#app.sorted_dirs]}, - [App | Apps3] + SortedDirs = lists:umerge(fun reltool_utils:app_dir_test/2, + [Dir], OldApp#app.sorted_dirs), + OldApp#app{sorted_dirs = SortedDirs} 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([]) -> + Apps2 = lists:ukeymerge(#app.name, [App], Apps), + merge_app_dirs(Rest, Apps2); +merge_app_dirs([], Apps) -> + set_active_dirs(Apps). + +%% First dir, i.e. the one with highest version, is set to active dir +set_active_dirs([#app{sorted_dirs = [ActiveDir|_]} = App | Apps]) -> + [App#app{active_dir = ActiveDir} | set_active_dirs(Apps)]; +set_active_dirs([#app{sorted_dirs = []} = App | Apps]) -> + [App#app{active_dir = undefined} | set_active_dirs(Apps)]; +set_active_dirs([]) -> []. + default_app(Name, Dir) -> App = default_app(Name), App#app{active_dir = Dir, @@ -1938,82 +1985,44 @@ default_app(Name, 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, - rels = 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_NAME 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_NAME 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) -> + status = missing}. + + + +refresh_apps(ConfigApps, [New | NewApps], Acc, Force, Status) -> + {New2, Status3} = + case lists:keymember(New#app.name,#app.name,ConfigApps) of + true -> + %% There is user defined config for this application, make + %% sure that the application exists and that correct + %% version is used. Set active directory. + {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, + refresh_app(New#app{label = OptLabel, + active_dir = ActiveDir, + vsn = Info#app_info.vsn, + info = Info}, + Force, + Status2); + false -> + %% There is no user defined config for this + %% application. This means that the app is found in the + %% lib dirs, and that the highest version shall be + %% used. I.e. the active_dir and vsn are already correct + %% from merge_app_dirs. + refresh_app(New, Force, Status) + end, + refresh_apps(ConfigApps, NewApps, [New2 | Acc], Force, Status3); +refresh_apps(_ConfigApps, [], Acc, _Force, Status) -> {lists:reverse(Acc), Status}. + ensure_app_info(#app{is_escript = true, active_dir = Dir, info = Info}, Status) -> {Info, Dir, Status}; @@ -2092,6 +2101,12 @@ get_base(Name, Dir) -> filename:basename(Dir) end. +sys_all_apps(C,Sys) -> + Sys#sys{apps = all_apps(C)}. + +all_apps(C) -> + ets:match_object(C#common.app_tab,'_'). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% sys callbacks diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl index 56c9cb5ed1..f2b63f78f0 100644 --- a/lib/reltool/test/reltool_server_SUITE.erl +++ b/lib/reltool/test/reltool_server_SUITE.erl @@ -1192,8 +1192,10 @@ set_app_and_undo(Config) -> ExclCover = Cover#mod{incl_cond=exclude}, Mods = Tools#app.mods, Tools1 = Tools#app{mods = lists:keyreplace(cover,#mod.name,Mods,ExclCover)}, - {ok,ToolsNoCover,[]} = ?msym({ok,_,[]}, reltool_server:set_app(Pid,Tools1)), - ?m({ok,[]}, reltool_server:get_status(Pid)), + {ok,ToolsNoCover,["a: Cannot parse app file"++_|_]} = + ?msym({ok,_,["a: Cannot parse app file"++_|_]}, + reltool_server:set_app(Pid,Tools1)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:get_status(Pid)), %% Check that the module is no longer included ?m({ok,ToolsNoCover}, reltool_server:get_app(Pid,tools)), @@ -1201,17 +1203,16 @@ set_app_and_undo(Config) -> reltool_server:get_mod(Pid,cover)), %% Undo - %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]},reltool_server:undo_config(Pid)), ?m({ok,Tools}, reltool_server:get_app(Pid,tools)), ?m({ok,Cover}, reltool_server:get_mod(Pid,cover)), ?msym({ok,["a: Cannot parse app file"++_|_]},reltool_server:get_status(Pid)), %% Undo again, to check that it toggles - ?m({ok,[]}, reltool_server:undo_config(Pid)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:undo_config(Pid)), ?m({ok,ToolsNoCover}, reltool_server:get_app(Pid,tools)), ?m({ok,NoIncludeCover}, reltool_server:get_mod(Pid,cover)), - ?m({ok,[]}, reltool_server:get_status(Pid)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. @@ -1238,8 +1239,9 @@ set_apps_and_undo(Config) -> %% Exclude one application with set_apps ExclTools = Tools#app{incl_cond=exclude}, - ?m({ok,[]}, reltool_server:set_apps(Pid,[ExclTools])), - ?m({ok,[]}, reltool_server:get_status(Pid)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, + reltool_server:set_apps(Pid,[ExclTools])), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:get_status(Pid)), %% Check that the app and its modules (one of them) are no longer included {ok,NoTools} = ?msym({ok,_}, reltool_server:get_app(Pid,tools)), @@ -1249,17 +1251,16 @@ set_apps_and_undo(Config) -> reltool_server:get_mod(Pid,cover)), %% Undo - %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]},reltool_server:undo_config(Pid)), ?m({ok,Tools}, reltool_server:get_app(Pid,tools)), ?m({ok,Cover}, reltool_server:get_mod(Pid,cover)), ?msym({ok,["a: Cannot parse app file"++_|_]},reltool_server:get_status(Pid)), %% Undo again, to check that it toggles - ?m({ok,[]}, reltool_server:undo_config(Pid)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:undo_config(Pid)), ?m({ok,NoTools}, reltool_server:get_app(Pid,tools)), ?m({ok,NoIncludeCover}, reltool_server:get_mod(Pid,cover)), - ?m({ok,[]}, reltool_server:get_status(Pid)), + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:get_status(Pid)), ?m(ok, reltool:stop(Pid)), ok. @@ -1327,7 +1328,7 @@ load_config_and_undo(Config) -> {app,sasl,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, {app,tools,[{incl_cond,derived}]}]}, - ?msym({ok,["a: Cannot parse app file"++_]}, + ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:load_config(Pid,Sys2)), %%% OTP-0702, 15) ?m({ok, Sys2}, reltool:get_config(Pid)), %%% Note that {incl_cond,exclude} is removed compared to Sys1 - @@ -1363,7 +1364,6 @@ load_config_and_undo(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Load config with escript -load_config_escript_path(_Config) -> {skip,"Known bug: loading config with escript at reltool start creates different #app record than loading same config with load_config"}; load_config_escript_path(Config) -> %% Create escript DataDir = ?config(data_dir,Config), @@ -1411,7 +1411,6 @@ load_config_escript_path(Config) -> %% Load config with same (source) escript twice and check that the %% application information is not changed. -load_config_same_escript_source(_Config) -> {skip,"Known bug: loading config with same escript (source) twice causes reltool to add same module twice in #app.mods"}; load_config_same_escript_source(_Config) -> %% Create escript ExDir = code:lib_dir(reltool, examples), @@ -1428,12 +1427,14 @@ load_config_same_escript_source(_Config) -> ]}, {ok, Pid} = ?msym({ok, _}, reltool:start_server([{config, Sys}])), - {ok,[#app{name='*escript* display_args'}=A]} = - ?msym({ok,[_]}, reltool_server:get_apps(Pid,whitelist)), +% {ok,[#app{name='*escript* display_args'}]} = + ?msym({ok,[#app{name='*escript* display_args',mods=[_]}]}, + reltool_server:get_apps(Pid,whitelist)), %% Load the same config again, then check that app is not changed ?m({ok,[]}, reltool_server:load_config(Pid,Sys)), - ?m({ok,[A]}, reltool_server:get_apps(Pid,whitelist)), + ?msym({ok,[#app{name='*escript* display_args',mods=[_]}]}, + reltool_server:get_apps(Pid,whitelist)), ?m(ok, reltool:stop(Pid)), @@ -1443,7 +1444,6 @@ load_config_same_escript_source(_Config) -> %% Load config with same (beam) escript twice and check that the %% application information is not changed. -load_config_same_escript_beam(_Config) -> {skip,"Known bug: loading config with same escript (with inlined beam) twice causes reltool to fail and say module is included by two different applications"}; load_config_same_escript_beam(Config) -> %% Create escript DataDir = ?config(data_dir,Config), @@ -1478,8 +1478,6 @@ load_config_same_escript_beam(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Load config with escript -%% BUG: see OTP-9792, 25) -load_config_add_escript(_Config) -> {skip,"Known bug: Can not load_config which in addition to an existing escript also adds another escript for which the name sorts before the existing one"}; load_config_add_escript(Config) -> %% First escript ExDir = code:lib_dir(reltool, examples), @@ -1566,7 +1564,6 @@ reset_config_and_undo(Config) -> reltool_server:get_mod(Pid,cover)), %% Reset - %%! warning can come twice here... :( ?msym({ok,["a: Cannot parse app file"++_|_]}, reltool_server:reset_config(Pid)), ?m({ok,Tools1}, reltool_server:get_app(Pid,tools)), |