From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/reltool/src/reltool_target.erl | 1226 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1226 insertions(+) create mode 100644 lib/reltool/src/reltool_target.erl (limited to 'lib/reltool/src/reltool_target.erl') diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl new file mode 100644 index 0000000000..895fc6702b --- /dev/null +++ b/lib/reltool/src/reltool_target.erl @@ -0,0 +1,1226 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% + +-module(reltool_target). + +%% Public +-export([ + gen_config/2, + gen_app/1, + gen_rel/2, + gen_rel_files/2, + gen_boot/1, + gen_script/4, + gen_spec/1, + eval_spec/3, + gen_target/2, + install/2 + ]). +-compile(export_all). +-include("reltool.hrl"). +-include_lib("kernel/include/file.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Hardcoded internals about the kernel application +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Mandatory modules are modules that must be loaded before processes +%% can be started. These are a collection of modules from the kernel +%% and stdlib applications. Nowadays, error_handler dynamically loads +%% almost every module. The error_handler self must still be there +%% though. + +mandatory_modules() -> + [error_handler]. + +%% Kernel processes are specially treated by the init process. If a +%% kernel process terminates the whole system terminates. + +kernel_processes(KernelApp) -> + [ + {kernelProcess, heart, {heart, start, []}}, + {kernelProcess, error_logger , {error_logger, start_link, []}}, + {kernelProcess, application_controller, {application_controller, start, [KernelApp]}} + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate the contents of a config file +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_config(#sys{root_dir = RootDir, + lib_dirs = LibDirs, + mod_cond = ModCond, + incl_cond = AppCond, + apps = Apps, + boot_rel = BootRel, + rels = Rels, + emu_name = EmuName, + profile = Profile, + incl_sys_filters = InclSysFiles, + excl_sys_filters = ExclSysFiles, + incl_app_filters = InclAppFiles, + excl_app_filters = ExclAppFiles, + incl_archive_filters = InclArchiveDirs, + excl_archive_filters = ExclArchiveDirs, + archive_opts = ArchiveOpts, + relocatable = Relocatable, + app_type = AppType, + app_file = AppFile, + debug_info = DebugInfo}, + InclDefaults) -> + ErtsItems = + case lists:keysearch(erts, #app.name, Apps) of + {value, Erts} -> + [{erts, gen_config(Erts, InclDefaults)}]; + false -> + [] + end, + AppsItems = + [{app, A#app.name, gen_config(A, InclDefaults)} + || A <- Apps, + A#app.name =/= ?MISSING_APP, + A#app.name =/= erts, + A#app.is_included =:= true, + A#app.is_escript =/= true], + EscriptItems = [{escript, A#app.active_dir, emit(incl_cond, A#app.incl_cond, undefined, InclDefaults)} + || A <- Apps, A#app.is_escript], + DefaultRels = reltool_utils:default_rels(), + RelsItems = + case {[{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefaults)} || R <- Rels], + [{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefaults)} || R <- DefaultRels]} of + {RI, RI} -> []; + {RI, _} -> RI + end, + X = fun(List) -> [Re || #regexp{source = Re} <- List] end, + {sys, + emit(root_dir, RootDir, code:root_dir(), InclDefaults) ++ + emit(lib_dirs, LibDirs, ?DEFAULT_LIBS, InclDefaults) ++ + EscriptItems ++ + emit(mod_cond, ModCond, ?DEFAULT_MOD_COND, InclDefaults) ++ + emit(incl_cond, AppCond, ?DEFAULT_INCL_COND, InclDefaults) ++ + ErtsItems ++ + AppsItems ++ + emit(boot_rel, BootRel, ?DEFAULT_REL_NAME, InclDefaults) ++ + RelsItems ++ + emit(emu_name, EmuName, ?DEFAULT_EMU_NAME, InclDefaults) ++ + emit(relocatable, Relocatable, ?DEFAULT_RELOCATABLE, InclDefaults) ++ + emit(profile, Profile, ?DEFAULT_PROFILE, InclDefaults) ++ + emit(incl_sys_filters, X(InclSysFiles), ?DEFAULT_INCL_SYS_FILTERS, InclDefaults) ++ + emit(excl_sys_filters, X(ExclSysFiles), ?DEFAULT_EXCL_SYS_FILTERS, InclDefaults) ++ + emit(incl_app_filters, X(InclAppFiles), ?DEFAULT_INCL_APP_FILTERS, InclDefaults) ++ + emit(excl_app_filters, X(ExclAppFiles), ?DEFAULT_EXCL_APP_FILTERS, InclDefaults) ++ + emit(incl_archive_filters, X(InclArchiveDirs), ?DEFAULT_INCL_ARCHIVE_FILTERS, InclDefaults) ++ + emit(excl_archive_filters, X(ExclArchiveDirs), ?DEFAULT_EXCL_ARCHIVE_FILTERS, InclDefaults) ++ + emit(archive_opts, ArchiveOpts, ?DEFAULT_ARCHIVE_OPTS, InclDefaults) ++ + emit(app_type, AppType, ?DEFAULT_APP_TYPE, InclDefaults) ++ + emit(app_file, AppFile, ?DEFAULT_APP_FILE, InclDefaults) ++ + emit(debug_info, DebugInfo, ?DEFAULT_DEBUG_INFO, InclDefaults)}; +gen_config(#app{name = _Name, + mod_cond = ModCond, + incl_cond = AppCond, + debug_info = DebugInfo, + app_file = AppFile, + incl_app_filters = InclAppFiles, + excl_app_filters = ExclAppFiles, + incl_archive_filters = InclArchiveDirs, + excl_archive_filters = ExclArchiveDirs, + archive_opts = ArchiveOpts, + use_selected_vsn = UseSelected, + vsn = Vsn, + mods = Mods}, + InclDefaults) -> + emit(mod_cond, ModCond, undefined, InclDefaults) ++ + emit(incl_cond, AppCond, undefined, InclDefaults) ++ + emit(debug_info, DebugInfo, undefined, InclDefaults) ++ + emit(app_file, AppFile, undefined, InclDefaults) ++ + emit(incl_app_filters, InclAppFiles, undefined, InclDefaults) ++ + emit(excl_app_filters, ExclAppFiles, undefined, InclDefaults) ++ + emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefaults) ++ + emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefaults) ++ + emit(archive_opts, ArchiveOpts, undefined, InclDefaults) ++ + emit(vsn, Vsn, undefined, InclDefaults orelse UseSelected =/= true) ++ + [{mod, M#mod.name, gen_config(M, InclDefaults)} || M <- Mods, M#mod.is_included =:= true]; +gen_config(#mod{name = _Name, + incl_cond = AppCond, + debug_info = DebugInfo}, + InclDefaults) -> + emit(incl_cond, AppCond, undefined, InclDefaults) ++ + emit(debug_info, DebugInfo, undefined, InclDefaults); +gen_config(#rel{name = _Name, + vsn = _Vsn, + rel_apps = RelApps}, + InclDefaults) -> + [gen_config(RA, InclDefaults) || RA <- RelApps]; +gen_config(#rel_app{name = Name, + app_type = Type, + incl_apps = InclApps}, + _InclDefaults) -> + case {Type, InclApps} of + {undefined, []} -> Name; + {undefined, _} -> {Name, InclApps}; + {_, []} -> {Name, Type}; + {_, _} -> {Name, Type, InclApps} + end; +gen_config({Tag, Val}, InclDefaults) -> + emit(Tag, Val, undefined, InclDefaults); +gen_config([], _InclDefaults) -> + []; +gen_config([H | T], InclDefaults) -> + lists:flatten([gen_config(H, InclDefaults), gen_config(T, InclDefaults)]). + +emit(Tag, Val, Default, InclDefaults) -> + if + Val == undefined -> []; + InclDefaults -> [{Tag, Val}]; + Val =/= Default -> [{Tag, Val}]; + true -> [] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate the contents of an app file +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_app(#app{name = Name, + info = #app_info{description = Desc, + id = Id, + vsn = Vsn, + modules = Mods, + maxP = MaxP, + maxT = MaxT, + registered = Regs, + incl_apps = InclApps, + applications = ReqApps, + env = Env, + mod = StartMod, + start_phases = StartPhases}}) -> + StartMod2 = + case StartMod =:= undefined of + true -> []; + false -> [{mod, StartMod}] + end, + {application, Name, + [{description, Desc}, + {vsn, Vsn}, + {id, Id}, + {modules, Mods}, + {registered, Regs}, + {applications, ReqApps}, + {included_applications, InclApps}, + {env, Env}, + {start_phases, StartPhases}, + {maxT, MaxT}, + {maxP, MaxP} | + StartMod2]}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate the contents of a rel file +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_rel(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps}, + #sys{apps = Apps}) -> + {value, Erts} = lists:keysearch(erts, #app.name, Apps), + {release, + {RelName, RelVsn}, + {erts, Erts#app.vsn}, + [app_to_rel(RA, Apps ) || RA <- RelApps]}. + +app_to_rel(#rel_app{name = Name, app_type = Type, incl_apps = InclApps}, Apps) -> + {value, #app{vsn = Vsn}} = lists:keysearch(Name, #app.name, Apps), + case {Type, InclApps} of + {undefined, []} -> {Name, Vsn}; + {undefined, _} -> {Name, Vsn, InclApps}; + {_, []} -> {Name, Vsn, Type}; + {_, _} -> {Name, Vsn, Type, InclApps} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate the contents of a boot file +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_boot({script, {_, _}, _} = Script) -> + {ok, term_to_binary(Script)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate the contents of a script file +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_script(Rel, Sys, PathFlag, Variables) -> + try + do_gen_script(Rel, Sys, PathFlag, Variables) + catch + throw:{error, Text} -> + {error, Text} + end. + +do_gen_script(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps}, + #sys{apps = Apps, app_type = DefaultType}, + PathFlag, + Variables) -> + {value, Erts} = lists:keysearch(erts, #app.name, Apps), + Preloaded = [Mod#mod.name || Mod <- Erts#app.mods], + Mandatory = mandatory_modules(), + Early = Mandatory ++ Preloaded, + MergedApps = [merge_app(RA, Apps, DefaultType) || RA <- RelApps], + SortedApps = sort_apps(MergedApps), + {value, KernelApp} = lists:keysearch(kernel, #app.name, SortedApps), + + InclApps = lists:append([I || #app{info = #app_info{incl_apps = I}} <- SortedApps]), + + %% Create the script + DeepList = + [ + %% Register preloaded modules + {preLoaded, lists:sort(Preloaded)}, + {progress, preloaded}, + + %% Load mandatory modules + {path, create_mandatory_path(SortedApps, PathFlag, Variables)}, + {primLoad, lists:sort(Mandatory)}, + {kernel_load_completed}, + {progress, kernel_load_completed}, + + %% Load remaining modules + [load_app_mods(A, Early, PathFlag, Variables) || A <- SortedApps], + {progress, modules_loaded}, + + %% Start kernel processes + {path, create_path(SortedApps, PathFlag, Variables)}, + kernel_processes(gen_app(KernelApp)), + {progress, init_kernel_started}, + + %% Load applications + [{apply, {application, load, [gen_app(A)]}} || + A = #app{name = Name, app_type = Type} <- SortedApps, + Name =/= kernel, + Type =/= none], + {progress, applications_loaded}, + + %% Start applications + [{apply, {application, start_boot, [Name, Type]}} || + #app{name = Name, app_type = Type} <- SortedApps, + Type =/= none, + Type =/= load, + not lists:member(Name, InclApps)], + + %% Apply user specific customizations + {apply, {c, erlangrc, []}}, + {progress, started} + ], + {ok, {script, {RelName, RelVsn}, lists:flatten(DeepList)}}. + +merge_app(#rel_app{name = Name, app_type = Type, incl_apps = RelIncl}, Apps, DefaultType) -> + {value, App} = lists:keysearch(Name, #app.name, Apps), + Type2 = + case {Type, App#app.app_type} of + {undefined, undefined} -> DefaultType; + {undefined, AppType} -> AppType; + {_, _} -> Type + end, + Info = App#app.info, + case RelIncl -- Info#app_info.incl_apps of + [] -> + App#app{app_type = Type2, info = Info#app_info{incl_apps = RelIncl}}; + BadIncl -> + reltool_utils:throw_error("~p: These applications are missing as " + "included_applications in the app file: ~p\n", + [Name, BadIncl]) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +load_app_mods(#app{mods = Mods} = App, Mand, PathFlag, Variables) -> + Path = cr_path(App, PathFlag, Variables), + PartNames = + lists:sort([{packages:split(M),M} || + #mod{name = M} <- Mods, + not lists:member(M, Mand)]), + SplitMods = + lists:foldl( + fun({Parts,M}, [{Last, Acc}|Rest]) -> + [_|Tail] = lists:reverse(Parts), + case lists:reverse(Tail) of + Subs when Subs == Last -> + [{Last,[M|Acc]}|Rest]; + Subs -> + [{Subs, [M]}|[{Last,Acc}|Rest]] + end + end, + [{[], + []}], + PartNames), + lists:foldl( + fun({Subs,Ms}, Cmds) -> + [{path, [filename:join([Path | Subs])]}, + {primLoad, lists:sort(Ms)} | Cmds] + end, + [], + SplitMods). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Function: sort_apps(Apps) -> {ok, Apps'} | throw({error, Error}) +%% Types: Apps = {{Name, Vsn}, #application}] +%% Purpose: Sort applications according to dependencies among +%% applications. If order doesn't matter, use the same +%% order as in the original list. +%% Alg. written by Ulf Wiger 970917 (etxuwig@etxb.ericsson.se) +%% Mod. by mbj +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sort_apps(Apps) -> + sort_apps(Apps, [], [], []). + +sort_apps([#app{name = Name, info = Info} = App | Apps], Missing, Circular, Visited) -> + {Uses, Apps1, NotFnd1} = find_all(Name, Info#app_info.applications, Apps, Visited, [], []), + {Incs, Apps2, NotFnd2} = find_all(Name, lists:reverse(Info#app_info.incl_apps), + Apps1, Visited, [], []), + + Missing1 = NotFnd1 ++ NotFnd2 ++ Missing, + case Uses ++ Incs of + [] -> + %% No more app that must be started before this one is + %% found; they are all already taken care of (and present + %% in Visited list) + [App | sort_apps(Apps, Missing1, Circular, [Name | Visited])]; + L -> + %% The apps in L must be started before the app. + %% Check if we have already taken care of some app in L, + %% in that case we have a circular dependency. + NewCircular = [N1 || N1 <- L, N2 <- Visited, N1 =:= N2], + Circular1 = case NewCircular of + [] -> Circular; + _ -> [Name | NewCircular] ++ Circular + end, + %% L must be started before N, try again, with all apps + %% in L added before N. + Apps3 = del_apps(NewCircular, L ++ [App | Apps2]), + sort_apps(Apps3, Missing1, Circular1, [Name | Visited]) + end; +sort_apps([], [], [], _) -> + []; +sort_apps([], Missing, [], _) -> + %% this has already been checked before, but as we have the info... + reltool_utils:throw_error("Undefined applications: ~p\n", [make_set(Missing)]); +sort_apps([], [], Circular, _) -> + reltool_utils:throw_error("Circular dependencies: ~p\n", [make_set(Circular)]); +sort_apps([], Missing, Circular, _) -> + reltool_utils:throw_error("Circular dependencies: ~p\n" + "Undefined applications: ~p\n", + [make_set(Circular), make_set(Missing)]). + +find_all(CheckingApp, [Name | Names], Apps, Visited, Found, NotFound) -> + case lists:keysearch(Name, #app.name, Apps) of + {value, #app{info = Info} = App} -> + %% It is OK to have a dependecy like + %% X includes Y, Y uses X. + case lists:member(CheckingApp, Info#app_info.incl_apps) of + true -> + case lists:member(Name, Visited) of + true -> + find_all(CheckingApp, Names, Apps, Visited, Found, NotFound); + false -> + find_all(CheckingApp, Names, Apps, Visited, Found, [Name | NotFound]) + end; + false -> + find_all(CheckingApp, Names, Apps -- [App], Visited, [App|Found], NotFound) + end; + false -> + case lists:member(Name, Visited) of + true -> + find_all(CheckingApp, Names, Apps, Visited, Found, NotFound); + false -> + find_all(CheckingApp, Names, Apps, Visited, Found, [Name|NotFound]) + end + end; +find_all(_CheckingApp, [], Apps, _Visited, Found, NotFound) -> + {Found, Apps, NotFound}. + +del_apps([Name | Names], Apps) -> + del_apps(Names, lists:keydelete(Name, #app.name, Apps)); +del_apps([], Apps) -> + Apps. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Create the load path used in the generated script. +%% If PathFlag is true a script intended to be used as a complete +%% system (e.g. in an embbeded system), i.e. all applications are +%% located under $ROOT/lib. +%% Otherwise all paths are set according to dir per application. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Create the complete path. +create_path(Apps, PathFlag, Variables) -> + make_set([cr_path(App, PathFlag, Variables) || App <- Apps]). + +%% Create the path to a specific application. +%% (The otp_build flag is only used for OTP internal system make) +cr_path(#app{label = Label}, true, []) -> + filename:join(["$ROOT", "lib", Label, "ebin"]); +cr_path(#app{name = Name, vsn = Vsn, label = Label, active_dir = Dir}, true, Variables) -> + Tail = [Label, "ebin"], + case variable_dir(Dir, atom_to_list(Name), Vsn, Variables) of + {ok, VarDir} -> + filename:join([VarDir] ++ Tail); + _ -> + filename:join(["$ROOT", "lib"] ++ Tail) + end; +cr_path(#app{name = Name}, otp_build, _) -> + filename:join(["$ROOT", "lib", atom_to_list(Name), "ebin"]); +cr_path(#app{active_dir = Dir}, _, _) -> + filename:join([Dir, "ebin"]). + +variable_dir(Dir, Name, Vsn, [{Var,Path} | Variables]) -> + case lists:prefix(Path, Dir) of + true -> + D0 = strip_prefix(Path, Dir), + case strip_name_ebin(D0, Name, Vsn) of + {ok, D} -> + {ok, filename:join(["\$" ++ Var] ++ D)}; + _ -> + %% We know at least that we are located + %% under the variable dir. + {ok, filename:join(["\$" ++ Var] ++ D0)} + end; + false -> + variable_dir(Dir, Name, Vsn, Variables) + end; +variable_dir(_Dir, _, _, []) -> + false. + +strip_prefix(Path, Dir) -> + L = length(filename:split(Path)), + lists:nthtail(L, filename:split(Dir)). + +strip_name_ebin(Dir, Name, Vsn) -> + FullName = Name ++ "-" ++ Vsn, + case lists:reverse(Dir) of + ["ebin", Name | D] -> {ok, lists:reverse(D)}; + ["ebin", FullName | D] -> {ok, lists:reverse(D)}; + _ -> false + end. + +%% Create the path to the kernel and stdlib applications. +create_mandatory_path(Apps, PathFlag, Variables) -> + Mandatory = [kernel, stdlib], + make_set(lists:map(fun(#app{name = Name} = App) -> + case lists:member(Name, Mandatory) of + true -> + cr_path(App, PathFlag, Variables); + false -> + "" + end + end, + Apps)). + +make_set([]) -> + []; +make_set([""|T]) -> % Ignore empty items. + make_set(T); +make_set([H|T]) -> + [H | [ Y || Y<- make_set(T), + Y =/= H]]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate rel, script and boot files +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_rel_files(Sys, TargetDir) -> + try + Spec = spec_rel_files(Sys), + eval_spec(Spec, Sys#sys.root_dir, TargetDir) + catch + throw:{error, Text} -> + {error, Text} + end. + +spec_rel_files(#sys{rels = Rels} = Sys) -> + lists:append([do_spec_rel_files(R, Sys) || R <- Rels]). + +do_spec_rel_files(#rel{name = Name} = Rel, Sys) -> + RelFile = Name ++ ".rel", + ScriptFile = Name ++ ".script", + BootFile = Name ++ ".boot", + GenRel = gen_rel(Rel, Sys), + PathFlag = true, + Variables = [], + {ok, Script} = do_gen_script(Rel, Sys, PathFlag, Variables), + {ok, BootBin} = gen_boot(Script), + Date = date(), + Time = time(), + RelIoList = io_lib:format("%% rel generated at ~w ~w\n~p.\n\n", + [Date, Time, GenRel]), + ScriptIoList = io_lib:format("%% script generated at ~w ~w\n~p.\n\n", + [Date, Time, Script]), + [ + {write_file, RelFile, RelIoList}, + {write_file, ScriptFile, ScriptIoList}, + {write_file, BootFile, BootBin} + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Generate a complete target system +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_target(Sys, TargetDir) -> + try + Spec = do_gen_spec(Sys), + eval_spec(Spec, Sys#sys.root_dir, TargetDir) + catch + throw:{error, Text} -> + {error, Text} + end. + +gen_spec(Sys) -> + try + {ok, do_gen_spec(Sys)} + catch + throw:{error, Text} -> + {error, Text} + end. + +do_gen_spec(#sys{root_dir = RootDir, + incl_sys_filters = InclRegexps, + excl_sys_filters = ExclRegexps, + relocatable = Relocatable, + apps = Apps} = Sys) -> + {create_dir, _, SysFiles} = spec_dir(RootDir), + {ExclRegexps2, SysFiles2} = strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps), + RelFiles = spec_rel_files(Sys), + {InclRegexps2, BinFiles} = spec_bin_files(Sys, SysFiles, SysFiles2, RelFiles, InclRegexps), + LibFiles = spec_lib_files(Sys), + {BootVsn, StartFile} = spec_start_file(Sys), + SysFiles3 = + [ + {create_dir, "releases", + [StartFile, + {create_dir,BootVsn, RelFiles}]}, + {create_dir, "bin", BinFiles} + ] ++ SysFiles2, + %% io:format("InclRegexps2: ~p\n", [InclRegexps2]), + %% io:format("ExclRegexps2: ~p\n", [ExclRegexps2]), + SysFiles4 = filter_spec(SysFiles3, InclRegexps2, ExclRegexps2), + SysFiles5 = SysFiles4 ++ [{create_dir, "lib", LibFiles}], + check_sys(["bin", "erts", "lib"], SysFiles5), + SysFiles5. + +strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps) -> + ExclRegexps2 = + case Relocatable of + true -> + ExtraExcl = ["^erts.*/bin/.*src$"], + reltool_utils:decode_regexps(excl_sys_filters, {add, ExtraExcl}, ExclRegexps); + false -> + ExclRegexps + end, + {value, Erts} = lists:keysearch(erts, #app.name, Apps), + FilterErts = + fun(Spec) -> + File = element(2, Spec), + case lists:prefix("erts", File) of + true -> + if + File =:= Erts#app.label -> + replace_dyn_erl(Relocatable, Spec); + true -> + false + end; + false -> + true + end + end, + SysFiles2 = lists:zf(FilterErts, SysFiles), + SysFiles3 = lists:foldl(fun(F, Acc) -> lists:keydelete(F, 2, Acc) end, + SysFiles2, + ["releases", "lib", "bin"]), + {ExclRegexps2, SysFiles3}. + +replace_dyn_erl(false, _ErtsSpec) -> + true; +replace_dyn_erl(true, {create_dir, ErtsDir, ErtsFiles}) -> + [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles), + case lookup_spec("dyn_erl", BinFiles) of + [] -> + case lookup_spec("erl.ini", BinFiles) of + [] -> + true; + [{copy_file, ErlIni}] -> + %% Remove Windows .ini file + BinFiles2 = lists:keydelete(ErlIni, 2, BinFiles), + ErtsFiles2 = lists:keyreplace("bin", 2, ErtsFiles, {create_dir, "bin", BinFiles2}), + {true, {create_dir, ErtsDir, ErtsFiles2}} + end; + [{copy_file, DynErlExe}] -> + %% Replace erl with dyn_erl + ErlExe = "erl" ++ filename:extension(DynErlExe), + BinFiles2 = lists:keydelete(DynErlExe, 2, BinFiles), + DynErlExe2 = filename:join([ErtsDir, "bin", DynErlExe]), + BinFiles3 = lists:keyreplace(ErlExe, 2, BinFiles2, {copy_file, ErlExe, DynErlExe2}), + ErtsFiles2 = lists:keyreplace("bin", 2, ErtsFiles, {create_dir, "bin", BinFiles3}), + {true, {create_dir, ErtsDir, ErtsFiles2}} + end. + +spec_bin_files(Sys, AllSysFiles, StrippedSysFiles, RelFiles, InclRegexps) -> + [{create_dir, ErtsLabel, ErtsFiles}] = safe_lookup_spec("erts", StrippedSysFiles), + [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles), + ErtsBin = filename:join([ErtsLabel, "bin"]), + Escripts = spec_escripts(Sys, ErtsBin, BinFiles), + Map = fun({copy_file, File}) -> + {copy_file, File, filename:join([ErtsBin, File])}; + ({copy_file, NewFile, OldFile}) -> + {_, OldFile2} = abs_to_rel_path(ErtsBin, filename:join([ErtsBin, OldFile])), + {copy_file, NewFile, OldFile2} + end, + + %% Do only copy those bin files from erts/bin that also exists in bin + [{create_dir, _, OldBinFiles}] = safe_lookup_spec("bin", AllSysFiles), + GoodNames = [F || {copy_file, F} <- OldBinFiles, + not lists:suffix(".boot", F), + not lists:suffix(".script", F)], + BinFiles2 = [Map(S) || S <- BinFiles, lists:member(element(2, S), GoodNames)], + BootFiles = [F || F <- RelFiles, lists:suffix(".boot", element(2, F))], + [{write_file, _, BootRel}] = safe_lookup_spec(Sys#sys.boot_rel ++ ".boot", BootFiles), + BootFiles2 = lists:keystore("start.boot", 2, BootFiles, {write_file, "start.boot", BootRel}), + MakeRegexp = fun(File) -> "^bin/" ++ element(2, File) ++ "(|.escript)$" end, + ExtraIncl = lists:map(MakeRegexp, Escripts), + InclRegexps2 = reltool_utils:decode_regexps(incl_sys_filters, {add, ExtraIncl}, InclRegexps), + {InclRegexps2, Escripts ++ BinFiles2 ++ BootFiles2}. + +spec_escripts(#sys{apps = Apps}, ErtsBin, BinFiles) -> + Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl, + is_pre_included = IsPre, name = Name, active_dir = File}) -> + if + Name =:= ?MISSING_APP -> + false; + not IsEscript -> + false; + IsIncl; IsPre -> + {true, do_spec_escript(File, ErtsBin, BinFiles)}; + true -> + false + end + end, + lists:flatten(lists:zf(Filter, Apps)). + +do_spec_escript(File, ErtsBin, BinFiles) -> + [{copy_file, EscriptExe}] = safe_lookup_spec("escript", BinFiles), + EscriptExt = ".escript", + Base = filename:basename(File, EscriptExt), + ExeExt = filename:extension(EscriptExe), + [{copy_file, Base ++ EscriptExt, File}, + {copy_file, Base ++ ExeExt, filename:join([ErtsBin, EscriptExe])}]. + +check_sys(Mandatory, SysFiles) -> + lists:foreach(fun(M) -> do_check_sys(M, SysFiles) end, Mandatory). + +do_check_sys(Prefix, Specs) -> + %%io:format("Prefix: ~p\n", [Prefix]), + case lookup_spec(Prefix, Specs) of + [] -> + reltool_utils:throw_error("Mandatory system directory ~s is not included", + [Prefix]); + _ -> + ok + end. + +spec_start_file(#sys{boot_rel = BootRelName, + rels = Rels, + apps = Apps}) -> + {value, Erts} = lists:keysearch(erts, #app.name, Apps), + {value, BootRel} = lists:keysearch(BootRelName, #rel.name, Rels), + Data = Erts#app.vsn ++ " " ++ BootRel#rel.vsn ++ "\n", + {BootRel#rel.vsn, {write_file, "start_erl.data", Data}}. + +lookup_spec(Prefix, Specs) -> + lists:filter(fun(S) -> lists:prefix(Prefix, element(2, S)) end, Specs). + +safe_lookup_spec(Prefix, Specs) -> + case lookup_spec(Prefix, Specs) of + [] -> + reltool_utils:throw_error("Mandatory system file ~s is not included", [Prefix]); + Match -> + Match + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Specify applications +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +spec_lib_files(#sys{apps = Apps} = Sys) -> + Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl, + is_pre_included = IsPre, name = Name}) -> + if + Name =:= ?MISSING_APP -> + false; + IsEscript -> + false; + IsIncl; IsPre -> + true; + true -> + false + end + end, + SelectedApps = lists:filter(Filter, Apps), + check_apps([kernel, stdlib], SelectedApps), + lists:flatten([spec_app(App, Sys) || App <- SelectedApps]). + +check_apps([Mandatory | Names], Apps) -> + case lists:keymember(Mandatory, #app.name, Apps) of + false -> + reltool_utils:throw_error("Mandatory application ~p is not included in ~p", + [Mandatory, Apps]); + true -> + check_apps(Names, Apps) + end; +check_apps([], _) -> + ok. + +spec_app(#app{name = Name, + mods = Mods, + active_dir = SourceDir, + incl_app_filters = AppInclRegexps, + excl_app_filters = AppExclRegexps} = App, + #sys{incl_app_filters = SysInclRegexps, + excl_app_filters = SysExclRegexps, + debug_info = SysDebugInfo} = Sys) -> + %% List files recursively + {create_dir, _, AppFiles} = spec_dir(SourceDir), + + %% Replace ebin + AppUpFilename = atom_to_list(Name) ++ ".appup", + EbinDir = filename:join([SourceDir, "ebin"]), + OptAppUpFileSpec = spec_opt_copy_file(EbinDir, AppUpFilename), + OptAppFileSpec = spec_app_file(App, Sys, EbinDir), + ModSpecs = [spec_mod(M, SysDebugInfo) || M <- Mods, M#mod.is_included, M#mod.exists], + NewEbin = {create_dir, "ebin", OptAppUpFileSpec ++ OptAppFileSpec ++ ModSpecs}, + AppFiles2 = lists:keystore("ebin", 2, AppFiles, NewEbin), + + %% Apply file filter + InclRegexps = reltool_utils:default_val(AppInclRegexps, SysInclRegexps), + ExclRegexps = reltool_utils:default_val(AppExclRegexps, SysExclRegexps), + AppFiles3 = filter_spec(AppFiles2, InclRegexps, ExclRegexps), + + %% Regular top directory and/or archive + spec_archive(App, Sys, AppFiles3). + +spec_archive(#app{label = Label, + active_dir = SourceDir, + incl_archive_filters = AppInclArchiveDirs, + excl_archive_filters = AppExclArchiveDirs, + archive_opts = AppArchiveOpts}, + #sys{root_dir = RootDir, + incl_archive_filters = SysInclArchiveDirs, + excl_archive_filters = SysExclArchiveDirs, + archive_opts = SysArchiveOpts}, + Files) -> + InclArchiveDirs = reltool_utils:default_val(AppInclArchiveDirs, SysInclArchiveDirs), + ExclArchiveDirs = reltool_utils:default_val(AppExclArchiveDirs, SysExclArchiveDirs), + ArchiveOpts = reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts), + Match = fun(F) -> match(element(2, F), InclArchiveDirs, ExclArchiveDirs) end, + case lists:filter(Match, Files) of + [] -> + %% Nothing to archive + [spec_create_dir(RootDir, SourceDir, Label, Files)]; + ArchiveFiles -> + OptDir = + case Files -- ArchiveFiles of + [] -> + []; + ExternalFiles -> + [spec_create_dir(RootDir, SourceDir, Label, ExternalFiles)] + end, + ArchiveOpts = reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts), + ArchiveDir = spec_create_dir(RootDir, SourceDir, Label, ArchiveFiles), + [{archive, Label ++ ".ez", ArchiveOpts, [ArchiveDir]} | OptDir] + end. + +spec_dir(Dir) -> + Base = filename:basename(Dir), + case erl_prim_loader:read_file_info(Dir) of + {ok, #file_info{type = directory}} -> + case erl_prim_loader:list_dir(Dir) of + {ok, Files} -> + %% Directory + {create_dir, Base, [spec_dir(filename:join([Dir, F])) || F <- Files]}; + error -> + reltool_utils:throw_error("list dir ~s failed\n", [Dir]) + end; + {ok, #file_info{type = regular}} -> + %% Plain file + {copy_file, Base}; + _ -> + reltool_utils:throw_error("read file info ~s failed\n", [Dir]) + end. + +spec_mod(Mod, DebugInfo) -> + File = atom_to_list(Mod#mod.name) ++ code:objfile_extension(), + case reltool_utils:default_val(Mod#mod.debug_info, DebugInfo) of + keep -> + {copy_file, File}; + strip -> + {strip_beam, File} + end. + +spec_app_file(#app{name = Name, + info = Info, + mods = Mods, + app_file = AppFile} = App, + #sys{app_file = SysAppFile}, + EbinDir) -> + AppFilename = atom_to_list(Name) ++ ".app", + case reltool_utils:default_val(AppFile, SysAppFile) of + keep -> + %% Copy if it exists + spec_opt_copy_file(EbinDir, AppFilename); + strip -> + %% Remove non-included modules + %% Generate new file + ModNames = [M#mod.name || M <- Mods, + M#mod.is_included, + lists:member(M#mod.name, + Info#app_info.modules)], + App2 = App#app{info = Info#app_info{modules = ModNames}}, + Contents = gen_app(App2), + AppIoList = io_lib:format("%% app generated at ~w ~w\n~p.\n\n", + [date(), time(), Contents]), + [{write_file, AppFilename, AppIoList}]; + all -> + %% Include all included modules + %% Generate new file + ModNames = [M#mod.name || M <- Mods, M#mod.is_included], + App2 = App#app{info = Info#app_info{modules = ModNames}}, + Contents = gen_app(App2), + AppIoList = io_lib:format("%% app generated at ~w ~w\n~p.\n\n", + [date(), time(), Contents]), + [{write_file, AppFilename, AppIoList}] + + end. + +spec_opt_copy_file(DirName, BaseName) -> + case filelib:is_regular(filename:join([DirName, BaseName]), erl_prim_loader) of + true -> [{copy_file, BaseName}]; + false -> [] + end. + +spec_create_dir(RootDir, SourceDir, BaseDir, Files) -> + LibDir = filename:join([RootDir, "lib"]), + case abs_to_rel_path(LibDir, SourceDir) of + {relative, Dir} -> {create_dir, Dir, Files}; + {absolute, Dir} -> {create_dir, BaseDir, Dir, Files} + end. + +abs_to_rel_path(RootDir, SourcePath) -> + R = filename:split(RootDir), + S = filename:split(SourcePath), + abs_to_rel_path(R, S, SourcePath). + +abs_to_rel_path([H | R], [H | S], SourcePath) -> + abs_to_rel_path(R, S, SourcePath); +abs_to_rel_path([], S, _SourcePath) -> + {relative, filename:join(S)}; +abs_to_rel_path(_, _, SourcePath) -> + {absolute, SourcePath}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Evaluate specification +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +eval_spec(Spec, SourceDir, TargetDir) -> + SourceDir2 = filename:absname(SourceDir), + TargetDir2 = filename:absname(TargetDir), + try + case filelib:is_dir(TargetDir2) of + true -> + do_eval_spec(Spec, SourceDir2, SourceDir2, TargetDir2), + ok; + false -> + {error, TargetDir2 ++ ": " ++ file:format_error(enoent)} + end + catch + throw:{error, Text} -> + cleanup_spec(Spec, TargetDir2), + {error, Text} + end. + +do_eval_spec(List, OrigSourceDir, SourceDir, TargetDir) when is_list(List) -> + lists:foreach(fun(F) -> do_eval_spec(F, OrigSourceDir, SourceDir, TargetDir) end, List); +%% do_eval_spec({source_dir, SourceDir2, Spec}, OrigSourceDir, _SourceDir, TargetDir) -> +%% %% Source dir is absolute or relative the original source dir +%% SourceDir3 = filename:join([OrigSourceDir, SourceDir2]), +%% do_eval_spec(Spec, OrigSourceDir, SourceDir3, TargetDir); +do_eval_spec({create_dir, Dir, Files}, OrigSourceDir, SourceDir, TargetDir) -> + SourceDir2 = filename:join([SourceDir, Dir]), + TargetDir2 = filename:join([TargetDir, Dir]), + reltool_utils:create_dir(TargetDir2), + do_eval_spec(Files, OrigSourceDir, SourceDir2, TargetDir2); +do_eval_spec({create_dir, NewDir, OldDir, Files}, OrigSourceDir, _SourceDir, TargetDir) -> + SourceDir2 = filename:join([OrigSourceDir, OldDir]), + TargetDir2 = filename:join([TargetDir, NewDir]), + reltool_utils:create_dir(TargetDir2), + do_eval_spec(Files, SourceDir2, SourceDir2, TargetDir2); +do_eval_spec({archive, Archive, Options, Files}, OrigSourceDir, SourceDir, TargetDir) -> + TmpSpec = {create_dir, "tmp", Files}, + TmpDir = filename:join([TargetDir, "tmp"]), + reltool_utils:create_dir(TmpDir), + do_eval_spec(Files, OrigSourceDir, SourceDir, TmpDir), + + ArchiveFile = filename:join([TargetDir, Archive]), + Files2 = [element(2, F) || F <- Files], + Res = zip:create(ArchiveFile, Files2, [{cwd, TmpDir} | Options]), + + cleanup_spec(TmpSpec, TargetDir), + case Res of + {ok, _} -> + ok; + {error, Reason} -> + reltool_utils:throw_error("create archive ~s: ~p\n", [ArchiveFile, Reason]) + end; +do_eval_spec({copy_file, File}, _OrigSourceDir, SourceDir, TargetDir) -> + SourceFile = filename:join([SourceDir, File]), + TargetFile = filename:join([TargetDir, File]), + reltool_utils:copy_file(SourceFile, TargetFile); +do_eval_spec({copy_file, NewFile, OldFile}, OrigSourceDir, _SourceDir, TargetDir) -> + SourceFile = filename:join([OrigSourceDir, OldFile]), + TargetFile = filename:join([TargetDir, NewFile]), + reltool_utils:copy_file(SourceFile, TargetFile); +do_eval_spec({write_file, File, IoList}, _OrigSourceDir, _SourceDir, TargetDir) -> + TargetFile = filename:join([TargetDir, File]), + reltool_utils:write_file(TargetFile, IoList); +do_eval_spec({strip_beam, File}, _OrigSourceDir, SourceDir, TargetDir) -> + SourceFile = filename:join([SourceDir, File]), + TargetFile = filename:join([TargetDir, File]), + BeamBin = reltool_utils:read_file(SourceFile), + {ok, {_, BeamBin2}} = beam_lib:strip(BeamBin), + reltool_utils:write_file(TargetFile, BeamBin2). + +cleanup_spec(List, TargetDir) when is_list(List) -> + lists:foreach(fun(F)-> cleanup_spec(F, TargetDir) end, List); +%% cleanup_spec({source_dir, _SourceDir, Spec}, TargetDir) -> +%% cleanup_spec(Spec, TargetDir); +cleanup_spec({create_dir, Dir, Files}, TargetDir) -> + TargetDir2 = filename:join([TargetDir, Dir]), + cleanup_spec(Files, TargetDir2), + file:del_dir(TargetDir2); +cleanup_spec({create_dir, NewDir, _OldDir, Files}, TargetDir) -> + TargetDir2 = filename:join([TargetDir, NewDir]), + cleanup_spec(Files, TargetDir2), + file:del_dir(TargetDir2); +cleanup_spec({archive, Archive, _Options, Files}, TargetDir) -> + TargetFile = filename:join([TargetDir, Archive]), + file:delete(TargetFile), + TmpDir = filename:join([TargetDir, "tmp"]), + cleanup_spec(Files, TmpDir), + file:del_dir(TmpDir); +cleanup_spec({copy_file, File}, TargetDir) -> + TargetFile = filename:join([TargetDir, File]), + file:delete(TargetFile); +cleanup_spec({copy_file, NewFile, _OldFile}, TargetDir) -> + TargetFile = filename:join([TargetDir, NewFile]), + file:delete(TargetFile); +cleanup_spec({write_file, File, _IoList}, TargetDir) -> + TargetFile = filename:join([TargetDir, File]), + file:delete(TargetFile); +cleanup_spec({strip_beam, File}, TargetDir) -> + TargetFile = filename:join([TargetDir, File]), + file:delete(TargetFile). + +filter_spec(List, InclRegexps, ExclRegexps) -> + do_filter_spec("", List, InclRegexps, ExclRegexps). + +do_filter_spec(Path, List, InclRegexps, ExclRegexps) when is_list(List) -> + lists:zf(fun(File) -> do_filter_spec(Path, File, InclRegexps, ExclRegexps) end, List); +%% do_filter_spec(Path, {source_dir, _SourceDir, Spec}, InclRegexps, ExclRegexps) -> +%% do_filter_spec(Path, Spec, InclRegexps, ExclRegexps); +do_filter_spec(Path, {create_dir, Dir, Files}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, Dir), + case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of + [] -> + case match(Path2, InclRegexps, ExclRegexps) of + true -> + {true, {create_dir, Dir, []}}; + false -> + false + end; + Files2 when is_list(Files2) -> + {true, {create_dir, Dir, Files2}} + end; +do_filter_spec(Path, {create_dir, NewDir, OldDir, Files}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, NewDir), + case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of + [] -> + case match(Path2, InclRegexps, ExclRegexps) of + true -> + {true, {create_dir, NewDir, OldDir, []}}; + false -> + false + end; + Files2 when is_list(Files2) -> + {true, {create_dir, NewDir, OldDir, Files2}} + end; +do_filter_spec(Path, {archive, Archive, Options, Files}, InclRegexps, ExclRegexps) -> + case do_filter_spec(Path, Files, InclRegexps, ExclRegexps) of + [] -> + case match(Path, InclRegexps, ExclRegexps) of + true -> + {true, {archive, Archive, Options, []}}; + false -> + false + end; + Files2 when is_list(Files2) -> + {true, {archive, Archive, Options, Files2}} + end; +do_filter_spec(Path, {copy_file, File}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, File), + match(Path2, InclRegexps, ExclRegexps); +do_filter_spec(Path, {copy_file, NewFile, _OldFile}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, NewFile), + match(Path2, InclRegexps, ExclRegexps); +do_filter_spec(Path, {write_file, File, _IoList}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, File), + match(Path2, InclRegexps, ExclRegexps); +do_filter_spec(Path, {strip_beam, File}, InclRegexps, ExclRegexps) -> + Path2 = opt_join(Path, File), + match(Path2, InclRegexps, ExclRegexps). + +opt_join([], File) -> + File; +opt_join(Path, File) -> + filename:join([Path, File]). + +match(String, InclRegexps, ExclRegexps) -> + %%case + match(String, InclRegexps) andalso not match(String, ExclRegexps). +%% of +%% true -> +%% true; +%% false -> +%% io:format("no match: ~p\n" +%% " incl: ~p\n" +%% " excl: ~p\n", +%% [String, InclRegexps, ExclRegexps]), +%% false +%% end. + +%% Match at least one regexp +match(_String, []) -> + false; +match(String, [#regexp{source = _, compiled = MP} | Regexps]) -> + %% io:format("Regexp: ~p ~p\n", [String, Regexp]), + case re:run(String, MP, [{capture, none}]) of + nomatch -> match(String, Regexps); + match -> true + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +install(RelName, TargetDir) -> + try + do_install(RelName, TargetDir) + catch + throw:{error, Text} -> + {error, Text} + end. + +do_install(RelName, TargetDir) -> + TargetDir2 = filename:absname(TargetDir), + RelDir = filename:join([TargetDir2, "releases"]), + DataFile = filename:join([RelDir, "start_erl.data"]), + Bin = reltool_utils:read_file(DataFile), + case string:tokens(binary_to_list(Bin), " \n") of + [ErlVsn, RelVsn | _] -> + ErtsBinDir = filename:join([TargetDir2, "erts-" ++ ErlVsn, "bin"]), + BinDir = filename:join([TargetDir2, "bin"]), + case os:type() of + {win32, _} -> + NativeRootDir = filename:nativename(TargetDir2), + %% NativeBinDir = filename:nativename(filename:join([BinDir, "win32"])), + NativeBinDir = filename:nativename(BinDir), + IniData = ["[erlang]\r\n", + "Bindir=", NativeBinDir, "\r\n", + "Progname=erl\r\n", + "Rootdir=", NativeRootDir, "\r\n"], + IniFile = filename:join([BinDir, "erl.ini"]), + ok = file:write_file(IniFile, IniData); + _ -> + subst_src_scripts(start_scripts(), ErtsBinDir, BinDir, + [{"FINAL_ROOTDIR", TargetDir2}, {"EMU", "beam"}], + [preserve]) + end, + RelFile = filename:join([RelDir, RelVsn, RelName ++ ".rel"]), + ok = release_handler:create_RELEASES(TargetDir2, RelFile), + ok; + _ -> + reltool_utils:throw_error("~s: Illegal syntax.\n", [DataFile]) + end. + +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> + Fun = fun(Script) -> subst_src_script(Script, SrcDir, DestDir, Vars, Opts) end, + lists:foreach(Fun, Scripts). + +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> + subst_file(filename:join([SrcDir, Script ++ ".src"]), + filename:join([DestDir, Script]), + Vars, + Opts). + +subst_file(Src, Dest, Vars, Opts) -> + Bin = reltool_utils:read_file(Src), + Chars = subst(binary_to_list(Bin), Vars), + reltool_utils:write_file(Dest, Chars), + case lists:member(preserve, Opts) of + true -> + FileInfo = reltool_utils:read_file_info(Src), + reltool_utils:write_file_info(Dest, FileInfo); + false -> + ok + end. + +%% subst(Str, Vars) +%% Vars = [{Var, Val}] +%% Var = Val = string() +%% Substitute all occurrences of %Var% for Val in Str, using the list +%% of variables in Vars. +%% +subst(Str, Vars) -> + subst(Str, Vars, []). + +subst([$%, C | Rest], Vars, Result) when $A =< C, C =< $Z -> + subst_var([C| Rest], Vars, Result, []); +subst([$%, C | Rest], Vars, Result) when $a =< C, C =< $z -> + subst_var([C| Rest], Vars, Result, []); +subst([$%, C | Rest], Vars, Result) when C == $_ -> + subst_var([C| Rest], Vars, Result, []); +subst([C| Rest], Vars, Result) -> + subst(Rest, Vars, [C| Result]); +subst([], _Vars, Result) -> + lists:reverse(Result). + +subst_var([$%| Rest], Vars, Result, VarAcc) -> + Key = lists:reverse(VarAcc), + case lists:keysearch(Key, 1, Vars) of + {value, {Key, Value}} -> + subst(Rest, Vars, lists:reverse(Value, Result)); + false -> + subst(Rest, Vars, [$% | VarAcc ++ [$% | Result]]) + end; +subst_var([C| Rest], Vars, Result, VarAcc) -> + subst_var(Rest, Vars, Result, [C| VarAcc]); +subst_var([], Vars, Result, VarAcc) -> + subst([], Vars, [VarAcc ++ [$% | Result]]). + + +start_scripts() -> + ["erl", "start", "start_erl"]. -- cgit v1.2.3