diff options
Diffstat (limited to 'lib/reltool/src')
-rw-r--r-- | lib/reltool/src/reltool.erl | 6 | ||||
-rw-r--r-- | lib/reltool/src/reltool.hrl | 21 | ||||
-rw-r--r-- | lib/reltool/src/reltool_app_win.erl | 19 | ||||
-rw-r--r-- | lib/reltool/src/reltool_mod_win.erl | 8 | ||||
-rw-r--r-- | lib/reltool/src/reltool_server.erl | 2024 | ||||
-rw-r--r-- | lib/reltool/src/reltool_sys_win.erl | 321 | ||||
-rw-r--r-- | lib/reltool/src/reltool_target.erl | 104 | ||||
-rw-r--r-- | lib/reltool/src/reltool_utils.erl | 95 |
8 files changed, 1391 insertions, 1207 deletions
diff --git a/lib/reltool/src/reltool.erl b/lib/reltool/src/reltool.erl index 54eb1ca9e1..2bdf222aa0 100644 --- a/lib/reltool/src/reltool.erl +++ b/lib/reltool/src/reltool.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -59,7 +59,7 @@ start_link(Options) when is_list(Options) -> {ok, _WinPid} = OK -> OK; {error, Reason} -> - {error, lists:flatten(io_lib:format("~p", [Reason]))} + {error, Reason} end. %% Start server process with options @@ -69,7 +69,7 @@ start_server(Options) -> {ok, ServerPid, _Common, _Sys} -> {ok, ServerPid}; {error, Reason} -> - {error, lists:flatten(io_lib:format("~p", [Reason]))} + {error, Reason} end. %% Start server process with options diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl index 93f47f6381..71d3b60a2b 100644 --- a/lib/reltool/src/reltool.hrl +++ b/lib/reltool/src/reltool.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -45,6 +45,7 @@ -type profile() :: development | embedded | standalone. -type relocatable() :: boolean(). -type escript_file() :: file(). +-type escript_app_name() :: app_name(). -type mod_name() :: atom(). -type app_name() :: atom(). -type app_vsn() :: string(). % e.g. "4.7" @@ -62,7 +63,8 @@ -type mod() :: {incl_cond, incl_cond()} | {debug_info, debug_info()}. -type app() :: {vsn, app_vsn()} - | {mod, mod_name(), mod()} + | {lib_dir, lib_dir()} + | {mod, mod_name(), [mod()]} | {mod_cond, mod_cond()} | {incl_cond, incl_cond()} | {app_file, app_file()} @@ -126,10 +128,7 @@ { sys_debug :: term(), wx_debug :: term(), - trap_exit :: boolean(), - app_tab :: ets:tab(), - mod_tab :: ets:tab(), - mod_used_by_tab :: ets:tab() + trap_exit :: boolean() }). -record(mod, @@ -170,8 +169,8 @@ -record(app, { %% Static info name :: app_name(), - is_escript :: boolean(), - use_selected_vsn :: boolean() | undefined, + is_escript :: boolean() | {inlined, escript_app_name()}, + use_selected_vsn :: vsn | dir | undefined, active_dir :: dir(), sorted_dirs :: [dir()], vsn :: app_vsn(), @@ -199,8 +198,8 @@ used_by_mods :: [mod_name()], uses_apps :: [app_name()], used_by_apps :: [app_name()], - is_pre_included :: boolean(), - is_included :: boolean(), + is_pre_included :: boolean() | undefined, + is_included :: boolean() | undefined, rels :: [rel_name()] }). @@ -208,7 +207,7 @@ { name :: app_name(), app_type :: app_type() | undefined, - incl_apps = [] :: [incl_app()] + incl_apps :: [incl_app()] | undefined }). -record(rel, diff --git a/lib/reltool/src/reltool_app_win.erl b/lib/reltool/src/reltool_app_win.erl index 70bd72b258..6cd0d2f90b 100644 --- a/lib/reltool/src/reltool_app_win.erl +++ b/lib/reltool/src/reltool_app_win.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -271,8 +271,8 @@ create_apps_list_ctrl(Panel, Sizer, Text) -> ListItem = wxListItem:new(), wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT), wxListItem:setText(ListItem, Text), + wxListItem:setWidth(ListItem, reltool_utils:get_column_width(ListCtrl)), wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem), - %% wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL, ?APPS_APP_COL_WIDTH), wxListItem:destroy(ListItem), wxSizer:add(Sizer, ListCtrl, @@ -292,7 +292,7 @@ create_deps_page(S, Derived) -> UsedByCtrl = create_mods_list_ctrl(Panel, Main, - "Modules used by others", + "Modules using this", " and their applications", undefined, undefined), @@ -611,7 +611,7 @@ handle_event(#state{sys = Sys, app = App} = S, Wx) -> redraw_window(S2); #wx{userData = use_selected_vsn} -> %% Use selected version - App2 = App#app{use_selected_vsn = true}, + App2 = App#app{use_selected_vsn = dir}, {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2), S2 = S#state{app = App3}, redraw_window(S2); @@ -619,7 +619,8 @@ handle_event(#state{sys = Sys, app = App} = S, Wx) -> event = #wxCommand{type = command_radiobox_selected, cmdString = ActiveDir}} -> %% Change app source - S2 = change_version(S, App, ActiveDir), + App2 = App#app{use_selected_vsn = dir}, + S2 = change_version(S, App2, ActiveDir), redraw_window(S2); #wx{userData = {mod_button, Action, ListCtrl}, event = #wxCommand{type = command_button_clicked}} -> @@ -942,11 +943,11 @@ redraw_config(#state{sys = #sys{incl_cond = GlobalIncl, LatestRadio, SelectedRadio, SourceBox, - fun(true) -> + fun(false) -> + 0; + (_) -> reltool_utils:elem_to_index(ActiveDir, - SortedDirs) - 1; - (false) -> - 0 + SortedDirs) - 1 end). redraw_double_box(Global, Local, GlobalRadio, LocalRadio, LocalBox, GetChoice) -> diff --git a/lib/reltool/src/reltool_mod_win.erl b/lib/reltool/src/reltool_mod_win.erl index e1c2fa5100..899423bb6d 100644 --- a/lib/reltool/src/reltool_mod_win.erl +++ b/lib/reltool/src/reltool_mod_win.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -215,7 +215,7 @@ create_deps_page(S) -> UsedByCtrl = create_mods_list_ctrl(Panel, Main, - "Modules used by others", + "Modules using this", " and their applications"), wxSizer:add(Main, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), @@ -314,8 +314,8 @@ do_create_code_page(#state{xref_pid = Xref, mod = M} = S, PageName) -> {ok, App} = reltool_server:get_app(Xref, M#mod.app_name), ErlBin = case App#app.is_escript of - true -> find_escript_bin(App, M); - false -> find_regular_bin(App, M) + false -> find_regular_bin(App, M); + _ -> find_escript_bin(App, M) end, load_code(Editor, ErlBin), diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl index 692baea0a4..034a42e1e2 100644 --- a/lib/reltool/src/reltool_server.erl +++ b/lib/reltool/src/reltool_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -51,7 +51,12 @@ sys, old_sys, status, - old_status}). + old_status, + app_tab, + old_app_tab, + mod_tab, + old_mod_tab, + mod_used_by_tab}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Client @@ -123,174 +128,165 @@ gen_spec(Pid) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Server -init(Options) -> +init([{parent,Parent}|_] = Options) -> try do_init(Options) catch + throw:{error,Reason} -> + proc_lib:init_ack(Parent,{error,Reason}); error:Reason -> exit({Reason, erlang:get_stacktrace()}) end. do_init(Options) -> - {S, Status} = parse_options(Options), - #state{parent_pid = ParentPid, common = C, sys = Sys} = S, - - %% 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} -> % BUGBUG: handle warnings - loop(S3#state{status = Status3, old_status = {ok, []}}); - {error, Reason} -> - exit(Reason) - 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}]), + AppTab = ets:new(reltool_apps1, [public, ordered_set, {keypos,#app.name}]), + OldAppTab = ets:new(reltool_apps2, [public, ordered_set, {keypos,#app.name}]), + ModTab = ets:new(reltool_mods1, [public, ordered_set, {keypos,#mod.name}]), + OldModTab = ets:new(reltool_mods2, [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}, - 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, []}). + S = #state{options = Options, + app_tab = AppTab, + old_app_tab = OldAppTab, + mod_tab = ModTab, + old_mod_tab = OldModTab, + mod_used_by_tab = ModUsesTab}, + + S2 = parse_options(S), + {S3, Apps, Status2} = refresh(S2), + Status3 = analyse(S3, Apps, Status2), + %% Set old_xxx equal to xxx to allow undo=nop + FakeBackup = {ets:tab2list(S3#state.app_tab),ets:tab2list(S3#state.mod_tab)}, + S4 = save_old(S3, S3, FakeBackup, Status3), + #state{parent_pid = Parent, sys=Sys, common=C} = S4, + proc_lib:init_ack(Parent, {ok, self(), C, Sys#sys{apps=undefined}}), + loop(S4). + +parse_options(S) -> + Sys = default_sys(), + C = #common{sys_debug = [], + wx_debug = 0, + trap_exit = true}, + parse_options(S#state.options, S, C, Sys). + +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). -parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) -> +parse_options([{Key, Val} | KeyVals], S, C, Sys) -> case Key of parent -> - parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys, Status); + parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys); sys_debug -> - parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys, Status); + parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys); wx_debug -> - parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys, Status); + parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys); trap_exit -> - parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys, Status); + parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys); config -> - {Sys2, Status2} = read_config(Sys, Val, Status), - parse_options(KeyVals, S, C, Sys2, Status2); + Sys2 = read_config(Sys, Val), + parse_options(KeyVals, S, C, Sys2); sys -> - {Sys2, Status2} = read_config(Sys, {sys, Val}, Status), - parse_options(KeyVals, S, C, Sys2, Status2); + Sys2 = read_config(Sys, {sys, Val}), + parse_options(KeyVals, S, C, Sys2); _ -> - 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) + reltool_utils:throw_error("Illegal option: ~p", [{Key, Val}]) 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) -> +parse_options([], S, C, Sys) -> + S#state{common = C, sys = Sys}; +parse_options(KeyVals, _S, _C, _Sys) -> + reltool_utils:throw_error("Illegal option: ~p", [KeyVals]). + +loop(#state{sys = Sys} = S) -> receive {system, From, Msg} -> sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, - C#common.sys_debug, + (S#state.common)#common.sys_debug, S); {call, ReplyTo, Ref, {get_config, InclDef, InclDeriv}} -> Reply = do_get_config(S, InclDef, InclDeriv), 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); + Fun = fun() -> do_load_config(S, SysConfig) end, + {S3, Status2} = config_and_refresh(S, Fun), + reltool_utils:reply(ReplyTo, Ref, Status2), + ?MODULE:loop(S3); {call, ReplyTo, Ref, {save_config, Filename, InclDef, InclDeriv}} -> Reply = do_save_config(S, Filename, InclDef, InclDeriv), 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, _} -> - %% Keep old state - S - end, - reltool_utils:reply(ReplyTo, Ref, Status3), - ?MODULE:loop(S6); + Fun = fun() -> parse_options(S) end, + {S3, Status2} = config_and_refresh(S, Fun), + reltool_utils:reply(ReplyTo, Ref, Status2), + ?MODULE:loop(S3); {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}, + old_sys = Sys, + status = S#state.old_status, + old_status = S#state.status, + app_tab = S#state.old_app_tab, + old_app_tab = S#state.app_tab, + mod_tab = S#state.old_mod_tab, + old_mod_tab = S#state.mod_tab}, + reltool_utils:reply(ReplyTo, Ref, ok), ?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} -> - reltool_target:gen_rel(Rel, Sys); + reltool_target:gen_rel(Rel, sys_all_apps(S)); false -> {error, "No such release: " ++ RelName} 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, Vars = [], - reltool_target:gen_script(Rel, Sys, PathFlag, Vars); + reltool_target:gen_script(Rel, sys_all_apps(S), + PathFlag, Vars); false -> {error, "No such release: " ++ RelName} end, @@ -298,7 +294,7 @@ loop(#state{common = C, sys = Sys} = S) -> ?MODULE:loop(S); {call, ReplyTo, Ref, {get_mod, ModName}} -> Reply = - case ets:lookup(C#common.mod_tab, ModName) of + case ets:lookup(S#state.mod_tab, ModName) of [M] -> {ok, M}; [] -> @@ -308,92 +304,83 @@ 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(S#state.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, 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} -> - %% Keep old state - reltool_utils:reply(ReplyTo, Ref, {error, Reason}), - ?MODULE:loop(S) - end; + Fun = fun() -> do_set_apps(S, [App]) end, + {S3, Status2} = config_and_refresh(S, Fun), + Reply = + case Status2 of + {ok, Warnings} -> + [App2] = ets:lookup(S3#state.app_tab,App#app.name), + {ok, App2, Warnings}; + {error, _} -> + Status2 + end, + reltool_utils:reply(ReplyTo, Ref, Reply), + ?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(S#state.app_tab, + [{#app{is_pre_included=true,_='_'}, + [], + ['$_']}]); + blacklist -> + %% Pre-excluded + ets:select(S#state.app_tab, + [{#app{is_pre_included=false,_='_'}, + [], + ['$_']}]); + source -> + %% Not included and not pre-excluded + ets:select(S#state.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(S#state.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, Status), + Fun = fun() -> do_set_apps(S, Apps) end, + {S3, Status2} = config_and_refresh(S, Fun), reltool_utils:reply(ReplyTo, Ref, Status2), - ?MODULE:loop(S3); + ?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, Status3} = - case Status2 of - {ok, _Warnings} -> % BUGBUG: handle warnings - {S4#state{status = Status2, - old_status = S#state.status}, - Status2}; - {error, _} -> - %% Keep old state - {S, Status2} - end, - reltool_utils:reply(ReplyTo, Ref, Status3), - ?MODULE:loop(S5); + Fun = fun() -> S#state{sys = Sys2#sys{apps = Sys#sys.apps}} end, + {S3, Status} = config_and_refresh(S, Fun), + reltool_utils:reply(ReplyTo, Ref, Status), + ?MODULE:loop(S3); {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(S), Dir) of ok -> {ok, []}; {error, Reason} -> @@ -402,11 +389,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(S), 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(S)), reltool_utils:reply(ReplyTo, Ref, Reply), ?MODULE:loop(S); {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid -> @@ -422,101 +409,215 @@ loop(#state{common = C, sys = Sys} = S) -> ?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, rels = Rels} = Sys} = S, - Status) -> - Apps = lists:keydelete(?MISSING_APP_NAME, #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_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} + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +do_set_apps(#state{sys = Sys} = S, ChangedApps) -> + %% Create new list of configured applications + SysApps = app_update_config(ChangedApps, Sys#sys.apps), + S#state{sys = Sys#sys{apps = SysApps}}. + +%% 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([#app{name=Name,is_escript={inlined,Escript}}|_],_SysApps) -> + reltool_utils:throw_error("Application ~p is inlined in ~p. Can not change " + "configuration for an inlined application.", + [Name,Escript]); +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, + is_escript = false})-> + {delete,Name}; +app_set_config_only(Mods,#app{name = Name, + incl_cond = InclCond, + mod_cond = ModCond, + use_selected_vsn = UseSelectedVsn, + 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, + is_escript = IsEscript, + label = Label, + info = Info, + active_dir = ActiveDir, + sorted_dirs = SortedDirs}) -> + App = (default_app(Name))#app{incl_cond = InclCond, + mod_cond = ModCond, + use_selected_vsn = UseSelectedVsn, + 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}, + + if IsEscript -> + %% Some fields shall only be set if it is an escript, e.g. label + %% must never be set for any other applications since that will + %% prevent refreshing. + App#app{is_escript = IsEscript, + active_dir = ActiveDir, + sorted_dirs = SortedDirs, + label = Label, + info = Info}; + UseSelectedVsn =:= dir -> + %% Must not loose active_dir if it is configured to be used + App#app{active_dir = ActiveDir, + sorted_dirs = [ActiveDir]}; + true -> + App end. -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). +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)]. -apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps, Status) -> - Mandatory = [{RelName, kernel}, {RelName, stdlib}], - Other = [{RelName, AppName} || - RA <- RelApps, - AppName <- [RA#rel_app.name | RA#rel_app.incl_apps], - not lists:keymember(AppName, 2, Mandatory)], - more_apps_in_rels(Mandatory ++ Other, Apps, [], Status). -more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc, Status) -> + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +analyse(#state{sys=Sys} = S, Apps, Status) -> + %% Create a list of {RelName,AppName}, one element for each + %% AppName that needs to be included for the given release. + RelApps = apps_in_rels(Sys#sys.rels, Apps), + + %% 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. + Status2 = apps_init_is_included(S, Apps, RelApps, Status), + + %% For each module that has #mod.is_included==true, propagate + %% is_included to the modules it uses. + propagate_is_included(S), + + %% 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(S), + + %% 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. + app_recap_dependencies(S), + + %% 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(S, RelApps, Status2). + +apps_in_rels(Rels, Apps) -> + AllRelApps = + lists:foldl(fun(Rel, RelApps) -> + MoreRelApps = apps_in_rel(Rel, Apps), + MoreRelApps ++ RelApps + end, + [], + Rels), + lists:reverse(AllRelApps). + +apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps) -> + Mandatory = [{RelName, kernel}, {RelName, stdlib}], + Other = + [{RelName, AppName} || + RA <- RelApps, + AppName <- [RA#rel_app.name | + %% Included applications in rel shall overwrite included + %% applications in .app. I.e. included applications in + %% .app shall only be used if it is not defined in rel. + case RA#rel_app.incl_apps of + undefined -> + case lists:keyfind(RA#rel_app.name, + #app.name, + Apps) of + #app{info = #app_info{incl_apps = IA}} -> + IA; + false -> + reltool_utils:throw_error( + "Release ~p uses non existing " + "application ~p", + [RelName,RA#rel_app.name]) + end; + IA -> + IA + end], + not lists:keymember(AppName, 2, Mandatory)], + more_apps_in_rels(Mandatory ++ Other, Apps, []). + +more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> case lists:member(RA, Acc) of true -> - more_apps_in_rels(RelApps, Apps, Acc, Status); + more_apps_in_rels(RelApps, Apps, Acc); false -> case lists:keyfind(AppName, #app.name, Apps) of #app{info = #app_info{applications = InfoApps}} -> Extra = [{RelName, N} || N <- InfoApps], - {Acc2, Status2} = - more_apps_in_rels(Extra, Apps, [RA | Acc], Status), - more_apps_in_rels(RelApps, Apps, Acc2, Status2); + Acc2 = more_apps_in_rels(Extra, Apps, [RA | Acc]), + more_apps_in_rels(RelApps, Apps, Acc2); false -> - Text = lists:concat(["Release ", RelName, - " uses non existing application ", - AppName]), - Status2 = reltool_utils:return_first_error(Status, Text), - more_apps_in_rels(RelApps, Apps, Acc, Status2) + reltool_utils:throw_error( + "Release ~p uses non existing application ~p", + [RelName,AppName]) end end; -more_apps_in_rels([], _Apps, Acc, Status) -> - {Acc, Status}. +more_apps_in_rels([], _Apps, Acc) -> + Acc. -app_init_is_included(C, - Sys, + +apps_init_is_included(S, Apps, RelApps, Status) -> + lists:foldl(fun(App, AccStatus) -> + app_init_is_included(S, App, RelApps, AccStatus) + end, + Status, + Apps). + +app_init_is_included(#state{app_tab = AppTab, mod_tab = ModTab, sys=Sys}, #app{name = AppName, mods = Mods} = A, RelApps, Status) -> @@ -538,18 +639,16 @@ app_init_is_included(C, {exclude, []} -> {undefined, false, false, Status}; {exclude, [RelName | _]} -> % App is included in at least one rel - Text = lists:concat(["Application ", AppName, " is used " - "in release ", RelName, " and cannot " - "be excluded"]), - TmpStatus = reltool_utils:return_first_error(Status, Text), - {undefined, false, false, TmpStatus}; + reltool_utils:throw_error( + "Application ~p is used in release ~p and cannot be excluded", + [AppName,RelName]); {derived, []} -> {undefined, undefined, undefined, Status}; {derived, [_ | _]} -> % App is included in at least one rel {true, undefined, true, Status} end, {Mods2,Status3} = lists:mapfoldl(fun(Mod,Acc) -> - mod_init_is_included(C, + mod_init_is_included(ModTab, Mod, ModCond, AppCond, @@ -562,10 +661,10 @@ 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(AppTab, A2), + Status3. -mod_init_is_included(C, M, ModCond, AppCond, Default, Status) -> +mod_init_is_included(ModTab, M, ModCond, AppCond, Default, Status) -> %% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]), IsIncl = case AppCond of @@ -602,43 +701,33 @@ mod_init_is_included(C, M, ModCond, AppCond, Default, Status) -> M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl}, Status2 = - case ets:lookup(C#common.mod_tab,M#mod.name) of + case ets:lookup(ModTab,M#mod.name) of [Existing] -> case {Existing#mod.is_included,IsIncl} of {false,_} -> - Warning = - lists:concat( - ["Module ",M#mod.name, - " exists in applications ", Existing#mod.app_name, - " and ", M#mod.app_name, - ". Using module from application ", - M#mod.app_name, "."]), - ets:insert(C#common.mod_tab, M2), - reltool_utils:add_warning(Status,Warning); + ets:insert(ModTab, M2), + reltool_utils:add_warning( + "Module ~p exists in applications ~p and ~p. " + "Using module from application ~p.", + [M#mod.name, Existing#mod.app_name, + M#mod.app_name, M#mod.app_name], + Status); {_,false} -> - Warning = - lists:concat( - ["Module ",M#mod.name, - " exists in applications ", Existing#mod.app_name, - " and ", M#mod.app_name, - ". Using module from application ", - Existing#mod.app_name, "."]), - - %% Don't insert in mod_tab - using Existing - reltool_utils:add_warning(Status,Warning); + %% Don't insert in ModTab - using Existing + reltool_utils:add_warning( + "Module ~p exists in applications ~p and ~p. " + "Using module from application ~p.", + [M#mod.name, Existing#mod.app_name, + M#mod.app_name,Existing#mod.app_name], + Status); {_,_} -> - Error = - lists:concat( - ["Module ",M#mod.name, - " potentially included by ", - "two different applications: ", - Existing#mod.app_name, " and ", - M#mod.app_name, "."]), - %% Don't insert in mod_tab - using Existing - reltool_utils:return_first_error(Status,Error) + reltool_utils:throw_error( + "Module ~p potentially included by two different " + "applications: ~p and ~p.", + [M#mod.name,Existing#mod.app_name,M#mod.app_name]) end; [] -> - ets:insert(C#common.mod_tab, M2), + ets:insert(ModTab, M2), Status end, @@ -651,55 +740,43 @@ 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(S) -> + ets:select(S#state.mod_tab, [{#mod{name='$1', + uses_mods='$2', + is_included=true, + _='_'}, + [], + [{{'$1','$2'}}]}]). + +propagate_is_included(S) -> + case lists:flatmap( + fun({ModName,UsesModNames}) -> + mod_mark_is_included(S,ModName,UsesModNames,[]) + end, + get_all_mods_and_dependencies(S)) 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(S#state.app_tab, MissingApp2), + ok + end. -mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> +mod_mark_is_included(#state{app_tab=AppTab, mod_tab=ModTab, sys=Sys} = S, + UsedByName, [ModName | ModNames], Acc) -> Acc3 = - case ets:lookup(C#common.mod_tab, ModName) of + case ets:lookup(ModTab, 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 -> @@ -711,16 +788,10 @@ mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) -> 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), + ets:insert(ModTab, M2), + [A] = ets:lookup(AppTab, M2#mod.app_name), Acc2 = case A#app.is_included of - true -> - Acc; - false -> - Acc; undefined -> ModCond = case A#app.mod_cond of @@ -738,63 +809,50 @@ 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, - Sys, + ets:insert(AppTab, A2), + mod_mark_is_included(S, 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) + mod_mark_is_included(S, ModName, M2#mod.uses_mods, 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}), + ets:insert(ModTab, M2), [M2 | Acc] end, - mod_mark_is_included(C, Sys, UsedByName, ModNames, Acc3); -mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) -> + mod_mark_is_included(S, UsedByName, ModNames, Acc3); +mod_mark_is_included(_S, _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. +propagate_is_used_by(S) -> + lists:foreach( + fun({Mod,UsesMods}) -> + lists:foreach( + fun(UsedMod) -> + ets:insert(S#state.mod_used_by_tab,{UsedMod,Mod}) + end, + UsesMods) + end, + get_all_mods_and_dependencies(S)). -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) -> - {Mods2, IsIncl2, Status2} = - mod_recap_dependencies(C, Sys, A, Mods, [], IsIncl, Status), +app_recap_dependencies(S) -> + ets:foldl(fun(App,_) -> app_recap_dependencies(S,App) end, + ok, S#state.app_tab). + +app_recap_dependencies(S, #app{mods = Mods, is_included = IsIncl} = A) -> + {Mods2, IsIncl2} = mod_recap_dependencies(S, A, Mods, [], IsIncl), AppStatus = case lists:keymember(missing, #mod.status, Mods2) of true -> missing; @@ -803,12 +861,12 @@ app_recap_dependencies(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Ap 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)], + M <- ets:lookup(S#state.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)], + M <- ets:lookup(S#state.mod_tab, ModName)], UsedByApps2 = lists:usort(UsedByApps), A2 = A#app{mods = Mods2, @@ -818,13 +876,11 @@ app_recap_dependencies(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Ap uses_apps = UsesApps2, 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}. + ets:insert(S#state.app_tab,A2), + ok. -mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, Status) -> - case ets:lookup(C#common.mod_tab, ModName) of +mod_recap_dependencies(S, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl) -> + case ets:lookup(S#state.mod_tab, ModName) of [M2] when M2#mod.app_name=:=A#app.name -> ModStatus = do_get_status(M2), %% print(M2#mod.name, hipe, "status -> ~p\n", [ModStatus]), @@ -832,32 +888,28 @@ mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, case M2#mod.is_included of true -> UsedByMods = - [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab, + [N || {_, N} <- ets:lookup(S#state.mod_used_by_tab, ModName)], {true, M2#mod{status = ModStatus, used_by_mods = UsedByMods}}; _ -> {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); + ets:insert(S#state.mod_tab, M3), + mod_recap_dependencies(S, A, Mods, [M3 | Acc], IsIncl2); [_] when A#app.is_included==false; M1#mod.incl_cond==exclude -> %% 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(S, A, Mods, [M1 | Acc], IsIncl); [M2] -> %% A module is potensially included by multiple %% applications. This is not allowed! - Error = - lists:concat( - ["Module ",ModName, - " 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) + reltool_utils:throw_error( + "Module ~p potentially included by two different applications: " + "~p and ~p", [ModName,A#app.name, " and ", M2#mod.app_name, "."]) end; -mod_recap_dependencies(_C, _Sys, _A, [], Acc, IsIncl, Status) -> - {lists:reverse(Acc), IsIncl, Status}. +mod_recap_dependencies(_S, _A, [], Acc, IsIncl) -> + {lists:reverse(Acc), IsIncl}. do_get_status(M) -> if @@ -867,48 +919,49 @@ 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(#state{app_tab=AppTab, sys=#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(AppTab, RA, Acc) end, + Status, + RelApps), + lists:foldl(fun(#rel{name = RelName}, Acc)-> + check_rel(RelName, RelApps, Acc) + end, + Status2, + Rels); + false -> + reltool_utils:throw_error( + "Release ~p is mandatory (used as boot_rel)",[BootRel]) + end. + +check_app(AppTab, {RelName, AppName}, Status) -> + case ets:lookup(AppTab, AppName) of + [#app{is_pre_included=IsPreIncl, is_included=IsIncl}] + when IsPreIncl; IsIncl -> + Status; + _ -> + reltool_utils:throw_error( + "Release ~p uses non included application ~p",[RelName,AppName]) end. +check_rel(RelName, RelApps, Status) -> + EnsureApp = + fun(AppName, Acc) -> + case lists:member({RelName, AppName}, RelApps) of + true -> + Acc; + false -> + reltool_utils:throw_error( + "Mandatory application ~p is not included in " + "release ~p", [AppName,RelName]) + end + end, + Mandatory = [kernel, stdlib], + lists:foldl(EnsureApp, Status, Mandatory). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% refresh_app(#app{name = AppName, @@ -937,30 +990,57 @@ 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 - AppInfoMods = AppInfo#app_info.modules, - AppModNames = - case AppInfo#app_info.mod of - {StartModName, _} -> - case lists:member(StartModName, AppInfoMods) of - true -> AppInfoMods; - false -> [StartModName | AppInfoMods] - end; - undefined -> - AppInfoMods - end, - MissingMods = add_missing_mods(AppName, EbinMods, AppModNames), + %% 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 = lists:usort(AppInfo#app_info.modules), + Status4 = + case AppInfo#app_info.modules -- AppInfoMods of + [] -> + Status3; + DuplicatedMods -> + lists:foldl( + fun(M,S) -> + reltool_utils:add_warning( + "Module ~p duplicated in app file for " + "application ~p.", [M, AppName], S) + end, + Status3, + DuplicatedMods) + end, + AppModNames = + case AppInfo#app_info.mod of + {StartModName, _} -> + case lists:member(StartModName, AppInfoMods) of + true -> AppInfoMods; + false -> [StartModName | AppInfoMods] + end; + undefined -> + AppInfoMods + 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/4). 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 @@ -971,7 +1051,7 @@ refresh_app(#app{name = AppName, label = AppLabel, info = AppInfo, mods = lists:keysort(#mod.name, Mods3)}, - {App2, Status3}; + {App2, Status4}; true -> {App, Status} end. @@ -988,22 +1068,21 @@ read_app_info(AppFileOrBin, AppFile, AppName, DefaultVsn, Status) -> AI = #app_info{vsn = DefaultVsn}, parse_app_info(AppFile, Info, AI, Status); {ok, _BadApp} -> - Text = lists:concat([AppName, - ": Illegal contents in app file ", AppFile, - ", application tuple with arity 3 expected."]), {missing_app_info(DefaultVsn), - reltool_utils:add_warning(Status, Text)}; + reltool_utils:add_warning("~p: Illegal contents in app file ~p, " + "application tuple with arity 3 expected.", + [AppName,AppFile], + Status)}; {error, Text} when Text =:= EnoentText -> - Text2 = lists:concat([AppName, - ": Missing app file ", AppFile, "."]), {missing_app_info(DefaultVsn), - reltool_utils:add_warning(Status, Text2)}; + reltool_utils:add_warning("~p: Missing app file ~p.", + [AppName,AppFile], + Status)}; {error, Text} -> - Text2 = lists:concat([AppName, - ": Cannot parse app file ", - AppFile, " (", Text, ")."]), {missing_app_info(DefaultVsn), - reltool_utils:add_warning(Status, Text2)} + reltool_utils:add_warning("~p: Cannot parse app file ~p (~p).", + [AppName,AppFile,Text], + Status)} end. parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) -> @@ -1037,10 +1116,11 @@ parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) -> parse_app_info(File, KeyVals, AI#app_info{start_phases = Val}, Status); _ -> - String = lists:concat(["Unexpected item ", - Key, "in app file ", File]), - parse_app_info(File, KeyVals, AI, - reltool_utils:add_warning(Status, String)) + Status2 = + reltool_utils:add_warning("Unexpected item ~p in app file ~p.", + [Key,File], + Status), + parse_app_info(File, KeyVals, AI, Status2) end; parse_app_info(_, [], AI, Status) -> {AI, Status}. @@ -1171,12 +1251,54 @@ set_mod_flags(Mods, AppModNames) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% do_get_config(S, InclDef, InclDeriv) -> - S2 = + AppTab = S#state.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) 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 -> + {undefined, [], undefined}; + vsn -> + {undefined, [], A#app.vsn}; + dir -> + {A#app.active_dir, [A#app.active_dir], undefined} + 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), @@ -1187,132 +1309,83 @@ 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, []}), - 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, _} -> - %% Keep old state - S - end, - {S4, Status3}; - {error, _} -> - %% Keep old state - {S, Status} - end. + S#state{sys = read_config(default_sys(), SysConfig)}. -read_config(OldSys, Filename, Status) when is_list(Filename) -> +read_config(OldSys, Filename) when is_list(Filename) -> case file:consult(Filename) of {ok, [SysConfig | _]} -> - read_config(OldSys, SysConfig, Status); + read_config(OldSys, SysConfig); {ok, Content} -> - Text = lists:flatten(io_lib:format("~p", [Content])), - {OldSys, - reltool_utils:return_first_error(Status, - "Illegal file content: " ++ - Text)}; + reltool_utils:throw_error("Illegal file content: ~p",[Content]); {error, Reason} -> - Text = file:format_error(Reason), - {OldSys, - reltool_utils:return_first_error(Status, - "Illegal config file " ++ - Filename ++ ": " ++ Text)} + reltool_utils:throw_error("Illegal config file ~p: ~s", + [Filename,file:format_error(Reason)]) end; -read_config(OldSys, {sys, KeyVals}, Status) -> - {NewSys, Status2} = - decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status), - case Status2 of - {ok, _Warnings} -> % BUGBUG: handle warnings - 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:keymember(NewSys2#sys.boot_rel, - #rel.name, - NewSys2#sys.rels) of - true -> - {NewSys2, Status2}; - false -> - Text2 = lists:concat(["Release " ++ NewSys2#sys.boot_rel, - " is mandatory (used as boot_rel)"]), - {OldSys, reltool_utils:return_first_error(Status2, Text2)} - end; - {error, _} -> - %% Keep old state - {OldSys, Status2} +read_config(OldSys, {sys, KeyVals}) -> + NewSys = decode(OldSys#sys{apps = [], rels = []}, KeyVals), + Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps], + Rels = + case NewSys#sys.rels of + [] -> reltool_utils:default_rels(); + Rs -> Rs + end, + NewSys2 = NewSys#sys{apps = lists:sort(Apps), + rels = lists:sort(Rels)}, + case lists:keymember(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of + true -> + NewSys2; + false -> + reltool_utils:throw_error( + "Release ~p is mandatory (used as boot_rel)", + [NewSys2#sys.boot_rel]) end; -read_config(OldSys, BadConfig, Status) -> - Text = lists:flatten(io_lib:format("~p", [BadConfig])), - {OldSys, - reltool_utils:return_first_error(Status, "Illegal content: " ++ Text)}. +read_config(_OldSys, BadConfig) -> + reltool_utils:throw_error("Illegal content: ~p", [BadConfig]). -decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals], - Status) +decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals]) 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) + App2= decode(App, AppKeyVals), + decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals); +decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals]) 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); + App2 = decode(App, AppKeyVals), + decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals); 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) + [{escript, File0, AppKeyVals} | SysKeyVals]) + when is_list(File0), is_list(AppKeyVals) -> + File = filename:absname(File0), + App = default_escript_app(File), + App2 = decode(App, AppKeyVals), + decode(Sys#sys{apps = [App2 | Apps], escripts = [File | Escripts]}, + SysKeyVals); +decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals]) 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} = + Rel2 = decode(Rel, RelApps), + decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals); +decode(#sys{} = Sys, [{Key, Val} | KeyVals]) -> + Sys3 = case Key of root_dir when is_list(Val) -> - {Sys#sys{root_dir = Val}, Status}; + Sys#sys{root_dir = Val}; lib_dirs when is_list(Val) -> - {Sys#sys{lib_dirs = Val}, Status}; + Sys#sys{lib_dirs = Val}; mod_cond when Val =:= all; Val =:= app; Val =:= ebin; Val =:= derived; Val =:= none -> - {Sys#sys{mod_cond = Val}, Status}; + Sys#sys{mod_cond = Val}; incl_cond when Val =:= include; Val =:= exclude; Val =:= derived -> - {Sys#sys{incl_cond = Val}, Status}; + Sys#sys{incl_cond = Val}; boot_rel when is_list(Val) -> - {Sys#sys{boot_rel = Val}, Status}; + Sys#sys{boot_rel = Val}; emu_name when is_list(Val) -> - {Sys#sys{emu_name = Val}, Status}; + Sys#sys{emu_name = Val}; profile when Val =:= development; Val =:= embedded; Val =:= standalone -> @@ -1321,198 +1394,167 @@ decode(#sys{} = Sys, [{Key, Val} | KeyVals], Status) -> InclApp = reltool_utils:choose_default(incl_app_filters, Val, false), ExclApp = reltool_utils:choose_default(excl_app_filters, Val, false), AppType = reltool_utils:choose_default(embedded_app_type, Val, false), - {Sys#sys{profile = Val, - incl_sys_filters = dec_re(incl_sys_filters, - InclSys, - Sys#sys.incl_sys_filters), - excl_sys_filters = dec_re(excl_sys_filters, - ExclSys, - Sys#sys.excl_sys_filters), - incl_app_filters = dec_re(incl_app_filters, - InclApp, - Sys#sys.incl_app_filters), - excl_app_filters = dec_re(excl_app_filters, - ExclApp, - Sys#sys.excl_app_filters), - embedded_app_type = AppType}, - Status}; + Sys#sys{profile = Val, + incl_sys_filters = dec_re(incl_sys_filters, + InclSys, + Sys#sys.incl_sys_filters), + excl_sys_filters = dec_re(excl_sys_filters, + ExclSys, + Sys#sys.excl_sys_filters), + incl_app_filters = dec_re(incl_app_filters, + InclApp, + Sys#sys.incl_app_filters), + excl_app_filters = dec_re(excl_app_filters, + ExclApp, + Sys#sys.excl_app_filters), + embedded_app_type = AppType}; incl_sys_filters -> - {Sys#sys{incl_sys_filters = - dec_re(Key, - Val, - Sys#sys.incl_sys_filters)}, - Status}; + Sys#sys{incl_sys_filters = + dec_re(Key, Val, Sys#sys.incl_sys_filters)}; excl_sys_filters -> - {Sys#sys{excl_sys_filters = - dec_re(Key, - Val, - Sys#sys.excl_sys_filters)}, - Status}; + Sys#sys{excl_sys_filters = + dec_re(Key, Val, Sys#sys.excl_sys_filters)}; incl_app_filters -> - {Sys#sys{incl_app_filters = - dec_re(Key, - Val, - Sys#sys.incl_app_filters)}, - Status}; + Sys#sys{incl_app_filters = + dec_re(Key, Val, Sys#sys.incl_app_filters)}; excl_app_filters -> - {Sys#sys{excl_app_filters = - dec_re(Key, - Val, - Sys#sys.excl_app_filters)}, - Status}; + Sys#sys{excl_app_filters = + dec_re(Key, Val, Sys#sys.excl_app_filters)}; incl_archive_filters -> - {Sys#sys{incl_archive_filters = - dec_re(Key, - Val, - Sys#sys.incl_archive_filters)}, - Status}; + Sys#sys{incl_archive_filters = + dec_re(Key, Val, Sys#sys.incl_archive_filters)}; excl_archive_filters -> - {Sys#sys{excl_archive_filters = - dec_re(Key, - Val, - Sys#sys.excl_archive_filters)}, - Status}; + Sys#sys{excl_archive_filters = + dec_re(Key, Val, Sys#sys.excl_archive_filters)}; archive_opts when is_list(Val) -> - {Sys#sys{archive_opts = Val}, Status}; + Sys#sys{archive_opts = Val}; relocatable when Val =:= true; Val =:= false -> - {Sys#sys{relocatable = Val}, Status}; + Sys#sys{relocatable = Val}; rel_app_type when Val =:= permanent; Val =:= transient; Val =:= temporary; Val =:= load; Val =:= none -> - {Sys#sys{rel_app_type = Val}, Status}; + Sys#sys{rel_app_type = Val}; embedded_app_type when Val =:= permanent; Val =:= transient; Val =:= temporary; Val =:= load; Val =:= none; Val =:= undefined -> - {Sys#sys{embedded_app_type = Val}, Status}; + Sys#sys{embedded_app_type = Val}; app_file when Val =:= keep; Val =:= strip; Val =:= all -> - {Sys#sys{app_file = Val}, Status}; + Sys#sys{app_file = Val}; debug_info when Val =:= keep; Val =:= strip -> - {Sys#sys{debug_info = Val}, Status}; + Sys#sys{debug_info = Val}; _ -> - Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), - {Sys, reltool_utils:return_first_error(Status, - "Illegal option: " ++ - Text)} + reltool_utils:throw_error("Illegal option: ~p", [{Key, Val}]) end, - decode(Sys3, KeyVals, Status3); -decode(#app{} = App, [{Key, Val} | KeyVals], Status) -> - {App2, Status2} = + decode(Sys3, KeyVals); +decode(#app{} = App, [{Key, Val} | KeyVals]) -> + App2 = case Key of mod_cond when Val =:= all; Val =:= app; Val =:= ebin; Val =:= derived; Val =:= none -> - {App#app{mod_cond = Val}, Status}; + App#app{mod_cond = Val}; incl_cond when Val =:= include; Val =:= exclude; Val =:= derived -> - {App#app{incl_cond = Val}, Status}; + App#app{incl_cond = Val}; debug_info when Val =:= keep; Val =:= strip -> - {App#app{debug_info = Val}, Status}; + App#app{debug_info = Val}; app_file when Val =:= keep; Val =:= strip; Val =:= all -> - {App#app{app_file = Val}, Status}; + App#app{app_file = Val}; app_type when Val =:= permanent; Val =:= transient; Val =:= temporary; Val =:= load; Val =:= none; Val =:= undefined -> - {App#app{app_type = Val}, Status}; + App#app{app_type = Val}; incl_app_filters -> - {App#app{incl_app_filters = - dec_re(Key, - Val, - App#app.incl_app_filters)}, - Status}; + App#app{incl_app_filters = + dec_re(Key, Val, App#app.incl_app_filters)}; excl_app_filters -> - {App#app{excl_app_filters = - dec_re(Key, - Val, - App#app.excl_app_filters)}, - Status}; + App#app{excl_app_filters = + dec_re(Key, Val, App#app.excl_app_filters)}; incl_archive_filters -> - {App#app{incl_archive_filters = - dec_re(Key, - Val, - App#app.incl_archive_filters)}, - Status}; + App#app{incl_archive_filters = + dec_re(Key, Val, App#app.incl_archive_filters)}; excl_archive_filters -> - {App#app{excl_archive_filters = - dec_re(Key, - Val, - App#app.excl_archive_filters)}, - Status}; + App#app{excl_archive_filters = + dec_re(Key, Val, App#app.excl_archive_filters)}; 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}; + App#app{archive_opts = Val}; + vsn when is_list(Val), App#app.use_selected_vsn=:=undefined -> + App#app{use_selected_vsn = vsn, vsn = Val}; + lib_dir when is_list(Val), App#app.use_selected_vsn=:=undefined -> + case filelib:is_dir(Val) of + true -> + Dir = reltool_utils:normalize_dir(Val), + App#app{use_selected_vsn = dir, + active_dir = Dir, + sorted_dirs = [Dir]}; + false -> + reltool_utils:throw_error("Illegal lib dir for ~p: ~p", + [App#app.name, Val]) + end; + SelectVsn when SelectVsn=:=vsn; SelectVsn=:=lib_dir -> + reltool_utils:throw_error("Mutual exclusive options " + "'vsn' and 'lib_dir'",[]); _ -> - Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), - {App, reltool_utils:return_first_error(Status, - "Illegal option: " ++ Text)} + reltool_utils:throw_error("Illegal option: ~p", [{Key, Val}]) 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} = + decode(App2, KeyVals); +decode(#app{mods = Mods} = App, [{mod, Name, ModKeyVals} | AppKeyVals]) -> + Mod = decode(#mod{name = Name}, ModKeyVals), + decode(App#app{mods = [Mod | Mods]}, AppKeyVals); +decode(#mod{} = Mod, [{Key, Val} | KeyVals]) -> + Mod2 = case Key of incl_cond when Val =:= include; Val =:= exclude; Val =:= derived -> - {Mod#mod{incl_cond = Val}, Status}; + Mod#mod{incl_cond = Val}; debug_info when Val =:= keep; Val =:= strip -> - {Mod#mod{debug_info = Val}, Status}; + Mod#mod{debug_info = Val}; _ -> - Text = lists:flatten(io_lib:format("~p", [{Key, Val}])), - {Mod, - reltool_utils:return_first_error(Status, - "Illegal option: " ++ Text)} + reltool_utils:throw_error("Illegal option: ~p", [{Key, Val}]) end, - decode(Mod2, KeyVals, Status2); -decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals], Status) -> + decode(Mod2, KeyVals); +decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals]) -> {ValidTypesAssigned, RA} = case RelApp of Name when is_atom(Name) -> {true, #rel_app{name = Name}}; - {Name, Type} when is_atom(Name) -> - {is_type(Type), #rel_app{name = Name, app_type = Type}}; {Name, InclApps} when is_atom(Name), is_list(InclApps) -> VI = lists:all(fun erlang:is_atom/1, InclApps), {VI, #rel_app{name = Name, incl_apps = InclApps}}; + {Name, Type} when is_atom(Name) -> + {is_type(Type), #rel_app{name = Name, app_type = Type}}; {Name, Type, InclApps} when is_atom(Name), is_list(InclApps) -> VT = is_type(Type), VI = lists:all(fun erlang:is_atom/1, InclApps), {VT andalso VI, #rel_app{name = Name, app_type = Type, incl_apps = InclApps}}; _ -> - {false, #rel_app{incl_apps = []}} + {false, #rel_app{}} end, case ValidTypesAssigned of true -> - decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals, Status); + decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals); false -> - Text = lists:flatten(io_lib:format("~p", [RelApp])), - Status2 = - reltool_utils:return_first_error(Status, - "Illegal option: " ++ Text), - decode(Rel, KeyVals, Status2) + reltool_utils:throw_error("Illegal option: ~p", [RelApp]) 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)}. +decode(Acc, []) -> + Acc; +decode(_Acc, KeyVal) -> + reltool_utils:throw_error("Illegal option: ~p", [KeyVal]). is_type(Type) -> case Type of @@ -1529,79 +1571,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) -> + 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 = libs_to_dirs(RootDir, LibDirs), -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, Status2} = escripts_to_apps(Escripts, MergedApps, {ok,[]}), + + %% 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, Status3} = refresh_apps(Sys#sys.apps, AllApps, [], + true, Status2), + + %% Make sure erts exists in app list and has a version (or warn) + {PatchedApps, Status4} = patch_erts_version(RootDir, RefreshedApps, Status3), + + %% 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, Status4}. patch_erts_version(RootDir, Apps, Status) -> AppName = erts, @@ -1615,18 +1628,17 @@ patch_erts_version(RootDir, Apps, Status) -> Apps2 = lists:keystore(AppName, #app.name, Apps, Erts2), {Apps2, Status}; Vsn =:= "" -> - {Apps, reltool_utils:add_warning(Status, - "erts has no version")}; + {Apps, reltool_utils:add_warning("erts has no version",[], + Status)}; true -> {Apps, Status} end; false -> - Text = "erts cannot be found in the root directory " ++ RootDir, - Status2 = reltool_utils:return_first_error(Status, Text), - {Apps, Status2} + reltool_utils:throw_error( + "erts cannot be found in the root directory ~p", [RootDir]) end. -libs_to_dirs(RootDir, LibDirs, Status) -> +libs_to_dirs(RootDir, LibDirs) -> case file:list_dir(RootDir) of {ok, RootFiles} -> RootLibDir = filename:join([RootDir, "lib"]), @@ -1648,21 +1660,16 @@ libs_to_dirs(RootDir, LibDirs, Status) -> end, ErtsFiles = [{erts, Fun(F)} || F <- RootFiles, lists:prefix("erts", F)], - app_dirs2(AllLibDirs, [ErtsFiles], Status); + app_dirs2(AllLibDirs, [ErtsFiles]); [Duplicate | _] -> - {[], - reltool_utils:return_first_error(Status, - "Duplicate library: " ++ - Duplicate)} + reltool_utils:throw_error("Duplicate library: ~p",[Duplicate]) end; {error, Reason} -> - Text = file:format_error(Reason), - {[], reltool_utils:return_first_error(Status, - "Missing root library " ++ - RootDir ++ ": " ++ Text)} + reltool_utils:throw_error("Missing root library ~p: ~s", + [RootDir,file:format_error(Reason)]) end. -app_dirs2([Lib | Libs], Acc, Status) -> +app_dirs2([Lib | Libs], Acc) -> case file:list_dir(Lib) of {ok, Files} -> Filter = @@ -1682,17 +1689,15 @@ app_dirs2([Lib | Libs], Acc, Status) -> end end, Files2 = lists:zf(Filter, Files), - app_dirs2(Libs, [Files2 | Acc], Status); + app_dirs2(Libs, [Files2 | Acc]); {error, Reason} -> - Text = file:format_error(Reason), - {[], reltool_utils:return_first_error(Status, - "Illegal library " ++ - Lib ++ ": " ++ Text)} + reltool_utils:throw_error("Illegal library ~p: ~s", + [Lib, file:format_error(Reason)]) end; -app_dirs2([], Acc, Status) -> - {lists:sort(lists:append(Acc)), Status}. +app_dirs2([], Acc) -> + lists:sort(lists:append(Acc)). -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}) -> @@ -1755,156 +1760,114 @@ 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(EscriptAppName, + 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)} + reltool_utils:throw_error("Illegal escript ~p: ~p", [Escript,Reason]) 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, - App2 = App1#app{is_escript = true, +escript_files_to_apps(EscriptAppName, + [{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, + EscriptAppName, + Dir, + missing_app_info(""), + [ModOrInfo], + Apps, + Status), + {[NewApp | Acc], Status2} + end; + app -> + {App, Status2} = init_escript_app(AppName, + EscriptAppName, + Dir, + ModOrInfo, + [], + Apps, + Status), + {[App | Acc], Status2} + end, + escript_files_to_apps(EscriptAppName, Files, NewAcc, Apps, Status3); +escript_files_to_apps(_EscriptAppName, [], Acc, Apps, Status) -> + {lists:ukeymerge(#app.name, lists:reverse(Acc), Apps), Status}. + +init_escript_app(AppName, EscriptAppName, Dir, Info, Mods, Apps, Status) -> + App1 = default_app(AppName, Dir), + IsEscript = + if AppName=:=EscriptAppName -> true; + true -> {inlined, EscriptAppName} + end, + InclCond = (lists:keyfind(EscriptAppName,#app.name,Apps))#app.incl_cond, + App2 = App1#app{is_escript = IsEscript, label = filename:basename(Dir, ".escript"), info = Info, mods = Mods, active_dir = Dir, - sorted_dirs = [Dir]}, + sorted_dirs = [Dir], + incl_cond = InclCond},% inlined apps inherit incl from escript case lists:keymember(AppName, #app.name, Apps) of true -> - Error = lists:concat([AppName, ": Application name clash. ", - "Escript ", Dir," contains application ", - AppName, "."]), - {App2, reltool_utils:return_first_error(Status, Error)}; + reltool_utils:throw_error( + "~p: Application name clash. Escript ~p contains application ~p.", + [AppName,Dir,AppName]); 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 = +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, +%% unless a specific dir is given in config +set_active_dirs([#app{use_selected_vsn = dir} = App | Apps]) -> + [App | set_active_dirs(Apps)]; +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, @@ -1913,91 +1876,55 @@ 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) -> + +ensure_app_info(#app{is_escript = IsEscript, active_dir = Dir, info = Info}, + Status) + when IsEscript=/=false -> + %% Escript or application which is inlined in an escript {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, sorted_dirs = []}, _Status) -> + reltool_utils:throw_error("~p: : Missing application directory.",[Name]); ensure_app_info(#app{name = Name, vsn = Vsn, + use_selected_vsn = UseSelectedVsn, + active_dir = ActiveDir, sorted_dirs = Dirs, info = undefined}, Status) -> @@ -2017,32 +1944,36 @@ ensure_app_info(#app{name = Name, %% No redundant info Status2; [BadVsn | _] -> - Error2 = - lists:concat([Name, ": Application version clash. ", - "Multiple directories contains version \"", - BadVsn, "\"."]), - reltool_utils:return_first_error(Status2, Error2) + reltool_utils:throw_error( + "~p: Application version clash. " + "Multiple directories contains version ~p.", + [Name,BadVsn]) 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 + UseSelectedVsn =:= dir -> + if ActiveDir =:= FirstDir -> + {FirstInfo, FirstDir, Status3}; + true -> + Info = find_dir(ActiveDir, AllInfo, Dirs), + {Info, ActiveDir, Status3} + end; + UseSelectedVsn =:= vsn -> + if Vsn =:= FirstInfo#app_info.vsn -> + {FirstInfo, FirstDir, Status3}; + true -> + case find_vsn(Vsn, AllInfo, Dirs) of + {Info, VsnDir} -> + {Info, VsnDir, Status3}; + false -> + reltool_utils:throw_error( + "~p: No application directory contains " + "selected version ~p", [Name,Vsn]) + end + end; + true -> + {FirstInfo, FirstDir, Status3} end; ensure_app_info(#app{active_dir = Dir, info = Info}, Status) -> {Info, Dir, Status}. @@ -2054,6 +1985,11 @@ find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) -> find_vsn(_, [], []) -> false. +find_dir(Dir, [Info | _], [Dir | _]) -> + Info; +find_dir(Dir, [_ | MoreInfo], [_ | MoreDirs]) -> + find_dir(Dir, MoreInfo, MoreDirs). + get_base(Name, Dir) -> case Name of erts -> @@ -2067,6 +2003,54 @@ get_base(Name, Dir) -> filename:basename(Dir) end. +sys_all_apps(#state{app_tab=AppTab, sys=Sys}) -> + Sys#sys{apps = ets:match_object(AppTab,'_')}. + +config_and_refresh(OldS, Fun) -> + try + S = Fun(), + {S2, Apps, Status2} = refresh(S), + %% Analyse will write to app_tab and mod_tab, so we first + %% backup these tables and clear them + Backup = backup(OldS), + try + Status3 = analyse(S2, Apps, Status2), + S3 = save_old(OldS, S2, Backup, Status3), + {S3, Status3} + catch throw:{error,_} = Error1 -> + restore(Backup,OldS), + throw(Error1) + end + catch throw:{error,_} = Error2 -> + {OldS, Error2} + end. + + +backup(S) -> + Apps = ets:tab2list(S#state.app_tab), + Mods = ets:tab2list(S#state.mod_tab), + ets:delete_all_objects(S#state.app_tab), + ets:delete_all_objects(S#state.mod_tab), + ets:delete_all_objects(S#state.mod_used_by_tab), %tmp tab, no backup needed + {Apps,Mods}. + +restore({Apps,Mods}, S) -> + insert_all(S#state.app_tab,Apps), + insert_all(S#state.mod_tab,Mods). + +save_old(#state{status=OldStatus,sys=OldSys},NewS,{OldApps,OldMods},NewStatus) -> + ets:delete_all_objects(NewS#state.old_app_tab), + ets:delete_all_objects(NewS#state.old_mod_tab), + insert_all(NewS#state.old_app_tab,OldApps), + insert_all(NewS#state.old_mod_tab,OldMods), + NewS#state{old_sys=OldSys, + old_status=OldStatus, + status=NewStatus}. + +insert_all(Tab,Items) -> + lists:foreach(fun(Item) -> ets:insert(Tab,Item) end, Items). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% sys callbacks diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl index 8b0f64eb45..0c0b295db1 100644 --- a/lib/reltool/src/reltool_sys_win.erl +++ b/lib/reltool/src/reltool_sys_win.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -56,7 +56,9 @@ derived, fgraph_wins, app_box, - mod_box + mod_box, + warning_list, + warning_wins }). -define(WIN_WIDTH, 800). @@ -88,6 +90,10 @@ -define(blacklist, "Excluded"). -define(derived, "Derived"). +-define(WARNING_COL, 0). +-define(DEFAULT_WARNING_TIP, "Warnings are listed in this window"). +-define(WARNING_POPUP_SIZE, {400,150}). + -define(safe_config,{sys,[{incl_cond,exclude}, {app,kernel,[{incl_cond,include}]}, {app,stdlib,[{incl_cond,include}]}, @@ -141,48 +147,44 @@ do_init([{safe_config, Safe}, {parent, Parent} | Options]) -> wx:debug(C#common.wx_debug), %% wx_misc:beginBusyCursor(), - case reltool_server:get_status(ServerPid) of - {ok, Warnings} -> - exit_dialog(Warnings), - {ok, Sys} = reltool_server:get_sys(ServerPid), - S = #state{parent_pid = Parent, - server_pid = ServerPid, - common = C, - config_file = filename:absname("config.reltool"), - target_dir = filename:absname("reltool_target_dir"), - app_wins = [], - sys = Sys, - fgraph_wins = []}, - S2 = create_window(S), - S5 = wx:batch(fun() -> - Title = atom_to_list(?APPLICATION), - wxFrame:setTitle(S2#state.frame, - Title), - %% wxFrame:setMinSize(Frame, - %% {?WIN_WIDTH, ?WIN_HEIGHT}), - wxStatusBar:setStatusText( - S2#state.status_bar, - "Done."), - S3 = redraw_apps(S2), - S4 = redraw_libs(S3), - redraw_config_page(S4) - end), - %% wx_misc:endBusyCursor(), - %% wxFrame:destroy(Frame), - proc_lib:init_ack(S#state.parent_pid, {ok, self()}), - loop(S5); - {error, Reason} -> - restart_server_safe_config(Safe,Parent,Reason) - end; + {ok, Warnings} = reltool_server:get_status(ServerPid), + exit_dialog(Warnings), + S = #state{parent_pid = Parent, + server_pid = ServerPid, + common = C, + config_file = filename:absname("config.reltool"), + target_dir = filename:absname("reltool_target_dir"), + app_wins = [], + sys = Sys, + fgraph_wins = [], + warning_wins = []}, + S2 = create_window(S), + S5 = wx:batch(fun() -> + Title = atom_to_list(?APPLICATION), + wxFrame:setTitle(S2#state.frame, + Title), + %% wxFrame:setMinSize(Frame, + %% {?WIN_WIDTH, ?WIN_HEIGHT}), + wxStatusBar:setStatusText( + S2#state.status_bar, + "Done."), + S3 = redraw_apps(S2), + S4 = redraw_libs(S3), + redraw_config_page(S4) + end), + %% wx_misc:endBusyCursor(), + %% wxFrame:destroy(Frame), + proc_lib:init_ack(S#state.parent_pid, {ok, self()}), + loop(S5); {error, Reason} -> - io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), - exit(Reason) + restart_server_safe_config(Safe,Parent,Reason) end. -restart_server_safe_config(true,_Parent,Reason) -> +restart_server_safe_config(true,Parent,Reason) -> io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), - exit(Reason); + proc_lib:init_ack(Parent, {error,Reason}); restart_server_safe_config(false,Parent,Reason) -> + wx:new(), Strings = [{?wxBLACK,"Could not start reltool server:\n\n"}, {?wxRED,Reason++"\n\n"}, @@ -197,7 +199,7 @@ restart_server_safe_config(false,Parent,Reason) -> do_init([{safe_config,true},{parent,Parent},?safe_config]); ?wxID_CANCEL -> io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), - exit(Reason) + proc_lib:init_ack(Parent,{error,Reason}) end. exit_dialog([]) -> @@ -240,10 +242,18 @@ loop(S) -> lists:keydelete(ObjRef, #fgraph_win.frame, FWs), ?MODULE:loop(S#state{fgraph_wins = FWs2}); false -> - error_logger:format("~p~p got unexpected " - "message:\n\t~p\n", - [?MODULE, self(), Msg]), - ?MODULE:loop(S) + WWs = S#state.warning_wins, + case lists:member(ObjRef, WWs) of + true -> + wxFrame:destroy(ObjRef), + WWs2 = lists:delete(ObjRef, WWs), + ?MODULE:loop(S#state{warning_wins = WWs2}); + false -> + error_logger:format("~p~p got unexpected " + "message:\n\t~p\n", + [?MODULE, self(), Msg]), + ?MODULE:loop(S) + end end end; #wx{id = ?CLOSE_ITEM, @@ -297,8 +307,8 @@ handle_child_exit({'EXIT', Pid, _Reason} = Exit, FWs, AWs) -> msg_warning(Exit, application_window), {FWs, lists:keydelete(Pid, #app_win.pid, AWs)}; false -> - msg_warning(Exit, unknown), - {FWs, AWs} + msg_warning(Exit, unknown), + {FWs, AWs} end end. @@ -340,8 +350,12 @@ create_window(S) -> fun create_main_release_page/1, fun create_config_page/1 ]), + + S4 = create_warning_list(S3), + Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Sizer, S4#state.warning_list, [{flag, ?wxEXPAND}]), wxPanel:setSizer(Panel, Sizer), wxSizer:fit(Sizer, Frame), @@ -349,7 +363,7 @@ create_window(S) -> wxFrame:connect(Frame, close_window), wxFrame:show(Frame), - S3. + S4. create_menubar(Frame) -> MenuBar = wxMenuBar:new(), @@ -444,6 +458,7 @@ create_app_list_ctrl(Panel, OuterSz, Title, Tick, Cross) -> ListItem = wxListItem:new(), wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT), wxListItem:setText(ListItem, Title), + wxListItem:setWidth(ListItem, reltool_utils:get_column_width(ListCtrl)), wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem), wxListItem:destroy(ListItem), @@ -642,6 +657,49 @@ redraw_config_page(#state{sys = Sys, app_box = AppBox, mod_box = ModBox} = S) -> wxRadioBox:setSelection(ModBox, ModChoice), S. +create_warning_list(#state{panel = Panel} = S) -> + ListCtrl = wxListCtrl:new(Panel, + [{style, + ?wxLC_REPORT bor + ?wxLC_HRULES bor + ?wxVSCROLL}, + {size, {?WIN_WIDTH,80}}]), + reltool_utils:assign_image_list(ListCtrl), + wxListCtrl:insertColumn(ListCtrl, ?WARNING_COL, "Warnings", + [{format,?wxLIST_FORMAT_LEFT}, + {width,reltool_utils:get_column_width(ListCtrl)}]), + wxListCtrl:setToolTip(ListCtrl, ?DEFAULT_WARNING_TIP), + wxEvtHandler:connect(ListCtrl, size, + [{skip, true}, {userData, warnings}]), + wxEvtHandler:connect(ListCtrl, command_list_item_activated, + [{userData, warnings}]), + wxEvtHandler:connect(ListCtrl, motion, [{userData, warnings}]), + wxEvtHandler:connect(ListCtrl, enter_window), + S#state{warning_list=ListCtrl}. + +redraw_warnings(S) -> + {ok,Warnings} = reltool_server:get_status(S#state.server_pid), + redraw_warnings(S#state.warning_list,Warnings), + length(Warnings). + +redraw_warnings(ListCtrl, []) -> + wxListCtrl:deleteAllItems(ListCtrl), + ok; +redraw_warnings(ListCtrl, Warnings) -> + wxListCtrl:deleteAllItems(ListCtrl), + Show = fun(Warning, Row) -> + wxListCtrl:insertItem(ListCtrl, Row, ""), + wxListCtrl:setItem(ListCtrl, + Row, + ?WARNING_COL, + Warning, + [{imageId, ?WARN_IMAGE}]), + Row + 1 + end, + wx:foldl(Show, 0, Warnings), + ok. + + create_main_release_page(#state{book = Book} = S) -> Panel = wxPanel:new(Book, []), RelBook = wxNotebook:new(Panel, ?wxID_ANY, []), @@ -783,6 +841,9 @@ escript_popup(S, File, Tree, Item) -> handle_event(S, #wx{id = Id, obj= ObjRef, userData = UserData, event = Event} = _Wx) -> %% io:format("wx: ~p\n", [Wx]), case Event of + _ when UserData =:= warnings; + is_tuple(UserData), element(1,UserData)=:=warning -> + handle_warning_event(S, ObjRef, UserData, Event); #wxSize{type = size, size = {W, _H}} when UserData =:= app_list_ctrl -> wxListCtrl:setColumnWidth(ObjRef, ?APPS_APP_COL, W), S; @@ -848,7 +909,9 @@ handle_event(S, #wx{id = Id, obj= ObjRef, userData = UserData, event = Event} = when S#state.popup_menu =/= undefined -> handle_popup_event(S, Type, Id, ObjRef, UserData, Str); #wxMouse{type = enter_window} -> - wxWindow:setFocus(ObjRef), + %% The following is commented out because it raises the + %% main system window on top of popup windows. + %% wxWindow:setFocus(ObjRef), S; _ -> case wxNotebook:getPageText(S#state.book, @@ -860,6 +923,110 @@ handle_event(S, #wx{id = Id, obj= ObjRef, userData = UserData, event = Event} = end end. +handle_warning_event(S, ObjRef, _, #wxSize{type = size}) -> + ColumnWidth = reltool_utils:get_column_width(ObjRef), + wxListCtrl:setColumnWidth(ObjRef, ?WARNING_COL, ColumnWidth), + S; +handle_warning_event(S, ObjRef, _, #wxMouse{type = motion, x=X, y=Y}) -> + Pos = reltool_utils:wait_for_stop_motion(ObjRef, {X,Y}), + warning_list_set_tool_tip(os:type(),ObjRef,Pos), + S; +handle_warning_event(S, ObjRef, _, #wxList{type = command_list_item_activated, + itemIndex = Pos}) -> + Text = wxListCtrl:getItemText(ObjRef, Pos), + S#state{warning_wins = [display_warning(S,Text) | S#state.warning_wins]}; +handle_warning_event(S, _ObjRef, {warning,Frame}, + #wxCommand{type = command_button_clicked}) -> + wxFrame:destroy(Frame), + S#state{warning_wins = lists:delete(Frame,S#state.warning_wins)}. + +warning_list_set_tool_tip({win32,_},ListCtrl,{_X,Y}) -> + case win_find_item(ListCtrl,Y,0) of + -1 -> + wxListCtrl:setToolTip(ListCtrl,?DEFAULT_WARNING_TIP); + _Index -> + %% The following is commented out because there seems to + %% be an utomatic tooltip under Windows that shows the + %% expanded list item in case it is truncated because it + %% is too long for column width. + %% Tip = + %% case wxListCtrl:getItemText(ListCtrl,Index) of + %% "" -> + %% ?DEFAULT_WARNING_TIP; + %% Text -> + %% "WARNING:\n" ++ Text + %% end, + %% wxListCtrl:setToolTip(ListCtrl,Tip), + ok + end; +warning_list_set_tool_tip(_,ListCtrl,Pos) -> + case wxListCtrl:findItem(ListCtrl,-1,Pos,0) of + Index when Index >= 0 -> + Tip = + case wxListCtrl:getItemText(ListCtrl,Index) of + "" -> + ?DEFAULT_WARNING_TIP; + Text -> + "WARNING:\n" ++ Text + end, + wxListCtrl:setToolTip(ListCtrl, Tip); + _ -> + ok + end. + +win_find_item(ListCtrl,YPos,Index) -> + case wxListCtrl:getItemRect(ListCtrl,Index) of + {true,{_,Y,_,H}} when YPos>=Y, YPos=<Y+H -> + Index; + {true,_} -> + win_find_item(ListCtrl,YPos,Index+1); + {false,_} -> + -1 + end. + +display_warning(S,Warning) -> + Pos = warning_popup_position(S,?WARNING_POPUP_SIZE), + Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Warning",[{pos,Pos}]), + Panel = wxPanel:new(Frame, []), + TextStyle = ?wxTE_READONLY bor ?wxTE_WORDWRAP bor ?wxTE_MULTILINE, + Text = wxTextCtrl:new(Panel, ?wxID_ANY, [{value, Warning}, + {style, TextStyle}, + {size, ?WARNING_POPUP_SIZE}]), + Attr = wxTextAttr:new(), + wxTextAttr:setLeftIndent(Attr,10), + wxTextAttr:setRightIndent(Attr,10), + true = wxTextCtrl:setDefaultStyle(Text, Attr), + wxTextAttr:destroy(Attr), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Text, [{border, 2}, + {flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}]), + + Close = wxButton:new(Panel, ?wxID_ANY, [{label, "Close"}]), + wxButton:setToolTip(Close, "Close window."), + wxButton:connect(Close, command_button_clicked, [{userData,{warning,Frame}}]), + wxSizer:add(Sizer, Close, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + + wxPanel:setSizer(Panel, Sizer), + wxSizer:fit(Sizer, Frame), + wxSizer:setSizeHints(Sizer, Frame), + wxFrame:connect(Frame, close_window), + + wxFrame:show(Frame), + Frame. + +warning_popup_position(#state{frame=MF,warning_list=WL},{WFW,WFH}) -> + {MFX,MFY} = wxWindow:getPosition(MF), + {MFW,MFH} = wxWindow:getSize(MF), + {_WLW,WLH} = wxWindow:getSize(WL), + + %% Position the popup in the middle of the main frame, above the + %% warning list, and with a small offset from the exact middle... + Offset = 50, + X = MFX + (MFW-WFW) div 2 - Offset, + Y = MFY + (MFH-WLH-WFH) div 2 - Offset, + {X,Y}. + handle_popup_event(S, _Type, 0, _ObjRef, _UserData, _Str) -> S#state{popup_menu = undefined}; handle_popup_event(#state{popup_menu = #root_popup{dir = OldDir, @@ -1079,16 +1246,16 @@ handle_app_event(S, Event, ObjRef, UserData) -> [?MODULE, self(), ObjRef, UserData, Event]), S. -handle_app_button(#state{server_pid = ServerPid, app_wins = AppWins} = S, +handle_app_button(#state{server_pid = ServerPid, + status_bar = Bar, + app_wins = AppWins} = S, Items, Action) -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), NewApps = [move_app(S, Item, Action) || Item <- Items], case reltool_server:set_apps(ServerPid, NewApps) of - {ok, []} -> + {ok, _Warnings} -> ok; - {ok, Warnings} -> - Msg = lists:flatten([[W, $\n] || W <- Warnings]), - display_message(Msg, ?wxICON_WARNING); {error, Reason} -> display_message(Reason, ?wxICON_ERROR) end, @@ -1125,23 +1292,22 @@ move_app(S, {_ItemNo, AppBase}, Action) -> end, OldApp#app{incl_cond = AppCond}. -do_set_app(#state{server_pid = ServerPid, app_wins = AppWins} = S, NewApp) -> +do_set_app(#state{server_pid = ServerPid, + status_bar = Bar, + app_wins = AppWins} = S, NewApp) -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), Result = reltool_server:set_app(ServerPid, NewApp), - [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins], - S2 = redraw_apps(S), ReturnApp = case Result of - {ok, AnalysedApp, []} -> - AnalysedApp; - {ok, AnalysedApp, Warnings} -> - Msg = lists:flatten([[W, $\n] || W <- Warnings]), - display_message(Msg, ?wxICON_WARNING), + {ok, AnalysedApp, _Warnings} -> AnalysedApp; {error, Reason} -> display_message(Reason, ?wxICON_ERROR), {ok,OldApp} = reltool_server:get_app(ServerPid, NewApp#app.name), OldApp end, + [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins], + S2 = redraw_apps(S), {ok, ReturnApp, S2}. redraw_apps(#state{server_pid = ServerPid, @@ -1164,8 +1330,14 @@ redraw_apps(#state{server_pid = ServerPid, WhiteN = redraw_apps(WhiteApps, WhiteCtrl, ?TICK_IMAGE, ?ERR_IMAGE), redraw_apps(BlackApps2, BlackCtrl, ?CROSS_IMAGE, ?WARN_IMAGE), DerivedN = redraw_apps(DerivedApps, DerivedCtrl, ?TICK_IMAGE, ?ERR_IMAGE), + + WarningsN = redraw_warnings(S), + WarningText = if WarningsN==1 -> "warning"; + true -> "warnings" + end, Status = lists:concat([WhiteN, " whitelisted modules and ", - DerivedN, " derived modules."]), + DerivedN, " derived modules, ", + WarningsN, " ", WarningText, "."]), wxStatusBar:setStatusText(S#state.status_bar, Status), S. @@ -1323,26 +1495,28 @@ save_config(#state{config_file = OldFile} = S, InclDefaults, InclDerivates) -> S end. -gen_rel_files(#state{target_dir = OldDir} = S) -> +gen_rel_files(#state{status_bar = Bar, target_dir = OldDir} = S) -> Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, case select_dir(S#state.frame, "Select a directory to generate rel, script and boot files to", OldDir, Style) of {ok, NewDir} -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), Status = reltool_server:gen_rel_files(S#state.server_pid, NewDir), check_and_refresh(S, Status); cancel -> S end. -gen_target(#state{target_dir = OldDir} = S) -> +gen_target(#state{status_bar = Bar, target_dir = OldDir} = S) -> Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, case select_dir(S#state.frame, "Select a directory to generate a target system to", OldDir, Style) of {ok, NewDir} -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), Status = reltool_server:gen_target(S#state.server_pid, NewDir), check_and_refresh(S#state{target_dir = NewDir}, Status); cancel -> @@ -1380,8 +1554,8 @@ check_and_refresh(S, Status) -> case Status of ok -> true; - {ok, Warnings} -> - undo_dialog(S, Warnings); + {ok, _Warnings} -> + true; {error, Reason} when is_list(Reason) -> display_message(Reason, ?wxICON_ERROR), false; @@ -1435,19 +1609,6 @@ question_dialog(Question, Details) -> wxDialog:destroy(Dialog), Answer. -undo_dialog(_S, []) -> - true; -undo_dialog(S, Warnings) -> - Question = "Do you want to perform the update despite these warnings?", - Details = lists:flatten([[W, $\n] || W <- Warnings]), - case question_dialog(Question, Details) of - ?wxID_OK -> - true; - ?wxID_CANCEL -> - reltool_server:undo_config(S#state.server_pid), - false - end. - display_message(Message, Icon) -> Dialog = wxMessageDialog:new(wx:null(), Message, @@ -1491,8 +1652,6 @@ add_text(_,_,[]) -> ok. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% sys callbacks diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl index 0fcf89a360..e3a7b02143 100644 --- a/lib/reltool/src/reltool_target.erl +++ b/lib/reltool/src/reltool_target.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -101,7 +101,7 @@ do_gen_config(#sys{root_dir = RootDir, || A <- Apps, A#app.name =/= ?MISSING_APP_NAME, A#app.name =/= erts, - not A#app.is_escript], + A#app.is_escript =/= true], EscriptItems = [{escript, A#app.active_dir, emit(incl_cond, A#app.incl_cond, undefined, InclDefs)} @@ -155,6 +155,7 @@ do_gen_config(#app{name = Name, archive_opts = ArchiveOpts, use_selected_vsn = UseSelected, vsn = Vsn, + active_dir = ActiveDir, mods = Mods, is_included = IsIncl}, InclDefs) -> @@ -170,9 +171,10 @@ do_gen_config(#app{name = Name, emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefs), emit(archive_opts, ArchiveOpts, undefined, InclDefs), if - IsIncl, InclDefs -> [{vsn, Vsn}]; - UseSelected -> [{vsn, Vsn}]; - true -> [] + IsIncl, InclDefs -> [{vsn, Vsn}, {lib_dir, ActiveDir}]; + UseSelected =:= vsn -> [{vsn, Vsn}]; + UseSelected =:= dir -> [{lib_dir, ActiveDir}]; + true -> [] end, [do_gen_config(M, InclDefs) || M <- Mods] ], @@ -208,10 +210,10 @@ do_gen_config(#rel_app{name = Name, incl_apps = InclApps}, _InclDefs) -> case {Type, InclApps} of - {undefined, []} -> Name; - {undefined, _} -> {Name, InclApps}; - {_, []} -> {Name, Type}; - {_, _} -> {Name, Type, InclApps} + {undefined, undefined} -> Name; + {undefined, _} -> {Name, InclApps}; + {_, undefined} -> {Name, Type}; + {_, _} -> {Name, Type, InclApps} end; do_gen_config({Tag, Val}, InclDefs) -> emit(Tag, Val, undefined, InclDefs); @@ -247,10 +249,15 @@ gen_app(#app{name = Name, env = Env, mod = StartMod, start_phases = StartPhases}}) -> - StartMod2 = - case StartMod =:= undefined of - true -> []; - false -> [{mod, StartMod}] + StartPhases2 = + case StartPhases of + undefined -> []; + _ -> [{start_phases, StartPhases}] + end, + Tail = + case StartMod of + undefined -> StartPhases2; + _ -> [{mod, StartMod} | StartPhases2] end, {application, Name, [{description, Desc}, @@ -261,10 +268,9 @@ gen_app(#app{name = Name, {applications, ReqApps}, {included_applications, InclApps}, {env, Env}, - {start_phases, StartPhases}, {maxT, MaxT}, {maxP, MaxP} | - StartMod2]}. + Tail]}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Generate the contents of a rel file @@ -279,7 +285,7 @@ gen_rel(Rel, Sys) -> {error, Text} end. -do_gen_rel(#rel{name = RelName, vsn = RelVsn}, +do_gen_rel(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps}, #sys{apps = Apps}, MergedApps) -> ErtsName = erts, @@ -288,7 +294,7 @@ do_gen_rel(#rel{name = RelName, vsn = RelVsn}, {release, {RelName, RelVsn}, {ErtsName, Erts#app.vsn}, - [strip_rel_info(App) || App <- MergedApps]}; + [strip_rel_info(App, RelApps) || App <- MergedApps]}; false -> reltool_utils:throw_error("Mandatory application ~p is " "not included", @@ -298,13 +304,17 @@ do_gen_rel(#rel{name = RelName, vsn = RelVsn}, strip_rel_info(#app{name = Name, vsn = Vsn, app_type = Type, - info = #app_info{incl_apps = InclApps}}) - when Type =/= undefined -> - case {Type, InclApps} of - {permanent, []} -> {Name, Vsn}; - {permanent, _} -> {Name, Vsn, InclApps}; - {_, []} -> {Name, Vsn, Type}; - {_, _} -> {Name, Vsn, Type, InclApps} + info = #app_info{incl_apps = AppInclApps}}, + RelApps) when Type =/= undefined -> + RelInclApps = case lists:keyfind(Name,#rel_app.name,RelApps) of + #rel_app{incl_apps = RIA} when RIA =/= undefined -> RIA; + _ -> undefined + end, + case {Type, RelInclApps} of + {permanent, undefined} -> {Name, Vsn}; + {permanent, _} -> {Name, Vsn, AppInclApps}; + {_, undefined} -> {Name, Vsn, Type}; + {_, _} -> {Name, Vsn, Type, AppInclApps} end. merge_apps(#rel{name = RelName, @@ -323,7 +333,7 @@ merge_apps(#rel{name = RelName, A#app.name =/= ?MISSING_APP_NAME, not lists:keymember(A#app.name, #app.name, MergedApps2)], MergedApps3 = do_merge_apps(RelName, Embedded, Apps, EmbAppType, MergedApps2), - sort_apps(MergedApps3). + sort_apps(lists:reverse(MergedApps3)). do_merge_apps(RelName, [#rel_app{name = Name} = RA | RelApps], Apps, RelAppType, Acc) -> case is_already_merged(Name, RelApps, Acc) of @@ -341,25 +351,18 @@ do_merge_apps(RelName, [Name | RelApps], Apps, RelAppType, Acc) -> true -> do_merge_apps(RelName, RelApps, Apps, RelAppType, Acc); false -> - RelApp = init_rel_app(Name, Apps), + RelApp = #rel_app{name = Name}, do_merge_apps(RelName, [RelApp | RelApps], Apps, RelAppType, Acc) end; do_merge_apps(_RelName, [], _Apps, _RelAppType, Acc) -> - lists:reverse(Acc). - -init_rel_app(Name, Apps) -> - {value, App} = lists:keysearch(Name, #app.name, Apps), - Info = App#app.info, - #rel_app{name = Name, - app_type = undefined, - incl_apps = Info#app_info.incl_apps}. + Acc. merge_app(RelName, - #rel_app{name = Name, - app_type = Type, - incl_apps = InclApps}, - RelAppType, - App) -> + #rel_app{name = Name, + app_type = Type, + incl_apps = InclApps0}, + RelAppType, + App) -> Type2 = case {Type, App#app.app_type} of {undefined, undefined} -> RelAppType; @@ -367,6 +370,11 @@ merge_app(RelName, {_, _} -> Type end, Info = App#app.info, + InclApps = + case InclApps0 of + undefined -> Info#app_info.incl_apps; + _ -> InclApps0 + end, case InclApps -- Info#app_info.incl_apps of [] -> App#app{app_type = Type2, info = Info#app_info{incl_apps = InclApps}}; @@ -421,7 +429,10 @@ do_gen_script(#rel{name = RelName, vsn = RelVsn}, Mandatory = mandatory_modules(), Early = Mandatory ++ Preloaded, {value, KernelApp} = lists:keysearch(kernel, #app.name, MergedApps), - InclApps = [I || #app{info = #app_info{incl_apps = I}} <- MergedApps], + InclApps = lists:flatmap(fun(#app{info = #app_info{incl_apps = I}}) -> + I + end, + MergedApps), %% Create the script DeepList = @@ -471,7 +482,7 @@ load_app_mods(#app{mods = Mods} = App, Mand, PathFlag, Variables) -> Path = cr_path(App, PathFlag, Variables), PartNames = lists:sort([{packages:split(M),M} || - #mod{name = M} <- Mods, + #mod{name = M, is_included=true} <- Mods, not lists:member(M, Mand)]), SplitMods = lists:foldl( @@ -513,7 +524,12 @@ sort_apps([#app{name = Name, info = Info} = App | Apps], Circular, Visited) -> {Uses, Apps1, NotFnd1} = - find_all(Name, Info#app_info.applications, Apps, Visited, [], []), + find_all(Name, + lists:reverse(Info#app_info.applications), + Apps, + Visited, + [], + []), {Incs, Apps2, NotFnd2} = find_all(Name, lists:reverse(Info#app_info.incl_apps), @@ -895,7 +911,7 @@ spec_escripts(#sys{apps = Apps}, ErtsBin, BinFiles) -> if Name =:= ?MISSING_APP_NAME -> false; - not IsEscript -> + IsEscript =/= true -> false; IsIncl; IsPre -> {true, do_spec_escript(File, ErtsBin, BinFiles)}; @@ -957,7 +973,7 @@ spec_lib_files(#sys{apps = Apps} = Sys) -> if Name =:= ?MISSING_APP_NAME -> false; - IsEscript -> + IsEscript =/= false -> false; IsIncl; IsPre -> true; diff --git a/lib/reltool/src/reltool_utils.erl b/lib/reltool/src/reltool_utils.erl index 39d057d994..6149d6ef06 100644 --- a/lib/reltool/src/reltool_utils.erl +++ b/lib/reltool/src/reltool_utils.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -22,15 +22,17 @@ -export([root_dir/0, erl_libs/0, lib_dirs/1, split_app_name/1, prim_consult/1, default_rels/0, choose_default/3, + normalize_dir/1, - assign_image_list/1, get_latest_resize/1, + assign_image_list/1, get_latest_resize/1, wait_for_stop_motion/2, mod_conds/0, list_to_mod_cond/1, mod_cond_to_index/1, incl_conds/0, list_to_incl_cond/1, incl_cond_to_index/1, elem_to_index/2, app_dir_test/2, split_app_dir/1, get_item/1, get_items/1, get_selected_items/3, select_items/3, select_item/2, + get_column_width/1, - safe_keysearch/5, print/4, return_first_error/2, add_warning/2, + safe_keysearch/5, print/4, add_warning/3, create_dir/1, list_dir/1, read_file_info/1, write_file_info/2, read_file/1, write_file/2, @@ -86,6 +88,21 @@ split_app_name(Name) -> {list_to_atom(Name), ""} end. + +normalize_dir(RelDir) -> + Tokens = filename:split(filename:absname(RelDir)), + filename:join(lists:reverse(normalize_dir(Tokens, []))). + +normalize_dir([".."|Dirs], [_Dir|Path]) -> + normalize_dir(Dirs, Path); +normalize_dir(["."|Dirs], Path) -> + normalize_dir(Dirs, Path); +normalize_dir([Dir|Dirs], Path) -> + normalize_dir(Dirs, [Dir|Path]); +normalize_dir([], Path) -> + Path. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% prim_consult(Bin) when is_binary(Bin) -> @@ -126,18 +143,14 @@ prim_parse(Tokens, Acc) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% default_rels() -> - %%Kernel = #rel_app{name = kernel, incl_apps = []}, - %%Stdlib = #rel_app{name = stdlib, incl_apps = []}, - Sasl = #rel_app{name = sasl, incl_apps = []}, + %% kernel and stdlib are added automatically in every release [ #rel{name = ?DEFAULT_REL_NAME, vsn = "1.0", rel_apps = []}, - %%rel_apps = [Kernel, Stdlib]}, #rel{name = "start_sasl", vsn = "1.0", - rel_apps = [Sasl]} - %%rel_apps = [Kernel, Sasl, Stdlib]} + rel_apps = [#rel_app{name = sasl}]} ]. choose_default(Tag, Profile, InclDefs) @@ -191,6 +204,16 @@ get_latest_resize(#wx{obj = ObjRef, event = #wxSize{}} = Wx) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +wait_for_stop_motion(ObjRef, {_,_}=Pos) -> + receive + #wx{obj = ObjRef, event = #wxMouse{type = motion, x=X, y=Y}} -> + wait_for_stop_motion(ObjRef, {X,Y}) + after 100 -> + Pos + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + mod_conds() -> ["all (ebin + app file)", "ebin + derived", "app file + derived", "derived", "none"]. @@ -249,7 +272,7 @@ app_dir_test(Dir1, Dir2) -> Name1 > Name2 -> false; Vsn1 < Vsn2 -> false; Vsn1 > Vsn2 -> true; - Parent1 < Parent2 -> true; + Parent1 =< Parent2 -> true; true -> false end. @@ -377,6 +400,26 @@ select_item(ListCtrl, [{ItemNo, Text} | Items]) -> select_item(_ListCtrl, []) -> ok. +get_column_width(ListCtrl) -> + wx:batch(fun() -> + {Total, _} = wxWindow:getClientSize(ListCtrl), + Total - scroll_size(ListCtrl) + end). + +scroll_size(ObjRef) -> + case os:type() of + {win32, nt} -> 0; + {unix, darwin} -> + %% I can't figure out is there is a visible scrollbar + %% Always make room for it + wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); + _ -> + case wxWindow:hasScrollbar(ObjRef, ?wxVERTICAL) of + true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X); + false -> 0 + end + end. + safe_keysearch(Key, Pos, List, Mod, Line) -> case lists:keysearch(Key, Pos, List) of false -> @@ -392,31 +435,13 @@ print(X, X, Format, Args) -> print(_, _, _, _) -> ok. -%% -define(SAFE(M,F,A), safe(M, F, A, ?MODULE, ?LINE)). -%% -%% safe(M, F, A, Mod, Line) -> -%% case catch apply(M, F, A) of -%% {'EXIT', Reason} -> -%% io:format("~p(~p): ~p:~p~p -> ~p\n", [Mod, Line, M, F, A, Reason]), -%% timer:sleep(infinity); -%% Res -> -%% Res -%% end. - -return_first_error(Status, NewError) when is_list(NewError) -> - case Status of - {ok, _Warnings} -> - {error, NewError}; - {error, OldError} -> - {error, OldError} - end. - -add_warning(Status, Warning) -> - case Status of - {ok, Warnings} -> - {ok, [Warning | Warnings]}; - {error, Error} -> - {error, Error} +add_warning(Format, Args, {ok,Warnings}) -> + Warning = lists:flatten(io_lib:format(Format,Args)), + case lists:member(Warning,Warnings) of + true -> + {ok,Warnings}; + false -> + {ok,[Warning|Warnings]} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |