%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(systools_make).
%% Purpose : Create start script. RelName.rel --> RelName.{script,boot}.
%% and create a tar file of a release (RelName.tar.gz)
-export([make_script/1, make_script/2, make_script/3,
make_tar/1, make_tar/2]).
-export([format_error/1, format_warning/1]).
-export([read_release/2, get_release/2, get_release/3,
get_release/4, pack_app/1]).
-export([read_application/4]).
-export([make_hybrid_boot/4]).
-import(lists, [filter/2, keysort/2, keysearch/3, map/2, reverse/1,
append/1, foldl/3, member/2, foreach/2]).
-include("systools.hrl").
-include_lib("kernel/include/file.hrl").
-define(XREF_SERVER, systools_make).
-compile({inline,[{badarg,2}]}).
%%-----------------------------------------------------------------
%% Create a boot script from a release file.
%% Options is a list of {path, Path} | silent | local
%% | warnings_as_errors
%% where path sets the search path, silent supresses error message
%% printing on console, local generates a script with references
%% to the directories there the applications are found,
%% and warnings_as_errors treats warnings as errors.
%%
%% New options: {path,Path} can contain wildcards
%% src_tests
%% {variables,[{Name,AbsString}]}
%% {machine, jam | beam | vee}
%% exref | {exref, [AppName]}
%% no_warn_sasl
%%-----------------------------------------------------------------
make_script(RelName) when is_list(RelName) ->
make_script(RelName, []);
make_script(RelName) ->
badarg(RelName,[RelName]).
make_script(RelName, Flags) when is_list(RelName), is_list(Flags) ->
case get_outdir(Flags) of
"" ->
make_script(RelName, RelName, Flags);
OutDir ->
%% To maintain backwards compatibility for make_script/3,
%% the boot script file name is constructed here, before
%% checking the validity of OutDir
%% (is done in check_args_script/1)
Output = filename:join(OutDir, filename:basename(RelName)),
make_script(RelName, Output, Flags)
end.
make_script(RelName, Output, Flags) when is_list(RelName),
is_list(Output),
is_list(Flags) ->
case check_args_script(Flags) of
[] ->
Path0 = get_path(Flags),
Path1 = mk_path(Path0), % expand wildcards etc.
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
case get_release(RelName, Path, ModTestP, machine(Flags)) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
true ->
Warnings1 = [W || {warning,W}<-Warnings],
return({error,?MODULE,
{warnings_treated_as_errors,Warnings1}},
Warnings,
Flags);
false ->
case generate_script(Output,Release,Appls,Flags) of
ok ->
return(ok,Warnings,Flags);
Error ->
return(Error,Warnings,Flags)
end
end;
Error ->
return(Error,[],Flags)
end;
ErrorVars ->
badarg(ErrorVars, [RelName, Flags])
end;
make_script(RelName, _Output, Flags) when is_list(Flags) ->
badarg(RelName,[RelName, Flags]);
make_script(RelName, _Output, Flags) ->
badarg(Flags,[RelName, Flags]).
wsasl(Options, Warnings) ->
case lists:member(no_warn_sasl,Options) of
true -> lists:delete({warning,missing_sasl},Warnings);
false -> Warnings
end.
badarg(BadArg, Args) ->
erlang:error({badarg,BadArg}, Args).
machine(Flags) ->
case get_flag(machine,Flags) of
{machine, Machine} when is_atom(Machine) -> Machine;
_ -> false
end.
get_path(Flags) ->
case get_flag(path,Flags) of
{path,Path} when is_list(Path) -> Path;
_ -> []
end.
get_outdir(Flags) ->
case get_flag(outdir,Flags) of
{outdir,OutDir} when is_list(OutDir) ->
OutDir;
_ -> % false | {outdir, Badarg}
""
end.
return(ok,Warnings,Flags) ->
case member(silent,Flags) of
true ->
{ok,?MODULE,Warnings};
_ ->
io:format("~ts",[format_warning(Warnings)]),
ok
end;
return({error,Mod,Error},_,Flags) ->
case member(silent,Flags) of
true ->
{error,Mod,Error};
_ ->
io:format("~ts",[Mod:format_error(Error)]),
error
end.
%%-----------------------------------------------------------------
%% Make hybrid boot file for upgrading emulator. The resulting boot
%% file is a combination of the two input files, where kernel, stdlib
%% and sasl versions are taken from the second file (the boot file of
%% the new release), and all other application versions from the first
%% file (the boot file of the old release).
%%
%% The most important thing that can fail here is that the input boot
%% files do not contain all three base applications - kernel, stdlib
%% and sasl.
%%
%% TmpVsn = string(),
%% Returns {ok,Boot} | {error,Reason}
%% Boot1 = Boot2 = Boot = binary()
%% Reason = {app_not_found,App} | {app_not_replaced,App}
%% App = stdlib | sasl
make_hybrid_boot(TmpVsn, Boot1, Boot2, Args) ->
catch do_make_hybrid_boot(TmpVsn, Boot1, Boot2, Args).
do_make_hybrid_boot(TmpVsn, OldBoot, NewBoot, Args) ->
{script,{_RelName1,_RelVsn1},OldScript} = binary_to_term(OldBoot),
{script,{NewRelName,_RelVsn2},NewScript} = binary_to_term(NewBoot),
%% Everyting upto kernel_load_completed must come from the new script
Fun1 = fun({progress,kernel_load_completed}) -> false;
(_) -> true
end,
{_OldKernelLoad,OldRest1} = lists:splitwith(Fun1,OldScript),
{NewKernelLoad,NewRest1} = lists:splitwith(Fun1,NewScript),
Fun2 = fun({progress,modules_loaded}) -> false;
(_) -> true
end,
{OldModLoad,OldRest2} = lists:splitwith(Fun2,OldRest1),
{NewModLoad,NewRest2} = lists:splitwith(Fun2,NewRest1),
Fun3 = fun({kernelProcess,_,_}) -> false;
(_) -> true
end,
{OldPaths,OldRest3} = lists:splitwith(Fun3,OldRest2),
{NewPaths,NewRest3} = lists:splitwith(Fun3,NewRest2),
Fun4 = fun({progress,init_kernel_started}) -> false;
(_) -> true
end,
{_OldKernelProcs,OldApps} = lists:splitwith(Fun4,OldRest3),
{NewKernelProcs,NewApps} = lists:splitwith(Fun4,NewRest3),
%% Then comes all module load, which for each app consist of:
%% {path,[AppPath]},
%% {primLoad,ModuleList}
%% Replace kernel, stdlib and sasl here
MatchPaths = get_regexp_path(),
ModLoad = replace_module_load(OldModLoad,NewModLoad,MatchPaths),
Paths = replace_paths(OldPaths,NewPaths,MatchPaths),
{Stdlib,Sasl} = get_apps(NewApps,undefined,undefined),
Apps0 = replace_apps(OldApps,Stdlib,Sasl),
Apps = add_apply_upgrade(Apps0,Args),
Script = NewKernelLoad++ModLoad++Paths++NewKernelProcs++Apps,
Boot = term_to_binary({script,{NewRelName,TmpVsn},Script}),
{ok,Boot}.
%% For each app, compile a regexp that can be used for finding its path
get_regexp_path() ->
{ok,KernelMP} = re:compile("kernel-[0-9\.]+",[unicode]),
{ok,StdlibMP} = re:compile("stdlib-[0-9\.]+",[unicode]),
{ok,SaslMP} = re:compile("sasl-[0-9\.]+",[unicode]),
[KernelMP,StdlibMP,SaslMP].
replace_module_load(Old,New,[MP|MatchPaths]) ->
replace_module_load(do_replace_module_load(Old,New,MP),New,MatchPaths);
replace_module_load(Script,_,[]) ->
Script.
do_replace_module_load([{path,[OldAppPath]},{primLoad,OldMods}|OldRest],New,MP) ->
case re:run(OldAppPath,MP,[{capture,none}]) of
nomatch ->
[{path,[OldAppPath]},{primLoad,OldMods}|
do_replace_module_load(OldRest,New,MP)];
match ->
get_module_load(New,MP) ++ OldRest
end;
do_replace_module_load([Other|Rest],New,MP) ->
[Other|do_replace_module_load(Rest,New,MP)];
do_replace_module_load([],_,_) ->
[].
get_module_load([{path,[AppPath]},{primLoad,Mods}|Rest],MP) ->
case re:run(AppPath,MP,[{capture,none}]) of
nomatch ->
get_module_load(Rest,MP);
match ->
[{path,[AppPath]},{primLoad,Mods}]
end;
get_module_load([_|Rest],MP) ->
get_module_load(Rest,MP);
get_module_load([],_) ->
[].
replace_paths([{path,OldPaths}|Old],New,MatchPaths) ->
{path,NewPath} = lists:keyfind(path,1,New),
[{path,do_replace_paths(OldPaths,NewPath,MatchPaths)}|Old];
replace_paths([Other|Old],New,MatchPaths) ->
[Other|replace_paths(Old,New,MatchPaths)].
do_replace_paths(Old,New,[MP|MatchPaths]) ->
do_replace_paths(do_replace_paths1(Old,New,MP),New,MatchPaths);
do_replace_paths(Paths,_,[]) ->
Paths.
do_replace_paths1([P|Ps],New,MP) ->
case re:run(P,MP,[{capture,none}]) of
nomatch ->
[P|do_replace_paths1(Ps,New,MP)];
match ->
get_path(New,MP) ++ Ps
end;
do_replace_paths1([],_,_) ->
[].
get_path([P|Ps],MP) ->
case re:run(P,MP,[{capture,none}]) of
nomatch ->
get_path(Ps,MP);
match ->
[P]
end;
get_path([],_) ->
[].
%% Return the entries for loading stdlib and sasl
get_apps([{apply,{application,load,[{application,stdlib,_}]}}=Stdlib|Script],
_,Sasl) ->
get_apps(Script,Stdlib,Sasl);
get_apps([{apply,{application,load,[{application,sasl,_}]}}=Sasl|_Script],
Stdlib,_) ->
{Stdlib,Sasl};
get_apps([_|Script],Stdlib,Sasl) ->
get_apps(Script,Stdlib,Sasl);
get_apps([],undefined,_) ->
throw({error,{app_not_found,stdlib}});
get_apps([],_,undefined) ->
throw({error,{app_not_found,sasl}}).
%% Replace the entries for loading the stdlib and sasl
replace_apps([{apply,{application,load,[{application,stdlib,_}]}}|Script],
Stdlib,Sasl) ->
[Stdlib|replace_apps(Script,undefined,Sasl)];
replace_apps([{apply,{application,load,[{application,sasl,_}]}}|Script],
_Stdlib,Sasl) ->
[Sasl|Script];
replace_apps([Stuff|Script],Stdlib,Sasl) ->
[Stuff|replace_apps(Script,Stdlib,Sasl)];
replace_apps([],undefined,_) ->
throw({error,{app_not_replaced,sasl}});
replace_apps([],_,_) ->
throw({error,{app_not_replaced,stdlib}}).
%% Finally add an apply of release_handler:new_emulator_upgrade - which will
%% complete the execution of the upgrade script (relup).
add_apply_upgrade(Script,Args) ->
[{progress, started} | RevScript] = lists:reverse(Script),
lists:reverse([{progress,started},
{apply,{release_handler,new_emulator_upgrade,Args}} |
RevScript]).
%%-----------------------------------------------------------------
%% Create a release package from a release file.
%% Options is a list of {path, Path} | silent |
%% {dirs, [src,include,examples,..]} | {erts, ErtsDir} where path
%% sets the search path, silent supresses error message printing,
%% dirs includes the specified directories (per application) in the
%% release package and erts specifies that the erts-Vsn/bin directory
%% should be included in the release package and there it can be found.
%%
%% New options: {path,Path} can contain wildcards
%% src_tests
%% exref | {exref, [AppName]}
%% {variables,[{Name,AbsString}]}
%% {machine, jam | beam | vee}
%% {var_tar, include | ownfile | omit}
%% no_warn_sasl
%% warnings_as_errors
%%
%% The tar file contains:
%% lib/App-Vsn/ebin
%% /priv
%% [/src]
%% [/include]
%% [/doc]
%% [/examples]
%% [/...]
%% Variable1.tar.gz
%% ...
%% VariableN.tar.gz
%% releases/RelName.rel
%% RelVsn/start.boot
%% relup
%% sys.config
%% sys.config.src
%% erts-EVsn[/bin]
%%-----------------------------------------------------------------
make_tar(RelName) when is_list(RelName) ->
make_tar(RelName, []);
make_tar(RelName) ->
badarg(RelName,[RelName]).
make_tar(RelName, Flags) when is_list(RelName), is_list(Flags) ->
case check_args_tar(Flags) of
[] ->
Path0 = get_path(Flags),
Path1 = mk_path(Path0),
Path = make_set(Path1 ++ code:get_path()),
ModTestP = {member(src_tests, Flags),xref_p(Flags)},
case get_release(RelName, Path, ModTestP, machine(Flags)) of
{ok, Release, Appls, Warnings0} ->
Warnings = wsasl(Flags, Warnings0),
case systools_lib:werror(Flags, Warnings) of
true ->
Warnings1 = [W || {warning,W}<-Warnings],
return({error,?MODULE,
{warnings_treated_as_errors,Warnings1}},
Warnings,
Flags);
false ->
case catch mk_tar(RelName, Release, Appls, Flags, Path1) of
ok ->
return(ok,Warnings,Flags);
Error ->
return(Error,Warnings,Flags)
end
end;
Error ->
return(Error,[],Flags)
end;
ErrorVars ->
badarg(ErrorVars, [RelName, Flags])
end;
make_tar(RelName, Flags) when is_list(Flags) ->
badarg(RelName,[RelName, Flags]);
make_tar(RelName, Flags) ->
badarg(Flags,[RelName, Flags]).
%%______________________________________________________________________
%% get_release(File, Path) ->
%% get_release(File, Path, ModTestP) ->
%% get_release(File, Path, ModTestP, Machine) ->
%% {ok, #release, [{{Name,Vsn},#application}], Warnings} | {error, What}
get_release(File, Path) ->
get_release(File, Path, {false,false}, false).
get_release(File, Path, ModTestP) ->
get_release(File, Path, ModTestP, false).
get_release(File, Path, ModTestP, Machine) ->
case catch get_release1(File, Path, ModTestP, Machine) of
{error, Error} ->
{error, ?MODULE, Error};
{'EXIT', Why} ->
{error, ?MODULE, {'EXIT',Why}};
Answer ->
Answer
end.
get_release1(File, Path, ModTestP, Machine) ->
{ok, Release, Warnings1} = read_release(File, Path),
{ok, Appls0} = collect_applications(Release, Path),
{ok, Appls1} = check_applications(Appls0),
{ok, Appls2} = sort_used_and_incl_appls(Appls1, Release), % OTP-4121, OTP-9984
{ok, Warnings2} = check_modules(Appls2, Path, ModTestP, Machine),
{ok, Appls} = sort_appls(Appls2),
{ok, Release, Appls, Warnings1 ++ Warnings2}.
%%______________________________________________________________________
%% read_release(File, Path) -> {ok, #release} | throw({error, What})
read_release(File, Path) ->
case read_file(File ++ ".rel", ["."|Path]) of
{ok, Release, _FullName} ->
check_rel(Release);
{error,Error} ->
throw({error,?MODULE,Error})
end.
check_rel(Release) ->
case catch check_rel1(Release) of
{ok, {Name,Vsn,Evsn,Appl,Incl}, Ws} ->
{ok, #release{name=Name, vsn=Vsn,
erts_vsn=Evsn,
applications=Appl,
incl_apps=Incl},
Ws};
{error, Error} ->
throw({error,?MODULE,Error});
Error ->
throw({error,?MODULE,Error})
end.
check_rel1({release,{Name,Vsn},{erts,EVsn},Appl}) when is_list(Appl) ->
Name = check_name(Name),
Vsn = check_vsn(Vsn),
EVsn = check_evsn(EVsn),
{{Appls,Incls},Ws} = check_appl(Appl),
{ok, {Name,Vsn,EVsn,Appls,Incls},Ws};
check_rel1(_) ->
{error, badly_formatted_release}.
check_name(Name) ->
case string_p(Name) of
true ->
Name;
_ ->
throw({error,{illegal_name, Name}})
end.
check_vsn(Vsn) ->
case string_p(Vsn) of
true ->
Vsn;
_ ->
throw({error,{illegal_form, Vsn}})
end.
check_evsn(Vsn) ->
case string_p(Vsn) of
true ->
Vsn;
_ ->
throw({error,{illegal_form, {erts,Vsn}}})
end.
check_appl(Appl) ->
case filter(fun({App,Vsn}) when is_atom(App) ->
not string_p(Vsn);
({App,Vsn,Incl}) when is_atom(App), is_list(Incl) ->
case {string_p(Vsn), a_list_p(Incl)} of
{true, true} -> false;
_ -> true
end;
({App,Vsn,Type}) when is_atom(App), is_atom(Type) ->
case {string_p(Vsn), is_app_type(Type)} of
{true, true} -> false;
_ -> true
end;
({App,Vsn,Type,Incl}) when is_atom(App),
is_atom(Type),
is_list(Incl) ->
case {string_p(Vsn),is_app_type(Type),a_list_p(Incl)} of
{true, true, true} -> false;
_ -> true
end;
(_) ->
true
end,
Appl) of
[] ->
{ApplsNoIncls,Incls} = split_app_incl(Appl),
{ok,Ws} = mandatory_applications(ApplsNoIncls,undefined,
undefined,undefined),
{{ApplsNoIncls,Incls},Ws};
Illegal ->
throw({error, {illegal_applications,Illegal}})
end.
mandatory_applications([{kernel,_,Type}|Apps],undefined,Stdlib,Sasl) ->
mandatory_applications(Apps,Type,Stdlib,Sasl);
mandatory_applications([{stdlib,_,Type}|Apps],Kernel,undefined,Sasl) ->
mandatory_applications(Apps,Kernel,Type,Sasl);
mandatory_applications([{sasl,_,Type}|Apps],Kernel,Stdlib,undefined) ->
mandatory_applications(Apps,Kernel,Stdlib,Type);
mandatory_applications([_|Apps],Kernel,Stdlib,Sasl) ->
mandatory_applications(Apps,Kernel,Stdlib,Sasl);
mandatory_applications([],Type,_,_) when Type=/=permanent ->
error_mandatory_application(kernel,Type);
mandatory_applications([],_,Type,_) when Type=/=permanent ->
error_mandatory_application(stdlib,Type);
mandatory_applications([],_,_,undefined) ->
{ok, [{warning,missing_sasl}]};
mandatory_applications([],_,_,_) ->
{ok,[]}.
error_mandatory_application(App,undefined) ->
throw({error, {missing_mandatory_app, App}});
error_mandatory_application(App,Type) ->
throw({error, {mandatory_app, App, Type}}).
split_app_incl(Appl) -> split_app_incl(Appl, [], []).
split_app_incl([{App,Vsn}|Appls], Apps, Incls) ->
split_app_incl(Appls, [{App,Vsn,permanent}|Apps], Incls);
split_app_incl([{App,Vsn,Incl}|Appls], Apps,Incls) when is_list(Incl) ->
split_app_incl(Appls, [{App,Vsn,permanent}|Apps], [{App,Incl}|Incls]);
split_app_incl([{App,Vsn,Type}|Appls], Apps, Incls) ->
split_app_incl(Appls, [{App,Vsn,Type}|Apps], Incls);
split_app_incl([{App,Vsn,Type,Incl}|Appls], Apps, Incls) when is_list(Incl) ->
split_app_incl(Appls, [{App,Vsn,Type}|Apps], [{App,Incl}|Incls]);
split_app_incl([], Apps, Incls) ->
{reverse(Apps),reverse(Incls)}.
%%______________________________________________________________________
%% collect_applications(#release, Path) ->
%% {ok,[{{Name,Vsn},#application}]} |
%% throw({error, What})
%% Read all the application files specified in the release descriptor
collect_applications(Release, Path) ->
Appls = Release#release.applications,
Incls = Release#release.incl_apps,
X = foldl(fun({Name,Vsn,Type}, {Ok, Errs}) ->
case read_application(to_list(Name), Vsn, Path, Incls) of
{ok, A} ->
case {A#application.name,A#application.vsn} of
{Name,Vsn} ->
{[{{Name,Vsn}, A#application{type=Type}} | Ok],
Errs};
E ->
{Ok, [{bad_application_name, {Name, E}} | Errs]}
end;
{error, What} ->
{Ok, [{error_reading, {Name, What}} | Errs]}
end
end, {[],[]}, Appls),
case X of
{A, []} ->
{ok, reverse(A)};
{_, Errs} ->
throw({error, Errs})
end.
%%______________________________________________________________________
%% read_application(Name, Vsn, Path, Incls) -> {ok, #release} | {error, What}
read_application(Name, Vsn, Path, Incls) ->
read_application(Name, Vsn, Path, Incls, false, no_fault).
read_application(Name, Vsn, [Dir|Path], Incls, Found, FirstError) ->
case read_file(Name ++ ".app", [Dir]) of
{ok, Term, FullName} ->
case parse_application(Term, FullName, Vsn, Incls) of
{error, {no_valid_version, {Vsn, OtherVsn}}} when FirstError == no_fault ->
NFE = {no_valid_version, {{"should be", Vsn},
{"found file", filename:join(Dir, Name++".app"),
OtherVsn}}},
read_application(Name, Vsn, Path, Incls, true, NFE);
{error, {no_valid_version, {Vsn, _OtherVsn}}} ->
read_application(Name, Vsn, Path, Incls, true, FirstError);
Res ->
Res
end;
{error, {parse, _File, {Line, _Mod, Err}}} when FirstError == no_fault ->
read_application(Name, Vsn, Path, Incls, Found,
{parse_error, {filename:join(Dir, Name++".app"), Line, Err}});
{error, {parse, _File, _Err}} ->
read_application(Name, Vsn, Path, Incls, Found, FirstError);
{error, _Err} -> %% Not found
read_application(Name, Vsn, Path, Incls, Found, FirstError)
end;
read_application(Name, Vsn, [], _, true, no_fault) ->
{error, {application_vsn, {Name,Vsn}}};
read_application(_Name, _Vsn, [], _, true, FirstError) ->
{error, FirstError};
read_application(Name, _, [], _, _, no_fault) ->
{error, {not_found, Name ++ ".app"}};
read_application(_Name, _, [], _, _, FirstError) ->
{error, FirstError}.
parse_application({application, Name, Dict}, File, Vsn, Incls)
when is_atom(Name),
is_list(Dict) ->
Items = [vsn,id,description,modules,registered,
applications,included_applications,mod,start_phases,env,maxT,maxP],
case catch get_items(Items, Dict) of
[Vsn,Id,Desc,Mods,Regs,Apps,Incs0,Mod,Phases,Env,MaxT,MaxP] ->
case override_include(Name, Incs0, Incls) of
{ok, Incs} ->
{ok, #application{name=Name,
vsn=Vsn,
id=Id,
description=Desc,
modules=Mods,
uses=Apps,
includes=Incs,
regs=Regs,
mod=Mod,
start_phases=Phases,
env=Env,
maxT=MaxT,
maxP=MaxP,
dir=filename:dirname(File)}};
{error, IncApps} ->
{error, {override_include, IncApps}}
end;
[OtherVsn,_,_,_,_,_,_,_,_,_,_,_] ->
{error, {no_valid_version, {Vsn, OtherVsn}}};
Err ->
{error, {Err, {application, Name, Dict}}}
end;
parse_application(Other, _, _, _) ->
{error, {badly_formatted_application, Other}}.
%% Test if all included applications specifed in the .rel file
%% exists in the {included_applications,Incs} specified in the
%% .app file.
override_include(Name, Incs, Incls) ->
case keysearch(Name, 1, Incls) of
{value, {Name, I}} ->
case specified(I, Incs) of
[] ->
{ok, I};
NotSpec ->
{error, NotSpec}
end;
_ ->
{ok, Incs}
end.
specified([App|Incls], Spec) ->
case member(App, Spec) of
true ->
specified(Incls, Spec);
_ ->
[App|specified(Incls, Spec)]
end;
specified([], _) ->
[].
get_items([H|T], Dict) ->
Item = check_item(keysearch(H, 1, Dict),H),
[Item|get_items(T, Dict)];
get_items([], _Dict) ->
[].
check_item({_,{mod,{M,A}}},_) when is_atom(M) ->
{M,A};
check_item({_,{mod,[]}},_) -> % default mod is [], so accept as entry
[];
check_item({_,{vsn,Vsn}},I) ->
case string_p(Vsn) of
true -> Vsn;
_ -> throw({bad_param, I})
end;
check_item({_,{id,Id}},I) ->
case string_p(Id) of
true -> Id;
_ -> throw({bad_param, I})
end;
check_item({_,{description,Desc}},I) ->
case string_p(Desc) of
true -> Desc;
_ -> throw({bad_param, I})
end;
check_item({_,{applications,Apps}},I) ->
case a_list_p(Apps) of
true -> Apps;
_ -> throw({bad_param, I})
end;
check_item({_,{included_applications,Apps}},I) ->
case a_list_p(Apps) of
true -> Apps;
_ -> throw({bad_param, I})
end;
check_item({_,{registered,Regs}},I) ->
case a_list_p(Regs) of
true -> Regs;
_ -> throw({bad_param, I})
end;
check_item({_,{modules,Mods}},I) ->
case a_list_p(Mods) of
true -> Mods;
_ -> throw({bad_param, I})
end;
check_item({_,{start_phases,undefined}},_) -> % default start_phase is undefined,
undefined; % so accept as entry
check_item({_,{start_phases,Phase}},I) ->
case t_list_p(Phase) of
true -> Phase;
_ -> throw({bad_param, I})
end;
check_item({_,{env,Env}},I) ->
case t_list_p(Env) of
true -> Env;
_ -> throw({bad_param, I})
end;
check_item({_,{maxT,MaxT}},I) ->
case MaxT of
MaxT when is_integer(MaxT), MaxT > 0 -> MaxT;
infinity -> infinity;
_ -> throw({bad_param, I})
end;
check_item({_,{maxP,MaxP}},I) ->
case MaxP of
MaxP when is_integer(MaxP), MaxP > 0 -> MaxP;
infinity -> infinity;
_ -> throw({bad_param, I})
end;
check_item(false, included_applications) -> % optional !
[];
check_item(false, mod) -> % mod is optional !
[];
check_item(false, env) -> % env is optional !
[];
check_item(false, id) -> % id is optional !
[];
check_item(false, start_phases) -> % start_phases is optional !
undefined;
check_item(false, maxT) -> % maxT is optional !
infinity;
check_item(false, maxP) -> % maxP is optional !
infinity;
check_item(_, Item) ->
throw({missing_param, Item}).
%%______________________________________________________________________
%% check_applications([{{Name,Vsn},#application}]) ->
%% ok | throw({error, Error})
%% check that all referenced applications exists and that no
%% application register processes with the same name.
%% Check that included_applications are not specified as used
%% in another application.
check_applications(Appls) ->
undef_appls(Appls),
dupl_regs(Appls),
%% Make a list Incs = [{Name,App,AppVsn,Dir}]
Incs = [{IncApp,App,Appv,A#application.dir} ||
{{App,Appv},A} <- Appls,
IncApp <- A#application.includes],
dupl_incls(Incs),
Res = add_top_apps_to_uses(Incs, Appls, []),
{ok, Res}.
undef_appls(Appls) ->
case undefined_applications(Appls) of
[] ->
ok;
L ->
throw({error, {undefined_applications, make_set(L)}})
end.
dupl_regs(Appls) ->
%% Make a list Regs = [{Name,App,AppVsn,Dir}]
Regs = [{Name,App,Appv,A#application.dir} ||
{{App,Appv},A} <- Appls,
Name <- A#application.regs],
case duplicates(Regs) of
[] ->
ok;
Dups ->
throw({error, {duplicate_register, Dups}})
end.
dupl_incls(Incs) ->
case duplicates(Incs) of
[] ->
ok;
Dups ->
throw({error, {duplicate_include, Dups}})
end.
%% If an application uses another application which is included in yet
%% another application, e.g. X uses A, A is included in T; then the A
%% application in the X applications uses-variable is changed to the T
%% application's top application to ensure the start order.
%% Exception: if both X and A have the same top, then it is not
%% added to avoid circular dependencies.
%%
%% add_top_apps_to_uses( list of all included applications in
%% the system,
%% list of all applications in the system,
%% temporary result)
%% -> new list of all applications
add_top_apps_to_uses(_InclApps, [], Res) ->
%% InclApps = [{IncApp, App, AppVsn, Dir}]
Res;
add_top_apps_to_uses(InclApps, [{Name,Appl} | Appls], Res) ->
MyTop = find_top_app(Appl#application.name, InclApps),
F = fun(UsedApp, AccIn) when UsedApp == MyTop ->
%% UW980513 This is a special case: The included app
%% uses its own top app. We'll allow it, but must
%% remove the top app from the uses list.
AccIn -- [MyTop];
(UsedApp, AccIn) ->
case lists:keysearch(UsedApp, 1, InclApps) of
false ->
AccIn;
{value, {_,DependApp,_,_}} ->
UsedAppTop = find_top_app(DependApp, InclApps),
case {lists:member(UsedAppTop, AccIn), MyTop} of
{true, _} ->
%% the top app is already in the uses
%% list, remove UsedApp
AccIn -- [UsedApp];
{_, UsedAppTop} ->
%% both are included in the same app
AccIn;
_ ->
%% change the used app to the used app's
%% top application
AccIn1 = AccIn -- [UsedApp],
AccIn1 ++ [UsedAppTop]
end
end
end,
NewUses = foldl(F, Appl#application.uses, Appl#application.uses),
add_top_apps_to_uses(InclApps, Appls,
Res++[{Name, Appl#application{uses = NewUses}}]).
find_top_app(App, InclApps) ->
case lists:keysearch(App, 1, InclApps) of
false ->
App;
{value, {_,TopApp,_,_}} ->
find_top_app(TopApp, InclApps)
end.
%%______________________________________________________________________
%% undefined_applications([{{Name,Vsn},#application}]) ->
%% [Name] list of applications that were declared in
%% use declarations but are not contained in the release descriptor
undefined_applications(Appls) ->
Uses = append(map(fun({_,A}) ->
A#application.uses ++ A#application.includes
end, Appls)),
Defined = map(fun({{X,_},_}) -> X end, Appls),
filter(fun(X) -> not member(X, Defined) end, Uses).
%%______________________________________________________________________
%% sort_used_and_incl_appls(Applications, Release) -> Applications
%% Applications = [{{Name,Vsn},#application}]
%% Release = #release{}
%%
%% OTP-4121, OTP-9984
%% Check that used and included applications are given in the same
%% order as in the release resource file (.rel). Otherwise load and
%% start instructions in the boot script, and consequently release
%% upgrade instructions in relup, may end up in the wrong order.
sort_used_and_incl_appls(Applications, Release) when is_tuple(Release) ->
{ok,
sort_used_and_incl_appls(Applications, Release#release.applications)};
sort_used_and_incl_appls([{Tuple,Appl}|Appls], OrderedAppls) ->
Incls2 =
case Appl#application.includes of
Incls when length(Incls)>1 ->
sort_appl_list(Incls, OrderedAppls);
Incls ->
Incls
end,
Uses2 =
case Appl#application.uses of
Uses when length(Uses)>1 ->
sort_appl_list(Uses, OrderedAppls);
Uses ->
Uses
end,
Appl2 = Appl#application{includes=Incls2, uses=Uses2},
[{Tuple,Appl2}|sort_used_and_incl_appls(Appls, OrderedAppls)];
sort_used_and_incl_appls([], _OrderedAppls) ->
[].
sort_appl_list(List, Order) ->
IndexedList = find_pos(List, Order),
SortedIndexedList = lists:keysort(1, IndexedList),
lists:map(fun({_Index,Name}) -> Name end, SortedIndexedList).
find_pos([Name|Incs], OrderedAppls) ->
[find_pos(1, Name, OrderedAppls)|find_pos(Incs, OrderedAppls)];
find_pos([], _OrderedAppls) ->
[].
find_pos(N, Name, [{Name,_Vsn,_Type}|_OrderedAppls]) ->
{N, Name};
find_pos(N, Name, [_OtherAppl|OrderedAppls]) ->
find_pos(N+1, Name, OrderedAppls).
%%______________________________________________________________________
%% check_modules(Appls, Path, TestP, Machine) ->
%% {ok, Warnings} | throw({error, What})
%% where Appls = [{App,Vsn}, #application}]
%% performs logical checking that we can find all the modules
%% etc.
check_modules(Appls, Path, TestP, Machine) ->
%% first check that all the module names are unique
%% Make a list M1 = [{Mod,App,Dir}]
M1 = [{Mod,App,A#application.dir} ||
{{App,_Appv},A} <- Appls,
Mod <- A#application.modules],
case duplicates(M1) of
[] ->
case check_mods(M1, Appls, Path, TestP, Machine) of
{error, Errors} ->
throw({error, {modules, Errors}});
Return ->
Return
end;
Dups ->
% io:format("** ERROR Duplicate modules: ~p\n", [Dups]),
throw({error, {duplicate_modules, Dups}})
end.
%%______________________________________________________________________
%% Check that all modules exists.
%% Use the module extension of the running machine as extension for
%% the checked modules.
check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) ->
SrcTestRes = check_src(Modules, Appls, Path, SrcTestP, Machine),
XrefRes = check_xref(Appls, Path, XrefP),
Res = SrcTestRes ++ XrefRes,
case filter(fun({error, _}) -> true;
(_) -> false
end,
Res) of
[] ->
{ok, filter(fun({warning, _}) -> true;
(_) -> false
end,
Res)};
Errors ->
{error, Errors}
end.
check_src(Modules, Appls, Path, true, Machine) ->
Ext = objfile_extension(Machine),
IncPath = create_include_path(Appls, Path),
append(map(fun(ModT) ->
{Mod,App,Dir} = ModT,
case check_mod(Mod,App,Dir,Ext,IncPath) of
ok ->
[];
{error, Error} ->
[{error,{Error, ModT}}];
{warning, Warn} ->
[{warning,{Warn,ModT}}]
end
end,
Modules));
check_src(_, _, _, _, _) ->
[].
check_xref(_Appls, _Path, false) ->
[];
check_xref(Appls, Path, XrefP) ->
AppDirsL = [{App,A#application.dir} || {{App,_Appv},A} <- Appls],
AppDirs0 = sofs:relation(AppDirsL),
AppDirs = case XrefP of
true ->
AppDirs0;
{true, Apps} ->
sofs:restriction(AppDirs0, sofs:set(Apps))
end,
XrefArgs = [{xref_mode, modules}],
case catch xref:start(?XREF_SERVER, XrefArgs) of
{ok, _Pid} ->
ok;
{error, {already_started, _Pid}} ->
xref:stop(?XREF_SERVER), %% Clear out any previous data
{ok,_} = xref:start(?XREF_SERVER, XrefArgs),
ok
end,
{ok, _} = xref:set_default(?XREF_SERVER, verbose, false),
LibPath = case Path == code:get_path() of
true -> code_path; % often faster
false -> Path
end,
ok = xref:set_library_path(?XREF_SERVER, LibPath),
check_xref(sofs:to_external(AppDirs)).
check_xref([{App,AppDir} | Appls]) ->
case xref:add_application(?XREF_SERVER, AppDir, {name,App}) of
{ok, _App} ->
check_xref(Appls);
Error ->
xref:stop(?XREF_SERVER),
[{error, Error}]
end;
check_xref([]) ->
R = case xref:analyze(?XREF_SERVER, undefined_functions) of
{ok, []} ->
[];
{ok, Undefined} ->
%% This clause is a (temporary?) fix for hipe.
adjust_for_hipe(Undefined);
Error ->
[{error, Error}]
end,
xref:stop(?XREF_SERVER),
R.
adjust_for_hipe(Undef) ->
case erlang:system_info(hipe_architecture) of
undefined ->
U = lists:filter(fun ({hipe_bifs,_,_}) -> false;
({hipe,_,_}) -> false;
(_) -> true
end, Undef),
if
[] == U ->
[];
true ->
[{warning, {exref_undef, U}}]
end;
_Arch ->
%% Some BIFs are not always available on all versions of HiPE.
U = lists:filter(fun ({hipe_bifs,write_u64,2}) -> false;
(_) -> true
end, Undef),
[{warning, {exref_undef, U}}]
end.
%% Perform cross reference checks between all modules specified
%% in .app files.
%%
xref_p(Flags) ->
case member(exref, Flags) of
true ->
exists_xref(true);
_ ->
case get_flag(exref, Flags) of
{exref, Appls} when is_list(Appls) ->
case a_list_p(Appls) of
true -> exists_xref({true, Appls});
_ -> false
end;
_ ->
false
end
end.
exists_xref(Flag) ->
case code:ensure_loaded(xref) of
{error, _} -> false;
_ -> Flag
end.
objfile_extension(false) ->
code:objfile_extension();
objfile_extension(Machine) ->
"." ++ atom_to_list(Machine).
check_mod(Mod,App,Dir,Ext,IncPath) ->
ObjFile = mod_to_filename(Dir, Mod, Ext),
case file:read_file_info(ObjFile) of
{ok,FileInfo} ->
LastModTime = FileInfo#file_info.mtime,
check_module(Mod, Dir, LastModTime, IncPath);
_ ->
{error, {module_not_found, App, Mod}}
end.
mod_to_filename(Dir, Mod, Ext) ->
filename:join(Dir, atom_to_list(Mod) ++ Ext).
check_module(Mod, Dir, ObjModTime, IncPath) ->
{SrcDirs,_IncDirs}= smart_guess(Dir,IncPath),
case locate_src(Mod,SrcDirs) of
{ok,_FDir,_File,LastModTime} ->
if
LastModTime > ObjModTime ->
{warning, obj_out_of_date};
true ->
ok
end;
_ ->
{warning, source_not_found}
end.
locate_src(Mod,[Dir|Dirs]) ->
File = mod_to_filename(Dir, Mod, ".erl"),
case file:read_file_info(File) of
{ok,FileInfo} ->
LastModTime = FileInfo#file_info.mtime,
{ok,Dir,File,LastModTime};
_ ->
locate_src(Mod,Dirs)
end;
locate_src(_,[]) ->
false.
%%______________________________________________________________________
%% smart_guess(Mod, Dir,IncludePath) -> {[Dirs],[IncDirs]}
%% Guess the src code and include directory. If dir contains .../ebin
%% src-dir should be one of .../src or .../src/e_src
%% If dir does not contain .../ebin set dir to the same directory.
smart_guess(Dir,IncPath) ->
case reverse(filename:split(Dir)) of
["ebin"|D] ->
D1 = reverse(D),
Dirs = [filename:join(D1 ++ ["src"]),
filename:join(D1 ++ ["src", "e_src"])],
{Dirs,Dirs ++ IncPath};
_ ->
{[Dir],[Dir] ++ IncPath}
end.
%%______________________________________________________________________
%% generate_script(#release,
%% [{{Name,Vsn},#application}], Flags) ->
%% ok | {error, Error}
%% Writes a script (a la magnus) to the file File.script
%% and a bootfile to File.boot.
generate_script(Output, Release, Appls, Flags) ->
PathFlag = path_flag(Flags),
Variables = get_variables(Flags),
Preloaded = preloaded(),
Mandatory = mandatory_modules(),
Script = {script, {Release#release.name,Release#release.vsn},
[{preLoaded, Preloaded},
{progress, preloaded},
{path, create_mandatory_path(Appls, PathFlag, Variables)},
{primLoad, Mandatory},
{kernel_load_completed},
{progress, kernel_load_completed}] ++
load_appl_mods(Appls, Mandatory ++ Preloaded,
PathFlag, Variables) ++
[{path, create_path(Appls, PathFlag, Variables)}] ++
create_kernel_procs(Appls) ++
create_load_appls(Appls) ++
create_start_appls(Appls) ++
script_end(lists:member(no_dot_erlang, Flags))
},
ScriptFile = Output ++ ".script",
case file:open(ScriptFile, [write,{encoding,utf8}]) of
{ok, Fd} ->
io:format(Fd, "%% ~s\n%% script generated at ~w ~w\n~tp.\n",
[epp:encoding_to_string(utf8), date(), time(), Script]),
case file:close(Fd) of
ok ->
BootFile = Output ++ ".boot",
case file:write_file(BootFile, term_to_binary(Script)) of
ok ->
ok;
{error, Reason} ->
{error, ?MODULE, {open,BootFile,Reason}}
end;
{error, Reason} ->
{error, ?MODULE, {close,ScriptFile,Reason}}
end;
{error, Reason} ->
{error, ?MODULE, {open,ScriptFile,Reason}}
end.
path_flag(Flags) ->
case {member(local,Flags), member(otp_build, Flags)} of
{true, _} -> local;
{_, true} -> otp_build;
{_, _} -> true
end.
get_variables(Flags) ->
case get_flag(variables, Flags) of
{variables, Variables} when is_list(Variables) ->
valid_variables(Variables);
_ ->
[]
end.
valid_variables([{Var,Path}|Variables]) when is_list(Var), is_list(Path) ->
[{Var,rm_tlsl(Path)}|valid_variables(Variables)];
valid_variables([{Var,Path}|Variables]) when is_atom(Var), is_list(Path) ->
[{to_list(Var),rm_tlsl(Path)}|valid_variables(Variables)];
valid_variables([_|Variables]) ->
valid_variables(Variables);
valid_variables(_) ->
[].
rm_tlsl(P) -> rm_tlsl1(reverse(P)).
rm_tlsl1([$/|P]) -> rm_tlsl1(P);
rm_tlsl1(P) -> reverse(P).
%%______________________________________________________________________
%% Start all applications.
%% Do not start applications that are included applications !
create_start_appls(Appls) ->
Included = append(map(fun({_,A}) ->
A#application.includes
end, Appls)),
create_start_appls(Appls, Included).
create_start_appls([{_,A}|T], Incl) ->
App = A#application.name,
case lists:member(App, Incl) of
false when A#application.type == none ->
create_start_appls(T, Incl);
false when A#application.type == load ->
create_start_appls(T, Incl);
false ->
[{apply, {application, start_boot, [App,A#application.type]}} |
create_start_appls(T, Incl)];
_ ->
create_start_appls(T, Incl)
end;
create_start_appls([], _) ->
[].
%%______________________________________________________________________
%% Load all applications.
create_load_appls([{{kernel,_},_}|T]) -> %Already added !!
create_load_appls(T);
create_load_appls([{_,A}|T]) when A#application.type == none ->
create_load_appls(T);
create_load_appls([{_,A}|T]) ->
[{apply, {application, load, [pack_app(A)]}} |
create_load_appls(T)];
create_load_appls([]) ->
[{progress, applications_loaded}].
%%______________________________________________________________________
%% The final part of the script.
script_end(false) -> %% Do not skip loading of $HOME/.erlang
[{apply, {c, erlangrc, []}},
{progress, started}];
script_end(true) -> %% Ignore loading of $HOME/.erlang
[{progress, started}].
%%-----------------------------------------------------------------
%% Function: sort_appls(Appls) -> {ok, Appls'} | throw({error, Error})
%% Types: Appls = {{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 ([email protected])
%% Mod. by mbj
%%-----------------------------------------------------------------
sort_appls(Appls) -> {ok, sort_appls(Appls, [], [], [])}.
sort_appls([{N, A}|T], Missing, Circular, Visited) ->
{Name,_Vsn} = N,
{Uses, T1, NotFnd1} = find_all(Name, lists:reverse(A#application.uses),
T, Visited, [], []),
{Incs, T2, NotFnd2} = find_all(Name, lists:reverse(A#application.includes),
T1, 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)
[{N, A}|sort_appls(T, Missing1, Circular, [N|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;
_ -> [N | NewCircular] ++ Circular
end,
%% L must be started before N, try again, with all apps
%% in L added before N.
Apps = del_apps(NewCircular, L ++ [{N, A}|T2]),
sort_appls(Apps, Missing1, Circular1, [N|Visited])
end;
sort_appls([], [], [], _) ->
[];
sort_appls([], Missing, [], _) ->
%% this has already been checked before, but as we have the info...
throw({error, {undefined_applications, make_set(Missing)}});
sort_appls([], [], Circular, _) ->
throw({error, {circular_dependencies, make_set(Circular)}});
sort_appls([], Missing, Circular, _) ->
throw({error, {apps, [{circular_dependencies, make_set(Circular)},
{undefined_applications, make_set(Missing)}]}}).
find_all(CheckingApp, [Name|T], L, Visited, Found, NotFound) ->
case find_app(Name, L) of
{value, App} ->
{_A,R} = App,
%% It is OK to have a dependecy like
%% X includes Y, Y uses X.
case lists:member(CheckingApp, R#application.includes) of
true ->
case lists:keymember(Name, 1, Visited) of
true ->
find_all(CheckingApp, T, L, Visited, Found, NotFound);
false ->
find_all(CheckingApp, T, L, Visited, Found, [Name|NotFound])
end;
false ->
find_all(CheckingApp, T, L -- [App], Visited, [App|Found], NotFound)
end;
false ->
case lists:keymember(Name, 1, Visited) of
true ->
find_all(CheckingApp, T, L, Visited, Found, NotFound);
false ->
find_all(CheckingApp, T, L, Visited, Found, [Name|NotFound])
end
end;
find_all(_CheckingApp, [], L, _Visited, Found, NotFound) ->
{Found, L, NotFound}.
find_app(Name, [{{Name,Vsn}, Application}|_]) ->
{value, {{Name,Vsn},Application}};
find_app(Name, [_|T]) ->
find_app(Name, T);
find_app(_Name, []) ->
false.
del_apps([Name|T], L) ->
del_apps(T, lists:keydelete(Name, 1, L));
del_apps([], L) ->
L.
%%______________________________________________________________________
%% 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(Appls, PathFlag, Variables) ->
make_set(map(fun({{Name,Vsn},App}) ->
cr_path(Name, Vsn, App, PathFlag, Variables)
end,
Appls)).
%% Create the path to a specific application.
%% (The otp_build flag is only used for OTP internal system make)
cr_path(Name, Vsn, _, true, []) ->
filename:join(["$ROOT", "lib", to_list(Name) ++ "-" ++ Vsn, "ebin"]);
cr_path(Name, Vsn, App, true, Variables) ->
Dir = App#application.dir,
N = to_list(Name),
Tail = [N ++ "-" ++ Vsn, "ebin"],
case variable_dir(Dir, N, Vsn, Variables) of
{ok, VarDir} ->
filename:join([VarDir] ++ Tail);
_ ->
filename:join(["$ROOT", "lib"] ++ Tail)
end;
cr_path(Name, _, _, otp_build, _) ->
filename:join(["$ROOT", "lib", to_list(Name), "ebin"]);
cr_path(_, _, App, _, _) ->
filename:absname(App#application.dir).
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;
_ ->
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 reverse(Dir) of
["ebin",Name|D] -> {ok, reverse(D)};
["ebin",FullName|D] -> {ok, reverse(D)};
_ -> false
end.
%% Create the path to the kernel and stdlib applications.
create_mandatory_path(Appls, PathFlag, Variables) ->
Dirs = [kernel, stdlib],
make_set(map(fun({{Name,Vsn}, A}) ->
case lists:member(Name, Dirs) of
true ->
cr_path(Name, Vsn, A, PathFlag, Variables);
_ ->
""
end
end,
Appls)).
%%______________________________________________________________________
%% Load all modules, except those in Mandatory_modules.
load_appl_mods([{{Name,Vsn},A}|Appls], Mand, PathFlag, Variables) ->
Mods = A#application.modules,
load_commands(filter(fun(Mod) -> not member(Mod, Mand) end, Mods),
cr_path(Name, Vsn, A, PathFlag, Variables)) ++
load_appl_mods(Appls, Mand, PathFlag, Variables);
% [{path, [cr_path(Name, Vsn, A, PathFlag, Variables)]},
% {primLoad, filter(fun(Mod) -> not member(Mod, Mand) end, Mods)} |
% load_appl_mods(Appls, Mand, PathFlag, Variables)];
load_appl_mods([], _, _, _) ->
[{progress, modules_loaded}].
load_commands(Mods, Path) ->
[{path, [filename:join([Path])]},
{primLoad,lists:sort(Mods)}].
%%______________________________________________________________________
%% Pack an application to an application term.
pack_app(#application{name=Name,vsn=V,id=Id,description=D,modules=M,
uses=App,includes=Incs,regs=Regs,mod=Mod,start_phases=SF,
env=Env,maxT=MaxT,maxP=MaxP}) ->
{application, Name,
[{description,D},
{vsn,V},
{id,Id},
{modules, M},
{registered, Regs},
{applications, App},
{included_applications, Incs},
{env, Env},
{maxT, MaxT},
{maxP, MaxP} |
behave([{start_phases,SF},{mod,Mod}])]}.
behave([{mod,[]}|T]) ->
behave(T);
behave([{start_phases,undefined}|T]) ->
behave(T);
behave([H|T]) ->
[H|behave(T)];
behave([]) ->
[].
mandatory_modules() ->
[error_handler, %Truly mandatory.
%% Modules that are almost always needed. Listing them here
%% helps the init module to load them faster. Modules not
%% listed here will be loaded by the error_handler module.
%%
%% Keep this list sorted.
application,
application_controller,
application_master,
code,
code_server,
erl_eval,
erl_lint,
erl_parse,
error_logger,
ets,
file,
filename,
file_server,
file_io_server,
gen,
gen_event,
gen_server,
heart,
kernel,
logger,
logger_filters,
logger_server,
logger_backend,
logger_config,
logger_simple,
lists,
proc_lib,
supervisor
].
%%______________________________________________________________________
%% This is the modules that are preloaded into the Erlang system.
preloaded() ->
%% Sorted
[erl_prim_loader,erl_tracer,erlang,
erts_code_purger,erts_dirty_process_signal_handler,
erts_internal,erts_literal_area_collector,
init,erl_init,prim_buffer,prim_eval,prim_file,
prim_inet,prim_zip,zlib].
%%______________________________________________________________________
%% Kernel processes; processes that are specially treated by the init
%% process. If a kernel process terminates the whole system terminates.
%% kernel_processes() -> [{Name, Mod, Func, Args}]
%% where Args is a term or a fun taking the list of applications as arg.
kernel_processes() ->
[{heart, heart, start, []},
{logger, logger_server, start_link, []},
{application_controller, application_controller, start,
fun(Appls) ->
[{_,App}] = filter(fun({{kernel,_},_App}) -> true;
(_) -> false
end,
Appls),
[pack_app(App)]
end}
].
%%______________________________________________________________________
%% Create the kernel processes.
create_kernel_procs(Appls) ->
map(fun({Name,Mod,Func,Args}) when is_function(Args) ->
{kernelProcess, Name, {Mod, Func, Args(Appls)}};
({Name,Mod,Func,Args}) ->
{kernelProcess, Name, {Mod, Func, Args}}
end,
kernel_processes()) ++
[{progress, init_kernel_started}].
%%______________________________________________________________________
%% Make a tar file of the release.
%% The tar file contains:
%% lib/App-Vsn/ebin
%% /priv
%% [/src]
%% [/include]
%% [/doc]
%% [/examples]
%% [/...]
%% Variable1.tar.gz
%% ...
%% VariableN.tar.gz
%% releases/RelName.rel
%% RelVsn/start.boot
%% relup
%% sys.config
%% sys.config.src
%% erts-EVsn[/bin]
%%
%% The VariableN.tar.gz files can also be stored as own files not
%% included in the main tar file or they can be omitted using
%% the var_tar option.
mk_tar(RelName, Release, Appls, Flags, Path1) ->
TarName = case get_outdir(Flags) of
"" ->
RelName ++ ".tar.gz";
OutDir ->
filename:join(OutDir, filename:basename(RelName))
++ ".tar.gz"
end,
Tar = open_main_tar(TarName),
case catch mk_tar(Tar, RelName, Release, Appls, Flags, Path1) of
{error,Error} ->
_ = del_tar(Tar, TarName),
{error,?MODULE,Error};
{'EXIT',Reason} ->
_ = del_tar(Tar, TarName),
{error,?MODULE,Reason};
_ ->
case erl_tar:close(Tar) of
ok -> ok;
{error,Reason} -> {error,?MODULE,{close,TarName,Reason}}
end
end.
open_main_tar(TarName) ->
case catch open_tar(TarName) of
{error, Error} ->
throw({error,?MODULE,Error});
Tar ->
Tar
end.
mk_tar(Tar, RelName, Release, Appls, Flags, Path1) ->
Variables = get_variables(Flags),
add_applications(Appls, Tar, Variables, Flags, false),
add_variable_tars(Variables, Appls, Tar, Flags),
add_system_files(Tar, RelName, Release, Path1),
add_erts_bin(Tar, Release, Flags).
add_applications(Appls, Tar, Variables, Flags, Var) ->
Res = foldl(fun({{Name,Vsn},App}, Errs) ->
case catch add_appl(to_list(Name), Vsn, App,
Tar, Variables, Flags, Var) of
ok ->
Errs;
{error, What} ->
[{error_add_appl, {Name,What}}|Errs]
end
end, [], Appls),
case Res of
[] ->
ok;
Errors ->
throw({error, Errors})
end.
%%______________________________________________________________________
%% Create a tar file for each Variable directory.
%% Deletes the temporary tar file.
add_variable_tars([Variable|Variables], Appls, Tar, Flags) ->
add_variable_tar(Variable, Appls, Tar, Flags),
add_variable_tars(Variables, Appls, Tar, Flags);
add_variable_tars([], _, _, _) ->
ok.
add_variable_tar({Variable,P}, Appls, Tar, Flags) ->
case var_tar_flag(Flags) of
omit ->
ok;
Flag ->
TarName = Variable ++ ".tar.gz",
VarTar = open_tar(TarName),
case catch add_applications(Appls, VarTar, [{Variable,P}],
Flags, Variable) of
ok when Flag == include ->
close_tar(VarTar,TarName),
add_to_tar(Tar, TarName, TarName),
del_file(TarName);
ok when Flag == ownfile ->
close_tar(VarTar,TarName);
Error ->
_ = del_tar(VarTar, TarName),
throw(Error)
end
end.
var_tar_flag(Flags) ->
case get_flag(var_tar, Flags) of
{var_tar, Flag} ->
case member(Flag, [include, ownfile, omit]) of
true -> Flag;
_ -> include
end;
_ ->
include
end.
%%______________________________________________________________________
%% Add all "other" files to Dir/releases/Svsn
%% add_system_files(Tar,Name,release#,Flags) ->
%% ok | throw({error,Error})
add_system_files(Tar, RelName, Release, Path1) ->
SVsn = Release#release.vsn,
RelName0 = filename:basename(RelName),
RelVsnDir = filename:join("releases", SVsn),
%% OTP-9746: store rel file in releases/<vsn>
%% Adding rel file to
%% 1) releases directory - so it can be easily extracted
%% separately (see release_handler:unpack_release)
%% 2) releases/<vsn> - so the file must not be explicitly moved
%% after unpack.
add_to_tar(Tar, RelName ++ ".rel",
filename:join("releases", RelName0 ++ ".rel")),
add_to_tar(Tar, RelName ++ ".rel",
filename:join(RelVsnDir, RelName0 ++ ".rel")),
%% OTP-6226 Look for the system files not only in cwd
%% --
%% (well, actually the boot file was looked for in the same
%% directory as RelName, which is not necessarily the same as cwd)
%% --
%% but also in the path specfied as an option to systools:make_tar
%% (but make sure to search the RelName directory and cwd first)
Path = case filename:dirname(RelName) of
"." ->
["."|Path1];
RelDir ->
[RelDir, "."|Path1]
end,
case lookup_file(RelName0 ++ ".boot", Path) of
false ->
throw({error, {tar_error,{add, RelName0++".boot",enoent}}});
Boot ->
add_to_tar(Tar, Boot, filename:join(RelVsnDir, "start.boot"))
end,
case lookup_file("relup", Path) of
false ->
ignore;
Relup ->
check_relup(Relup),
add_to_tar(Tar, Relup, filename:join(RelVsnDir, "relup"))
end,
case lookup_file("sys.config.src", Path) of
false ->
case lookup_file("sys.config", Path) of
false ->
ignore;
Sys ->
check_sys_config(Sys),
add_to_tar(Tar, Sys, filename:join(RelVsnDir, "sys.config"))
end;
SysSrc ->
add_to_tar(Tar, SysSrc, filename:join(RelVsnDir, "sys.config.src"))
end,
ok.
lookup_file(Name, [Dir|Path]) ->
File = filename:join(Dir, Name),
case filelib:is_file(File) of
true ->
File;
false ->
lookup_file(Name, Path)
end;
lookup_file(_Name, []) ->
false.
%% Check that relup can be parsed and has expected format
check_relup(File) ->
case file:consult(File) of
{ok,[{Vsn,UpFrom,DownTo}]} when is_list(Vsn), is_integer(hd(Vsn)),
is_list(UpFrom), is_list(DownTo) ->
ok;
{ok,_} ->
throw({error,{tar_error,{add,"relup",[invalid_format]}}});
Other ->
throw({error,{tar_error,{add,"relup",[Other]}}})
end.
%% Check that sys.config can be parsed and has expected format
check_sys_config(File) ->
case file:consult(File) of
{ok,[SysConfig]} ->
case lists:all(fun({App,KeyVals}) when is_atom(App),
is_list(KeyVals)->
true;
(OtherConfig) when is_list(OtherConfig),
is_integer(hd(OtherConfig)) ->
true;
(_) ->
false
end,
SysConfig) of
true ->
ok;
false ->
throw({error,{tar_error,
{add,"sys.config",[invalid_format]}}})
end;
{ok,_} ->
throw({error,{tar_error,{add,"sys.config",[invalid_format]}}});
Other ->
throw({error,{tar_error,{add,"sys.config",[Other]}}})
end.
%%______________________________________________________________________
%% Add either a application located under a variable dir or all other
%% applications to a tar file.
%% add_appl(Name,Vsn,application#,Tar,Variables,Flags,Var) ->
%% ok | {error,Error}
add_appl(Name, Vsn, App, Tar, Variables, Flags, Var) ->
AppDir = App#application.dir,
case add_to(AppDir,Name,Vsn,Variables,Var) of
false ->
ok;
{ok, ToDir} ->
ADir = appDir(AppDir),
add_priv(ADir, ToDir, Tar),
case get_flag(dirs,Flags) of
{dirs,Dirs} ->
add_dirs(ADir, Dirs, ToDir, Tar);
_ ->
ok
end,
BinDir = filename:join(ToDir, "ebin"),
add_to_tar(Tar,
filename:join(AppDir, Name ++ ".app"),
filename:join(BinDir, Name ++ ".app")),
add_modules(map(fun(Mod) -> to_list(Mod) end,
App#application.modules),
Tar,
AppDir,
BinDir,
objfile_extension(machine(Flags)))
end.
%%______________________________________________________________________
%% If an application directory contains a Variable (in AppDir) the
%% application will be placed in the tar file (if it is this Variable
%% we corrently is actually storing).
add_to(AppDir,Name,Vsn,Variables,Variable) ->
case var_dir(AppDir,Name,Vsn,Variables) of
{ok, Variable, RestPath} ->
{ok, filename:join(RestPath ++ [Name ++ "-" ++ Vsn])};
{ok, _, _} ->
false;
_ when Variable == false ->
{ok, filename:join("lib", Name ++ "-" ++ Vsn)};
_ ->
false
end.
var_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, Var, D};
_ ->
false
end;
_ ->
var_dir(Dir, Name, Vsn, Variables)
end;
var_dir(_Dir, _, _, []) ->
false.
appDir(AppDir) ->
case filename:basename(AppDir) of
"ebin" -> filename:dirname(AppDir);
_ -> AppDir
end.
add_modules(Modules, Tar, AppDir, ToDir, Ext) ->
foreach(fun(Mod) ->
add_to_tar(Tar,
filename:join(AppDir, Mod ++ Ext),
filename:join(ToDir, Mod ++ Ext))
end, Modules).
%%
%% Add own specified directories to include in the release.
%% If not found, skip it.
%%
add_dirs(AppDir, Dirs, ToDir, Tar) ->
foreach(fun(Dir) -> catch add_dir(AppDir, to_list(Dir), ToDir, Tar) end,
Dirs).
add_dir(TopDir, Dir, ToDir, Tar) ->
FromD = filename:join(TopDir, Dir),
case dirp(FromD) of
true ->
add_to_tar(Tar, FromD, filename:join(ToDir, Dir));
_ ->
ok
end.
%%
%% Add the priv dir if it exists.
add_priv(ADir, ToDir, Tar) ->
Priv = filename:join(ADir, "priv"),
case dirp(Priv) of
true ->
add_to_tar(Tar, Priv, filename:join(ToDir, "priv"));
_ ->
ok
end.
add_erts_bin(Tar, Release, Flags) ->
case get_flag(erts,Flags) of
{erts,ErtsDir} ->
EVsn = Release#release.erts_vsn,
FromDir = filename:join([to_list(ErtsDir),
"erts-" ++ EVsn, "bin"]),
dirp(FromDir),
ToDir = filename:join("erts-" ++ EVsn, "bin"),
add_to_tar(Tar, FromDir, ToDir);
_ ->
ok
end.
%%______________________________________________________________________
%% Tar functions.
open_tar(TarName) ->
case erl_tar:open(TarName, [write, compressed]) of
{ok, Tar} ->
Tar;
{error, Error} ->
throw({error,{tar_error, {open, TarName, Error}}})
end.
close_tar(Tar,File) ->
case erl_tar:close(Tar) of
ok -> ok;
{error,Reason} -> throw({error,{close,File,Reason}})
end.
del_tar(Tar, TarName) ->
_ = erl_tar:close(Tar),
file:delete(TarName).
add_to_tar(Tar, FromFile, ToFile) ->
case catch erl_tar:add(Tar, FromFile, ToFile, [compressed, dereference]) of
ok -> ok;
{'EXIT', Reason} ->
throw({error, {tar_error, {add, FromFile, Reason}}});
{error, Error} ->
throw({error, {tar_error, {add, FromFile, Error}}})
end.
%%______________________________________________________________________
%%______________________________________________________________________
%% utilities!
make_set([]) -> [];
make_set([""|T]) -> % Ignore empty items.
make_set(T);
make_set([H|T]) ->
[H | [ Y || Y<- make_set(T),
Y =/= H]].
to_list(A) when is_atom(A) -> atom_to_list(A);
to_list(L) -> L.
mk_path(Path0) ->
Path1 = map(fun(Dir) when is_atom(Dir) -> atom_to_list(Dir);
(Dir) -> Dir
end, Path0),
systools_lib:get_path(Path1).
%% duplicates([Tuple]) -> List of pairs where
%% element(1, T1) == element(1, T2) and where T1 and T2 are
%% taken from [Tuple]
duplicates(X) -> duplicates(keysort(1,X), []).
duplicates([H1,H2|T], L) ->
case {element(1,H1),element(1,H2)} of
{X,X} -> duplicates([H2|T],[{H1,H2}|L]);
_ -> duplicates([H2|T],L)
end;
duplicates(_, L) -> L.
%% read_file(File, Path) -> {ok, Term, FullName} | {error, Error}
%% read a file and check the syntax, i.e. that it contains a correct
%% Erlang term.
read_file(File, Path) ->
case file:path_open(Path, File, [read]) of
{ok, Stream, FullName} ->
Return = case systools_lib:read_term_from_stream(Stream, File) of
{ok, Term} ->
{ok, Term, FullName};
Other ->
Other
end,
case file:close(Stream) of
ok -> Return;
{error, Error} -> {error, {close,File,Error}}
end;
_Other ->
{error, {not_found, File}}
end.
del_file(File) ->
case file:delete(File) of
ok -> ok;
{error, Error} ->
throw({error, {delete, File, Error}})
end.
dirp(Dir) ->
case file:read_file_info(Dir) of
{ok, FileInfo} -> FileInfo#file_info.type == directory;
_ -> false
end.
%% Create the include path. Assumptions about the code path is done
%% and an include directory is added.
%% Add the official include dir for each found application first in
%% path !!
%% If .../ebin exists in a path an .../include directory is assumed to
%% exist at the same level. If .../ebin is not existing the .../include
%% directory is assumed anyhow.
%% Local includes are added for each application later on.
create_include_path(Appls, Path) ->
FoundAppDirs = map(fun({_,A}) -> A#application.dir end, Appls),
map(fun(Dir) ->
case reverse(filename:split(Dir)) of
["ebin"|D] ->
filename:join(reverse(D) ++ ["include"]);
_ ->
filename:join(Dir, "include")
end
end,
FoundAppDirs ++ no_dupl(Path, FoundAppDirs)).
no_dupl([Dir|Path], FoundAppDirs) ->
case member(Dir, FoundAppDirs) of
true ->
no_dupl(Path, FoundAppDirs);
_ ->
[Dir|no_dupl(Path, FoundAppDirs)]
end;
no_dupl([], _) ->
[].
is_app_type(permanent) -> true;
is_app_type(transient) -> true;
is_app_type(temporary) -> true;
is_app_type(none) -> true;
is_app_type(load) -> true;
is_app_type(_) -> false.
% check if a term is a string.
string_p(S) ->
case unicode:characters_to_list(S) of
S -> true;
_ -> false
end.
% check if a term is a list of two tuples with the first
% element as an atom.
t_list_p([{A,_}|T]) when is_atom(A) -> t_list_p(T);
t_list_p([]) -> true;
t_list_p(_) -> false.
% check if a term is a list of atoms.
a_list_p([A|T]) when is_atom(A) -> a_list_p(T);
a_list_p([]) -> true;
a_list_p(_) -> false.
%% Get a key-value tuple flag from a list.
get_flag(F,[{F,D}|_]) -> {F,D};
get_flag(F,[_|Fs]) -> get_flag(F,Fs);
get_flag(_,_) -> false.
%% Check Options for make_script
check_args_script(Args) ->
cas(Args, []).
cas([], X) ->
X;
%%% path ---------------------------------------------------------------
cas([{path, P} | Args], X) when is_list(P) ->
case check_path(P) of
ok ->
cas(Args, X);
error ->
cas(Args, X++[{path,P}])
end;
%%% silent -------------------------------------------------------------
cas([silent | Args], X) ->
cas(Args, X);
%%% local --------------------------------------------------------------
cas([local | Args], X) ->
cas(Args, X);
%%% src_tests -------------------------------------------------------
cas([src_tests | Args], X) ->
cas(Args, X);
%%% variables ----------------------------------------------------------
cas([{variables, V} | Args], X) when is_list(V) ->
case check_vars(V) of
ok ->
cas(Args, X);
error ->
cas(Args, X++[{variables, V}])
end;
%%% machine ------------------------------------------------------------
cas([{machine, M} | Args], X) when is_atom(M) ->
cas(Args, X);
%%% exref --------------------------------------------------------------
cas([exref | Args], X) ->
cas(Args, X);
%%% exref Apps ---------------------------------------------------------
cas([{exref, Apps} | Args], X) when is_list(Apps) ->
case check_apps(Apps) of
ok ->
cas(Args, X);
error ->
cas(Args, X++[{exref, Apps}])
end;
%%% outdir Dir ---------------------------------------------------------
cas([{outdir, Dir} | Args], X) when is_list(Dir) ->
cas(Args, X);
%%% otp_build (secret, not documented) ---------------------------------
cas([otp_build | Args], X) ->
cas(Args, X);
%%% warnings_as_errors -------------------------------------------------
cas([warnings_as_errors | Args], X) ->
cas(Args, X);
%%% no_warn_sasl -------------------------------------------------------
cas([no_warn_sasl | Args], X) ->
cas(Args, X);
%%% no_module_tests (kept for backwards compatibility, but ignored) ----
cas([no_module_tests | Args], X) ->
cas(Args, X);
cas([no_dot_erlang | Args], X) ->
cas(Args, X);
%%% ERROR --------------------------------------------------------------
cas([Y | Args], X) ->
cas(Args, X++[Y]).
%% Check Options for make_tar
check_args_tar(Args) ->
cat(Args, []).
cat([], X) ->
X;
%%% path ---------------------------------------------------------------
cat([{path, P} | Args], X) when is_list(P) ->
case check_path(P) of
ok ->
cat(Args, X);
error ->
cat(Args, X++[{path,P}])
end;
%%% silent -------------------------------------------------------------
cat([silent | Args], X) ->
cat(Args, X);
%%% dirs ---------------------------------------------------------------
cat([{dirs, D} | Args], X) ->
case check_dirs(D) of
ok ->
cat(Args, X);
error ->
cat(Args, X++[{dirs, D}])
end;
%%% erts ---------------------------------------------------------------
cat([{erts, E} | Args], X) when is_list(E)->
cat(Args, X);
%%% src_tests ----------------------------------------------------
cat([src_tests | Args], X) ->
cat(Args, X);
%%% variables ----------------------------------------------------------
cat([{variables, V} | Args], X) when is_list(V) ->
case check_vars(V) of
ok ->
cat(Args, X);
error ->
cat(Args, X++[{variables, V}])
end;
%%% var_tar ------------------------------------------------------------
cat([{var_tar, VT} | Args], X) when VT == include;
VT == ownfile;
VT == omit ->
cat(Args, X);
%%% machine ------------------------------------------------------------
cat([{machine, M} | Args], X) when is_atom(M) ->
cat(Args, X);
%%% exref --------------------------------------------------------------
cat([exref | Args], X) ->
cat(Args, X);
%%% exref Apps ---------------------------------------------------------
cat([{exref, Apps} | Args], X) when is_list(Apps) ->
case check_apps(Apps) of
ok ->
cat(Args, X);
error ->
cat(Args, X++[{exref, Apps}])
end;
%%% outdir Dir ---------------------------------------------------------
cat([{outdir, Dir} | Args], X) when is_list(Dir) ->
cat(Args, X);
%%% otp_build (secret, not documented) ---------------------------------
cat([otp_build | Args], X) ->
cat(Args, X);
%%% warnings_as_errors ----
cat([warnings_as_errors | Args], X) ->
cat(Args, X);
%%% no_warn_sasl ----
cat([no_warn_sasl | Args], X) ->
cat(Args, X);
%%% no_module_tests (kept for backwards compatibility, but ignored) ----
cat([no_module_tests | Args], X) ->
cat(Args, X);
%%% ERROR --------------------------------------------------------------
cat([Y | Args], X) ->
cat(Args, X++[Y]).
check_path([]) ->
ok;
check_path([H|T]) when is_list(H) ->
check_path(T);
check_path([_H|_T]) ->
error.
check_dirs([]) ->
ok;
check_dirs([H|T]) when is_atom(H) ->
check_dirs(T);
check_dirs([_H|_T]) ->
error.
check_vars([]) ->
ok;
check_vars([{Name, Dir} | T]) ->
if
is_atom(Name), is_list(Dir) ->
check_vars(T);
is_list(Name), is_list(Dir) ->
check_vars(T);
true ->
error
end;
check_vars(_) ->
error.
check_apps([]) ->
ok;
check_apps([H|T]) when is_atom(H) ->
check_apps(T);
check_apps(_) ->
error.
%% Format error
format_error(badly_formatted_release) ->
io_lib:format("Syntax error in the release file~n",[]);
format_error({illegal_name, Name}) ->
io_lib:format("Illegal name (~tp) in the release file~n",[Name]);
format_error({illegal_form, Form}) ->
io_lib:format("Illegal tag in the release file: ~tp~n",[Form]);
format_error({missing_parameter,Par}) ->
io_lib:format("Missing parameter (~p) in the release file~n",[Par]);
format_error({illegal_applications,Names}) ->
io_lib:format("Illegal applications in the release file: ~p~n",
[Names]);
format_error({missing_mandatory_app,Name}) ->
io_lib:format("Mandatory application ~w must be specified in the release file~n",
[Name]);
format_error({mandatory_app,Name,Type}) ->
io_lib:format("Mandatory application ~w must be of type 'permanent' in the release file. Is '~p'.~n",
[Name,Type]);
format_error({duplicate_register,Dups}) ->
io_lib:format("Duplicated register names: ~n~ts",
[map(fun({{Reg,App1,_,_},{Reg,App2,_,_}}) ->
io_lib:format("\t~tw registered in ~w and ~w~n",
[Reg,App1,App2])
end, Dups)]);
format_error({undefined_applications,Apps}) ->
io_lib:format("Undefined applications: ~p~n",[Apps]);
format_error({duplicate_modules,Dups}) ->
io_lib:format("Duplicated modules: ~n~ts",
[map(fun({{Mod,App1,_},{Mod,App2,_}}) ->
io_lib:format("\t~w specified in ~w and ~w~n",
[Mod,App1,App2])
end, Dups)]);
format_error({included_and_used, Dups}) ->
io_lib:format("Applications both used and included: ~p~n",[Dups]);
format_error({duplicate_include, Dups}) ->
io_lib:format("Duplicated application included: ~n~ts",
[map(fun({{Name,App1,_,_},{Name,App2,_,_}}) ->
io_lib:format("\t~w included in ~w and ~w~n",
[Name,App1,App2])
end, Dups)]);
format_error({modules,ModErrs}) ->
format_errors(ModErrs);
format_error({circular_dependencies,Apps}) ->
io_lib:format("Circular dependencies among applications: ~p~n",[Apps]);
format_error({not_found,File}) ->
io_lib:format("File not found: ~tp~n",[File]);
format_error({parse,File,{Line,Mod,What}}) ->
Str = Mod:format_error(What),
io_lib:format("~ts:~w: ~ts\n",[File, Line, Str]);
format_error({read,File}) ->
io_lib:format("Cannot read ~tp~n",[File]);
format_error({open,File,Error}) ->
io_lib:format("Cannot open ~tp - ~ts~n",
[File,file:format_error(Error)]);
format_error({close,File,Error}) ->
io_lib:format("Cannot close ~tp - ~ts~n",
[File,file:format_error(Error)]);
format_error({delete,File,Error}) ->
io_lib:format("Cannot delete ~tp - ~ts~n",
[File,file:format_error(Error)]);
format_error({tar_error,What}) ->
form_tar_err(What);
format_error({warnings_treated_as_errors,Warnings}) ->
io_lib:format("Warnings being treated as errors:~n~ts",
[map(fun(W) -> form_warn("",W) end, Warnings)]);
format_error(ListOfErrors) when is_list(ListOfErrors) ->
format_errors(ListOfErrors);
format_error(E) -> io_lib:format("~tp~n",[E]).
format_errors(ListOfErrors) ->
map(fun({error,E}) -> form_err(E);
(E) -> form_err(E)
end, ListOfErrors).
form_err({bad_application_name,{Name,Found}}) ->
io_lib:format("~p: Mismatched application id: ~p~n",[Name,Found]);
form_err({error_reading, {Name, What}}) ->
io_lib:format("~p: ~ts~n",[Name,form_reading(What)]);
form_err({module_not_found,App,Mod}) ->
io_lib:format("~w: Module (~w) not found~n",[App,Mod]);
form_err({error_add_appl, {Name, {tar_error, What}}}) ->
io_lib:format("~p: ~ts~n",[Name,form_tar_err(What)]);
form_err(E) ->
io_lib:format("~tp~n",[E]).
form_reading({not_found,File}) ->
io_lib:format("File not found: ~tp~n",[File]);
form_reading({application_vsn, {Name,Vsn}}) ->
io_lib:format("Application ~ts with version ~tp not found~n",[Name, Vsn]);
form_reading({parse,File,{Line,Mod,What}}) ->
Str = Mod:format_error(What),
io_lib:format("~ts:~w: ~ts\n",[File, Line, Str]);
form_reading({read,File}) ->
io_lib:format("Cannot read ~tp~n",[File]);
form_reading({{bad_param, P},_}) ->
io_lib:format("Bad parameter in .app file: ~tp~n",[P]);
form_reading({{missing_param,P},_}) ->
io_lib:format("Missing parameter in .app file: ~p~n",[P]);
form_reading({badly_formatted_application,_}) ->
io_lib:format("Syntax error in .app file~n",[]);
form_reading({override_include,Apps}) ->
io_lib:format("Tried to include not (in .app file) specified applications: ~p~n",
[Apps]);
form_reading({no_valid_version, {{_, SVsn}, {_, File, FVsn}}}) ->
io_lib:format("No valid version (~tp) of .app file found. Found file ~tp with version ~tp~n",
[SVsn, File, FVsn]);
form_reading({parse_error, {File, Line, Error}}) ->
io_lib:format("Parse error in file: ~tp. Line: ~w Error: ~tp; ~n", [File, Line, Error]);
form_reading(W) ->
io_lib:format("~tp~n",[W]).
form_tar_err({open, File, Error}) ->
io_lib:format("Cannot open tar file ~ts - ~ts~n",
[File, erl_tar:format_error(Error)]);
form_tar_err({add, File, Error}) ->
io_lib:format("Cannot add file ~ts to tar file - ~ts~n",
[File, erl_tar:format_error(Error)]).
%% Format warning
format_warning(Warnings) ->
map(fun({warning,W}) -> form_warn("*WARNING* ", W) end, Warnings).
form_warn(Prefix, {source_not_found,{Mod,App,_}}) ->
io_lib:format("~ts~w: Source code not found: ~w.erl~n",
[Prefix,App,Mod]);
form_warn(Prefix, {{parse_error, File},{_,_,App,_,_}}) ->
io_lib:format("~ts~w: Parse error: ~tp~n",
[Prefix,App,File]);
form_warn(Prefix, {obj_out_of_date,{Mod,App,_}}) ->
io_lib:format("~ts~w: Object code (~w) out of date~n",
[Prefix,App,Mod]);
form_warn(Prefix, {exref_undef, Undef}) ->
F = fun({M,F,A}) ->
io_lib:format("~tsUndefined function ~w:~tw/~w~n",
[Prefix,M,F,A])
end,
map(F, Undef);
form_warn(Prefix, missing_sasl) ->
io_lib:format("~tsMissing application sasl. "
"Can not upgrade with this release~n",
[Prefix]);
form_warn(Prefix, What) ->
io_lib:format("~ts~tp~n", [Prefix,What]).