1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19
20-module(reltool_target).
21
22%% Public
23-export([
24         gen_config/2,
25         gen_app/1,
26         gen_rel/2,
27         gen_rel_files/2,
28         gen_boot/1,
29         gen_script/4,
30         gen_spec/1,
31         eval_spec/3,
32         gen_target/2,
33         install/2
34        ]).
35
36-include("reltool.hrl").
37-include_lib("kernel/include/file.hrl").
38
39%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40%% Hardcoded internals about the kernel application
41%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
42
43%% Mandatory modules are modules that must be loaded before processes
44%% can be started. These are a collection of modules from the kernel
45%% and stdlib applications. Nowadays, error_handler dynamically loads
46%% almost every module. The error_handler self must still be there
47%% though.
48
49mandatory_modules() ->
50    [error_handler].
51
52%% Kernel processes are specially treated by the init process. If a
53%% kernel process terminates the whole system terminates.
54
55kernel_processes(KernelApp) ->
56    [
57     {kernelProcess, heart, {heart, start, []}},
58     {kernelProcess, logger , {logger_server, start_link, []}},
59     {kernelProcess,
60      application_controller,
61      {application_controller, start, [KernelApp]}}
62    ].
63
64%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65%% Generate the contents of a config file
66%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
67
68gen_config(Sys, InclDefs) ->
69    {ok, do_gen_config(Sys, InclDefs)}.
70
71do_gen_config(#sys{root_dir          	= RootDir,
72		   lib_dirs          	= LibDirs,
73		   mod_cond          	= ModCond,
74		   incl_cond         	= AppCond,
75		   apps              	= Apps,
76		   boot_rel          	= BootRel,
77		   rels              	= Rels,
78		   emu_name          	= EmuName,
79		   profile           	= Profile,
80		   incl_sys_filters  	= InclSysFiles,
81		   excl_sys_filters  	= ExclSysFiles,
82		   incl_app_filters  	= InclAppFiles,
83		   excl_app_filters  	= ExclAppFiles,
84		   incl_archive_filters = InclArchiveDirs,
85		   excl_archive_filters = ExclArchiveDirs,
86		   archive_opts      	= ArchiveOpts,
87		   relocatable       	= Relocatable,
88		   rel_app_type        	= RelAppType,
89		   embedded_app_type   	= InclAppType,
90		   app_file          	= AppFile,
91		   debug_info        	= DebugInfo},
92	      InclDefs) ->
93    ErtsItems =
94        case lists:keyfind(erts, #app.name, Apps) of
95	    false ->
96		[];
97            Erts ->
98                [{erts, do_gen_config(Erts, InclDefs)}]
99        end,
100    AppsItems =
101        [do_gen_config(A, InclDefs)
102	 || A <- Apps,
103	    A#app.name =/= ?MISSING_APP_NAME,
104	    A#app.name =/= erts,
105	    A#app.is_escript =/= true],
106    EscriptItems = [{escript,
107		     A#app.active_dir,
108		     emit(incl_cond, A#app.incl_cond, undefined, InclDefs)}
109		    || A <- Apps, A#app.is_escript],
110    DefaultRels = reltool_utils:default_rels(),
111    RelsItems = [do_gen_config(R, InclDefs) || R <- Rels],
112    DefaultRelsItems = [do_gen_config(R, InclDefs) || R <- DefaultRels],
113    RelsItems2 =
114	case InclDefs of
115	    true  -> RelsItems;
116	    false -> RelsItems -- DefaultRelsItems
117	end,
118    X = fun(List) -> [Re || #regexp{source = Re} <- List] end,
119    {sys,
120     emit(root_dir, RootDir, code:root_dir(), InclDefs) ++
121     emit(lib_dirs, LibDirs, ?DEFAULT_LIBS, InclDefs) ++
122     EscriptItems ++
123     emit(mod_cond, ModCond, ?DEFAULT_MOD_COND, InclDefs) ++
124     emit(incl_cond, AppCond, ?DEFAULT_INCL_COND, InclDefs) ++
125     ErtsItems ++
126     lists:flatten(AppsItems) ++
127     emit(boot_rel, BootRel, ?DEFAULT_REL_NAME, InclDefs) ++
128     RelsItems2 ++
129     emit(emu_name, EmuName, ?DEFAULT_EMU_NAME, InclDefs) ++
130     emit(relocatable, Relocatable, ?DEFAULT_RELOCATABLE, InclDefs) ++
131     emit(profile, Profile, ?DEFAULT_PROFILE, InclDefs) ++
132     emit(incl_sys_filters, X(InclSysFiles), reltool_utils:choose_default(incl_sys_filters, Profile, InclDefs), InclDefs) ++
133     emit(excl_sys_filters, X(ExclSysFiles), reltool_utils:choose_default(excl_sys_filters, Profile, InclDefs), InclDefs) ++
134     emit(incl_app_filters, X(InclAppFiles), reltool_utils:choose_default(incl_app_filters, Profile, InclDefs), InclDefs) ++
135     emit(excl_app_filters, X(ExclAppFiles), reltool_utils:choose_default(excl_app_filters, Profile, InclDefs), InclDefs) ++
136     emit(incl_archive_filters, X(InclArchiveDirs), ?DEFAULT_INCL_ARCHIVE_FILTERS, InclDefs) ++
137     emit(excl_archive_filters, X(ExclArchiveDirs), ?DEFAULT_EXCL_ARCHIVE_FILTERS, InclDefs) ++
138     emit(archive_opts, ArchiveOpts, ?DEFAULT_ARCHIVE_OPTS, InclDefs) ++
139     emit(rel_app_type, RelAppType, ?DEFAULT_REL_APP_TYPE, InclDefs) ++
140     emit(embedded_app_type, InclAppType, reltool_utils:choose_default(embedded_app_type, Profile, InclDefs), InclDefs) ++
141     emit(app_file, AppFile, ?DEFAULT_APP_FILE, InclDefs) ++
142     emit(debug_info, DebugInfo, ?DEFAULT_DEBUG_INFO, InclDefs)};
143do_gen_config(#app{name = Name,
144		   mod_cond = ModCond,
145		   incl_cond  = AppCond,
146		   debug_info = DebugInfo,
147		   app_file = AppFile,
148		   incl_app_filters = InclAppFiles,
149		   excl_app_filters = ExclAppFiles,
150		   incl_archive_filters = InclArchiveDirs,
151		   excl_archive_filters = ExclArchiveDirs,
152		   archive_opts = ArchiveOpts,
153		   use_selected_vsn  = UseSelected,
154		   vsn = Vsn,
155		   active_dir = ActiveDir,
156		   mods = Mods,
157		   is_included = IsIncl},
158	      InclDefs) ->
159    AppConfig =
160	[
161	 emit(mod_cond, ModCond, undefined, InclDefs),
162	 emit(incl_cond, AppCond, undefined, InclDefs),
163	 emit(debug_info, DebugInfo, undefined, InclDefs),
164	 emit(app_file, AppFile, undefined, InclDefs),
165	 emit(incl_app_filters, InclAppFiles, undefined, InclDefs),
166	 emit(excl_app_filters, ExclAppFiles, undefined, InclDefs),
167	 emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefs),
168	 emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefs),
169	 emit(archive_opts, ArchiveOpts, undefined, InclDefs),
170	 if
171	     IsIncl, InclDefs    -> [{vsn, Vsn}, {lib_dir, ActiveDir}];
172	     UseSelected =:= vsn -> [{vsn, Vsn}];
173	     UseSelected =:= dir -> [{lib_dir, ActiveDir}];
174	     true                -> []
175	 end,
176	 [do_gen_config(M, InclDefs) || M <- Mods]
177	],
178    case lists:flatten(AppConfig) of
179	FlatAppConfig when FlatAppConfig =/= []; IsIncl ->
180	    [{app, Name, FlatAppConfig}];
181	[] ->
182	    []
183    end;
184do_gen_config(#mod{name = Name,
185		   incl_cond = AppCond,
186		   debug_info = DebugInfo,
187		   is_included = IsIncl},
188	      InclDefs) ->
189    ModConfig =
190	[
191	 emit(incl_cond,  AppCond, undefined, InclDefs),
192	 emit(debug_info, DebugInfo, undefined, InclDefs)
193	],
194    case lists:flatten(ModConfig) of
195	FlatModConfig when FlatModConfig =/= []; IsIncl ->
196	    [{mod, Name, FlatModConfig}];
197	_ ->
198	    []
199    end;
200do_gen_config(#rel{name = Name,
201                   vsn = Vsn,
202                   rel_apps = RelApps,
203                   load_dot_erlang = LoadDotErlang},
204              InclDefs) ->
205    RelAppsConfig = [do_gen_config(RA, InclDefs) || RA <- RelApps],
206    if
207        LoadDotErlang =:= false ->
208            {rel, Name, Vsn, RelAppsConfig, [{load_dot_erlang, false}]};
209        InclDefs =:= true ->
210            {rel, Name, Vsn, RelAppsConfig, [{load_dot_erlang, true}]};
211        LoadDotErlang =:= true ->
212            {rel, Name, Vsn, RelAppsConfig}
213    end;
214do_gen_config(#rel_app{name = Name,
215		       app_type = Type,
216		       incl_apps = InclApps},
217	      _InclDefs) ->
218    case {Type, InclApps} of
219        {undefined, undefined} -> Name;
220        {undefined, _}         -> {Name, InclApps};
221        {_, undefined}         -> {Name, Type};
222        {_, _}                 -> {Name, Type, InclApps}
223    end;
224do_gen_config({Tag, Val}, InclDefs) ->
225    emit(Tag, Val, undefined, InclDefs);
226do_gen_config([], _InclDefs) ->
227    [];
228do_gen_config([H | T], InclDefs) ->
229    lists:flatten([do_gen_config(H, InclDefs), do_gen_config(T, InclDefs)]).
230
231emit(Tag, Val, Default, InclDefs) ->
232    %% io:format("~p(~p):\n\t~p\n\t~p\n",
233    %%           [Tag, Val =/= Default, Val, Default]),
234    if
235        Val == undefined -> [];
236        InclDefs         -> [{Tag, Val}];
237        Val =/= Default  -> [{Tag, Val}];
238        true             -> []
239    end.
240
241%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
242%% Generate the contents of an app file
243%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
244
245gen_app(#app{name = Name,
246             info = #app_info{description = Desc,
247                              id = Id,
248                              vsn = Vsn,
249                              modules = Mods,
250                              maxP = MaxP,
251                              maxT = MaxT,
252                              registered = Regs,
253                              incl_apps = InclApps,
254                              applications = ReqApps,
255                              env = Env,
256                              mod = StartMod,
257                              start_phases = StartPhases}}) ->
258    StartPhases2 =
259        case StartPhases of
260            undefined -> [];
261            _         -> [{start_phases, StartPhases}]
262        end,
263    Tail =
264        case StartMod of
265            undefined -> StartPhases2;
266            _         -> [{mod, StartMod} | StartPhases2]
267        end,
268    {application, Name,
269     [{description, Desc},
270      {vsn, Vsn},
271      {id, Id},
272      {modules, Mods},
273      {registered, Regs},
274      {applications, ReqApps},
275      {included_applications, InclApps},
276      {env, Env},
277      {maxT, MaxT},
278      {maxP, MaxP} |
279      Tail]}.
280
281%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282%% Generate the contents of a rel file
283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
284
285gen_rel(Rel, Sys) ->
286    try
287	MergedApps = merge_apps(Rel, Sys),
288	{ok, do_gen_rel(Rel, Sys, MergedApps)}
289    catch
290        throw:{error, Text} ->
291            {error, Text}
292    end.
293
294do_gen_rel(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
295	   #sys{apps = Apps},
296	   MergedApps) ->
297    ErtsName = erts,
298    case lists:keysearch(ErtsName, #app.name, Apps) of
299	{value, Erts} ->
300	    {release,
301	     {RelName, RelVsn},
302	     {ErtsName, Erts#app.vsn},
303	     [strip_rel_info(App, RelApps) || App <- MergedApps]};
304	false ->
305	    reltool_utils:throw_error("Mandatory application ~w is "
306				      "not included",
307                                      [ErtsName])
308    end.
309
310strip_rel_info(#app{name = Name,
311		    vsn = Vsn,
312		    app_type = Type,
313		    info = #app_info{incl_apps = AppInclApps}},
314	       RelApps) when Type =/= undefined ->
315    RelInclApps = case lists:keyfind(Name,#rel_app.name,RelApps) of
316		      #rel_app{incl_apps = RIA} when RIA =/= undefined -> RIA;
317		      _ -> undefined
318		  end,
319    case {Type, RelInclApps} of
320        {permanent, undefined} -> {Name, Vsn};
321	{permanent, _}         -> {Name, Vsn, AppInclApps};
322        {_, undefined}         -> {Name, Vsn, Type};
323        {_, _}                 -> {Name, Vsn, Type, AppInclApps}
324    end.
325
326merge_apps(#rel{name = RelName,
327		rel_apps = RelApps},
328	   #sys{apps = Apps,
329		rel_app_type = RelAppType,
330		embedded_app_type = EmbAppType}) ->
331    Mandatory = [kernel, stdlib],
332    MergedApps = do_merge_apps(RelName, Mandatory, Apps, permanent, []),
333    MergedApps2 = do_merge_apps(RelName, RelApps, Apps, RelAppType, MergedApps),
334    Embedded =
335	[A#app.name || A <- Apps,
336		       EmbAppType =/= undefined,
337		       A#app.is_included,
338		       A#app.name =/= erts,
339		       A#app.name =/= ?MISSING_APP_NAME,
340		       not lists:keymember(A#app.name, #app.name, MergedApps2)],
341    MergedApps3 = do_merge_apps(RelName, Embedded, Apps, EmbAppType, MergedApps2),
342    RevMerged = lists:reverse(MergedApps3),
343    MergedSortedUsedAndIncs = sort_used_and_incl_apps(RevMerged,RevMerged),
344    sort_apps(MergedSortedUsedAndIncs).
345
346do_merge_apps(RelName, [#rel_app{name = Name} = RA | RelApps], Apps, RelAppType, Acc) ->
347    case is_already_merged(Name, RelApps, Acc) of
348	true ->
349	    do_merge_apps(RelName, RelApps, Apps, RelAppType, Acc);
350	false ->
351	    {value, App} = lists:keysearch(Name, #app.name, Apps),
352	    MergedApp = merge_app(RelName, RA, RelAppType, App),
353	    ReqNames = (MergedApp#app.info)#app_info.applications,
354	    IncNames = (MergedApp#app.info)#app_info.incl_apps,
355	    Acc2 = [MergedApp | Acc],
356	    do_merge_apps(RelName, ReqNames ++ IncNames ++ RelApps,
357			  Apps, RelAppType, Acc2)
358    end;
359do_merge_apps(RelName, [Name | RelApps], Apps, RelAppType, Acc) ->
360  case is_already_merged(Name, RelApps, Acc) of
361	true ->
362	  do_merge_apps(RelName, RelApps, Apps, RelAppType, Acc);
363	false ->
364	  RelApp = #rel_app{name = Name},
365	  do_merge_apps(RelName, [RelApp | RelApps], Apps, RelAppType, Acc)
366  end;
367do_merge_apps(_RelName, [], _Apps, _RelAppType, Acc) ->
368    Acc.
369
370merge_app(RelName,
371	  #rel_app{name = Name,
372		   app_type = Type,
373		   incl_apps = InclApps0},
374	  RelAppType,
375	  App) ->
376    Type2 =
377        case {Type, App#app.app_type} of
378            {undefined, undefined} -> RelAppType;
379            {undefined, AppAppType} -> AppAppType;
380            {_, _} -> Type
381        end,
382    Info = App#app.info,
383    InclApps =
384	case InclApps0 of
385	    undefined -> Info#app_info.incl_apps;
386	    _ -> InclApps0
387	end,
388    case InclApps -- Info#app_info.incl_apps of
389        [] ->
390	    App#app{app_type = Type2, info = Info#app_info{incl_apps = InclApps}};
391        BadIncl ->
392            reltool_utils:throw_error("~w: These applications are "
393				      "used by release ~ts but are "
394				      "missing as included_applications "
395				      "in the app file: ~p",
396                                      [Name, RelName, BadIncl])
397    end.
398
399is_already_merged(Name, [Name | _], _MergedApps) ->
400    true;
401is_already_merged(Name, [#rel_app{name = Name} | _], _MergedApps) ->
402    true;
403is_already_merged(Name, [_ | RelApps], MergedApps) ->
404    is_already_merged(Name, RelApps, MergedApps);
405is_already_merged(Name, [], [#app{name = Name} | _MergedApps]) ->
406    true;
407is_already_merged(Name, [] = RelApps, [_ | MergedApps]) ->
408    is_already_merged(Name, RelApps, MergedApps);
409is_already_merged(_Name, [], []) ->
410    false.
411
412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
413%% Generate the contents of a boot file
414%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
415
416gen_boot({script, {_, _}, _} = Script) ->
417    {ok, term_to_binary(Script)}.
418
419%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
420%% Generate the contents of a script file
421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
422
423gen_script(Rel, Sys, PathFlag, Variables) ->
424    try
425	MergedApps = merge_apps(Rel, Sys),
426        do_gen_script(Rel, Sys, MergedApps, PathFlag, Variables)
427    catch
428        throw:{error, Text} ->
429            {error, Text}
430    end.
431
432do_gen_script(#rel{name = RelName, vsn = RelVsn, load_dot_erlang=LoadErlangRc},
433              #sys{apps = Apps},
434	      MergedApps,
435              PathFlag,
436              Variables) ->
437    {value, Erts} = lists:keysearch(erts, #app.name, Apps),
438    Preloaded = [Mod#mod.name || Mod <- Erts#app.mods],
439    Mandatory = mandatory_modules(),
440    Early = Mandatory ++ Preloaded,
441    {value, KernelApp} = lists:keysearch(kernel, #app.name, MergedApps),
442    InclApps = lists:flatmap(fun(#app{info = #app_info{incl_apps = I}}) ->
443				     I
444			     end,
445			     MergedApps),
446
447    %% Create the script
448    DeepList =
449        [
450         %% Register preloaded modules
451         {preLoaded, lists:sort(Preloaded)},
452         {progress, preloaded},
453
454         %% Load mandatory modules
455         {path, create_mandatory_path(MergedApps, PathFlag, Variables)},
456         {primLoad, lists:sort(Mandatory)},
457         {kernel_load_completed},
458         {progress, kernel_load_completed},
459
460         %% Load remaining modules
461         [load_app_mods(A, Early, PathFlag, Variables) || A <- MergedApps],
462         {progress, modules_loaded},
463
464         %% Start kernel processes
465         {path, create_path(MergedApps, PathFlag, Variables)},
466         kernel_processes(gen_app(KernelApp)),
467         {progress, init_kernel_started},
468
469         %% Load applications
470         [{apply, {application, load, [gen_app(A)]}} ||
471             A = #app{name = Name, app_type = Type} <- MergedApps,
472             Name =/= kernel,
473             Type =/= none],
474         {progress, applications_loaded},
475
476         %% Start applications
477         [{apply, {application, start_boot, [Name, Type]}} ||
478             #app{name = Name, app_type = Type} <- MergedApps,
479             Type =/= none,
480             Type =/= load,
481             not lists:member(Name, InclApps)],
482         %% Apply user specific customizations
483         case LoadErlangRc of
484             true -> {apply, {c, erlangrc, []}};
485             false -> []
486         end,
487         {progress, started}
488        ],
489    {ok, {script, {RelName, RelVsn}, lists:flatten(DeepList)}}.
490
491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
492
493load_app_mods(#app{mods = Mods0} = App, Mand, PathFlag, Variables) ->
494    Path = cr_path(App, PathFlag, Variables),
495    Mods = [M || #mod{name = M, is_included=true} <- Mods0,
496		 not lists:member(M, Mand)],
497    [{path, [filename:join([Path])]},
498     {primLoad, lists:sort(Mods)}].
499
500%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
501%% sort_used_and_incl_apps(Apps, OrderedApps) -> Apps
502%%   Apps = [#app{}]
503%%   OrderedApps = [#app{}]
504%%
505%% OTP-4121, OTP-9984
506%% (Tickets are written for systools, but needs to be implemented here
507%% as well.)
508%% Make sure that used and included applications are given in the same
509%% order as in the release resource file (.rel). Otherwise load and
510%% start instructions in the boot script, and consequently release
511%% upgrade instructions in relup, may end up in the wrong order.
512
513sort_used_and_incl_apps([#app{info=Info} = App|Apps], OrderedApps) ->
514    Incls2 =
515	case Info#app_info.incl_apps of
516	    Incls when length(Incls)>1 ->
517		sort_appl_list(Incls, OrderedApps);
518	    Incls ->
519		Incls
520	end,
521    Uses2 =
522	case Info#app_info.applications of
523	    Uses when length(Uses)>1 ->
524		sort_appl_list(Uses, OrderedApps);
525	    Uses ->
526		Uses
527	end,
528    App2 = App#app{info=Info#app_info{incl_apps=Incls2, applications=Uses2}},
529    [App2|sort_used_and_incl_apps(Apps, OrderedApps)];
530sort_used_and_incl_apps([], _OrderedApps) ->
531    [].
532
533sort_appl_list(List, Order) ->
534    IndexedList = find_pos(List, Order),
535    SortedIndexedList = lists:keysort(1, IndexedList),
536    lists:map(fun({_Index,Name}) -> Name end, SortedIndexedList).
537
538find_pos([Name|Incs], OrderedApps) ->
539    [find_pos(1, Name, OrderedApps)|find_pos(Incs, OrderedApps)];
540find_pos([], _OrderedApps) ->
541    [].
542
543find_pos(N, Name, [#app{name=Name}|_OrderedApps]) ->
544    {N, Name};
545find_pos(N, Name, [_OtherAppl|OrderedApps]) ->
546    find_pos(N+1, Name, OrderedApps).
547
548
549
550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
551%% Function: sort_apps(Apps) -> {ok, Apps'} | throw({error, Error})
552%% Types: Apps = {{Name, Vsn}, #application}]
553%% Purpose: Sort applications according to dependencies among
554%%          applications.  If order doesn't matter, use the same
555%%          order as in the original list.
556%% Alg. written by Ulf Wiger 970917 (etxuwig@etxb.ericsson.se)
557%% Mod. by mbj
558%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
559
560sort_apps(Apps) ->
561    sort_apps(Apps, [], [], []).
562
563sort_apps([#app{name = Name, info = Info} = App | Apps],
564	  Missing,
565	  Circular,
566	  Visited) ->
567    {Uses, Apps1, NotFnd1} =
568	find_all(Name,
569		 lists:reverse(Info#app_info.applications),
570		 Apps,
571		 Visited,
572		 [],
573		 []),
574    {Incs, Apps2, NotFnd2} =
575	find_all(Name,
576		 lists:reverse(Info#app_info.incl_apps),
577		 Apps1,
578		 Visited,
579		 [],
580		 []),
581    Missing1 = NotFnd1 ++ NotFnd2 ++ Missing,
582    case Uses ++ Incs of
583        [] ->
584            %% No more app that must be started before this one is
585            %% found; they are all already taken care of (and present
586            %% in Visited list)
587            [App | sort_apps(Apps, Missing1, Circular, [Name | Visited])];
588        L ->
589            %% The apps in L must be started before the app.
590            %% Check if we have already taken care of some app in L,
591            %% in that case we have a circular dependency.
592            NewCircular = [N || #app{name = N} <- L, N2 <- Visited, N =:= N2],
593            Circular1 = case NewCircular of
594                            [] -> Circular;
595                            _ -> [Name | NewCircular] ++ Circular
596                        end,
597            %% L must be started before N, try again, with all apps
598            %% in L added before N.
599            Apps3 = del_apps(NewCircular, L ++ [App | Apps2]),
600            sort_apps(Apps3, Missing1, Circular1, [Name | Visited])
601    end;
602sort_apps([], [], [], _) ->
603    [];
604sort_apps([], Missing, [], _) ->
605    %% this has already been checked before, but as we have the info...
606    reltool_utils:throw_error("Undefined applications: ~p",
607			      [make_set(Missing)]);
608sort_apps([], [], Circular, _) ->
609    reltool_utils:throw_error("Circular dependencies: ~p",
610			      [make_set(Circular)]);
611sort_apps([], Missing, Circular, _) ->
612    reltool_utils:throw_error("Circular dependencies: ~p"
613                              "Undefined applications: ~p\n",
614                              [make_set(Circular), make_set(Missing)]).
615
616find_all(CheckingApp, [Name | Names], Apps, Visited, Found, NotFound) ->
617    case lists:keyfind(Name, #app.name, Apps) of
618        #app{info = Info} = App ->
619            %% It is OK to have a dependency like
620            %% X includes Y, Y uses X.
621            case lists:member(CheckingApp, Info#app_info.incl_apps) of
622                true ->
623                    case lists:member(Name, Visited) of
624                        true ->
625                            find_all(CheckingApp,
626				     Names,
627				     Apps,
628				     Visited,
629				     Found,
630				     NotFound);
631                        false ->
632                            find_all(CheckingApp,
633				     Names,
634				     Apps,
635				     Visited,
636				     Found,
637				     [Name | NotFound])
638                    end;
639                false ->
640                    find_all(CheckingApp,
641			     Names,
642			     Apps -- [App],
643			     Visited,
644			     [App|Found],
645			     NotFound)
646            end;
647        false ->
648            case lists:member(Name, Visited) of
649                true ->
650                    find_all(CheckingApp,
651			     Names,
652			     Apps,
653			     Visited,
654			     Found,
655			     NotFound);
656                false ->
657                    find_all(CheckingApp,
658			     Names,
659			     Apps,
660			     Visited,
661			     Found,
662			     [Name|NotFound])
663            end
664    end;
665find_all(_CheckingApp, [], Apps, _Visited, Found, NotFound) ->
666    {Found, Apps, NotFound}.
667
668del_apps([Name | Names], Apps) ->
669    del_apps(Names, lists:keydelete(Name, #app.name, Apps));
670del_apps([], Apps) ->
671    Apps.
672
673%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674%% Create the load path used in the generated script.
675%% If PathFlag is true a script intended to be used as a complete
676%% system (e.g. in an embbeded system), i.e. all applications are
677%% located under $ROOT/lib.
678%% Otherwise all paths are set according to dir per application.
679%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
680
681%% Create the complete path.
682create_path(Apps, PathFlag, Variables) ->
683    make_set([cr_path(App, PathFlag, Variables) || App <- Apps]).
684
685%% Create the path to a specific application.
686%% (The otp_build flag is only used for OTP internal system make)
687cr_path(#app{label = Label}, true, []) ->
688    filename:join(["$ROOT", "lib", Label, "ebin"]);
689cr_path(#app{name = Name, vsn = Vsn, label = Label, active_dir = Dir},
690	true,
691	Variables) ->
692    Tail = [Label, "ebin"],
693    case variable_dir(Dir, atom_to_list(Name), Vsn, Variables) of
694        {ok, VarDir} ->
695            filename:join([VarDir] ++ Tail);
696        _ ->
697            filename:join(["$ROOT", "lib"] ++ Tail)
698    end;
699cr_path(#app{name = Name}, otp_build, _) ->
700    filename:join(["$ROOT", "lib", atom_to_list(Name), "ebin"]);
701cr_path(#app{active_dir = Dir}, _, _) ->
702    filename:join([Dir, "ebin"]).
703
704variable_dir(Dir, Name, Vsn, [{Var,Path} | Variables]) ->
705    case lists:prefix(Path, Dir) of
706        true ->
707            D0 = strip_prefix(Path, Dir),
708            case strip_name_ebin(D0, Name, Vsn) of
709                {ok, D} ->
710                    {ok, filename:join(["\$" ++ Var] ++ D)};
711                _ ->
712                    %% We know at least that we are located
713                    %% under the variable dir.
714                    {ok, filename:join(["\$" ++ Var] ++ D0)}
715            end;
716        false ->
717            variable_dir(Dir, Name, Vsn, Variables)
718    end;
719variable_dir(_Dir, _, _, []) ->
720    false.
721
722strip_prefix(Path, Dir) ->
723    L = length(filename:split(Path)),
724    lists:nthtail(L, filename:split(Dir)).
725
726strip_name_ebin(Dir, Name, Vsn) ->
727    FullName = Name ++ "-" ++ Vsn,
728    case lists:reverse(Dir) of
729        ["ebin", Name     | D] -> {ok, lists:reverse(D)};
730        ["ebin", FullName | D] -> {ok, lists:reverse(D)};
731        [Name     | D] -> {ok, lists:reverse(D)};
732        [FullName | D] -> {ok, lists:reverse(D)};
733        _                      -> false
734    end.
735
736%% Create the path to the kernel and stdlib applications.
737create_mandatory_path(Apps, PathFlag, Variables) ->
738    Mandatory = [kernel, stdlib],
739    make_set(lists:map(fun(#app{name = Name} = App) ->
740                               case lists:member(Name, Mandatory) of
741                                   true ->
742                                       cr_path(App, PathFlag, Variables);
743                                   false ->
744                                       ""
745                               end
746                       end,
747                       Apps)).
748
749make_set([]) ->
750    [];
751make_set([""|T]) -> % Ignore empty items.
752    make_set(T);
753make_set([H|T]) ->
754    [H | [ Y || Y<- make_set(T),
755                Y =/= H]].
756
757%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
758%% Generate rel, script and boot files
759%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
760
761gen_rel_files(Sys, TargetDir) ->
762    try
763        Spec = spec_rel_files(Sys),
764        eval_spec(Spec, Sys#sys.root_dir, TargetDir)
765    catch
766        throw:{error, Text} ->
767            {error, Text}
768    end.
769
770spec_rel_files(#sys{rels = Rels} = Sys) ->
771    lists:append([do_spec_rel_files(R, Sys) || R <- Rels]).
772
773do_spec_rel_files(#rel{name = RelName} = Rel,  Sys) ->
774    RelFile = RelName ++ ".rel",
775    ScriptFile = RelName ++ ".script",
776    BootFile = RelName ++ ".boot",
777    MergedApps = merge_apps(Rel, Sys),
778    GenRel = do_gen_rel(Rel, Sys, MergedApps),
779    Variables =
780	case Sys#sys.excl_lib of
781	    otp_root ->
782		%% All applications that are fetched from somewhere
783		%% other than $OTP_ROOT/lib will get $RELTOOL_EXT_LIB
784		%% as path prefix in the .script file.
785		[{"RELTOOL_EXT_LIB",LibDir} ||  LibDir <- Sys#sys.lib_dirs] ++
786		    [{"RELTOOL_EXT_LIB",filename:dirname(AppLibDir)} ||
787			#app{active_dir=AppLibDir,use_selected_vsn=dir}
788			    <- MergedApps];
789	    _ ->
790		[]
791	end,
792    PathFlag = true,
793    {ok, Script} = do_gen_script(Rel, Sys, MergedApps, PathFlag, Variables),
794    {ok, BootBin} = gen_boot(Script),
795    Date = date(),
796    Time = time(),
797    RelIoList = io_lib:format("%% rel generated at ~w ~w\n~tp.\n\n",
798                              [Date, Time, GenRel]),
799    ScriptIoList = io_lib:format("%% script generated at ~w ~w\n~tp.\n\n",
800                                 [Date, Time, Script]),
801    [
802     {write_file, RelFile, to_utf8_bin_with_enc_comment(RelIoList)},
803     {write_file, ScriptFile, to_utf8_bin_with_enc_comment(ScriptIoList)},
804     {write_file, BootFile, BootBin}
805    ].
806
807to_utf8_bin_with_enc_comment(IoList) when is_list(IoList) ->
808    unicode:characters_to_binary("%% " ++ epp:encoding_to_string(utf8) ++ "\n"
809                                 ++ IoList).
810
811%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
812%% Generate a complete target system
813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
814
815gen_target(Sys, TargetDir) ->
816    try
817        Spec = do_gen_spec(Sys),
818        eval_spec(Spec, Sys#sys.root_dir, TargetDir)
819    catch
820        throw:{error, Text} ->
821            {error, Text}
822    end.
823
824gen_spec(Sys) ->
825    try
826        {ok, do_gen_spec(Sys)}
827    catch
828        throw:{error, Text} ->
829            {error, Text}
830    end.
831
832do_gen_spec(#sys{root_dir = RootDir,
833		 excl_lib = ExclLib,
834                 incl_sys_filters = InclRegexps,
835                 excl_sys_filters = ExclRegexps,
836                 relocatable = Relocatable,
837                 apps = Apps} = Sys) ->
838    RelFiles = spec_rel_files(Sys),
839    {SysFiles, InclRegexps2, ExclRegexps2, Mandatory} =
840	case ExclLib of
841	    otp_root ->
842		{[],InclRegexps,ExclRegexps,["lib"]};
843	    _ ->
844		{create_dir, _, SF} = spec_dir(RootDir),
845		{ER2, SF2} = strip_sys_files(Relocatable, SF, Apps, ExclRegexps),
846		{IR2, BinFiles} =
847		    spec_bin_files(Sys, SF, SF2, RelFiles, InclRegexps),
848		SF3 = [{create_dir, "bin", BinFiles}] ++ SF2,
849		{SF3,IR2,ER2,["bin","erts","lib"]}
850	end,
851    LibFiles = spec_lib_files(Sys),
852    {BootVsn, StartFile} = spec_start_file(Sys),
853    SysFiles2 =
854        [{create_dir, "releases",
855	  [StartFile,
856	   {create_dir,BootVsn, RelFiles}]}] ++ SysFiles,
857    SysFiles3 = filter_spec(SysFiles2, InclRegexps2, ExclRegexps2),
858    SysFiles4 = SysFiles3 ++ [{create_dir, "lib", LibFiles}],
859    check_sys(Mandatory, SysFiles4),
860    SysFiles4.
861
862strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps) ->
863    ExclRegexps2 =
864	case Relocatable of
865	    true ->
866		ExtraExcl = ["^erts.*/bin/.*src\$"],
867		reltool_utils:decode_regexps(excl_sys_filters,
868					     {add, ExtraExcl},
869					     ExclRegexps);
870	    false ->
871		ExclRegexps
872	end,
873    {value, Erts} = lists:keysearch(erts, #app.name, Apps),
874    FilterErts =
875        fun(Spec) ->
876		File = element(2, Spec),
877		case File of
878		    "erts" ->
879			reltool_utils:throw_error("This system is not installed. "
880						  "The directory ~ts is missing.",
881				    [Erts#app.label]);
882		    _ when File =:= Erts#app.label ->
883			replace_dyn_erl(Relocatable, Spec);
884                    "erts-" ++ _ ->
885			false;
886                    _ ->
887                        true
888                end
889        end,
890    SysFiles2 = lists:zf(FilterErts, SysFiles),
891    SysFiles3 = lists:foldl(fun(F, Acc) -> lists:keydelete(F, 2, Acc) end,
892			    SysFiles2,
893			    ["releases", "lib", "bin"]),
894    {ExclRegexps2, SysFiles3}.
895
896replace_dyn_erl(false, _ErtsSpec) ->
897    true;
898replace_dyn_erl(true, {create_dir, ErtsDir, ErtsFiles}) ->
899    [{create_dir, _, BinFiles}] =
900	safe_lookup_spec("bin", ErtsFiles),
901    case lookup_spec("dyn_erl", BinFiles) of
902        [] ->
903            case lookup_spec("erl.ini", BinFiles) of
904                [] ->
905                    true;
906                [{copy_file, ErlIni}] ->
907                    %% Remove Windows .ini file
908                    BinFiles2 = lists:keydelete(ErlIni, 2, BinFiles),
909                    ErtsFiles2 =
910			lists:keyreplace("bin",
911					 2,
912					 ErtsFiles,
913					 {create_dir, "bin", BinFiles2}),
914                    {true, {create_dir, ErtsDir, ErtsFiles2}}
915            end;
916        [{copy_file, DynErlExe}] ->
917            %% Replace erl with dyn_erl
918            ErlExe = "erl" ++ filename:extension(DynErlExe),
919            BinFiles2 = lists:keydelete(DynErlExe, 2, BinFiles),
920            DynErlExe2 = filename:join([ErtsDir, "bin", DynErlExe]),
921            BinFiles3 = lists:keyreplace(ErlExe,
922					 2,
923					 BinFiles2,
924					 {copy_file, ErlExe, DynErlExe2}),
925            ErtsFiles2 = lists:keyreplace("bin",
926					  2,
927					  ErtsFiles,
928					  {create_dir, "bin", BinFiles3}),
929            {true, {create_dir, ErtsDir, ErtsFiles2}}
930    end.
931
932spec_bin_files(Sys, AllSysFiles, StrippedSysFiles, RelFiles, InclRegexps) ->
933    [{create_dir, ErtsLabel, ErtsFiles}] =
934	safe_lookup_spec("erts", StrippedSysFiles),
935    [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles),
936    ErtsBin = filename:join([ErtsLabel, "bin"]),
937    Escripts = spec_escripts(Sys, ErtsBin, BinFiles),
938    Map = fun({copy_file, File}) ->
939                  {copy_file, File, filename:join([ErtsBin, File])};
940             ({copy_file, NewFile, OldFile}) ->
941                  {_, OldFile2} =
942		      abs_to_rel_path(ErtsBin,
943				      filename:join([ErtsBin, OldFile])),
944                  {copy_file, NewFile, OldFile2}
945          end,
946
947    %% Do only copy those bin files from erts/bin that also exists in bin
948    [{create_dir, _, OldBinFiles}] = safe_lookup_spec("bin", AllSysFiles),
949    GoodNames = [F || {copy_file, F} <- OldBinFiles,
950		      not lists:suffix(".boot", F),
951		      not lists:suffix(".script", F)],
952    BinFiles2 = [Map(S) || S <- BinFiles,
953			   lists:member(element(2, S), GoodNames)],
954    BootFiles = [F || F <- RelFiles, lists:suffix(".boot", element(2, F))],
955    [{write_file, _, BootRel}] =
956	safe_lookup_spec(Sys#sys.boot_rel ++ ".boot", BootFiles),
957    BootFiles2 = lists:keystore("start.boot",
958				2,
959				BootFiles,
960				{write_file, "start.boot", BootRel}),
961    MakeRegexp =
962	fun(File) -> "^bin/" ++ element(2, File) ++ "(|.escript)\$" end,
963    ExtraIncl = lists:map(MakeRegexp, Escripts),
964    InclRegexps2 = reltool_utils:decode_regexps(incl_sys_filters,
965						{add, ExtraIncl},
966						InclRegexps),
967    {InclRegexps2, Escripts ++ BinFiles2 ++ BootFiles2}.
968
969spec_escripts(#sys{apps = Apps}, ErtsBin, BinFiles) ->
970    Filter = fun(#app{is_escript = IsEscript,
971		      is_included = IsIncl,
972                      is_pre_included = IsPre,
973		      name = Name,
974		      active_dir = File}) ->
975                     if
976                         Name =:= ?MISSING_APP_NAME ->
977                             false;
978                         IsEscript =/= true ->
979                             false;
980                         IsIncl; IsPre ->
981                             {true, do_spec_escript(File, ErtsBin, BinFiles)};
982                         true ->
983                             false
984                     end
985             end,
986    lists:flatten(lists:zf(Filter, Apps)).
987
988do_spec_escript(File, ErtsBin, BinFiles) ->
989    [{copy_file, EscriptExe}] = safe_lookup_spec("escript", BinFiles),
990    EscriptExt = ".escript",
991    Base = filename:basename(File, EscriptExt),
992    ExeExt = filename:extension(EscriptExe),
993    [{copy_file, Base ++ EscriptExt, File},
994     {copy_file, Base ++ ExeExt, filename:join([ErtsBin, EscriptExe])}].
995
996check_sys(Mandatory, SysFiles) ->
997    lists:foreach(fun(M) -> do_check_sys(M, SysFiles) end, Mandatory).
998
999do_check_sys(Prefix, Specs) ->
1000    case lookup_spec(Prefix, Specs) of
1001        [] ->
1002            reltool_utils:throw_error("Mandatory system directory ~ts "
1003				      "is not included",
1004                                      [Prefix]);
1005        _ ->
1006            ok
1007    end.
1008
1009spec_start_file(#sys{boot_rel = BootRelName,
1010                     rels = Rels,
1011                     apps = Apps}) ->
1012    {value, Erts} = lists:keysearch(erts, #app.name, Apps),
1013    {value, BootRel} = lists:keysearch(BootRelName, #rel.name, Rels),
1014    Data = Erts#app.vsn ++ " " ++ BootRel#rel.vsn ++ "\n",
1015    {BootRel#rel.vsn, {write_file, "start_erl.data",
1016                       unicode:characters_to_binary(Data)}}.
1017
1018lookup_spec(Prefix, Specs) ->
1019    lists:filter(fun(S) -> lists:prefix(Prefix, element(2, S)) end, Specs).
1020
1021safe_lookup_spec(Prefix, Specs) ->
1022    case lookup_spec(Prefix, Specs) of
1023        [] ->
1024	    %% io:format("lookup fail ~ts:\n\t~p\n", [Prefix, Specs]),
1025            reltool_utils:throw_error("Mandatory system file ~ts is "
1026				      "not included", [Prefix]);
1027        Match ->
1028            Match
1029    end.
1030
1031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1032%% Specify applications
1033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1034
1035spec_lib_files(#sys{root_dir = RootDir,
1036		    apps = Apps,
1037		    excl_lib = ExclLib} = Sys) ->
1038    Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl,
1039                      is_pre_included = IsPre, name = Name,
1040		      active_dir = ActiveDir}) ->
1041                     if
1042                         Name =:= ?MISSING_APP_NAME ->
1043                             false;
1044                         IsEscript =/= false ->
1045                             false;
1046                         IsIncl; IsPre ->
1047			     case ExclLib of
1048				 otp_root ->
1049				     not lists:prefix(RootDir,ActiveDir);
1050				 _ ->
1051				     true
1052			     end;
1053                         true ->
1054                             false
1055                     end
1056             end,
1057    SelectedApps = lists:filter(Filter, Apps),
1058    case ExclLib of
1059	otp_root ->
1060	    ok;
1061	_ ->
1062	    check_apps([kernel, stdlib], SelectedApps)
1063    end,
1064    lists:flatten([spec_app(App, Sys) || App <- SelectedApps]).
1065
1066check_apps([Mandatory | Names], Apps) ->
1067    case lists:keymember(Mandatory, #app.name, Apps) of
1068        false ->
1069            reltool_utils:throw_error("Mandatory application ~w is "
1070				      "not included in ~p",
1071                                      [Mandatory, Apps]);
1072        true ->
1073            check_apps(Names, Apps)
1074    end;
1075check_apps([], _) ->
1076    ok.
1077
1078spec_app(#app{name              = Name,
1079              mods              = Mods,
1080              active_dir        = SourceDir,
1081              incl_app_filters  = AppInclRegexps,
1082              excl_app_filters  = AppExclRegexps} = App,
1083         #sys{incl_app_filters  = SysInclRegexps,
1084              excl_app_filters  = SysExclRegexps,
1085              debug_info        = SysDebugInfo} = Sys) ->
1086    %% List files recursively
1087    {create_dir, _, AppFiles} = spec_dir(SourceDir),
1088
1089    %% Replace ebin
1090    AppUpFilename = atom_to_list(Name) ++ ".appup",
1091    EbinDir = filename:join([SourceDir, "ebin"]),
1092    OptAppUpFileSpec = spec_opt_copy_file(EbinDir, AppUpFilename),
1093    OptAppFileSpec = spec_app_file(App, Sys, EbinDir),
1094    ModSpecs = [spec_mod(M, SysDebugInfo) || M <- Mods,
1095					     M#mod.is_included,
1096					     M#mod.exists],
1097    NewEbin = {create_dir,
1098	       "ebin",
1099	       OptAppUpFileSpec ++ OptAppFileSpec ++ ModSpecs},
1100    AppFiles2 = lists:keystore("ebin", 2, AppFiles, NewEbin),
1101
1102    %% Apply file filter
1103    InclRegexps = reltool_utils:default_val(AppInclRegexps, SysInclRegexps),
1104    ExclRegexps = reltool_utils:default_val(AppExclRegexps, SysExclRegexps),
1105    AppFiles3 = filter_spec(AppFiles2, InclRegexps, ExclRegexps),
1106
1107    %% Regular top directory and/or archive
1108    spec_archive(App, Sys, AppFiles3).
1109
1110spec_archive(#app{label               = Label,
1111                  active_dir          = SourceDir,
1112                  incl_archive_filters = AppInclArchiveDirs,
1113                  excl_archive_filters = AppExclArchiveDirs,
1114                  archive_opts        = AppArchiveOpts},
1115             #sys{root_dir            = RootDir,
1116                  incl_archive_filters = SysInclArchiveDirs,
1117                  excl_archive_filters = SysExclArchiveDirs,
1118                  archive_opts        = SysArchiveOpts},
1119             Files) ->
1120    InclArchiveDirs =
1121	reltool_utils:default_val(AppInclArchiveDirs, SysInclArchiveDirs),
1122    ExclArchiveDirs =
1123	reltool_utils:default_val(AppExclArchiveDirs, SysExclArchiveDirs),
1124    ArchiveOpts =
1125	reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
1126    Match = fun(F) -> match(element(2, F), InclArchiveDirs, ExclArchiveDirs) end,
1127    case lists:filter(Match, Files) of
1128        [] ->
1129            %% Nothing to archive
1130            [spec_create_dir(RootDir, SourceDir, Label, Files)];
1131        ArchiveFiles ->
1132            OptDir =
1133                case Files -- ArchiveFiles of
1134                    [] ->
1135                        [];
1136                    ExternalFiles ->
1137                        [spec_create_dir(RootDir,
1138					 SourceDir,
1139					 Label,
1140					 ExternalFiles)]
1141                end,
1142            ArchiveOpts =
1143		reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
1144            ArchiveDir =
1145		spec_create_dir(RootDir, SourceDir, Label, ArchiveFiles),
1146            [{archive, Label ++ ".ez", ArchiveOpts, [ArchiveDir]} | OptDir]
1147    end.
1148
1149spec_dir(Dir) ->
1150    Base = filename:basename(Dir),
1151    case erl_prim_loader:read_file_info(Dir) of
1152        {ok, #file_info{type = directory}} ->
1153            case erl_prim_loader:list_dir(Dir) of
1154                {ok, Files} ->
1155                    %% Directory
1156                    {create_dir,
1157		     Base,
1158		     [spec_dir(filename:join([Dir, F])) || F <- Files]};
1159                error ->
1160                    reltool_utils:throw_error("list dir ~ts failed", [Dir])
1161            end;
1162        {ok, #file_info{type = regular}} ->
1163            %% Plain file
1164            {copy_file, Base};
1165        _ ->
1166            reltool_utils:throw_error("read file info ~ts failed", [Dir])
1167    end.
1168
1169spec_mod(Mod, DebugInfo) ->
1170    File = atom_to_list(Mod#mod.name) ++ code:objfile_extension(),
1171    case reltool_utils:default_val(Mod#mod.debug_info, DebugInfo) of
1172        keep ->
1173            {copy_file, File};
1174        strip ->
1175            {strip_beam, File}
1176    end.
1177
1178spec_app_file(#app{name = Name,
1179                   info = Info,
1180                   mods = Mods,
1181                   app_file = AppFile} = App,
1182              #sys{app_file = SysAppFile},
1183              EbinDir) ->
1184    AppFilename = atom_to_list(Name) ++ ".app",
1185    case reltool_utils:default_val(AppFile, SysAppFile) of
1186        keep ->
1187            %% Copy if it exists
1188            spec_opt_copy_file(EbinDir, AppFilename);
1189        strip ->
1190            %% Remove non-included modules
1191            %% Generate new file
1192            ModNames = [M#mod.name || M <- Mods,
1193                                      M#mod.is_included,
1194                                      lists:member(M#mod.name,
1195                                                   Info#app_info.modules)],
1196            App2 = App#app{info = Info#app_info{modules = ModNames}},
1197            Contents = gen_app(App2),
1198            AppIoList = io_lib:format("%% app generated at ~w ~w\n~tp.\n\n",
1199                                      [date(), time(), Contents]),
1200            [{write_file, AppFilename, to_utf8_bin_with_enc_comment(AppIoList)}];
1201        all ->
1202            %% Include all included modules
1203            %% Generate new file
1204            ModNames = [M#mod.name || M <- Mods, M#mod.is_included],
1205            App2 = App#app{info = Info#app_info{modules = ModNames}},
1206            Contents = gen_app(App2),
1207            AppIoList = io_lib:format("%% app generated at ~w ~w\n~tp.\n\n",
1208                                      [date(), time(), Contents]),
1209            [{write_file, AppFilename, to_utf8_bin_with_enc_comment(AppIoList)}]
1210
1211    end.
1212
1213spec_opt_copy_file(DirName, BaseName) ->
1214    case filelib:is_regular(filename:join([DirName, BaseName]),
1215			    erl_prim_loader) of
1216        true -> [{copy_file, BaseName}];
1217        false -> []
1218    end.
1219
1220spec_create_dir(RootDir, SourceDir, BaseDir, Files) ->
1221    LibDir = filename:join([RootDir, "lib"]),
1222    case abs_to_rel_path(LibDir, SourceDir) of
1223        {relative, Dir} ->  {create_dir, Dir, Files};
1224        {absolute, Dir} ->  {create_dir, BaseDir, Dir, Files}
1225    end.
1226
1227abs_to_rel_path(RootDir, SourcePath) ->
1228    R = filename:split(RootDir),
1229    S = filename:split(SourcePath),
1230    abs_to_rel_path(R, S, SourcePath).
1231
1232abs_to_rel_path([H | R], [H | S], SourcePath) ->
1233    abs_to_rel_path(R, S, SourcePath);
1234abs_to_rel_path([], S, _SourcePath) ->
1235    {relative, filename:join(S)};
1236abs_to_rel_path(_, _, SourcePath) ->
1237    {absolute, SourcePath}.
1238
1239%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1240%% Evaluate specification
1241%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1242
1243eval_spec(Spec, SourceDir, TargetDir) ->
1244    SourceDir2 = filename:absname(SourceDir),
1245    TargetDir2 = filename:absname(TargetDir),
1246    try
1247        case filelib:is_dir(TargetDir2) of
1248            true ->
1249                do_eval_spec(Spec, SourceDir2, SourceDir2, TargetDir2),
1250                ok;
1251            false ->
1252                {error, TargetDir2 ++ ": " ++ file:format_error(enoent)}
1253        end
1254    catch
1255        throw:{error, Text} ->
1256            cleanup_spec(Spec, TargetDir2),
1257            {error, Text}
1258    end.
1259
1260do_eval_spec(List, OrigSourceDir, SourceDir, TargetDir) when is_list(List) ->
1261    lists:foreach(fun(F) ->
1262			  do_eval_spec(F, OrigSourceDir, SourceDir, TargetDir)
1263		  end,
1264		  List);
1265%% do_eval_spec({source_dir, SourceDir2, Spec}, OrigSourceDir, _SourceDir, TargetDir) ->
1266%%     %% Source dir is absolute or relative the original source dir
1267%%     SourceDir3 = filename:join([OrigSourceDir, SourceDir2]),
1268%%     do_eval_spec(Spec, OrigSourceDir, SourceDir3, TargetDir);
1269do_eval_spec({create_dir, Dir, Files}, OrigSourceDir, SourceDir, TargetDir) ->
1270    SourceDir2 = filename:join([SourceDir, Dir]),
1271    TargetDir2 = filename:join([TargetDir, Dir]),
1272    reltool_utils:create_dir(TargetDir2),
1273    do_eval_spec(Files, OrigSourceDir, SourceDir2, TargetDir2);
1274do_eval_spec({create_dir, Dir, OldDir, Files},
1275	     OrigSourceDir,
1276	     _SourceDir,
1277	     TargetDir) ->
1278    SourceDir2 = filename:join([OrigSourceDir, OldDir]),
1279    TargetDir2 = filename:join([TargetDir, Dir]),
1280    reltool_utils:create_dir(TargetDir2),
1281    do_eval_spec(Files, SourceDir2, SourceDir2, TargetDir2);
1282do_eval_spec({archive, Archive, Options, Files},
1283	     OrigSourceDir,
1284	     SourceDir,
1285	     TargetDir) ->
1286    TmpSpec = {create_dir, "tmp", Files},
1287    TmpDir = filename:join([TargetDir, "tmp"]),
1288    reltool_utils:create_dir(TmpDir),
1289    do_eval_spec(Files, OrigSourceDir, SourceDir, TmpDir),
1290
1291    ArchiveFile = filename:join([TargetDir, Archive]),
1292    Files2 = [element(2, F) || F <- Files],
1293    Res = zip:create(ArchiveFile, Files2, [{cwd, TmpDir} | Options]),
1294
1295    cleanup_spec(TmpSpec, TargetDir),
1296    case Res of
1297        {ok, _} ->
1298            ok;
1299        {error, Reason} ->
1300            reltool_utils:throw_error("create archive ~ts failed: ~tp",
1301				      [ArchiveFile, Reason])
1302    end;
1303do_eval_spec({copy_file, File}, _OrigSourceDir, SourceDir, TargetDir) ->
1304    SourceFile = filename:join([SourceDir, File]),
1305    TargetFile = filename:join([TargetDir, File]),
1306    reltool_utils:copy_file(SourceFile, TargetFile);
1307do_eval_spec({copy_file, File, OldFile},
1308	     OrigSourceDir,
1309	     _SourceDir,
1310	     TargetDir) ->
1311    SourceFile = filename:join([OrigSourceDir, OldFile]),
1312    TargetFile = filename:join([TargetDir, File]),
1313    reltool_utils:copy_file(SourceFile, TargetFile);
1314do_eval_spec({write_file, File, Bin},
1315	     _OrigSourceDir,
1316	     _SourceDir,
1317	     TargetDir) ->
1318    TargetFile = filename:join([TargetDir, File]),
1319    reltool_utils:write_file(TargetFile, Bin);
1320do_eval_spec({strip_beam, File}, _OrigSourceDir, SourceDir, TargetDir) ->
1321    SourceFile = filename:join([SourceDir, File]),
1322    TargetFile = filename:join([TargetDir, File]),
1323    BeamBin = reltool_utils:read_file(SourceFile),
1324    {ok, {_, BeamBin2}} = beam_lib:strip(BeamBin),
1325    reltool_utils:write_file(TargetFile, BeamBin2).
1326
1327cleanup_spec(List, TargetDir) when is_list(List) ->
1328    lists:foreach(fun(F) -> cleanup_spec(F, TargetDir) end, List);
1329%% cleanup_spec({source_dir, _SourceDir, Spec}, TargetDir) ->
1330%%     cleanup_spec(Spec, TargetDir);
1331cleanup_spec({create_dir, Dir, Files}, TargetDir) ->
1332    TargetDir2 = filename:join([TargetDir, Dir]),
1333    cleanup_spec(Files, TargetDir2),
1334    file:del_dir(TargetDir2);
1335cleanup_spec({create_dir, Dir, _OldDir, Files}, TargetDir) ->
1336    TargetDir2 = filename:join([TargetDir, Dir]),
1337    cleanup_spec(Files, TargetDir2),
1338    file:del_dir(TargetDir2);
1339cleanup_spec({archive, Archive, _Options, Files}, TargetDir) ->
1340    TargetFile = filename:join([TargetDir, Archive]),
1341    file:delete(TargetFile),
1342    TmpDir = filename:join([TargetDir, "tmp"]),
1343    cleanup_spec(Files, TmpDir),
1344    file:del_dir(TmpDir);
1345cleanup_spec({copy_file, File}, TargetDir) ->
1346    TargetFile = filename:join([TargetDir, File]),
1347    file:delete(TargetFile);
1348cleanup_spec({copy_file, NewFile, _OldFile}, TargetDir) ->
1349    TargetFile = filename:join([TargetDir, NewFile]),
1350    file:delete(TargetFile);
1351cleanup_spec({write_file, File, _}, TargetDir) ->
1352    TargetFile = filename:join([TargetDir, File]),
1353    file:delete(TargetFile);
1354cleanup_spec({strip_beam, File}, TargetDir) ->
1355    TargetFile = filename:join([TargetDir, File]),
1356    file:delete(TargetFile).
1357
1358filter_spec(List, InclRegexps, ExclRegexps) ->
1359    do_filter_spec("", List, InclRegexps, ExclRegexps).
1360
1361do_filter_spec(Path, List, InclRegexps, ExclRegexps) when is_list(List) ->
1362    lists:zf(fun(File) ->
1363		     do_filter_spec(Path, File, InclRegexps, ExclRegexps)
1364	     end,
1365	     List);
1366%% do_filter_spec(Path, {source_dir, _SourceDir, Spec}, InclRegexps, ExclRegexps) ->
1367%%     do_filter_spec(Path, Spec, InclRegexps, ExclRegexps);
1368do_filter_spec(Path, {create_dir, Dir, Files}, InclRegexps, ExclRegexps) ->
1369    Path2 = opt_join(Path, Dir),
1370    case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of
1371        [] ->
1372            case match(Path2, InclRegexps, ExclRegexps) of
1373                true ->
1374                    {true, {create_dir, Dir, []}};
1375                false ->
1376                    false
1377            end;
1378        Files2 when is_list(Files2) ->
1379            {true, {create_dir, Dir, Files2}}
1380    end;
1381do_filter_spec(Path,
1382	       {create_dir, NewDir, OldDir, Files},
1383	       InclRegexps,
1384	       ExclRegexps) ->
1385    Path2 = opt_join(Path, NewDir),
1386    case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of
1387        [] ->
1388            case match(Path2, InclRegexps, ExclRegexps) of
1389                true ->
1390                    {true, {create_dir, NewDir, OldDir, []}};
1391                false ->
1392                    false
1393            end;
1394        Files2 when is_list(Files2) ->
1395            {true, {create_dir, NewDir, OldDir, Files2}}
1396    end;
1397do_filter_spec(Path,
1398	       {archive, Archive, Options, Files},
1399	       InclRegexps,
1400	       ExclRegexps) ->
1401    case do_filter_spec(Path, Files, InclRegexps, ExclRegexps) of
1402        [] ->
1403            case match(Path, InclRegexps, ExclRegexps) of
1404                true ->
1405                    {true, {archive, Archive, Options, []}};
1406                false ->
1407                    false
1408            end;
1409        Files2 when is_list(Files2) ->
1410            {true, {archive, Archive, Options, Files2}}
1411    end;
1412do_filter_spec(Path, {copy_file, File}, InclRegexps, ExclRegexps) ->
1413    Path2 = opt_join(Path, File),
1414    match(Path2, InclRegexps, ExclRegexps);
1415do_filter_spec(Path,
1416	       {copy_file, NewFile, _OldFile},
1417	       InclRegexps,
1418	       ExclRegexps) ->
1419    Path2 = opt_join(Path, NewFile),
1420    match(Path2, InclRegexps, ExclRegexps);
1421do_filter_spec(Path, {write_file, File, _}, InclRegexps, ExclRegexps) ->
1422    Path2 = opt_join(Path, File),
1423    match(Path2, InclRegexps, ExclRegexps);
1424do_filter_spec(Path, {strip_beam, File}, InclRegexps, ExclRegexps) ->
1425    Path2 = opt_join(Path, File),
1426    match(Path2, InclRegexps, ExclRegexps).
1427
1428opt_join([], File) ->
1429    File;
1430opt_join(Path, File) ->
1431    filename:join([Path, File]).
1432
1433match(String, InclRegexps, ExclRegexps) ->
1434    match(String, InclRegexps) andalso not match(String, ExclRegexps).
1435
1436%% Match at least one regexp
1437match(_String, []) ->
1438    false;
1439match(String, [#regexp{source = _, compiled = MP} | Regexps]) ->
1440    %% io:format("Regexp: ~p ~p\n", [String, Regexp]),
1441    case re:run(String, MP, [{capture, none}]) of
1442        nomatch -> match(String, Regexps);
1443        match   -> true
1444    end.
1445
1446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1447%% Old style installation
1448%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1449
1450install(RelName, TargetDir) ->
1451    try
1452        do_install(RelName, TargetDir)
1453    catch
1454        throw:{error, Text} ->
1455            {error, Text}
1456    end.
1457
1458do_install(RelName, TargetDir) ->
1459    TargetDir2 = filename:absname(TargetDir),
1460    RelDir = filename:join([TargetDir2, "releases"]),
1461    DataFile = filename:join([RelDir, "start_erl.data"]),
1462    Bin = reltool_utils:read_file(DataFile),
1463    case string:lexemes(unicode:characters_to_list(Bin), " \n") of
1464        [ErlVsn, RelVsn | _] ->
1465            ErtsBinDir = filename:join([TargetDir2, "erts-" ++ ErlVsn, "bin"]),
1466            BinDir = filename:join([TargetDir2, "bin"]),
1467	    case os:type() of
1468		{win32, _} ->
1469		    NativeRootDir = nativename(TargetDir2),
1470		    NativeErtsBinDir = nativename(ErtsBinDir),
1471		    IniData0 = ["[erlang]\r\n",
1472				"Bindir=", NativeErtsBinDir, "\r\n",
1473				"Progname=erl\r\n",
1474				"Rootdir=", NativeRootDir, "\r\n"],
1475		    IniData = unicode:characters_to_binary(IniData0),
1476		    IniFile = filename:join([BinDir, "erl.ini"]),
1477                    ok = file:write_file(IniFile, IniData);
1478		_ ->
1479		    subst_src_scripts(start_scripts(),
1480				      ErtsBinDir,
1481				      BinDir,
1482				      [{"FINAL_ROOTDIR", TargetDir2},
1483				       {"EMU", "beam"}],
1484				      [preserve])
1485	    end,
1486            RelFile = filename:join([RelDir, RelVsn, RelName ++ ".rel"]),
1487            ok = release_handler:create_RELEASES(TargetDir2, RelFile),
1488            ok;
1489        _ ->
1490            reltool_utils:throw_error("~ts: Illegal data file syntax",[DataFile])
1491    end.
1492
1493nativename(Dir) ->
1494    escape_backslash(filename:nativename(Dir)).
1495escape_backslash([$\\|T]) ->
1496    [$\\,$\\|escape_backslash(T)];
1497escape_backslash([H|T]) ->
1498    [H|escape_backslash(T)];
1499escape_backslash([]) ->
1500    [].
1501
1502subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
1503    Fun = fun(Script) ->
1504		  subst_src_script(Script, SrcDir, DestDir, Vars, Opts)
1505	  end,
1506    lists:foreach(Fun, Scripts).
1507
1508subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
1509    subst_file(filename:join([SrcDir, Script ++ ".src"]),
1510               filename:join([DestDir, Script]),
1511               Vars,
1512               Opts).
1513
1514subst_file(Src, Dest, Vars, Opts) ->
1515    Bin = reltool_utils:read_file(Src),
1516    Chars = subst(unicode:characters_to_list(Bin), Vars),
1517    reltool_utils:write_file(Dest, unicode:characters_to_binary(Chars)),
1518    case lists:member(preserve, Opts) of
1519        true ->
1520            FileInfo = reltool_utils:read_file_info(Src),
1521            reltool_utils:write_file_info(Dest, FileInfo);
1522        false ->
1523            ok
1524    end.
1525
1526%% subst(Str, Vars)
1527%% Vars = [{Var, Val}]
1528%% Var = Val = string()
1529%% Substitute all occurrences of %Var% for Val in Str, using the list
1530%% of variables in Vars.
1531%%
1532subst(Str, Vars) ->
1533    subst(Str, Vars, []).
1534
1535subst([$%, C | Rest], Vars, Result) when $A =< C, C =< $Z ->
1536    subst_var([C| Rest], Vars, Result, []);
1537subst([$%, C | Rest], Vars, Result) when $a =< C, C =< $z ->
1538    subst_var([C| Rest], Vars, Result, []);
1539subst([$%, C | Rest], Vars, Result) when  C == $_ ->
1540    subst_var([C| Rest], Vars, Result, []);
1541subst([C| Rest], Vars, Result) ->
1542    subst(Rest, Vars, [C| Result]);
1543subst([], _Vars, Result) ->
1544    lists:reverse(Result).
1545
1546subst_var([$%| Rest], Vars, Result, VarAcc) ->
1547    Key = lists:reverse(VarAcc),
1548    case lists:keyfind(Key, 1, Vars) of
1549        {Key, Value} ->
1550            subst(Rest, Vars, lists:reverse(Value, Result));
1551        false ->
1552            subst(Rest, Vars, [$% | VarAcc ++ [$% | Result]])
1553    end;
1554subst_var([C| Rest], Vars, Result, VarAcc) ->
1555    subst_var(Rest, Vars, Result, [C| VarAcc]);
1556subst_var([], Vars, Result, VarAcc) ->
1557    subst([], Vars, [VarAcc ++ [$% | Result]]).
1558
1559start_scripts() ->
1560    ["erl", "start", "start_erl"].
1561