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