~statik/ubuntu/maverick/erlang/erlang-merge-testing

« back to all changes in this revision

Viewing changes to lib/reltool/src/reltool_server.erl

  • Committer: Bazaar Package Importer
  • Author(s): Sergei Golovan
  • Date: 2009-05-01 10:14:38 UTC
  • mfrom: (3.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20090501101438-6qlr6rsdxgyzrg2z
Tags: 1:13.b-dfsg-2
* Cleaned up patches: removed unneeded patch which helped to support
  different SCTP library versions, made sure that changes for m68k
  architecture applied only when building on this architecture.
* Removed duplicated information from binary packages descriptions.
* Don't require libsctp-dev build-dependency on solaris-i386 architecture
  which allows to build Erlang on Nexenta (thanks to Tim Spriggs for
  the suggestion).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%%
 
2
%% %CopyrightBegin%
 
3
%% 
 
4
%% Copyright Ericsson AB 2009. All Rights Reserved.
 
5
%% 
 
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/.
 
11
%% 
 
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
 
15
%% under the License.
 
16
%% 
 
17
%% %CopyrightEnd%
 
18
 
 
19
-module(reltool_server).
 
20
 
 
21
%% Public
 
22
-export([
 
23
         start/0, start/1,
 
24
         get_config/1, load_config/2, save_config/2,
 
25
         get_rel/2, get_script/2,
 
26
         reset_config/1, undo_config/1,
 
27
         get_mod/2,
 
28
         get_app/2, set_app/2,
 
29
         get_apps/2, set_apps/2,
 
30
         get_sys/1, set_sys/2,
 
31
         gen_rel_files/2, gen_target/2
 
32
        ]).
 
33
 
 
34
%% Internal
 
35
-export([init/1, loop/1]).
 
36
 
 
37
%% sys callback functions
 
38
-export([
 
39
         system_continue/3,
 
40
         system_terminate/4,
 
41
         system_code_change/4
 
42
        ]).
 
43
 
 
44
-include("reltool.hrl").
 
45
 
 
46
-record(state, 
 
47
        {options,
 
48
         parent_pid,
 
49
         common,
 
50
         sys,
 
51
         old_sys}).
 
52
 
 
53
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
54
%% Client
 
55
 
 
56
start() ->
 
57
    start([]).
 
58
 
 
59
start(Options) ->
 
60
    proc_lib:start_link(?MODULE, init, [[{parent, self()} | Options]], infinity, []).
 
61
 
 
62
get_config(Pid) ->
 
63
    call(Pid, get_config).
 
64
 
 
65
load_config(Pid, FilenameOrConfig) ->
 
66
    call(Pid, {load_config, FilenameOrConfig}).
 
67
 
 
68
save_config(Pid, Filename) ->
 
69
    call(Pid, {save_config, Filename}).
 
70
 
 
71
reset_config(Pid) ->
 
72
    call(Pid, reset_config).
 
73
 
 
74
undo_config(Pid) ->
 
75
    call(Pid, undo_config).
 
76
 
 
77
get_rel(Pid, RelName) ->
 
78
    call(Pid, {get_rel, RelName}).
 
79
 
 
80
get_script(Pid, RelName) ->
 
81
    call(Pid, {get_script, RelName}).
 
82
 
 
83
get_mod(Pid, ModName) ->
 
84
    call(Pid, {get_mod, ModName}).
 
85
 
 
86
get_app(Pid, AppName) ->
 
87
    call(Pid, {get_app, AppName}).
 
88
 
 
89
set_app(Pid, App) ->
 
90
    call(Pid, {set_app, App}).
 
91
 
 
92
get_apps(Pid, Kind) ->
 
93
    call(Pid, {get_apps, Kind}).
 
94
 
 
95
set_apps(Pid, Apps) ->
 
96
    call(Pid, {set_apps, Apps}).
 
97
 
 
98
get_sys(Pid) ->
 
99
    call(Pid, get_sys).
 
100
 
 
101
set_sys(Pid, Sys) ->
 
102
    call(Pid, {set_sys, Sys}).
 
103
 
 
104
gen_rel_files(Pid, Dir) ->
 
105
    call(Pid, {gen_rel_files, Dir}).
 
106
 
 
107
gen_target(Pid, Dir) ->
 
108
    call(Pid, {gen_target, Dir}).
 
109
 
 
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},
 
116
    receive
 
117
        {Ref, Reply} ->
 
118
            %% io:format("Rec~p: ~p\n", [self(), Reply]),
 
119
            erlang:demonitor(Ref, [flush]),
 
120
            Reply;
 
121
        {'DOWN', Ref, _, _, Reason} ->
 
122
            {error, Reason}
 
123
    end.
 
124
 
 
125
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
126
%% Server
 
127
 
 
128
reply(Pid, Ref, Msg) ->
 
129
    Pid ! {Ref, Msg}.
 
130
 
 
131
init(Options) ->
 
132
    try
 
133
        do_init(Options)
 
134
    catch
 
135
        error:Reason ->
 
136
            exit({Reason, erlang:get_stacktrace()})
 
137
    end.
 
138
 
 
139
do_init(Options) ->
 
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),
 
146
            case Status3 of
 
147
                {ok, _} ->
 
148
                    loop(S3);
 
149
                {error, Reason} ->
 
150
                    exit(Reason)
 
151
            end
 
152
    end.
 
153
 
 
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,
 
162
               emu_name = "beam",
 
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(),
 
170
               escripts = [],
 
171
               apps = [],
 
172
               boot_rel = ?DEFAULT_REL_NAME,
 
173
               rels = reltool_utils:default_rels()},
 
174
    C2 = #common{sys_debug = [],
 
175
                 wx_debug = 0,
 
176
                 trap_exit = true,
 
177
                 app_tab = AppTab,
 
178
                 mod_tab = ModTab,
 
179
                 mod_used_by_tab = ModUsesTab},
 
180
    S = #state{options = Opts},
 
181
    parse_options(Opts, S, C2, Sys, {ok, []}).
 
182
 
 
183
parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) ->
 
184
    case Key of
 
185
        parent ->
 
186
            parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys, Status);
 
187
        sys_debug ->
 
188
            parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys, Status);
 
189
        wx_debug ->
 
190
            parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys, Status);
 
191
        trap_exit ->
 
192
            parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys, Status);
 
193
        config ->
 
194
            {Sys2, Status2} = read_config(Sys, Val, Status),
 
195
            parse_options(KeyVals, S, C, Sys2, Status2);
 
196
        incl_cond ->
 
197
            parse_options(KeyVals, S, C, Sys#sys{incl_cond = Val}, Status);
 
198
        mod_cond ->
 
199
            parse_options(KeyVals, S, C, Sys#sys{mod_cond = Val}, Status);
 
200
        root_dir ->
 
201
            parse_options(KeyVals, S, C, Sys#sys{root_dir = Val}, Status);
 
202
        lib_dirs ->
 
203
            parse_options(KeyVals, S, C, Sys#sys{lib_dirs = Val}, Status);
 
204
        escripts ->
 
205
            parse_options(KeyVals, S, C, Sys#sys{escripts = Val}, Status);
 
206
        _ ->
 
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)
 
210
    end;
 
211
parse_options([], S, C, Sys, Status) ->
 
212
    {S#state{common = C, sys = Sys}, Status}.
 
213
 
 
214
loop(#state{common = C, sys = Sys} = S) ->
 
215
    receive
 
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),
 
221
            ?MODULE:loop(S);
 
222
        {ReplyTo, Ref, {load_config, SysConfig}} ->
 
223
            {S2, Reply} = do_load_config(S, SysConfig),
 
224
            reply(ReplyTo, Ref, Reply),
 
225
            ?MODULE:loop(S2);
 
226
        {ReplyTo, Ref, {save_config, Filename}} ->
 
227
            Reply = do_save_config(S, Filename),
 
228
            reply(ReplyTo, Ref, Reply),
 
229
            ?MODULE:loop(S);
 
230
        {ReplyTo, Ref, reset_config} ->
 
231
            {S2, Status} = parse_options(S#state.options),
 
232
            S3 = shrink_sys(S2),
 
233
            {S4, Status2} = refresh(S3, true, Status),
 
234
            {S5, Status3} = analyse(S4#state{old_sys = S4#state.sys}, Status2),
 
235
            S6 = 
 
236
                case Status3 of
 
237
                    {ok, _} ->
 
238
                        S5;
 
239
                    {error, _} ->
 
240
                        S
 
241
                end,
 
242
            reply(ReplyTo, Ref, Status2),
 
243
            ?MODULE:loop(S6);
 
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}} ->
 
248
            Sys = S#state.sys,
 
249
            Reply = 
 
250
                case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
 
251
                    {value, Rel} ->
 
252
                        {ok, reltool_target:gen_rel(Rel, Sys)};
 
253
                    false ->
 
254
                        {error, "No such release"}
 
255
                end,
 
256
            reply(ReplyTo, Ref, Reply),
 
257
            ?MODULE:loop(S);
 
258
        {ReplyTo, Ref, {get_script, RelName}} ->
 
259
            Sys = S#state.sys,
 
260
            Reply = 
 
261
                case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
 
262
                    {value, Rel} ->
 
263
                        PathFlag = true,
 
264
                        Variables = [],
 
265
                        reltool_target:gen_script(Rel, Sys, PathFlag, Variables);
 
266
                    false ->
 
267
                        {error, "No such release"}
 
268
                end,
 
269
            reply(ReplyTo, Ref, Reply),
 
270
            ?MODULE:loop(S);
 
271
        {ReplyTo, Ref, {get_mod, ModName}} ->
 
272
            Reply =
 
273
                case ets:lookup(C#common.mod_tab, ModName) of
 
274
                    [M] ->
 
275
                        {ok, M};
 
276
                    [] ->
 
277
                        {ok, missing_mod(ModName, ?MISSING_APP)}
 
278
                end,
 
279
            reply(ReplyTo, Ref, Reply),
 
280
            ?MODULE:loop(S);
 
281
        {ReplyTo, Ref, {get_app, AppName}} when is_atom(AppName) ->
 
282
            Reply = 
 
283
                case lists:keysearch(AppName, #app.name, Sys#sys.apps) of
 
284
                    {value, App} ->
 
285
                        {ok, App};
 
286
                    false ->
 
287
                        {error, enoent}
 
288
                end,
 
289
            reply(ReplyTo, Ref, Reply),
 
290
            ?MODULE:loop(S);
 
291
        {ReplyTo, Ref, {set_app, App}} ->
 
292
            {S2, Status} = do_set_app(S, App, {ok, []}),
 
293
            {S3, Status2} = analyse(S2, Status),
 
294
            case Status2 of
 
295
                {ok, Warnings} ->
 
296
                    App2 = ?KEYSEARCH(App#app.name,
 
297
                                      #app.name,
 
298
                                      (S3#state.sys)#sys.apps),
 
299
                    reply(ReplyTo, Ref, {ok, App2, Warnings}),
 
300
                    ?MODULE:loop(S3);
 
301
                {error, Reason} ->
 
302
                    reply(ReplyTo, Ref, {error, Reason}),
 
303
                    ?MODULE:loop(S)
 
304
            end;
 
305
        {ReplyTo, Ref, {get_apps, Kind}} ->
 
306
            AppNames =
 
307
                case Kind of
 
308
                    whitelist -> 
 
309
                        [A ||
 
310
                            A <- Sys#sys.apps,
 
311
                            A#app.is_pre_included =:= true];
 
312
                    blacklist -> 
 
313
                        [A ||
 
314
                            A <- Sys#sys.apps,
 
315
                            A#app.is_pre_included =:= false];
 
316
                    source -> 
 
317
                        [A ||
 
318
                            A <- Sys#sys.apps,
 
319
                            A#app.is_included =/= true,
 
320
                            A#app.is_pre_included =/= false];
 
321
                    derived ->
 
322
                        [A ||
 
323
                            A <- Sys#sys.apps,
 
324
                            A#app.is_included =:= true,
 
325
                            A#app.is_pre_included =/= true]
 
326
                end,
 
327
            reply(ReplyTo, Ref, {ok, AppNames}),
 
328
            ?MODULE:loop(S);
 
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),
 
333
            ?MODULE:loop(S3);
 
334
        {ReplyTo, Ref, get_sys} ->
 
335
            reply(ReplyTo, Ref, {ok, Sys#sys{apps = undefined}}),
 
336
            ?MODULE:loop(S);
 
337
        {ReplyTo, Ref, {set_sys, Sys2}} ->
 
338
            S2 = S#state{sys = Sys2#sys{apps = Sys#sys.apps}},
 
339
            Force = 
 
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),
 
345
            S6 = 
 
346
                case Status2 of
 
347
                    {ok, _} ->
 
348
                        S4;
 
349
                    {error, _} ->
 
350
                        S
 
351
                end,
 
352
            reply(ReplyTo, Ref, Status),
 
353
            ?MODULE:loop(S6);
 
354
        {ReplyTo, Ref, {gen_rel_files, Dir}} ->
 
355
            Status = 
 
356
                case reltool_target:gen_rel_files(S#state.sys, Dir) of
 
357
                    ok ->
 
358
                        {ok, []};
 
359
                    {error, Reason} ->
 
360
                        {error, Reason}
 
361
                end,
 
362
            reply(ReplyTo, Ref, Status),
 
363
            ?MODULE:loop(S);
 
364
        {ReplyTo, Ref, {gen_target, Dir}} ->
 
365
            Reply = reltool_target:gen_target(S#state.sys, Dir),
 
366
            reply(ReplyTo, Ref, Reply),
 
367
            ?MODULE:loop(S);
 
368
        {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
 
369
            exit(Reason);
 
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}}),
 
374
            ?MODULE:loop(S);
 
375
        Msg ->
 
376
            error_logger:format("~p~p got unexpected message:\n\t~p\n",
 
377
                                [?MODULE, self(), Msg]),
 
378
            ?MODULE:loop(S)
 
379
    end.
 
380
 
 
381
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
382
 
 
383
do_set_app(#state{sys = Sys} = S, App, Status) ->
 
384
    AppName = App#app.name,
 
385
    {App2, Status2} = refresh_app(App, false, Status),
 
386
    Apps = Sys#sys.apps,
 
387
    Apps2 = lists:keystore(AppName, #app.name, Apps, App2),
 
388
    Sys2 = Sys#sys{apps = Apps2},
 
389
    {S#state{sys = Sys2}, Status2}.
 
390
 
 
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),
 
398
 
 
399
    Apps2 = app_init_is_included(C, Sys, Apps, []),
 
400
    Apps3 = 
 
401
        case app_propagate_is_included(C, Sys, Apps2, []) of
 
402
            [] ->
 
403
                Apps2;
 
404
            MissingMods ->
 
405
                %% io:format("Missing mods: ~p\n", [MissingMods]),
 
406
                MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT,
 
407
                                             info = missing_app_info(""),
 
408
                                             mods = MissingMods, 
 
409
                                             status = missing,
 
410
                                             uses_mods = []},
 
411
                [MissingApp2 | Apps2]
 
412
        end,
 
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},
 
417
    try
 
418
        Status2 = verify_config(Sys2, Status),
 
419
        {S#state{sys = Sys2}, Status2}
 
420
    catch
 
421
        throw:{error, Status3} ->
 
422
            {S, Status3}
 
423
    end.
 
424
 
 
425
app_init_is_included(C, Sys, [#app{mods = Mods} = A | Apps], Acc) ->
 
426
    AppCond = 
 
427
        case A#app.incl_cond of
 
428
            undefined -> Sys#sys.incl_cond;
 
429
            _         -> A#app.incl_cond
 
430
        end,
 
431
    ModCond =
 
432
        case A#app.mod_cond of
 
433
            undefined -> Sys#sys.mod_cond;
 
434
            _         -> A#app.mod_cond
 
435
        end,
 
436
    IsIncl =
 
437
        case AppCond of
 
438
            include -> true;
 
439
            exclude -> false;
 
440
            derived -> undefined
 
441
        end,
 
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) ->
 
447
    lists:reverse(Acc).
 
448
 
 
449
mod_init_is_included(C, [M | Mods], ModCond, AppCond) ->
 
450
    %% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]),
 
451
    IsIncl =
 
452
        case AppCond of
 
453
            include ->
 
454
                case M#mod.incl_cond of
 
455
                    include ->
 
456
                        true;
 
457
                    exclude ->
 
458
                        false;
 
459
                    undefined ->
 
460
                        %% print(M#mod.name, hipe, "mod_cond -> ~p\n", [ModCond]),
 
461
                        case ModCond of
 
462
                            all     -> true;
 
463
                            app     -> false_to_undefined(M#mod.is_app_mod);
 
464
                            ebin    -> false_to_undefined(M#mod.is_ebin_mod);
 
465
                            derived -> undefined;
 
466
                            none    -> false
 
467
                        end
 
468
                end;
 
469
            exclude ->
 
470
                false;
 
471
            derived ->
 
472
                case M#mod.incl_cond of
 
473
                    include ->
 
474
                        true;
 
475
                    exclude ->
 
476
                        false;
 
477
                    undefined ->
 
478
                        undefined
 
479
                end
 
480
        end,
 
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) ->
 
486
    ok.
 
487
 
 
488
false_to_undefined(Bool) ->
 
489
    case Bool of
 
490
        false -> undefined;
 
491
        _     -> Bool
 
492
    end.
 
493
             
 
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) ->
 
498
    Acc.
 
499
 
 
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]),
 
504
    Acc2 = 
 
505
        case M2#mod.is_included of
 
506
            true ->
 
507
                %% Propagate include mark
 
508
                mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc);
 
509
            false ->
 
510
                Acc;
 
511
            undefined ->
 
512
                Acc
 
513
        end,
 
514
    mod_propagate_is_included(C, Sys, A, Mods, Acc2);
 
515
mod_propagate_is_included(_C, _Sys, _A, [], Acc) ->
 
516
    Acc.
 
517
 
 
518
mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) ->
 
519
    Acc3 = 
 
520
        case ets:lookup(C#common.mod_tab, ModName) of
 
521
            [M] -> 
 
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
 
525
                    true ->
 
526
                        %% Already marked
 
527
                        Acc;
 
528
                    false ->
 
529
                        %% Already marked
 
530
                        Acc;
 
531
                    undefined ->
 
532
                        %% Mark and propagate
 
533
                        M2 = 
 
534
                            case M#mod.incl_cond of
 
535
                                include ->
 
536
                                    M#mod{is_pre_included = true, is_included = true};
 
537
                                exclude ->
 
538
                                    M#mod{is_pre_included = true, is_included = true};
 
539
                                undefined ->
 
540
                                    M#mod{is_included = true}
 
541
                            end,
 
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),
 
545
                        Acc2 = 
 
546
                            case A#app.is_included of
 
547
                                true ->
 
548
                                    Acc;
 
549
                                false ->
 
550
                                    Acc;
 
551
                                undefined ->
 
552
                                    ModCond =
 
553
                                        case A#app.mod_cond of
 
554
                                            undefined -> Sys#sys.mod_cond;
 
555
                                            _         -> A#app.mod_cond
 
556
                                        end,
 
557
                                    Filter = 
 
558
                                        fun(M3) ->
 
559
                                                case ModCond of
 
560
                                                    all     -> true;
 
561
                                                    app     -> M3#mod.is_app_mod;
 
562
                                                    ebin    -> M3#mod.is_ebin_mod;
 
563
                                                    derived -> false;
 
564
                                                    none    -> false
 
565
                                                end
 
566
                                        end,
 
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)
 
573
                            end,
 
574
                        mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc2)
 
575
                end;
 
576
            [] ->
 
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}),
 
581
                [M2 | Acc]
 
582
        end,
 
583
    mod_mark_is_included(C, Sys, UsedByName, ModNames, Acc3);
 
584
mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) ->
 
585
    Acc.
 
586
 
 
587
app_propagate_is_used_by(C, [#app{mods = Mods, name = Name} | Apps]) ->
 
588
    case Name =:= ?MISSING_APP of
 
589
        true -> ok;
 
590
        false -> ok
 
591
    end,
 
592
    mod_propagate_is_used_by(C, Mods),
 
593
    app_propagate_is_used_by(C, Apps);
 
594
app_propagate_is_used_by(_C, []) ->
 
595
    ok.
 
596
 
 
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
 
600
        true ->
 
601
            [ets:insert(C#common.mod_used_by_tab, {UsedModName, ModName}) ||
 
602
                UsedModName <- M#mod.uses_mods];
 
603
        false ->
 
604
            ignore;
 
605
        undefined ->
 
606
            ignore
 
607
    end,
 
608
     mod_propagate_is_used_by(C, Mods);
 
609
mod_propagate_is_used_by(_C, []) ->
 
610
    ok.
 
611
 
 
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]]),
 
615
    Status = 
 
616
        case lists:keysearch(missing, #mod.status, Mods2) of
 
617
            {value, _} -> missing;
 
618
            false      -> ok
 
619
        end,
 
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),
 
628
    
 
629
    A2 = A#app{mods = Mods2,
 
630
               status = Status,
 
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) ->
 
638
    lists:reverse(Acc).
 
639
 
 
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]),
 
644
    {IsIncl2, M3} = 
 
645
        case M2#mod.is_included of
 
646
            true ->
 
647
                UsedByMods = [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab, ModName)],
 
648
                {true, M2#mod{status = Status, used_by_mods = UsedByMods}};
 
649
            _    -> 
 
650
                {IsIncl, M2#mod{status = Status, used_by_mods = []}}
 
651
        end,
 
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}.
 
656
 
 
657
get_status(M) ->
 
658
    if
 
659
        M#mod.exists =:= false, M#mod.is_included =/= false ->
 
660
            missing;
 
661
        true ->
 
662
            ok
 
663
    end.
 
664
 
 
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}}.
 
668
 
 
669
filter_app(A) -> 
 
670
    Mods = [M#mod{is_app_mod = undefined,
 
671
                  is_ebin_mod = undefined,
 
672
                  uses_mods = undefined,
 
673
                  exists = false,
 
674
                  is_pre_included = undefined,
 
675
                  is_included = undefined} ||
 
676
               M <- A#app.mods, 
 
677
               M#mod.incl_cond =/= undefined],
 
678
    if
 
679
        Mods =:= [],
 
680
        A#app.mod_cond =:= undefined,
 
681
        A#app.incl_cond =:= undefined,
 
682
        A#app.use_selected_vsn =:= undefined ->
 
683
            false;
 
684
        true ->
 
685
            {Dir, Dirs} = 
 
686
                case A#app.use_selected_vsn of
 
687
                    true -> {A#app.active_dir, [A#app.active_dir]};
 
688
                    false -> {shrinked, []};
 
689
                    undefined -> {shrinked, []}
 
690
                end,
 
691
            OptVsn =
 
692
                case A#app.use_selected_vsn of
 
693
                    undefined -> undefined;
 
694
                    false -> undefined;
 
695
                    true -> A#app.vsn
 
696
                end,
 
697
            {true, A#app{active_dir = Dir,
 
698
                         sorted_dirs = Dirs,
 
699
                         vsn = OptVsn,
 
700
                         label = undefined,
 
701
                         info = undefined,
 
702
                         mods = Mods,
 
703
                         uses_mods = undefined,
 
704
                         is_included = undefined}}
 
705
    end.
 
706
 
 
707
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
708
 
 
709
refresh_app(#app{name = AppName,
 
710
                 is_escript = IsEscript,
 
711
                 active_dir = ActiveDir,
 
712
                 label = OptLabel,
 
713
                 mods = Mods} = App,
 
714
            Force,
 
715
            Status) ->
 
716
    if
 
717
        Force; OptLabel =:= undefined ->
 
718
            {AppInfo, EbinMods, Status3} = 
 
719
                case IsEscript of
 
720
                    false ->
 
721
                        
 
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};
 
729
                    true ->
 
730
                        {App#app.info, Mods, Status}
 
731
                end,
 
732
            
 
733
            %% Add non-existing modules 
 
734
            AppModNames = AppInfo#app_info.modules,
 
735
            MissingMods = add_missing_mods(AppName, EbinMods, AppModNames),
 
736
            
 
737
            %% Add optional user config for each module
 
738
            Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods),
 
739
            
 
740
            %% Set app flag for each module in app file
 
741
            Mods3 = set_mod_flags(Mods2, AppModNames),
 
742
            AppVsn = AppInfo#app_info.vsn,
 
743
            AppLabel =
 
744
                case AppVsn of
 
745
                    "" -> atom_to_list(AppName);
 
746
                    _  -> atom_to_list(AppName) ++ "-" ++ AppVsn
 
747
                end,
 
748
            App2 = App#app{vsn = AppVsn,
 
749
                           label = AppLabel, 
 
750
                           info = AppInfo,
 
751
                           mods = lists:keysort(#mod.name, Mods3)},
 
752
            {App2, Status3};
 
753
        true ->
 
754
            {App, Status}
 
755
    end.
 
756
 
 
757
missing_app_info(Vsn) ->
 
758
    #app_info{vsn = Vsn}.
 
759
 
 
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);
 
767
        {ok, _BadApp} ->
 
768
            Text = lists:concat([AppName, ": Illegal contents in app file ", AppFile]),
 
769
            {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text)};
 
770
        {error, Text} ->
 
771
            Text2 = lists:concat([AppName, ": Cannot parse app file ", AppFile, " (", Text, ")."]),
 
772
            {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text2)}
 
773
    end.
 
774
 
 
775
parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) ->
 
776
    case Key of
 
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])))
 
790
    end;
 
791
parse_app_info(_, [], AI, Status) ->
 
792
    {AI, Status}.
 
793
 
 
794
read_ebin_mods(Ebin, AppName) ->
 
795
    case erl_prim_loader:list_dir(Ebin) of
 
796
        {ok, Files} ->
 
797
            Ext = code:objfile_extension(),
 
798
            InitMod = fun(F) ->
 
799
                              File = filename:join([Ebin, F]),
 
800
                              init_mod(AppName, File, File, Ext) 
 
801
                      end,
 
802
            Files2 = [F || F <- Files, filename:extension(F) =:= Ext],
 
803
            pmap(InitMod, Files2);
 
804
        error ->
 
805
            []
 
806
    end.
 
807
 
 
808
pmap(Fun, List) ->
 
809
    lists:map(Fun, List).
 
810
    %% N = erlang:system_info(schedulers) * 2,
 
811
    %% pmap(Fun, List, 0, N, 0, [], []).
 
812
 
 
813
%% -record(pmap_res, {count, ref, res}).
 
814
%% -record(pmap_wait, {count, ref, pid}).
 
815
%% 
 
816
%% pmap(Fun, [H | T], N, Max, Count, WaitFor, Results) when N < Max ->
 
817
%%     Ref = make_ref(),
 
818
%%     Parent = self(),
 
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) ->
 
827
%%     receive
 
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} ->
 
832
%%          exit(Reason)
 
833
%%     end.
 
834
 
 
835
init_mod(AppName, File, FileOrBin, Ext) ->
 
836
    UsesMods = xref_mod(FileOrBin),
 
837
    Base = filename:basename(File, Ext),
 
838
    ModName = list_to_atom(Base),
 
839
    #mod{name = ModName,
 
840
         app_name = AppName,
 
841
         incl_cond = undefined,
 
842
         is_ebin_mod = true,
 
843
         uses_mods = UsesMods,
 
844
         exists = true}.
 
845
 
 
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),
 
854
    Res;
 
855
xref_mod(File) when is_list(File) ->
 
856
    {ok, Pid} = xref:start([{xref_mode, modules}]),
 
857
    link(Pid),
 
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", [])]),
 
865
    unlink(Pid),
 
866
    xref:stop(Pid),
 
867
    UnknownMods.
 
868
 
 
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].
 
873
 
 
874
missing_mod(ModName, AppName) ->
 
875
    %% io:format("Missing: ~p -> ~p\n", [AppName, ModName]),
 
876
    #mod{name = ModName,
 
877
         app_name = AppName,
 
878
         incl_cond = undefined,
 
879
         is_ebin_mod = false,
 
880
         exists = false,
 
881
         status = missing,
 
882
         uses_mods = []}.
 
883
 
 
884
add_mod_config(Mods, ModConfigs) ->
 
885
    AddConfig =
 
886
        fun(Config, Acc) ->
 
887
                case lists:keysearch(Config#mod.name, #mod.name, Mods) of
 
888
                    {value, M} ->
 
889
                        M2 = M#mod{incl_cond = Config#mod.incl_cond},
 
890
                        lists:keystore(Config#mod.name, #mod.name, Acc, M2);
 
891
                    false ->
 
892
                        Config2 = Config#mod{uses_mods = [], exists = false},
 
893
                        [Config2 | Acc]
 
894
                end
 
895
        end,
 
896
    lists:foldl(AddConfig, Mods, ModConfigs).
 
897
 
 
898
set_mod_flags(Mods, AppModNames) ->
 
899
    SetFlags =
 
900
        fun(#mod{name = N} = M) ->
 
901
                M#mod{is_app_mod = lists:member(N, AppModNames)}
 
902
        end,
 
903
    lists:map(SetFlags, Mods).
 
904
 
 
905
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
906
 
 
907
do_get_config(S) ->
 
908
    S2 = shrink_sys(S),
 
909
    {ok, reltool_target:gen_config(S2#state.sys)}.
 
910
 
 
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).
 
916
 
 
917
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
918
 
 
919
do_load_config(S, SysConfig) ->
 
920
    OldSys = S#state.sys,
 
921
    S2 = shrink_sys(S),
 
922
    ShrinkedSys = S2#state.sys,
 
923
    {NewSys, Status} = read_config(ShrinkedSys#sys{apps = []}, SysConfig, {ok, []}),
 
924
    case Status of
 
925
        {ok, _Warnings} ->
 
926
            Force = false,
 
927
            {MergedSys, Status2} = merge_config(OldSys, NewSys, Force, Status),
 
928
            {S3, Status3} = analyse(S2#state{sys = MergedSys, old_sys = OldSys}, Status2),
 
929
            S4 = 
 
930
                case Status3 of
 
931
                    {ok, _} ->
 
932
                        S3;
 
933
                    {error, _} ->
 
934
                        S
 
935
                end,
 
936
            {S4, Status3};
 
937
        {error, _} ->
 
938
            %% Keep old state
 
939
            {S, Status}
 
940
    end.
 
941
 
 
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);
 
946
        {ok, Content} ->
 
947
            Text = lists:flatten(io_lib:format("~p", [Content])),
 
948
            {OldSys, reltool_utils:return_first_error(Status, "Illegal file content: " ++ Text)};
 
949
        {error, Reason} ->
 
950
            Text = file:format_error(Reason),
 
951
            {OldSys, reltool_utils:return_first_error(Status, "File access: " ++ Text)}
 
952
    end;
 
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();
 
958
        Rels -> ok
 
959
    end,
 
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
 
962
        {value, _} ->
 
963
            {NewSys2, Status2};
 
964
        false ->
 
965
            Text = "Missing rel: " ++ NewSys2#sys.boot_rel,
 
966
            {OldSys, reltool_utils:return_first_error(Status2, Text)}
 
967
    end;
 
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)}.
 
971
 
 
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) ->
 
991
    {Sys2, Status2} = 
 
992
        case Key of
 
993
            mod_cond when Val =:= all; Val =:= app;
 
994
                          Val =:= ebin; Val =:= derived;
 
995
                          Val =:= none -> 
 
996
                {Sys#sys{mod_cond = Val}, Status};
 
997
            incl_cond when Val =:= include; Val =:= exclude;
 
998
                           Val =:= derived -> 
 
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};
 
1008
            incl_erts_dirs ->
 
1009
                decode_dirs(Key, Val, #sys.incl_erts_dirs, Sys, Status);
 
1010
            excl_erts_dirs ->
 
1011
                decode_dirs(Key, Val, #sys.excl_erts_dirs, Sys, Status);
 
1012
            incl_app_dirs ->
 
1013
                decode_dirs(Key, Val, #sys.incl_app_dirs, Sys, Status);
 
1014
            excl_app_dirs ->
 
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};
 
1022
            _ ->
 
1023
                Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
 
1024
                {Sys, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
 
1025
        end,
 
1026
    decode(Sys2, KeyVals, Status2);
 
1027
decode(#app{} = App, [{Key, Val} | KeyVals], Status) ->
 
1028
    {App2, Status2} = 
 
1029
        case Key of
 
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};
 
1038
            incl_app_dirs ->
 
1039
                decode_dirs(Key, Val, #app.incl_app_dirs, App, Status);
 
1040
            excl_app_dirs ->
 
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};
 
1044
            _ ->
 
1045
                Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
 
1046
                {App, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
 
1047
        end,
 
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) ->
 
1053
    {Mod2, Status2} = 
 
1054
        case Key of
 
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};
 
1059
            _ ->
 
1060
                Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
 
1061
                {Mod, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
 
1062
        end,
 
1063
    decode(Mod2, KeyVals, Status2);
 
1064
decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals], Status) ->
 
1065
    RA =
 
1066
        case RelApp of
 
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};
 
1075
            _ ->
 
1076
                #rel_app{incl_apps = []}
 
1077
        end,
 
1078
    IsType = is_type(RA#rel_app.type),
 
1079
    NonAtoms = [IA || IA <- RA#rel_app.incl_apps, not is_atom(IA)],
 
1080
    if
 
1081
        IsType, NonAtoms =:= [] ->
 
1082
            decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals, Status);
 
1083
        true ->
 
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)
 
1087
    end;
 
1088
decode(Acc, [], Status) ->
 
1089
    {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)}.
 
1093
 
 
1094
decode_dirs(Key,Val, Pos, Rec, Status) ->
 
1095
    case Val of
 
1096
        all ->
 
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) ->       
 
1101
            New =
 
1102
                case element(Pos, Rec) of
 
1103
                    all ->
 
1104
                        all;
 
1105
                    Old when is_list(Old) ->
 
1106
                        lists:usort(Old ++ List)
 
1107
                end,
 
1108
            {setelement(Pos, Rec, New), Status};
 
1109
        {del, List} when is_list(List) ->
 
1110
            New =
 
1111
                case element(Pos, Rec) of
 
1112
                    all ->
 
1113
                        all;
 
1114
                    Old when is_list(Old) ->
 
1115
                        Old -- List
 
1116
                end,
 
1117
            {setelement(Pos, Rec, New), Status};
 
1118
        _ ->
 
1119
            Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
 
1120
            {Rec, reltool_utils:return_first_error(Status, "Illegal parameter: " ++ Text)}
 
1121
    end.
 
1122
 
 
1123
is_type(Type) ->
 
1124
    case Type of
 
1125
        undefined -> true;
 
1126
        permanent -> true;
 
1127
        transient -> true;
 
1128
        temporary -> true;
 
1129
        load      -> true;
 
1130
        none      -> true;
 
1131
        _         -> false
 
1132
    end.
 
1133
            
 
1134
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
1135
 
 
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}.
 
1139
 
 
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,
 
1153
                         lib_dirs = LibDirs,
 
1154
                         escripts = Escripts,
 
1155
                         apps = PatchedApps},
 
1156
    {NewSys2, Status5}.
 
1157
 
 
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
 
1163
        true -> 
 
1164
            lists:foreach(fun(Rel)-> check_rel(Rel, Sys, Status) end, Sys#sys.rels),
 
1165
            Status;
 
1166
        false ->
 
1167
            Text = lists:concat([Sys#sys.boot_rel, ": release is mandatory"]),
 
1168
            Status2 = reltool_utils:return_first_error(Status, Text),
 
1169
            throw({error, Status2})
 
1170
    end.
 
1171
 
 
1172
check_dir(Label, SubDir, Incl, Excl, Status) ->
 
1173
    case lists:member(SubDir, Incl -- Excl) of
 
1174
        true ->
 
1175
            Status;
 
1176
        false ->
 
1177
            Text = lists:concat([Label, ": directory ", SubDir, " is mandatory"]),
 
1178
            Status2 = reltool_utils:return_first_error(Status, Text),
 
1179
            throw({error, Status2})
 
1180
    end.
 
1181
 
 
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).
 
1186
 
 
1187
default_val(Val, Default) ->
 
1188
    case Val of
 
1189
        undefined -> Default;
 
1190
        _         -> Val
 
1191
    end.
 
1192
 
 
1193
check_rel(#rel{name = RelName, rel_apps = RelApps}, #sys{apps = Apps}, Status) ->
 
1194
    EnsureApp =
 
1195
        fun(AppName) ->
 
1196
                case lists:keymember(AppName, #rel_app.name, RelApps) of
 
1197
                    true ->
 
1198
                        ok;
 
1199
                    false ->
 
1200
                        Text = lists:concat([RelName, ": ", AppName, " is not included."]),
 
1201
                        Status2 = reltool_utils:return_first_error(Status, Text),
 
1202
                        throw({error, Status2})
 
1203
                end
 
1204
        end,
 
1205
    EnsureApp(kernel),
 
1206
    EnsureApp(stdlib),
 
1207
    CheckRelApp =
 
1208
        fun(#rel_app{name = AppName}) ->
 
1209
                case lists:keysearch(AppName, #app.name, Apps) of
 
1210
                    {value, App} when App#app.is_pre_included ->
 
1211
                        ok;
 
1212
                    {value, App} when App#app.is_included ->
 
1213
                        ok;
 
1214
                    _ ->
 
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
 
1219
                        ({error, Status2})
 
1220
                end
 
1221
        end,
 
1222
    lists:foreach(CheckRelApp, RelApps).
 
1223
 
 
1224
patch_erts_version(RootDir, Apps, Status) ->
 
1225
    AppName = erts,
 
1226
    case lists:keysearch(AppName, #app.name, Apps) of
 
1227
        {value, Erts} ->
 
1228
            LocalRoot = code:root_dir(),
 
1229
            Vsn = Erts#app.vsn,
 
1230
            if
 
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),
 
1235
                    {Apps2, Status};
 
1236
                Vsn =:= "" ->
 
1237
                    {Apps, reltool_utils:add_warning(Status, "erts has no version")};
 
1238
                true ->
 
1239
                    {Apps, Status}
 
1240
            end;
 
1241
        false ->
 
1242
            Text = "erts cannnot be found in the root directory " ++ RootDir,
 
1243
            Status2 = reltool_utils:return_first_error(Status, Text),       
 
1244
            {Apps, Status2}
 
1245
    end.
 
1246
 
 
1247
libs_to_dirs(RootDir, LibDirs, Status) ->
 
1248
    case file:list_dir(RootDir) of
 
1249
        {ok, RootFiles} ->
 
1250
            RootLibDir = filename:join([RootDir, "lib"]),
 
1251
            SortedLibDirs = lists:sort(LibDirs),
 
1252
            AllLibDirs = [RootLibDir | SortedLibDirs],
 
1253
            case AllLibDirs -- lists:usort(AllLibDirs) of
 
1254
                [] ->
 
1255
                    Fun = fun(Base) ->
 
1256
                                  AppDir = filename:join([RootLibDir, Base]),
 
1257
                                  case filelib:is_dir(filename:join([AppDir, "ebin"]), erl_prim_loader) of
 
1258
                                      true ->
 
1259
                                          AppDir;
 
1260
                                      false ->
 
1261
                                          filename:join([RootDir, Base, "preloaded"])
 
1262
                                  end
 
1263
                          end,
 
1264
                    ErtsFiles = [{erts, Fun(F)} || F <- RootFiles, lists:prefix("erts", F)],
 
1265
                    app_dirs2(AllLibDirs, [ErtsFiles], Status);
 
1266
                [Duplicate | _] ->
 
1267
                    {[], reltool_utils:return_first_error(Status, "Duplicate library: " ++ Duplicate)}
 
1268
            end;
 
1269
        {error, Reason} ->
 
1270
            Text = file:format_error(Reason),
 
1271
            {[], reltool_utils:return_first_error(Status, "Missing root library " ++ RootDir ++ ": " ++ Text)}
 
1272
    end.
 
1273
 
 
1274
app_dirs2([Lib | Libs], Acc, Status) ->
 
1275
    case file:list_dir(Lib) of
 
1276
        {ok, Files} ->
 
1277
            Filter =
 
1278
                fun(Base) ->
 
1279
                        AppDir = filename:join([Lib, Base]),
 
1280
                        EbinDir = filename:join([AppDir, "ebin"]),
 
1281
                        case filelib:is_dir(EbinDir, erl_prim_loader) of
 
1282
                            true -> 
 
1283
                                {Name, _Vsn} = reltool_utils:split_app_name(Base),
 
1284
                                case Name of
 
1285
                                    erts -> false;
 
1286
                                    _    -> {true, {Name, AppDir}}
 
1287
                                end;
 
1288
                            false ->
 
1289
                                false
 
1290
                        end
 
1291
                end,
 
1292
            Files2 = lists:zf(Filter, Files),
 
1293
            app_dirs2(Libs, [Files2 | Acc], Status);
 
1294
        {error, Reason} ->
 
1295
            Text = file:format_error(Reason),
 
1296
            {[], reltool_utils:return_first_error(Status, "Illegal library " ++ Lib ++ ": " ++ Text)}
 
1297
    end;
 
1298
app_dirs2([], Acc, Status) ->
 
1299
    {lists:sort(lists:append(Acc)), Status}.
 
1300
 
 
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),
 
1306
                  case Components of
 
1307
                      [AppLabel, "ebin", File] ->
 
1308
                          case filename:extension(File) of
 
1309
                              ".app" ->
 
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};
 
1316
                              E when E =:= Ext ->
 
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};
 
1321
                              _ ->
 
1322
                                  {FileAcc, StatusAcc}
 
1323
                          end;
 
1324
                      ["."] ->
 
1325
                          Bin = GetBin(),
 
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};
 
1330
                      [File] ->
 
1331
                          case filename:extension(File) of
 
1332
                              E when E =:= Ext ->
 
1333
                                  Mod = init_mod(EscriptAppName, File, {File, GetBin()}, Ext),
 
1334
                                  {[{EscriptAppName, mod, File, Mod} | FileAcc], StatusAcc};
 
1335
                              _ ->
 
1336
                                  {FileAcc, StatusAcc}
 
1337
                          end;
 
1338
                      _ ->
 
1339
                          {FileAcc, StatusAcc}
 
1340
                  end
 
1341
          end,
 
1342
    try
 
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);
 
1347
            {error, Reason} ->
 
1348
                Text = lists:flatten(io_lib:format("~p", [Reason])),
 
1349
                {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Text)}
 
1350
        end
 
1351
    catch 
 
1352
        throw:Reason2 when is_list(Reason2) ->
 
1353
            {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Reason2)}
 
1354
    end;
 
1355
escripts_to_apps([], Apps, Status) ->
 
1356
    {Apps, Status}.
 
1357
 
 
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) ->
 
1361
    case Type of
 
1362
        mod ->
 
1363
            case Acc of
 
1364
                [] ->
 
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);
 
1371
                [App | Acc2] ->
 
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)
 
1376
            end;
 
1377
        app ->
 
1378
            {App, Status2} = new_escript_app(AppName, Dir, ModOrInfo, [], Apps, Status),
 
1379
            files_to_apps(Escript, Files, [App | Acc], Apps, Status2)
 
1380
    end;
 
1381
files_to_apps(_Escript, [], Acc, _Apps, Status) ->
 
1382
    {lists:keysort(#app.name, Acc), Status}.
 
1383
 
 
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
 
1388
        {value, _} ->
 
1389
            Error = lists:concat([AppName, ": Application name clash. ",
 
1390
                                  "Escript ", Dir," contains application ", AppName, "."]),
 
1391
            {App2, reltool_utils:return_first_error(Status, Error)};
 
1392
        false ->
 
1393
            {App2, Status}
 
1394
    end.
 
1395
 
 
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) ->
 
1402
    %% Initate app
 
1403
    Apps2 = sort_app_dirs(Apps),
 
1404
    Apps4 =
 
1405
        case lists:keysearch(Name, #app.name, Apps) of
 
1406
            false ->
 
1407
                case lists:keysearch(Name, #app.name, OldApps) of
 
1408
                    {value, OldApp} when OldApp#app.active_dir =:= Dir ->
 
1409
                        [OldApp | Apps2];
 
1410
                    {value, OldApp} ->
 
1411
                        App = 
 
1412
                            case filter_app(OldApp) of
 
1413
                                {true, NewApp} ->
 
1414
                                    NewApp#app{active_dir = Dir, sorted_dirs = [Dir]};
 
1415
                                false ->
 
1416
                                    default_app(Name, Dir)
 
1417
                            end,
 
1418
                        [App | Apps2];
 
1419
                    false ->
 
1420
                        App = default_app(Name, Dir),
 
1421
                        [App | Apps2]
 
1422
                end;
 
1423
            {value, OldApp} ->
 
1424
                Apps3 = lists:keydelete(Name, #app.name, Apps2),
 
1425
                App = OldApp#app{sorted_dirs = [Dir | OldApp#app.sorted_dirs]},
 
1426
                [App | Apps3]
 
1427
        end,
 
1428
    merge_app_dirs(Rest, Apps4, OldApps);
 
1429
merge_app_dirs([], Apps, _OldApps) ->
 
1430
    Apps2 = sort_app_dirs(Apps),
 
1431
    lists:reverse(Apps2).
 
1432
 
 
1433
sort_app_dirs([#app{sorted_dirs = Dirs} = App | Acc]) ->
 
1434
    SortedDirs = lists:sort(fun reltool_utils:app_dir_test/2, Dirs),
 
1435
    case SortedDirs of
 
1436
        [ActiveDir | _] -> ok;
 
1437
        [] -> ActiveDir = undefined
 
1438
    end,
 
1439
    [App#app{active_dir = ActiveDir, sorted_dirs = SortedDirs} | Acc];
 
1440
sort_app_dirs([]) ->
 
1441
    [].
 
1442
 
 
1443
default_app(Name, Dir) ->
 
1444
    App = default_app(Name),
 
1445
    App#app{active_dir = Dir,
 
1446
            sorted_dirs = [Dir]}.
 
1447
 
 
1448
default_app(Name) ->
 
1449
    #app{name = Name,
 
1450
         is_escript = false,
 
1451
         label = undefined,
 
1452
         mod_cond = undefined,
 
1453
         incl_cond = undefined,
 
1454
         use_selected_vsn = undefined,
 
1455
         active_dir = undefined,
 
1456
         sorted_dirs = [],
 
1457
         vsn = undefined,
 
1458
         info = undefined,
 
1459
         mods = [],
 
1460
         status = missing,
 
1461
         uses_mods = undefined,
 
1462
         is_pre_included = undefined,
 
1463
         is_included = undefined}.
 
1464
 
 
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),
 
1468
    OptLabel = 
 
1469
        case Info#app_info.vsn =:= New#app.vsn of
 
1470
            true -> New#app.label;
 
1471
            false -> undefined % Cause refresh
 
1472
        end,
 
1473
    {Refreshed, Status3} =
 
1474
        refresh_app(New#app{label = OptLabel,
 
1475
                            active_dir = ActiveDir,
 
1476
                            vsn = Info#app_info.vsn,
 
1477
                            info = Info}, 
 
1478
                    Force,
 
1479
                    Status2),
 
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.
 
1488
    Status2 =
 
1489
        case Old#app.name =:= ?MISSING_APP of
 
1490
            true ->
 
1491
                Status;
 
1492
            false ->
 
1493
                Warning = lists:concat([Old#app.name, ": The source dirs does not contain the application anymore."]),
 
1494
                reltool_utils:add_warning(Status, Warning)
 
1495
        end,
 
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.
 
1503
    Status2 =
 
1504
        case Old#app.name =:= ?MISSING_APP of
 
1505
            true ->
 
1506
                Status;
 
1507
            false ->
 
1508
                Warning = lists:concat([Old#app.name, ": The source dirs ",
 
1509
                                        "does not contain the application anymore."]),
 
1510
                reltool_utils:add_warning(Status, Warning)
 
1511
        end,
 
1512
    refresh_apps(OldApps, [], Acc, Force, Status2);
 
1513
refresh_apps([], [], Acc, _Force, Status) ->
 
1514
    {lists:reverse(Acc), Status}.
 
1515
 
 
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) ->
 
1523
    ReadInfo =
 
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)
 
1530
        end,
 
1531
    {AllInfo, Status2} = lists:mapfoldl(ReadInfo, Status, Dirs),
 
1532
    AllVsns = [I#app_info.vsn || I <- AllInfo],
 
1533
    Status3 =
 
1534
        case AllVsns -- lists:usort(AllVsns) of
 
1535
            [] ->
 
1536
                %% No redundant info
 
1537
                Status2;
 
1538
            [BadVsn | _] ->
 
1539
                Error2 = lists:concat([Name, ": Application version clash. ",
 
1540
                                       "Multiple directories contains version \"", BadVsn, "\"."]),
 
1541
                reltool_utils:return_first_error(Status2, Error2)
 
1542
        end,
 
1543
    FirstInfo = hd(AllInfo),
 
1544
    FirstDir = hd(Dirs),
 
1545
    if
 
1546
        Vsn =:= undefined ->
 
1547
            {FirstInfo, FirstDir, Status3};
 
1548
        Vsn =:= FirstInfo#app_info.vsn ->
 
1549
            {FirstInfo, FirstDir, Status3};
 
1550
        true ->
 
1551
            case find_vsn(Vsn, AllInfo, Dirs) of
 
1552
                {Info, VsnDir} ->
 
1553
                    {Info, VsnDir, Status3};
 
1554
                false ->
 
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}
 
1558
            end
 
1559
    end;
 
1560
ensure_app_info(#app{active_dir = Dir, info = Info}, Status) ->
 
1561
    {Info, Dir, Status}.
 
1562
 
 
1563
find_vsn(Vsn, [#app_info{vsn = Vsn} = Info | _], [Dir | _]) ->
 
1564
    {Info, Dir};
 
1565
find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) ->
 
1566
    find_vsn(Vsn, MoreInfo, MoreDirs);
 
1567
find_vsn(_, [], []) ->
 
1568
    false.
 
1569
 
 
1570
get_base(Name, Dir) ->
 
1571
    case Name of
 
1572
        erts ->
 
1573
            case filename:basename(Dir) of
 
1574
                "preloaded" ->
 
1575
                    filename:basename(filename:dirname(Dir));
 
1576
                TmpBase ->
 
1577
                    TmpBase
 
1578
            end;
 
1579
        _ ->
 
1580
            filename:basename(Dir)
 
1581
    end.
 
1582
 
 
1583
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
1584
%% sys callbacks
 
1585
 
 
1586
system_continue(_Parent, _Debug, S) ->
 
1587
    ?MODULE:loop(S).
 
1588
 
 
1589
system_terminate(Reason, _Parent, _Debug, _S) ->
 
1590
    exit(Reason).
 
1591
 
 
1592
system_code_change(S,_Module,_OldVsn,_Extra) ->
 
1593
    {ok, S}.