4
%% Copyright Ericsson AB 2009. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
19
-module(reltool_server).
24
get_config/1, load_config/2, save_config/2,
25
get_rel/2, get_script/2,
26
reset_config/1, undo_config/1,
29
get_apps/2, set_apps/2,
31
gen_rel_files/2, gen_target/2
35
-export([init/1, loop/1]).
37
%% sys callback functions
44
-include("reltool.hrl").
53
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
60
proc_lib:start_link(?MODULE, init, [[{parent, self()} | Options]], infinity, []).
63
call(Pid, get_config).
65
load_config(Pid, FilenameOrConfig) ->
66
call(Pid, {load_config, FilenameOrConfig}).
68
save_config(Pid, Filename) ->
69
call(Pid, {save_config, Filename}).
72
call(Pid, reset_config).
75
call(Pid, undo_config).
77
get_rel(Pid, RelName) ->
78
call(Pid, {get_rel, RelName}).
80
get_script(Pid, RelName) ->
81
call(Pid, {get_script, RelName}).
83
get_mod(Pid, ModName) ->
84
call(Pid, {get_mod, ModName}).
86
get_app(Pid, AppName) ->
87
call(Pid, {get_app, AppName}).
90
call(Pid, {set_app, App}).
92
get_apps(Pid, Kind) ->
93
call(Pid, {get_apps, Kind}).
95
set_apps(Pid, Apps) ->
96
call(Pid, {set_apps, Apps}).
102
call(Pid, {set_sys, Sys}).
104
gen_rel_files(Pid, Dir) ->
105
call(Pid, {gen_rel_files, Dir}).
107
gen_target(Pid, Dir) ->
108
call(Pid, {gen_target, Dir}).
110
call(Name, Msg) when is_atom(Name) ->
111
call(whereis(Name), Msg);
112
call(Pid, Msg) when is_pid(Pid) ->
113
Ref = erlang:monitor(process, Pid),
114
%% io:format("Send~p: ~p\n", [self(), Msg]),
115
Pid ! {self(), Ref, Msg},
118
%% io:format("Rec~p: ~p\n", [self(), Reply]),
119
erlang:demonitor(Ref, [flush]),
121
{'DOWN', Ref, _, _, Reason} ->
125
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
128
reply(Pid, Ref, Msg) ->
136
exit({Reason, erlang:get_stacktrace()})
140
case parse_options(Options) of
141
{#state{parent_pid = ParentPid, common = C, sys = Sys} = S, Status} ->
142
%% process_flag(trap_exit, (S#state.common)#common.trap_exit),
143
proc_lib:init_ack(ParentPid, {ok, self(), C, Sys#sys{apps = undefined}}),
144
{S2, Status2} = refresh(S, true, Status),
145
{S3, Status3} = analyse(S2#state{old_sys = S2#state.sys}, Status2),
154
parse_options(Opts) ->
155
AppTab = ets:new(reltool_apps, [public, ordered_set, {keypos, #app.name}]),
156
ModTab = ets:new(reltool_mods, [public, ordered_set, {keypos, #mod.name}]),
157
ModUsesTab = ets:new(reltool_mod_uses, [public, bag, {keypos, 1}]),
158
Sys = #sys{incl_cond = ?DEFAULT_INCL_COND,
159
mod_cond = ?DEFAULT_MOD_COND,
160
debug_info = ?DEFAULT_DEBUG_INFO,
161
app_file = ?DEFAULT_APP_FILE,
163
profile = development,
164
incl_erts_dirs = ?DEFAULT_INCL_ERTS_DIRS,
165
excl_erts_dirs = ?DEFAULT_EXCL_ERTS_DIRS,
166
incl_app_dirs = ?DEFAULT_INCL_APP_DIRS,
167
excl_app_dirs = ?DEFAULT_EXCL_APP_DIRS,
168
root_dir = reltool_utils:root_dir(),
169
lib_dirs = reltool_utils:erl_libs(),
172
boot_rel = ?DEFAULT_REL_NAME,
173
rels = reltool_utils:default_rels()},
174
C2 = #common{sys_debug = [],
179
mod_used_by_tab = ModUsesTab},
180
S = #state{options = Opts},
181
parse_options(Opts, S, C2, Sys, {ok, []}).
183
parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) ->
186
parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys, Status);
188
parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys, Status);
190
parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys, Status);
192
parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys, Status);
194
{Sys2, Status2} = read_config(Sys, Val, Status),
195
parse_options(KeyVals, S, C, Sys2, Status2);
197
parse_options(KeyVals, S, C, Sys#sys{incl_cond = Val}, Status);
199
parse_options(KeyVals, S, C, Sys#sys{mod_cond = Val}, Status);
201
parse_options(KeyVals, S, C, Sys#sys{root_dir = Val}, Status);
203
parse_options(KeyVals, S, C, Sys#sys{lib_dirs = Val}, Status);
205
parse_options(KeyVals, S, C, Sys#sys{escripts = Val}, Status);
207
Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
208
Status2 = reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text),
209
parse_options(KeyVals, S, C, Sys, Status2)
211
parse_options([], S, C, Sys, Status) ->
212
{S#state{common = C, sys = Sys}, Status}.
214
loop(#state{common = C, sys = Sys} = S) ->
216
{system, From, Msg} ->
217
sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, C#common.sys_debug, S);
218
{ReplyTo, Ref, get_config} ->
219
Reply = do_get_config(S),
220
reply(ReplyTo, Ref, Reply),
222
{ReplyTo, Ref, {load_config, SysConfig}} ->
223
{S2, Reply} = do_load_config(S, SysConfig),
224
reply(ReplyTo, Ref, Reply),
226
{ReplyTo, Ref, {save_config, Filename}} ->
227
Reply = do_save_config(S, Filename),
228
reply(ReplyTo, Ref, Reply),
230
{ReplyTo, Ref, reset_config} ->
231
{S2, Status} = parse_options(S#state.options),
233
{S4, Status2} = refresh(S3, true, Status),
234
{S5, Status3} = analyse(S4#state{old_sys = S4#state.sys}, Status2),
242
reply(ReplyTo, Ref, Status2),
244
{ReplyTo, Ref, undo_config} ->
245
reply(ReplyTo, Ref, ok),
246
?MODULE:loop(S#state{sys = S#state.old_sys, old_sys = S#state.sys});
247
{ReplyTo, Ref, {get_rel, RelName}} ->
250
case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
252
{ok, reltool_target:gen_rel(Rel, Sys)};
254
{error, "No such release"}
256
reply(ReplyTo, Ref, Reply),
258
{ReplyTo, Ref, {get_script, RelName}} ->
261
case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
265
reltool_target:gen_script(Rel, Sys, PathFlag, Variables);
267
{error, "No such release"}
269
reply(ReplyTo, Ref, Reply),
271
{ReplyTo, Ref, {get_mod, ModName}} ->
273
case ets:lookup(C#common.mod_tab, ModName) of
277
{ok, missing_mod(ModName, ?MISSING_APP)}
279
reply(ReplyTo, Ref, Reply),
281
{ReplyTo, Ref, {get_app, AppName}} when is_atom(AppName) ->
283
case lists:keysearch(AppName, #app.name, Sys#sys.apps) of
289
reply(ReplyTo, Ref, Reply),
291
{ReplyTo, Ref, {set_app, App}} ->
292
{S2, Status} = do_set_app(S, App, {ok, []}),
293
{S3, Status2} = analyse(S2, Status),
296
App2 = ?KEYSEARCH(App#app.name,
298
(S3#state.sys)#sys.apps),
299
reply(ReplyTo, Ref, {ok, App2, Warnings}),
302
reply(ReplyTo, Ref, {error, Reason}),
305
{ReplyTo, Ref, {get_apps, Kind}} ->
311
A#app.is_pre_included =:= true];
315
A#app.is_pre_included =:= false];
319
A#app.is_included =/= true,
320
A#app.is_pre_included =/= false];
324
A#app.is_included =:= true,
325
A#app.is_pre_included =/= true]
327
reply(ReplyTo, Ref, {ok, AppNames}),
329
{ReplyTo, Ref, {set_apps, Apps}} ->
330
{S2, Status} = lists:foldl(fun(A, {X, Y}) -> do_set_app(X, A, Y) end, {S, {ok, []}}, Apps),
331
{S3, Status2} = analyse(S2, Status),
332
reply(ReplyTo, Ref, Status2),
334
{ReplyTo, Ref, get_sys} ->
335
reply(ReplyTo, Ref, {ok, Sys#sys{apps = undefined}}),
337
{ReplyTo, Ref, {set_sys, Sys2}} ->
338
S2 = S#state{sys = Sys2#sys{apps = Sys#sys.apps}},
340
(Sys2#sys.root_dir =/= Sys#sys.root_dir) orelse
341
(Sys2#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse
342
(Sys2#sys.escripts =/= Sys#sys.escripts),
343
{S3, Status} = refresh(S2, Force, {ok, []}),
344
{S4, Status2} = analyse(S3#state{old_sys = S#state.sys}, Status),
352
reply(ReplyTo, Ref, Status),
354
{ReplyTo, Ref, {gen_rel_files, Dir}} ->
356
case reltool_target:gen_rel_files(S#state.sys, Dir) of
362
reply(ReplyTo, Ref, Status),
364
{ReplyTo, Ref, {gen_target, Dir}} ->
365
Reply = reltool_target:gen_target(S#state.sys, Dir),
366
reply(ReplyTo, Ref, Reply),
368
{'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
370
{ReplyTo, Ref, Msg} when is_pid(ReplyTo), is_reference(Ref) ->
371
error_logger:format("~p~p got unexpected call:\n\t~p\n",
372
[?MODULE, self(), Msg]),
373
reply(ReplyTo, Ref, {error, {invalid_call, Msg}}),
376
error_logger:format("~p~p got unexpected message:\n\t~p\n",
377
[?MODULE, self(), Msg]),
381
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383
do_set_app(#state{sys = Sys} = S, App, Status) ->
384
AppName = App#app.name,
385
{App2, Status2} = refresh_app(App, false, Status),
387
Apps2 = lists:keystore(AppName, #app.name, Apps, App2),
388
Sys2 = Sys#sys{apps = Apps2},
389
{S#state{sys = Sys2}, Status2}.
391
analyse(#state{common = C, sys = #sys{apps = Apps0} = Sys} = S, Status) ->
392
Apps = lists:keydelete(?MISSING_APP, #app.name, Apps0),
393
ets:delete_all_objects(C#common.app_tab),
394
ets:delete_all_objects(C#common.mod_tab),
395
ets:delete_all_objects(C#common.mod_used_by_tab),
396
MissingApp = default_app(?MISSING_APP, "missing"),
397
ets:insert(C#common.app_tab, MissingApp),
399
Apps2 = app_init_is_included(C, Sys, Apps, []),
401
case app_propagate_is_included(C, Sys, Apps2, []) of
405
%% io:format("Missing mods: ~p\n", [MissingMods]),
406
MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT,
407
info = missing_app_info(""),
411
[MissingApp2 | Apps2]
413
app_propagate_is_used_by(C, Apps3),
414
Apps4 = read_apps(C, Sys, Apps3, []),
415
%% io:format("Missing app: ~p\n", [lists:keysearch(?MISSING_APP, #app.name, Apps4)]),
416
Sys2 = Sys#sys{apps = Apps4},
418
Status2 = verify_config(Sys2, Status),
419
{S#state{sys = Sys2}, Status2}
421
throw:{error, Status3} ->
425
app_init_is_included(C, Sys, [#app{mods = Mods} = A | Apps], Acc) ->
427
case A#app.incl_cond of
428
undefined -> Sys#sys.incl_cond;
432
case A#app.mod_cond of
433
undefined -> Sys#sys.mod_cond;
442
A2 = A#app{is_pre_included = IsIncl, is_included = IsIncl},
443
ets:insert(C#common.app_tab, A2),
444
mod_init_is_included(C, Mods, ModCond, AppCond),
445
app_init_is_included(C, Sys, Apps, [A2 | Acc]);
446
app_init_is_included(_C, _Sys, [], Acc) ->
449
mod_init_is_included(C, [M | Mods], ModCond, AppCond) ->
450
%% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]),
454
case M#mod.incl_cond of
460
%% print(M#mod.name, hipe, "mod_cond -> ~p\n", [ModCond]),
463
app -> false_to_undefined(M#mod.is_app_mod);
464
ebin -> false_to_undefined(M#mod.is_ebin_mod);
465
derived -> undefined;
472
case M#mod.incl_cond of
481
M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl},
482
%% print(M#mod.name, hipe, "~p -> ~p\n", [M2, IsIncl]),
483
ets:insert(C#common.mod_tab, M2),
484
mod_init_is_included(C, Mods, ModCond, AppCond);
485
mod_init_is_included(_C, [], _ModCond, _AppCond) ->
488
false_to_undefined(Bool) ->
494
app_propagate_is_included(C, Sys, [#app{mods = Mods} = A | Apps], Acc) ->
495
Acc2 = mod_propagate_is_included(C, Sys, A, Mods, Acc),
496
app_propagate_is_included(C, Sys, Apps, Acc2);
497
app_propagate_is_included(_C, _Sys, [], Acc) ->
500
mod_propagate_is_included(C, Sys, A, [#mod{name = ModName} | Mods], Acc) ->
501
[M2] = ets:lookup(C#common.mod_tab, ModName),
502
%% print(ModName, file, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]),
503
%% print(ModName, filename, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]),
505
case M2#mod.is_included of
507
%% Propagate include mark
508
mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc);
514
mod_propagate_is_included(C, Sys, A, Mods, Acc2);
515
mod_propagate_is_included(_C, _Sys, _A, [], Acc) ->
518
mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) ->
520
case ets:lookup(C#common.mod_tab, ModName) of
522
%% print(UsedByName, file, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]),
523
%% print(UsedByName, filename, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]),
524
case M#mod.is_included of
532
%% Mark and propagate
534
case M#mod.incl_cond of
536
M#mod{is_pre_included = true, is_included = true};
538
M#mod{is_pre_included = true, is_included = true};
540
M#mod{is_included = true}
542
ets:insert(C#common.mod_tab, M2),
543
%% io:format("Propagate mod: ~p -> ~p (~p)\n", [UsedByName, ModName, M#mod.incl_cond]),
544
[A] = ets:lookup(C#common.app_tab, M2#mod.app_name),
546
case A#app.is_included of
553
case A#app.mod_cond of
554
undefined -> Sys#sys.mod_cond;
561
app -> M3#mod.is_app_mod;
562
ebin -> M3#mod.is_ebin_mod;
567
Mods = lists:filter(Filter, A#app.mods),
568
%% io:format("Propagate app: ~p ~p -> ~p\n",
569
%% [UsedByName, A#app.name, [M3#mod.name || M3 <- Mods]]),
570
A2 = A#app{is_included = true},
571
ets:insert(C#common.app_tab, A2),
572
mod_mark_is_included(C, Sys, ModName, [M3#mod.name || M3 <- Mods], Acc)
574
mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc2)
577
M = missing_mod(ModName, ?MISSING_APP),
578
M2 = M#mod{is_included = true},
579
ets:insert(C#common.mod_tab, M2),
580
ets:insert(C#common.mod_used_by_tab, {UsedByName, ModName}),
583
mod_mark_is_included(C, Sys, UsedByName, ModNames, Acc3);
584
mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) ->
587
app_propagate_is_used_by(C, [#app{mods = Mods, name = Name} | Apps]) ->
588
case Name =:= ?MISSING_APP of
592
mod_propagate_is_used_by(C, Mods),
593
app_propagate_is_used_by(C, Apps);
594
app_propagate_is_used_by(_C, []) ->
597
mod_propagate_is_used_by(C, [#mod{name = ModName} | Mods]) ->
598
[M] = ets:lookup(C#common.mod_tab, ModName),
599
case M#mod.is_included of
601
[ets:insert(C#common.mod_used_by_tab, {UsedModName, ModName}) ||
602
UsedModName <- M#mod.uses_mods];
608
mod_propagate_is_used_by(C, Mods);
609
mod_propagate_is_used_by(_C, []) ->
612
read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) ->
613
{Mods2, IsIncl2} = read_apps(C, Sys, A, Mods, [], IsIncl),
614
%% reltool_utils:print(A#app.name, stdlib, "Mods2: ~p\n", [[M#mod.status || M <- Mods2]]),
616
case lists:keysearch(missing, #mod.status, Mods2) of
617
{value, _} -> missing;
620
UsesMods = [M#mod.uses_mods || M <- Mods2, M#mod.is_included =:= true],
621
UsesMods2 = lists:usort(lists:flatten(UsesMods)),
622
UsesApps = [M#mod.app_name || ModName <- UsesMods2, M <- ets:lookup(C#common.mod_tab, ModName)],
623
UsesApps2 = lists:usort(UsesApps),
624
UsedByMods = [M#mod.used_by_mods || M <- Mods2, M#mod.is_included =:= true],
625
UsedByMods2 = lists:usort(lists:flatten(UsedByMods)),
626
UsedByApps = [M#mod.app_name || ModName <- UsedByMods2, M <- ets:lookup(C#common.mod_tab, ModName)],
627
UsedByApps2 = lists:usort(UsedByApps),
629
A2 = A#app{mods = Mods2,
631
uses_mods = UsesMods2,
632
used_by_mods = UsedByMods2,
633
uses_apps = UsesApps2,
634
used_by_apps = UsedByApps2,
635
is_included = IsIncl2},
636
read_apps(C, Sys, Apps, [A2 | Acc]);
637
read_apps(_C, _Sys, [], Acc) ->
640
read_apps(C, Sys, A, [#mod{name = ModName} | Mods], Acc, IsIncl) ->
641
[M2] = ets:lookup(C#common.mod_tab, ModName),
642
Status = get_status(M2),
643
%% print(M2#mod.name, hipe, "status -> ~p\n", [Status]),
645
case M2#mod.is_included of
647
UsedByMods = [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab, ModName)],
648
{true, M2#mod{status = Status, used_by_mods = UsedByMods}};
650
{IsIncl, M2#mod{status = Status, used_by_mods = []}}
652
ets:insert(C#common.mod_tab, M3),
653
read_apps(C, Sys, A, Mods, [M3 | Acc], IsIncl2);
654
read_apps(_C, _Sys, _A, [], Acc, IsIncl) ->
655
{lists:reverse(Acc), IsIncl}.
659
M#mod.exists =:= false, M#mod.is_included =/= false ->
665
shrink_sys(#state{sys = #sys{apps = Apps} = Sys} = S) ->
666
Apps2 = lists:zf(fun filter_app/1, Apps),
667
S#state{sys = Sys#sys{apps = Apps2}}.
670
Mods = [M#mod{is_app_mod = undefined,
671
is_ebin_mod = undefined,
672
uses_mods = undefined,
674
is_pre_included = undefined,
675
is_included = undefined} ||
677
M#mod.incl_cond =/= undefined],
680
A#app.mod_cond =:= undefined,
681
A#app.incl_cond =:= undefined,
682
A#app.use_selected_vsn =:= undefined ->
686
case A#app.use_selected_vsn of
687
true -> {A#app.active_dir, [A#app.active_dir]};
688
false -> {shrinked, []};
689
undefined -> {shrinked, []}
692
case A#app.use_selected_vsn of
693
undefined -> undefined;
697
{true, A#app{active_dir = Dir,
703
uses_mods = undefined,
704
is_included = undefined}}
707
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
709
refresh_app(#app{name = AppName,
710
is_escript = IsEscript,
711
active_dir = ActiveDir,
717
Force; OptLabel =:= undefined ->
718
{AppInfo, EbinMods, Status3} =
722
%% Add info from .app file
723
Base = get_base(AppName, ActiveDir),
724
{_, DefaultVsn} = reltool_utils:split_app_name(Base),
725
Ebin = filename:join([ActiveDir, "ebin"]),
726
AppFile = filename:join([Ebin, atom_to_list(AppName) ++ ".app"]),
727
{AI, Status2} = read_app_info(AppFile, AppFile, AppName, DefaultVsn, Status),
728
{AI, read_ebin_mods(Ebin, AppName), Status2};
730
{App#app.info, Mods, Status}
733
%% Add non-existing modules
734
AppModNames = AppInfo#app_info.modules,
735
MissingMods = add_missing_mods(AppName, EbinMods, AppModNames),
737
%% Add optional user config for each module
738
Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods),
740
%% Set app flag for each module in app file
741
Mods3 = set_mod_flags(Mods2, AppModNames),
742
AppVsn = AppInfo#app_info.vsn,
745
"" -> atom_to_list(AppName);
746
_ -> atom_to_list(AppName) ++ "-" ++ AppVsn
748
App2 = App#app{vsn = AppVsn,
751
mods = lists:keysort(#mod.name, Mods3)},
757
missing_app_info(Vsn) ->
758
#app_info{vsn = Vsn}.
760
read_app_info(_AppFileOrBin, _AppFile, erts, DefaultVsn, Status) ->
761
{missing_app_info(DefaultVsn), Status};
762
read_app_info(AppFileOrBin, AppFile, AppName, DefaultVsn, Status) ->
763
case reltool_utils:prim_consult(AppFileOrBin) of
764
{ok, [{application, AppName, Info}]} ->
765
AI = #app_info{vsn = DefaultVsn},
766
parse_app_info(AppFile, Info, AI, Status);
768
Text = lists:concat([AppName, ": Illegal contents in app file ", AppFile]),
769
{missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text)};
771
Text2 = lists:concat([AppName, ": Cannot parse app file ", AppFile, " (", Text, ")."]),
772
{missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text2)}
775
parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) ->
777
description -> parse_app_info(File, KeyVals, AI#app_info{description = Val}, Status);
778
id -> parse_app_info(File, KeyVals, AI#app_info{id = Val}, Status);
779
vsn -> parse_app_info(File, KeyVals, AI#app_info{vsn = Val}, Status);
780
modules -> parse_app_info(File, KeyVals, AI#app_info{modules = Val}, Status);
781
maxP -> parse_app_info(File, KeyVals, AI#app_info{maxP = Val}, Status);
782
maxT -> parse_app_info(File, KeyVals, AI#app_info{maxT = Val}, Status);
783
registered -> parse_app_info(File, KeyVals, AI#app_info{registered = Val}, Status);
784
included_applications -> parse_app_info(File, KeyVals, AI#app_info{incl_apps = Val}, Status);
785
applications -> parse_app_info(File, KeyVals, AI#app_info{applications = Val}, Status);
786
env -> parse_app_info(File, KeyVals, AI#app_info{env = Val}, Status);
787
mod -> parse_app_info(File, KeyVals, AI#app_info{mod = Val}, Status);
788
start_phases -> parse_app_info(File, KeyVals, AI#app_info{start_phases = Val}, Status);
789
_ -> parse_app_info(File, KeyVals, AI, reltool_utils:add_warning(Status, lists:concat(["Unexpected item ", Key, "in app file ", File])))
791
parse_app_info(_, [], AI, Status) ->
794
read_ebin_mods(Ebin, AppName) ->
795
case erl_prim_loader:list_dir(Ebin) of
797
Ext = code:objfile_extension(),
799
File = filename:join([Ebin, F]),
800
init_mod(AppName, File, File, Ext)
802
Files2 = [F || F <- Files, filename:extension(F) =:= Ext],
803
pmap(InitMod, Files2);
809
lists:map(Fun, List).
810
%% N = erlang:system_info(schedulers) * 2,
811
%% pmap(Fun, List, 0, N, 0, [], []).
813
%% -record(pmap_res, {count, ref, res}).
814
%% -record(pmap_wait, {count, ref, pid}).
816
%% pmap(Fun, [H | T], N, Max, Count, WaitFor, Results) when N < Max ->
819
%% Count2 = Count + 1,
820
%% Pid = spawn_link(fun() -> Parent ! #pmap_res{count = Count2, ref = Ref, res = Fun(H)}, unlink(Parent) end),
821
%% PW = #pmap_wait{count = Count2, pid = Pid, ref = Ref},
822
%% pmap(Fun, T, N + 1, Max, Count2, [PW | WaitFor], Results);
823
%% pmap(_Fun, [], _N, _Max, _Count, [], Results) ->
824
%% %% Sort results and return them in the same orderas the original list
825
%% [PR#pmap_res.res || PR <- lists:keysort(#pmap_res.count, Results)];
826
%% pmap(Fun, List, N, Max, Count, WaitFor, Results) ->
828
%% #pmap_res{ref = Ref} = PR ->
829
%% WaitFor2 = lists:keydelete(Ref, #pmap_wait.ref, WaitFor),
830
%% pmap(Fun, List, N - 1, Max, Count, WaitFor2, [PR | Results]);
831
%% {'EXIT', Reason} ->
835
init_mod(AppName, File, FileOrBin, Ext) ->
836
UsesMods = xref_mod(FileOrBin),
837
Base = filename:basename(File, Ext),
838
ModName = list_to_atom(Base),
841
incl_cond = undefined,
843
uses_mods = UsesMods,
846
xref_mod({Base, Bin}) when is_binary(Bin) ->
847
Dir = filename:absname("reltool_server.tmp"),
848
ok = filelib:ensure_dir(filename:join([Dir, "foo"])),
849
File = filename:join([Dir, Base]),
850
ok = file:write_file(File, Bin),
851
Res = xref_mod(File),
852
ok = file:delete(File),
853
ok = file:del_dir(Dir),
855
xref_mod(File) when is_list(File) ->
856
{ok, Pid} = xref:start([{xref_mode, modules}]),
858
ok = xref:set_default(Pid, [{verbose,false}, {warnings, false}]),
859
ok = xref:set_library_path(Pid, []),
860
{ok, _} = xref:add_module(Pid, File, []),
861
{ok, UnknownMods} = xref:q(Pid, "UM", []),
862
%% {ok, ExportedFuns} = xref:q(Pid, "X", []),
863
%% io:format("Unres: ~p\n", [xref:variables(Pid, [predefined])]),
864
%% io:format("Q: ~p\n", [xref:q(Pid, "XU", [])]),
869
add_missing_mods(AppName, EbinMods, AppModNames) ->
870
EbinModNames = [M#mod.name || M <- EbinMods],
871
MissingModNames = AppModNames -- EbinModNames,
872
[missing_mod(ModName, AppName) || ModName <- MissingModNames].
874
missing_mod(ModName, AppName) ->
875
%% io:format("Missing: ~p -> ~p\n", [AppName, ModName]),
878
incl_cond = undefined,
884
add_mod_config(Mods, ModConfigs) ->
887
case lists:keysearch(Config#mod.name, #mod.name, Mods) of
889
M2 = M#mod{incl_cond = Config#mod.incl_cond},
890
lists:keystore(Config#mod.name, #mod.name, Acc, M2);
892
Config2 = Config#mod{uses_mods = [], exists = false},
896
lists:foldl(AddConfig, Mods, ModConfigs).
898
set_mod_flags(Mods, AppModNames) ->
900
fun(#mod{name = N} = M) ->
901
M#mod{is_app_mod = lists:member(N, AppModNames)}
903
lists:map(SetFlags, Mods).
905
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
909
{ok, reltool_target:gen_config(S2#state.sys)}.
911
do_save_config(S, Filename) ->
912
{ok, Config} = do_get_config(S),
913
IoList = io_lib:format("%% config generated at ~w ~w\n~p.\n\n",
914
[date(), time(), Config]),
915
file:write_file(Filename, IoList).
917
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
919
do_load_config(S, SysConfig) ->
920
OldSys = S#state.sys,
922
ShrinkedSys = S2#state.sys,
923
{NewSys, Status} = read_config(ShrinkedSys#sys{apps = []}, SysConfig, {ok, []}),
927
{MergedSys, Status2} = merge_config(OldSys, NewSys, Force, Status),
928
{S3, Status3} = analyse(S2#state{sys = MergedSys, old_sys = OldSys}, Status2),
942
read_config(OldSys, Filename, Status) when is_list(Filename) ->
943
case file:consult(Filename) of
944
{ok, [SysConfig | _]} ->
945
read_config(OldSys, SysConfig, Status);
947
Text = lists:flatten(io_lib:format("~p", [Content])),
948
{OldSys, reltool_utils:return_first_error(Status, "Illegal file content: " ++ Text)};
950
Text = file:format_error(Reason),
951
{OldSys, reltool_utils:return_first_error(Status, "File access: " ++ Text)}
953
read_config(OldSys, {sys, KeyVals}, Status) ->
954
{NewSys, Status2} = decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status),
955
Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps],
956
case NewSys#sys.rels of
957
[] -> Rels = reltool_utils:default_rels();
960
NewSys2 = NewSys#sys{apps = lists:sort(Apps), rels = lists:sort(Rels)},
961
case lists:keysearch(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of
965
Text = "Missing rel: " ++ NewSys2#sys.boot_rel,
966
{OldSys, reltool_utils:return_first_error(Status2, Text)}
968
read_config(OldSys, BadConfig, Status) ->
969
Text = lists:flatten(io_lib:format("~p", [BadConfig])),
970
{OldSys, reltool_utils:return_first_error(Status, "Illegal content: " ++ Text)}.
972
decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals], Status)
973
when is_atom(Name), is_list(AppKeyVals) ->
974
App = default_app(Name),
975
{App2, Status2} = decode(App, AppKeyVals, Status),
976
decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2);
977
decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals], Status)
978
when is_atom(Name), is_list(AppKeyVals) ->
979
App = default_app(Name),
980
{App2, Status2} = decode(App, AppKeyVals, Status),
981
decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2);
982
decode(Sys, [{boot_rel, RelName} | SysKeyVals], Status)
983
when is_list(RelName) ->
984
decode(Sys#sys{boot_rel = RelName}, SysKeyVals, Status);
985
decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals], Status)
986
when is_list(Name), is_list(Vsn), is_list(RelApps) ->
987
Rel = #rel{name = Name, vsn = Vsn, rel_apps = []},
988
{Rel2, Status2} = decode(Rel, RelApps, Status),
989
decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals, Status2);
990
decode(#sys{} = Sys, [{Key, Val} | KeyVals], Status) ->
993
mod_cond when Val =:= all; Val =:= app;
994
Val =:= ebin; Val =:= derived;
996
{Sys#sys{mod_cond = Val}, Status};
997
incl_cond when Val =:= include; Val =:= exclude;
999
{Sys#sys{incl_cond = Val}, Status};
1000
profile when Val =:= standalone; Val =:= development; Val =:= embedded ->
1001
{Sys#sys{profile = Val}, Status};
1002
emu_name when is_list(Val) ->
1003
{Sys#sys{emu_name = Val}, Status};
1004
debug_info when Val =:= keep; Val =:= strip ->
1005
{Sys#sys{debug_info = Val}, Status};
1006
app_file when Val =:= keep; Val =:= strip, Val =:= all ->
1007
{Sys#sys{app_file = Val}, Status};
1009
decode_dirs(Key, Val, #sys.incl_erts_dirs, Sys, Status);
1011
decode_dirs(Key, Val, #sys.excl_erts_dirs, Sys, Status);
1013
decode_dirs(Key, Val, #sys.incl_app_dirs, Sys, Status);
1015
decode_dirs(Key, Val, #sys.excl_app_dirs, Sys, Status);
1016
root_dir when is_list(Val) ->
1017
{Sys#sys{root_dir = Val}, Status};
1018
lib_dirs when is_list(Val) ->
1019
{Sys#sys{lib_dirs = Val}, Status};
1020
escripts when is_list(Val) ->
1021
{Sys#sys{escripts = Val}, Status};
1023
Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
1024
{Sys, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
1026
decode(Sys2, KeyVals, Status2);
1027
decode(#app{} = App, [{Key, Val} | KeyVals], Status) ->
1030
mod_cond when Val =:= all; Val =:= app; Val =:= ebin; Val =:= derived; Val =:= none ->
1031
{App#app{mod_cond = Val}, Status};
1032
incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
1033
{App#app{incl_cond = Val}, Status};
1034
debug_info when Val =:= keep; Val =:= strip ->
1035
{App#app{debug_info = Val}, Status};
1036
app_file when Val =:= keep; Val =:= strip, Val =:= all ->
1037
{App#app{app_file = Val}, Status};
1039
decode_dirs(Key, Val, #app.incl_app_dirs, App, Status);
1041
decode_dirs(Key, Val, #app.excl_app_dirs, App, Status);
1042
vsn when is_list(Val) ->
1043
{App#app{use_selected_vsn = true, vsn = Val}, Status};
1045
Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
1046
{App, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
1048
decode(App2, KeyVals, Status2);
1049
decode(#app{mods = Mods} = App, [{mod, Name, ModKeyVals} | AppKeyVals], Status) ->
1050
{Mod, Status2} = decode(#mod{name = Name}, ModKeyVals, Status),
1051
decode(App#app{mods = [Mod | Mods]}, AppKeyVals, Status2);
1052
decode(#mod{} = Mod, [{Key, Val} | KeyVals], Status) ->
1055
incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
1056
{Mod#mod{incl_cond = Val}, Status};
1057
debug_info when Val =:= keep; Val =:= strip ->
1058
{Mod#mod{debug_info = Val}, Status};
1060
Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
1061
{Mod, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
1063
decode(Mod2, KeyVals, Status2);
1064
decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals], Status) ->
1067
Name when is_atom(Name) ->
1068
#rel_app{name = Name, type = undefined, incl_apps = []};
1069
{Name, Type} when is_atom(Name) ->
1070
#rel_app{name = Name, type = Type, incl_apps = []};
1071
{Name, InclApps} when is_atom(Name), is_list(InclApps) ->
1072
#rel_app{name = Name, type = undefined, incl_apps = InclApps};
1073
{Name, Type, InclApps} when is_atom(Name), is_list(InclApps) ->
1074
#rel_app{name = Name, type = Type, incl_apps = InclApps};
1076
#rel_app{incl_apps = []}
1078
IsType = is_type(RA#rel_app.type),
1079
NonAtoms = [IA || IA <- RA#rel_app.incl_apps, not is_atom(IA)],
1081
IsType, NonAtoms =:= [] ->
1082
decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals, Status);
1084
Text = lists:flatten(io_lib:format("~p", [RelApp])),
1085
Status2 = reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text),
1086
decode(Rel, KeyVals, Status2)
1088
decode(Acc, [], Status) ->
1090
decode(Acc, KeyVal, Status) ->
1091
Text = lists:flatten(io_lib:format("~p", [KeyVal])),
1092
{Acc, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}.
1094
decode_dirs(Key,Val, Pos, Rec, Status) ->
1097
{setelement(Pos, Rec, Val), Status};
1098
List when is_list(List) ->
1099
{setelement(Pos, Rec, Val), Status};
1100
{add, List} when is_list(List) ->
1102
case element(Pos, Rec) of
1105
Old when is_list(Old) ->
1106
lists:usort(Old ++ List)
1108
{setelement(Pos, Rec, New), Status};
1109
{del, List} when is_list(List) ->
1111
case element(Pos, Rec) of
1114
Old when is_list(Old) ->
1117
{setelement(Pos, Rec, New), Status};
1119
Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
1120
{Rec, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
1134
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1136
refresh(#state{sys = Sys} = S, Force, Status) ->
1137
{Sys2, Status2} = merge_config(Sys, Sys#sys{apps = []}, Force, Status),
1138
{S#state{sys = Sys2}, Status2}.
1140
merge_config(OldSys, NewSys, Force, Status) ->
1141
RootDir = filename:absname(NewSys#sys.root_dir),
1142
LibDirs = [filename:absname(D) || D <- NewSys#sys.lib_dirs],
1143
Escripts = [filename:absname(E) || E <- NewSys#sys.escripts],
1144
{SourceDirs, Status2} =
1145
libs_to_dirs(RootDir, LibDirs, Status),
1146
MergedApps = merge_app_dirs(SourceDirs, NewSys#sys.apps, OldSys#sys.apps),
1147
{AllApps, Status3} =
1148
escripts_to_apps(Escripts, MergedApps, Status2),
1149
{RefreshedApps, Status4} =
1150
refresh_apps(OldSys#sys.apps, AllApps, [], Force, Status3),
1151
{PatchedApps, Status5} = patch_erts_version(RootDir, RefreshedApps, Status4),
1152
NewSys2 = NewSys#sys{root_dir = RootDir,
1154
escripts = Escripts,
1155
apps = PatchedApps},
1158
verify_config(Sys, Status) ->
1159
check_dir("erts_dirs", "bin", Sys#sys.incl_erts_dirs, Sys#sys.excl_erts_dirs, Status),
1160
check_dir("app_dirs", "ebin", Sys#sys.incl_app_dirs, Sys#sys.excl_app_dirs, Status),
1161
lists:foreach(fun(App) -> check_app(App, Sys, Status) end, Sys#sys.apps),
1162
case lists:keymember(Sys#sys.boot_rel, #rel.name, Sys#sys.rels) of
1164
lists:foreach(fun(Rel)-> check_rel(Rel, Sys, Status) end, Sys#sys.rels),
1167
Text = lists:concat([Sys#sys.boot_rel, ": release is mandatory"]),
1168
Status2 = reltool_utils:return_first_error(Status, Text),
1169
throw({error, Status2})
1172
check_dir(Label, SubDir, Incl, Excl, Status) ->
1173
case lists:member(SubDir, Incl -- Excl) of
1177
Text = lists:concat([Label, ": directory ", SubDir, " is mandatory"]),
1178
Status2 = reltool_utils:return_first_error(Status, Text),
1179
throw({error, Status2})
1182
check_app(App, Sys, Status) ->
1183
Incl = default_val(App#app.incl_app_dirs, Sys#sys.incl_app_dirs),
1184
Excl = default_val(App#app.excl_app_dirs, Sys#sys.excl_app_dirs),
1185
check_dir(App#app.name, "ebin", Incl, Excl, Status).
1187
default_val(Val, Default) ->
1189
undefined -> Default;
1193
check_rel(#rel{name = RelName, rel_apps = RelApps}, #sys{apps = Apps}, Status) ->
1196
case lists:keymember(AppName, #rel_app.name, RelApps) of
1200
Text = lists:concat([RelName, ": ", AppName, " is not included."]),
1201
Status2 = reltool_utils:return_first_error(Status, Text),
1202
throw({error, Status2})
1208
fun(#rel_app{name = AppName}) ->
1209
case lists:keysearch(AppName, #app.name, Apps) of
1210
{value, App} when App#app.is_pre_included ->
1212
{value, App} when App#app.is_included ->
1215
Text = lists:concat([RelName, ": uses application ",
1216
AppName, " that not is included."]),
1217
Status2 = reltool_utils:return_first_error(Status, Text),
1218
%% throw BUGBUG: add throw
1222
lists:foreach(CheckRelApp, RelApps).
1224
patch_erts_version(RootDir, Apps, Status) ->
1226
case lists:keysearch(AppName, #app.name, Apps) of
1228
LocalRoot = code:root_dir(),
1231
LocalRoot =:= RootDir, Vsn =:= "" ->
1232
Vsn2 = erlang:system_info(version),
1233
Erts2 = Erts#app{vsn = Vsn2, label = "erts-" ++ Vsn2},
1234
Apps2 = lists:keystore(AppName, #app.name, Apps, Erts2),
1237
{Apps, reltool_utils:add_warning(Status, "erts has no version")};
1242
Text = "erts cannnot be found in the root directory " ++ RootDir,
1243
Status2 = reltool_utils:return_first_error(Status, Text),
1247
libs_to_dirs(RootDir, LibDirs, Status) ->
1248
case file:list_dir(RootDir) of
1250
RootLibDir = filename:join([RootDir, "lib"]),
1251
SortedLibDirs = lists:sort(LibDirs),
1252
AllLibDirs = [RootLibDir | SortedLibDirs],
1253
case AllLibDirs -- lists:usort(AllLibDirs) of
1256
AppDir = filename:join([RootLibDir, Base]),
1257
case filelib:is_dir(filename:join([AppDir, "ebin"]), erl_prim_loader) of
1261
filename:join([RootDir, Base, "preloaded"])
1264
ErtsFiles = [{erts, Fun(F)} || F <- RootFiles, lists:prefix("erts", F)],
1265
app_dirs2(AllLibDirs, [ErtsFiles], Status);
1267
{[], reltool_utils:return_first_error(Status, "Duplicate library: " ++ Duplicate)}
1270
Text = file:format_error(Reason),
1271
{[], reltool_utils:return_first_error(Status, "Missing root library " ++ RootDir ++ ": " ++ Text)}
1274
app_dirs2([Lib | Libs], Acc, Status) ->
1275
case file:list_dir(Lib) of
1279
AppDir = filename:join([Lib, Base]),
1280
EbinDir = filename:join([AppDir, "ebin"]),
1281
case filelib:is_dir(EbinDir, erl_prim_loader) of
1283
{Name, _Vsn} = reltool_utils:split_app_name(Base),
1286
_ -> {true, {Name, AppDir}}
1292
Files2 = lists:zf(Filter, Files),
1293
app_dirs2(Libs, [Files2 | Acc], Status);
1295
Text = file:format_error(Reason),
1296
{[], reltool_utils:return_first_error(Status, "Illegal library " ++ Lib ++ ": " ++ Text)}
1298
app_dirs2([], Acc, Status) ->
1299
{lists:sort(lists:append(Acc)), Status}.
1301
escripts_to_apps([Escript | Escripts], Apps, Status) ->
1302
EscriptAppName = list_to_atom("*escript* " ++ filename:basename(Escript)),
1303
Ext = code:objfile_extension(),
1304
Fun = fun(FullName, _GetInfo, GetBin, {FileAcc, StatusAcc}) ->
1305
Components = filename:split(FullName),
1307
[AppLabel, "ebin", File] ->
1308
case filename:extension(File) of
1310
{AppName, DefaultVsn} = reltool_utils:split_app_name(AppLabel),
1311
AppFileName = filename:join([Escript, FullName]),
1312
{Info, StatusAcc2} =
1313
read_app_info(GetBin(), AppFileName, AppName, DefaultVsn, Status),
1314
Dir = filename:join([Escript, AppName]),
1315
{[{AppName, app, Dir, Info} | FileAcc], StatusAcc2};
1317
{AppName, _} = reltool_utils:split_app_name(AppLabel),
1318
Mod = init_mod(AppName, File, {File, GetBin()}, Ext),
1319
Dir = filename:join([Escript, AppName]),
1320
{[{AppName, mod, Dir, Mod} | FileAcc], StatusAcc};
1322
{FileAcc, StatusAcc}
1326
{ok, {ModName, _}} = beam_lib:version(Bin),
1327
ModStr = atom_to_list(ModName) ++ Ext,
1328
Mod = init_mod(EscriptAppName, ModStr, {ModStr, GetBin()}, Ext),
1329
{[{EscriptAppName, mod, Escript, Mod} | FileAcc], StatusAcc};
1331
case filename:extension(File) of
1333
Mod = init_mod(EscriptAppName, File, {File, GetBin()}, Ext),
1334
{[{EscriptAppName, mod, File, Mod} | FileAcc], StatusAcc};
1336
{FileAcc, StatusAcc}
1339
{FileAcc, StatusAcc}
1343
case escript:foldl(Fun, {[], Status}, Escript) of
1344
{ok, {Files, Status2}} ->
1345
{Apps2, Status3} = files_to_apps(Escript, lists:sort(Files), Apps, Apps, Status2),
1346
escripts_to_apps(Escripts, Apps2, Status3);
1348
Text = lists:flatten(io_lib:format("~p", [Reason])),
1349
{[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Text)}
1352
throw:Reason2 when is_list(Reason2) ->
1353
{[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Reason2)}
1355
escripts_to_apps([], Apps, Status) ->
1358
%% Assume that all files for an app are in consecutive order
1359
%% Assume the app info is before the mods
1360
files_to_apps(Escript, [{AppName, Type, Dir, ModOrInfo} | Files] = AllFiles, Acc, Apps, Status) ->
1365
Info = missing_app_info(""),
1366
{NewApp, Status2} = new_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, Status),
1367
files_to_apps(Escript, AllFiles, [NewApp | Acc], Apps, Status2);
1368
[App | Acc2] when App#app.name =:= ModOrInfo#mod.app_name ->
1369
App2 = App#app{mods = [ModOrInfo | App#app.mods]},
1370
files_to_apps(Escript, Files, [App2 | Acc2], Apps, Status);
1372
PrevApp = App#app{mods = lists:keysort(#mod.name, App#app.mods)},
1373
Info = missing_app_info(""),
1374
{NewApp, Status2} = new_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, Status),
1375
files_to_apps(Escript, Files, [NewApp, PrevApp | Acc2], Apps, Status2)
1378
{App, Status2} = new_escript_app(AppName, Dir, ModOrInfo, [], Apps, Status),
1379
files_to_apps(Escript, Files, [App | Acc], Apps, Status2)
1381
files_to_apps(_Escript, [], Acc, _Apps, Status) ->
1382
{lists:keysort(#app.name, Acc), Status}.
1384
new_escript_app(AppName, Dir, Info, Mods, Apps, Status) ->
1385
App = default_app(AppName, Dir),
1386
App2 = App#app{is_escript = true, info = Info, mods = Mods},
1387
case lists:keysearch(AppName, #app.name, Apps) of
1389
Error = lists:concat([AppName, ": Application name clash. ",
1390
"Escript ", Dir," contains application ", AppName, "."]),
1391
{App2, reltool_utils:return_first_error(Status, Error)};
1396
merge_app_dirs([{Name, Dir} | Rest], [App | Apps], OldApps)
1397
when App#app.name =:= Name ->
1398
%% Add new dir to app
1399
App2 = App#app{sorted_dirs = [Dir | App#app.sorted_dirs]},
1400
merge_app_dirs(Rest, [App2 | Apps], OldApps);
1401
merge_app_dirs([{Name, Dir} | Rest], Apps, OldApps) ->
1403
Apps2 = sort_app_dirs(Apps),
1405
case lists:keysearch(Name, #app.name, Apps) of
1407
case lists:keysearch(Name, #app.name, OldApps) of
1408
{value, OldApp} when OldApp#app.active_dir =:= Dir ->
1412
case filter_app(OldApp) of
1414
NewApp#app{active_dir = Dir, sorted_dirs = [Dir]};
1416
default_app(Name, Dir)
1420
App = default_app(Name, Dir),
1424
Apps3 = lists:keydelete(Name, #app.name, Apps2),
1425
App = OldApp#app{sorted_dirs = [Dir | OldApp#app.sorted_dirs]},
1428
merge_app_dirs(Rest, Apps4, OldApps);
1429
merge_app_dirs([], Apps, _OldApps) ->
1430
Apps2 = sort_app_dirs(Apps),
1431
lists:reverse(Apps2).
1433
sort_app_dirs([#app{sorted_dirs = Dirs} = App | Acc]) ->
1434
SortedDirs = lists:sort(fun reltool_utils:app_dir_test/2, Dirs),
1436
[ActiveDir | _] -> ok;
1437
[] -> ActiveDir = undefined
1439
[App#app{active_dir = ActiveDir, sorted_dirs = SortedDirs} | Acc];
1440
sort_app_dirs([]) ->
1443
default_app(Name, Dir) ->
1444
App = default_app(Name),
1445
App#app{active_dir = Dir,
1446
sorted_dirs = [Dir]}.
1448
default_app(Name) ->
1452
mod_cond = undefined,
1453
incl_cond = undefined,
1454
use_selected_vsn = undefined,
1455
active_dir = undefined,
1461
uses_mods = undefined,
1462
is_pre_included = undefined,
1463
is_included = undefined}.
1465
%% Assume that the application are sorted
1466
refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name =:= Old#app.name ->
1467
{Info, ActiveDir, Status2} = ensure_app_info(New, Status),
1469
case Info#app_info.vsn =:= New#app.vsn of
1470
true -> New#app.label;
1471
false -> undefined % Cause refresh
1473
{Refreshed, Status3} =
1474
refresh_app(New#app{label = OptLabel,
1475
active_dir = ActiveDir,
1476
vsn = Info#app_info.vsn,
1480
refresh_apps(OldApps, NewApps, [Refreshed | Acc], Force, Status3);
1481
refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name < Old#app.name ->
1482
%% No old app version exists. Use new as is.
1483
%% BUGBUG: Issue warning if the active_dir is not defined
1484
{New2, Status2} = refresh_app(New, Force, Status),
1485
refresh_apps([Old | OldApps], NewApps, [New2 | Acc], Force, Status2);
1486
refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name > Old#app.name ->
1487
%% No new version. Remove the old.
1489
case Old#app.name =:= ?MISSING_APP of
1493
Warning = lists:concat([Old#app.name, ": The source dirs does not contain the application anymore."]),
1494
reltool_utils:add_warning(Status, Warning)
1496
refresh_apps(OldApps, [New | NewApps], Acc, Force, Status2);
1497
refresh_apps([], [New | NewApps], Acc, Force, Status) ->
1498
%% No old app version exists. Use new as is.
1499
{New2, Status2} = refresh_app(New, Force, Status),
1500
refresh_apps([], NewApps, [New2 | Acc], Force, Status2);
1501
refresh_apps([Old | OldApps], [], Acc, Force, Status) ->
1502
%% No new version. Remove the old.
1504
case Old#app.name =:= ?MISSING_APP of
1508
Warning = lists:concat([Old#app.name, ": The source dirs ",
1509
"does not contain the application anymore."]),
1510
reltool_utils:add_warning(Status, Warning)
1512
refresh_apps(OldApps, [], Acc, Force, Status2);
1513
refresh_apps([], [], Acc, _Force, Status) ->
1514
{lists:reverse(Acc), Status}.
1516
ensure_app_info(#app{is_escript = true, active_dir = Dir, info = Info}, Status) ->
1517
{Info, Dir, Status};
1518
ensure_app_info(#app{name = Name, sorted_dirs = []}, Status) ->
1519
Error = lists:concat([Name, ": Missing application directory."]),
1520
Status2 = reltool_utils:return_first_error(Status, Error),
1521
{missing_app_info(""), undefined, Status2};
1522
ensure_app_info(#app{name = Name, vsn = Vsn, sorted_dirs = Dirs, info = undefined}, Status) ->
1524
fun(Dir, StatusAcc) ->
1525
Base = get_base(Name, Dir),
1526
Ebin = filename:join([Dir, "ebin"]),
1527
{_, DefaultVsn} = reltool_utils:split_app_name(Base),
1528
AppFile = filename:join([Ebin, atom_to_list(Name) ++ ".app"]),
1529
read_app_info(AppFile, AppFile, Name, DefaultVsn, StatusAcc)
1531
{AllInfo, Status2} = lists:mapfoldl(ReadInfo, Status, Dirs),
1532
AllVsns = [I#app_info.vsn || I <- AllInfo],
1534
case AllVsns -- lists:usort(AllVsns) of
1536
%% No redundant info
1539
Error2 = lists:concat([Name, ": Application version clash. ",
1540
"Multiple directories contains version \"", BadVsn, "\"."]),
1541
reltool_utils:return_first_error(Status2, Error2)
1543
FirstInfo = hd(AllInfo),
1544
FirstDir = hd(Dirs),
1546
Vsn =:= undefined ->
1547
{FirstInfo, FirstDir, Status3};
1548
Vsn =:= FirstInfo#app_info.vsn ->
1549
{FirstInfo, FirstDir, Status3};
1551
case find_vsn(Vsn, AllInfo, Dirs) of
1553
{Info, VsnDir, Status3};
1555
Error3 = lists:concat([Name, ": No application directory contains selected version \"", Vsn, "\"."]),
1556
Status4 = reltool_utils:return_first_error(Status3, Error3),
1557
{FirstInfo, FirstDir, Status4}
1560
ensure_app_info(#app{active_dir = Dir, info = Info}, Status) ->
1561
{Info, Dir, Status}.
1563
find_vsn(Vsn, [#app_info{vsn = Vsn} = Info | _], [Dir | _]) ->
1565
find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) ->
1566
find_vsn(Vsn, MoreInfo, MoreDirs);
1567
find_vsn(_, [], []) ->
1570
get_base(Name, Dir) ->
1573
case filename:basename(Dir) of
1575
filename:basename(filename:dirname(Dir));
1580
filename:basename(Dir)
1583
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1586
system_continue(_Parent, _Debug, S) ->
1589
system_terminate(Reason, _Parent, _Debug, _S) ->
1592
system_code_change(S,_Module,_OldVsn,_Extra) ->