Find file
Fetching contributors…
Cannot retrieve contributors at this time
1588 lines (1434 sloc) 45.1 KB
%%%----------------------------------------------------------------------
%%% File : builder.erl
%%% Author : Mats Cronqvist <mats.cronquist@etx.ericsson.se>
%%% : Ulf Wiger <ulf.wiger@ericsson.com>
%%% Purpose : Simplify build of OTP applications & boot scripts
%%% Created : 2 Jan 2001 by Mats Cronqvist <etxmacr@avc386>
%%%----------------------------------------------------------------------
%%% @doc OTP release script builder.
%%%
%%% <p>This program compiles .rel, .script, .boot and sys.config files
%%% for erlang applications. It supports incremental and recursive builds,
%%% and is intended to be (much) easier and safer to use than doing it all
%%% manually. This program does not generate beam code from Erlang source
%%% files.</p>
%%%
%%% <p>The program makes some assumptions:</p>
%%% <ul><li>The application is store in a directory whose name is
%%% composed with the name of the application '-' the version
%%% number of the application (i.e. myapp-1.0)</li>
%%% <li>The release and app file used in input are simplified subset
%%% of the final Erlang release and app file. Some differences:
%%% Versions are handled automatically; You do not need to add
%%% the erts version tuple in the rel.src file; the application,
%%% currently being built is mandatory in the list of apps in the
%%% rel.src file; etc.</li>
%%% </ul>
%%%
%%% <p>The program (henceforth called 'builder') can be customized
%%% using a number of options. The options are prioritized in
%%% the following order: (1) those given as arguments to the
%%% <code>builder:go/1</code> function, those in the BUILD_OPTIONS
%%% file, and (3) the hard-coded defaults.</p>
%%%
%%% <p>Valid options are</p>
%%% <dl>
%%% <dt>{app_dir, dirname()}</dt>
%%% <dd>The application directory of the application being built.
%%% The default value is Current Working Directory.</dd>
%%%
%%% <dt>{build_options, filename()}</dt>
%%% <dd>A filename pointing to a BUILD_OPTIONS file. This is a file
%%% containing erlang expressions, each terminated by "." -- just
%%% like a file processed using file:eval/1. The last expression
%%% in the file should result in a list of
%%% <code>{Key,Value}</code> options.
%%% If this option is not given, builder will look in [AppDir] and
%%% <code>[AppDir]/src</code> for a file called BUILD_OPTIONS.<br/>
%%% Pre-bound variables are:
%%% <ul>
%%% <li><code>ScriptName : filename()</code>,
%%% the name of the script file.</li>
%%% </ul></dd>
%%% <dt>{report, Level : atom()}</dt>
%%% <dd>Specifies the reporting level. The following levels are recognized:
%%% none, progress, verbose, debug.</dd>
%%%
%%% <dt>{out_dir, dirname()}</dt>
%%% <dd>Specifies where [AppName].script, [AppName].boot, and sys.config
%%% should be written. Default is <code>[AppDir]/priv</code>.</dd>
%%%
%%% <dt>{sys_config, filename()}</dt>
%%% <dd>Specifies the location of the sys.config file. Default is
%%% <code>[OutDir]/sys.config</code>.</dd>
%%%
%%% <dt>{rel_file, filename()}</dt>
%%% <dd>Specifies which .rel file should be used as input for the
%%% build process. Default is
%%% <code>[AppDir]/src/[AppName].rel.src</code>.</dd>
%%%
%%% <dt>{rel_name, string()}</dt>
%%% <dd>This option can be used if the release should be called something
%%% other than <code>[AppName]</code> or whatever is in the
%%% <code>.rel</code> file.</dd>
%%%
%%% <dt>{app_vsn, string()}</dt>
%%% <dd>This option can be used to assign a version to the current
%%% application. If the application directory has a version suffix,
%%% the version suffix will override this option. If the directory
%%% has no suffix, the default vsn will be "BLDR", unless 'app_vsn'
%%% has been specified.</dd>
%%%
%%% <dt>{apps, [App : AppName | {AppName,Vsn} | {AppName,Vsn,EbinDir}]}</dt>
%%% <dd>This is a way to identify which applications should be included
%%% in the build. The way builder determines which applications should
%%% be included is this:<ul>
%%% <li>If there is a <code>.rel.src</code> file, the applications
%%% listed in this file are included first, in the order in which
%%% they are listed.</li>
%%% <li>After this, all applications listed in the 'applications'
%%% attribute of the <code>.app.src</code> file are added (if not
%%% already listed), also in the order in which they are listed.</li>
%%% <li>Then, all remaining applications in the 'apps' list are added
%%% in order.</li>
%%% <li>Finally, any included applications listed in the
%%% <code>.rel.src</code> file, not already listed, are added.
%%% (note that applications listed in 'included_applications' of
%%% the <code>.app.src</code> file are not included here. The
%%% <code>.rel</code> file can be used to override this definition,
%%% so it would be an error to include it at this point.)</li>
%%% </ul>
%%% </dd>
%%%
%%% <dt>{path, [PathExpr]}</dt>
%%% <dd>Specifies the search path when locating applications. Default is
%%% code:get_path(). PathExpr can contain wildcards, e.g.
%%% <code>"/OTP/lib/kernel*/ebin"</code> (basically anything that
%%% <code>regexp:sh_to_awk/1</code> understands.)</dd>
%%%
%%% <dt>{skip, [ModuleName : atom()]}</dt>
%%% <dd>Lists modules that should not be included in the generated .app
%%% file for the current application. If not specified, builder will
%%% locate all modules under <code>[AppDir]/src</code>, extract
%%% their module names, and include these module names in the
%%% <code>'modules'</code> list of the <code>.app</code> file.</dd>
%%%
%%% <dt>{make_app, true | false | auto}</dt>
%%% <dd>If true, builder will generate an .app file from an .app.src
%%% file; if false, it will try to use an existing .app file; if
%%% auto, it will build an .app file if no .app file exists
%%% Default is 'auto'.</dd>
%%%
%%% <dt>{make_rel, true | false}</dt>
%%% <dd>If true, builder will generate a .rel file from a .rel.src file;
%%% if false, it will assume that there is a working .rel file.
%%% Default: false if the rel_file option has been specified; true
%%% otherwise.</dd>
%%%
%%% <dt>{make_boot, true | false}</dt>
%%% <dd>If true, builder will run <code>systools:make_script()</code>
%%% to generate .script and .boot files in [OutDir]. If false, it
%%% will assume that there is a working .boot file. Default is true.</dd>
%%% </dl>
%%%
%%% <dt>{systools, Options : [Opt]}</dt>
%%% <dd>If specified, <code>Options</code> will be passed to the
%%% <code>systools:make_script()</code> command for building the
%%% <code>.boot</code> script. Note that the <code>'path'</code> option
%%% is generated by builder. It cannot be overridden.
%%% See <code>erl -man systools</code> for available options.</dd>
%%%
%%% <dt>{sh_script, auto | none}</dt>
%%% <dd>If <code>'auto'</code>, builder will generate a small sh script
%%% that makes it easier to start the system; If <code>'none'</code>,
%%% no script will be generated. Default is <code>'none'</code> on
%%% non-UNIX systems and <code>'auto'</code> on UNIX systems.</dd>
%%%
%%% <dt>{erl_opts, Opts : string()}</dt>
%%% <dd>If specified, and if a sh_script is generated (see above),
%%% <code>Opts</code> will be appended to the <code>erl</code>
%%% command line. The builder will automatically put in options to
%%% identify the boot script, sys.config, and possible boot variables.</dd>
%%%
%%% <dt>{config, Config : {file, filename()} | {M,F,A} | Data}</dt>
%%% <dd>If present, this option should either point to a file that
%%% contains a subset of data for a sys.config file (same format as
%%% sys.config), or give a <code>{M,F,A}</code> tuple, where
%%% <code>apply(M,F,A)</code> results in a list,
%%% <code>[{AppName, [{Key, Value}]}]</code>, or finally just specify
%%% the data in place. The builder will look for similar <code>config</code>
%%% directives in any BUILD_OPTIONS files of other applications that
%%% are part of the build, and all inputs will be merged
%%% into a common <code>sys.config</code>.</dd>
%%%
%%% <dt>{{config,OtherApp}, Config}</dt>
%%% <dd>This is a way to specify environment variables for another
%%% application in the build options. <code>Config</code> in this case
%%% is the same as <code>Config</code> above. The difference between
%%% this option and specifying environment variables for the other
%%% application using the above option, is that with a simple
%%% <code>{config, ...}</code> directive, builder will also check
%%% for a similar directive in the other application, and all
%%% different inputs will be merged. Using a <code>{{config,App},...}</code>
%%% option, builder will not look into that application further.</dd>
%%% @end
%%% =====================================================================
-module(builder).
-author('mats.cronquist@etx.ericsson.se').
-author('ulf.wiger@ericsson.com').
-export([go/0, go/1, find_app/2]).
-export([early_debug_on/0]).
-compile(export_all).
-include_lib("kernel/include/file.hrl").
-import(dict, [fetch/2]).
-define(report(Level, Format, Args),
case do_report(Level) of
true ->
io:format("[~p:~p] " ++ Format, [?MODULE,?LINE|Args]);
false ->
ok
end).
-define(f(D, Expr), fun(D) -> Expr end).
-define(herewith(D), fun(D) ->
?report(debug, "herewith~n", []),
D
end).
early_debug_on() ->
put(builder_debug, true).
go() ->
go([]).
go(Options) ->
%% We use a dictionary for the options. The options are prioritized in
%% the following order: (1) those given as arguments to the go/1 function,
%% those in the BUILD_OPTIONS file, and (3) the hard-coded defaults.
%% The options stored last in the dictionary take precedence, but since
%% we want to allow for the Options list to guide us in finding the
%% BUILD_OPTIONS file (under certain circumstances), we store the defaults
%% and Options list in the dictionary, then locate and read the file, then
%% store the Options list again.
Dict0 = mk_dict(default_options() ++ Options),
Dict = with(Dict0,
[fun(D) -> read_options_file(D) end,
?herewith(D), % to help debugging
fun(D) -> store_options(Options, D) end,
?herewith(D),
fun(D) -> post_process_options(D) end,
?herewith(D),
fun(D) ->
{Path, [App, Vsn]} = get_app(D),
store_options([{app_name, list_to_atom(App)},
{app_vsn, Vsn}], D)
end,
?herewith(D),
fun(D) ->
[Descr, Id, Reg, Apps, Env, Mod, Phases] =
read_app_file(D),
Vsn = fetch(app_vsn, D),
store_options([{app, [{vsn,Vsn}, Descr, Id, Reg,
Apps, Env, Mod, Phases]}], D)
end,
?herewith(D),
fun(D) -> case dict:find(rel_name, D) of
{ok,_} -> D;
error ->
RelName = atom_to_list(
fetch(app_name, D)),
dict:store(rel_name, RelName, D)
end
end,
?herewith(D),
fun(D) ->
RelFname = get_rel_filename(D),
case file:consult(RelFname) of
{ok, [{release,{_Name,_RelVsn},
_RelApps}= Rel]} ->
?report(debug,"rel_src = ~p~n",[Rel]),
dict:store(rel_src,Rel, D);
_ ->
D
end
end,
?herewith(D),
fun(D) -> AppInfo = find_apps(D),
?report(debug, "find_apps(D) -> ~p~n", [AppInfo]),
dict:store(app_info, AppInfo, D)
end,
?herewith(D),
fun(D) ->
BootVars = boot_vars(D),
dict:store(boot_vars, BootVars, D)
end]),
case lists:member({ok,true}, [dict:find(make_boot,Dict),
dict:find(make_rel,Dict),
dict:find(make_config, Dict)]) of
true ->
verify_dir(fetch(out_dir,Dict));
false ->
ok
end,
Dict1 = make_rel(Dict),
make_app(Dict1),
Res = make_boot(Dict1),
make_config(Dict1),
make_sh_script(Dict1),
cleanup(Dict1),
Res.
find_app(App, Path) ->
{Found,_} = expand_path(Path, [App], [], []),
Found.
post_process_options(Dict) ->
D = with(Dict,
[fun(D) -> case dict:find(out_dir, D) of
{ok,_} -> D;
error -> dict:store(out_dir, out_dir(D), D)
end
end,
fun(D) -> case dict:find(rel_file, D) of
{ok,_} -> dict:store(make_rel, false);
error -> dict:store(make_rel, true, D)
end
end,
fun(D) -> case dict:find(config, D) of
{ok,_} -> dict:store(make_config, true, D);
_ -> D
end
end,
fun(D) -> case dict:find(make_boot, D) of
{ok, _} -> D;
error -> dict:store(make_boot, true, D)
end
end,
fun(D) -> case dict:find(make_app, D) of
{ok,_} -> D;
error -> dict:store(make_app, auto, D)
end
end,
fun(D) -> case dict:find(sh_script, D) of
{ok, _} -> D;
error ->
case os:type() of
{unix,_} ->
dict:store(sh_script,auto,D);
_ ->
dict:store(sh_script,none,D)
end
end
end,
fun(D) -> case dict:find(systools, D) of
{ok, _} -> D;
error -> dict:store(systools, [], D)
end
end]),
?report(debug, "Options = ~p~n", [dict:to_list(D)]),
D.
with(Dict, Actions) ->
lists:foldl(fun(F,D) ->
F(D)
end, Dict, Actions).
cleanup(Dict) ->
pop_report_level().
default_options() ->
[{app_dir, cwd()}, {skip, []},
{path, code:get_path()}].
read_options_file(Dict) ->
case dict:find(build_options, Dict) of
{ok, BuildOptsF} ->
case script(BuildOptsF) of
{error, empty_script} ->
%% We accept this
Dict;
{ok, Terms} ->
?report(debug, "Stored terms (~s) =~n ~p~n",
[BuildOptsF, Terms]),
store_options(Terms, Dict);
{error, Reason} ->
exit({bad_options_file, {BuildOptsF, Reason}})
end;
error ->
Path =
case dict:find(app_dir, Dict) of
{ok, AppDir} ->
[AppDir, filename:join(AppDir, "src")];
error ->
[".", "src"]
end,
case path_script(Path, "BUILD_OPTIONS") of
{ok, Terms, Fullname} ->
Dict1 = store_options(Terms, Dict),
?report(debug, "Stored terms (~s) =~n ~p~n",
[Fullname, Terms]),
Dict1;
{error, enoent} ->
Dict;
Error ->
exit({build_options, Error})
end
end.
mk_dict(Options) ->
%% pust a unique ref onto the dictionary. This will allow us to
%% redefine e.g. the report_level (for the same instance of
%% the dictionary, and stack the same (for new instances of the
%% dictionary -- recursive build.)
store_options([{'#ref#', make_ref()}|Options], dict:new()).
store_options(Options, Dict) ->
?report(debug, "store_options(~p)~n", [Options]),
lists:foldl(
fun({report, Level}, D) ->
push_report_level(Level, D),
dict:store(report, Level, D);
({Key,Value}, D) ->
dict:store(Key,Value,D)
end, Dict, Options).
make_rel(Dict) ->
case dict:find(make_rel, Dict) of
{ok,true} ->
?report(debug, "will make rel~n", []),
App = atom_to_list(fetch(app_name, Dict)),
Vsn = fetch_key(vsn, fetch(app, Dict)),
Apps = fetch(app_info, Dict),
AppInfo = [{A,V} || {A,V,F} <- Apps],
?report(debug,"AppInfo = ~p~n", [AppInfo]),
{RelName, Rel} =
case dict:find(rel_src, Dict) of
%% mremond: Added this case clause:
%% Without it you get an error if you define
%% the erts version in your release file subset.
%% The ERTS version is anyway replace by the
%% system ERTS
{ok, {release,{Name,RelVsn}, {erts, ErtsVsn}, RelApps}} ->
?report(debug,"found rel_src: RelApps=~p~n",
[RelApps]),
{Name,
{release, {Name,RelVsn}, {erts, get_erts_vsn()},
AppInfo}};
{ok, {release,{Name,RelVsn},RelApps}} ->
%% here we should check that Apps is a subset of
%% RelApps; if so, use RelApps; otherwise exit
?report(debug,"found rel_src: RelApps=~p~n",
[RelApps]),
{Name,
{release, {Name,RelVsn}, {erts, get_erts_vsn()},
AppInfo}};
error ->
{App,
{release, {App, Vsn}, {erts, get_erts_vsn()},
AppInfo}}
end,
RelFileTarget =
filename:join(fetch(out_dir,Dict), RelName ++ ".rel"),
out(RelFileTarget, Rel),
store_options([{rel_name, RelName},
{rel_file, RelFileTarget}], Dict);
_ ->
?report(debug, "will NOT make rel~n", []),
Dict
end.
make_config(Dict) ->
AppInfo = fetch(app_info, Dict),
AppName = fetch(app_name, Dict),
ForeignData = lists:foldl(
fun({A,V,Ebin}, Acc) when A =/= AppName ->
Acc ++ try_get_config(A,Ebin,Dict);
(_, Acc) ->
Acc
end, [], AppInfo),
Data = case dict:find(config, Dict) of
{ok, Type} ->
get_config_data(Type);
error ->
[]
end,
merge_config(Data ++ ForeignData, Dict).
try_get_config(AppName, Ebin, Dict) ->
case dict:find({config, AppName}, Dict) of
{ok, Type} ->
get_config_data(Type);
error ->
BuildOpts = filename:join(
filename:join(filename:dirname(Ebin), "src"),
"BUILD_OPTIONS"),
case script(BuildOpts) of
{error, enoent} ->
[];
{ok, Result} ->
?report(debug, "script(~p) ->~n ~p~n",
[BuildOpts, Result]),
case lists:keysearch(config, 1, Result) of
{value, {_, Type}} ->
get_config_data(Type);
false ->
[]
end
end
end.
get_config_data({file,F}) ->
?report(debug, "get_config_data({file, ~p})~n", [F]),
case file:consult(F) of
{ok, [Terms]} when list(Terms) ->
Terms;
{error, Reason} ->
exit({config, {F, Reason}})
end;
get_config_data({M,F,A}) ->
?report(debug, "get_config_data(~p)~n", [{M,F,A}]),
apply(M,F,A);
get_config_data(Data) when list(Data) ->
Data.
merge_config([], Dict) ->
ok;
merge_config(Data, Dict) ->
ConfigF = sys_config(Dict),
case file:consult(ConfigF) of
{error, enoent} ->
out(ConfigF, Data);
{ok, [Terms]} ->
?report(debug, "merging terms (~p,~p), F = ~p~n",
[Data,Terms, ConfigF]),
NewData = merge_terms(Data, Terms),
out(ConfigF, NewData)
end.
merge_terms([{App,Vars}|Data], Old) ->
case lists:keysearch(App, 1, Old) of
false ->
merge_terms(Data, Old ++ [{App,Vars}]);
{value, {_, OldVars}} ->
?report(debug, "merging vars (~p,~p) for ~p~n",
[Vars,OldVars, App]),
NewVars = merge_vars(Vars, OldVars, App),
merge_terms(Data, lists:keyreplace(App,1,Old,{App,NewVars}))
end;
merge_terms([], Data) ->
Data.
merge_vars([{Key,Vals}|Data], Old, App) ->
case lists:keysearch(Key, 1, Old) of
{value, {_, OldVals}} when OldVals =/= Vals ->
exit({config_conflict, {App, Key}});
{value, {_, OldVals}} when OldVals == Vals ->
merge_vars(Data, Old, App);
false ->
merge_vars(Data, Old ++ [{Key,Vals}], App)
end;
merge_vars([], Data, _App) ->
Data.
fetch_key(Key, L) ->
{value, {_, Value}} = lists:keysearch(Key, 1, L),
Value.
make_app(Dict) ->
case dict:find(make_app, Dict) of
{ok,true} ->
do_make_app(Dict);
{ok,auto} ->
AppName = fetch(app_name, Dict),
AppF = filename:join(ebin_dir(Dict),
atom_to_list(AppName) ++ ".app"),
case file:read_file_info(AppF) of
{ok, _} ->
ok;
{error, enoent} ->
do_make_app(AppF, AppName, Dict)
end;
_ ->
ok
end.
do_make_app(Dict) ->
AppName = fetch(app_name, Dict),
AppF = filename:join(ebin_dir(Dict), atom_to_list(AppName) ++ ".app"),
do_make_app(AppF, AppName, Dict).
do_make_app(AppF, AppName, Dict) ->
[Vsn, Descr, Id, Reg, Apps, Env, Mod, Phases] = fetch(app, Dict),
Modules = modules(Dict),
Remove = case {Mod, Phases} of
{{_,[]},{_,undefined}} -> [Mod, Phases];
{{_,{_,_}},{_,undefined}} -> [Phases];
{{_,[]}, {_,_}} -> [Mod];
_ -> []
end,
Options = [Vsn, Descr, Id, Modules,
Reg, Apps, Env, Mod, Phases] -- Remove,
out(AppF, {application, AppName, Options}).
make_boot(Dict) ->
case dict:find(make_boot, Dict) of
{ok,true} ->
ensure_rel_file(Dict),
App = fetch(app_name, Dict),
CWD = cwd(),
ok = file:set_cwd(fetch(out_dir,Dict)),
SystoolsOpts0 = merge_opts([{path, systools_path(Dict)}],
fetch(systools, Dict)),
SystoolsOpts = maybe_local(SystoolsOpts0, Dict),
?report(debug, "SystoolsOpts = ~p~n", [SystoolsOpts]),
RelName = fetch(rel_name, Dict),
Res = systools:make_script(RelName, SystoolsOpts),
?report(progress, "systools:make_script() -> ~p~n", [Res]),
make_load_script(RelName),
ok = file:set_cwd(CWD),
case Res of
error ->
exit(error_in_make_script);
ok ->
ok
end;
_ ->
ok
end.
maybe_local(Opts, Dict) ->
case lists:keymember(variables, 1, Opts) of
true ->
Opts;
false ->
[local|Opts -- [local]]
end.
make_load_script(RelName) ->
{ok, Bin} = file:read_file(RelName ++ ".boot"),
{script,Id,Cmds} = binary_to_term(Bin),
Keep =
lists:filter(
fun({apply,{application,start_boot,[kernel,Type]}}) -> true;
({apply,{application,start_boot,[stdlib,Type]}}) -> true;
({apply,{application,start_boot,[sasl,Type]}}) -> true;
({apply,{application,start_boot,[_,Type]}}) -> false;
(_) ->
true
end, Cmds),
NewScript = {script,Id,Keep},
file:write_file(RelName ++ "_load.boot", term_to_binary(NewScript)),
out(RelName ++ "_load.script", NewScript).
boot_vars(Dict) ->
case dict:find(systools, Dict) of
{ok, Opts} when Opts =/= [] ->
?report(debug, "systools opts = ~p~n", [Opts]),
case lists:keysearch(variables, 1, Opts) of
{value, {_, Vars}} ->
Vars;
false ->
[]
end;
_ ->
?report(debug,"will make boot_vars~n", []),
{ok,[[Root]]} = init:get_argument(root),
Apps = dict:fetch(app_info, Dict),
prefixes(Apps, [{"ROOT",Root}])
end.
prefixes([{A,_,D}|Apps], Prefixes) ->
case [P || {_,P} <- Prefixes,
lists:prefix(P, D)] of
[] ->
prefixes(Apps, guess_root(D, Prefixes));
[_|_] ->
prefixes(Apps, Prefixes)
end;
prefixes([], Prefixes) ->
Prefixes.
guess_root(D, Pfxs) ->
case lists:reverse(filename:split(D)) of
["ebin",App,"lib",Dir|T] ->
guess(to_upper(Dir), 0,
filename:join(lists:reverse([Dir|T])), Pfxs);
["ebin",App,Dir|T] ->
guess(to_upper(Dir), 0,
filename:join(lists:reverse([Dir|T])), Pfxs)
end.
guess(Guess,N,RootDir, Pfxs) ->
case lists:keymember(Guess,1,Pfxs) of
false ->
?report(debug, "manufactured boot_var {~p,~p}~n",
[Guess,RootDir]),
[{Guess,RootDir}|Pfxs];
true ->
?report(debug, "duplicate Guess = ~p~n", [Guess]),
guess(increment(Guess,N), N+1, RootDir, Pfxs)
end.
increment(Guess,0) ->
Guess ++ "-1";
increment(Guess,N) when N < 10 ->
[_,$-|T] = lists:reverse(Guess),
lists:reverse(T) ++ [$-|integer_to_list(N+1)].
to_upper([H|T]) when H >= $a, H =< $z ->
[$A+(H-$a)|to_upper(T)];
to_upper([H|T]) ->
[H|T];
to_upper([]) ->
[].
make_sh_script(Dict) ->
case dict:find(sh_script, Dict) of
{ok, auto} ->
OutDir = fetch(out_dir, Dict),
RelName = fetch(rel_name, Dict),
StartF = filename:join(OutDir, RelName ++ ".start"),
LoadF = filename:join(OutDir, RelName ++ ".load"),
do_make_sh_script(StartF,start,Dict),
do_make_sh_script(LoadF,load,Dict);
_ ->
ok
end.
do_make_sh_script(ScriptF, Type, Dict) ->
ok = file:write_file(ScriptF, list_to_binary(sh_script(Type,Dict))),
{ok,FI} = file:read_file_info(ScriptF),
Mode = FI#file_info.mode bor 8#00100,
ok = file:write_file_info(ScriptF, FI#file_info{mode = Mode}).
sh_script(Mode,Dict) ->
OutDir = fetch(out_dir, Dict),
AppDir = fetch(app_dir, Dict),
XOpts = case dict:find(erl_opts, Dict) of
{ok, Opts} ->
[" ", Opts];
error ->
""
end,
%% mremond: The path it need to be sure the script is predictible
% Path = [" -pa ", filename:join(AppDir, "ebin")],
RelName = fetch(rel_name, Dict),
Boot = case Mode of
start -> [" -boot ", filename:join(OutDir, RelName)];
load -> [" -boot ", filename:join(OutDir, RelName++"_load")]
end,
Sys = case dict:find(make_config, Dict) of
{ok, true} ->
[" -config ", filename:join(OutDir, "sys")];
_ ->
[]
end,
BootVars = [[" -boot_var ", Var, " ", Dir] ||
{Var,Dir} <- dict:fetch(boot_vars, Dict)],
% ["#/bin/sh\n"
% "erl ", Path, Boot, Sys, BootVars, XOpts, "\n"].
["#/bin/sh\n"
"erl ", Boot, Sys, BootVars, XOpts, "\n"].
ensure_rel_file(Dict) ->
OutDir = fetch(out_dir, Dict),
case dict:find(rel_file, Dict) of
{ok, F} ->
case filename:dirname(F) of
D when D == OutDir ->
ok;
_ ->
%% This doesn't necessarily mean that it's not there
RelName = fetch(rel_name, Dict),
{ok, [{release, {N,Vsn}, Erts, Apps}]} =
file:consult(F),
RelF = filename:join(OutDir,
fetch(rel_name,Dict) ++ ".rel"),
out(RelF, {release, {RelName,Vsn}, Erts, Apps})
end;
error ->
exit(no_rel_file)
end.
systools_path(Dict) ->
AppInfo = fetch(app_info, Dict),
[D || {_A,_V,D} <- AppInfo].
merge_opts(Opts, [{path,P}|Opts2]) ->
exit({not_allowed, {systools, {path,P}}});
merge_opts(Opts, [Opt|Opts2]) ->
[Opt|merge_opts(Opts, Opts2)];
merge_opts(Opts, []) ->
Opts.
cwd() ->
{ok, CWD} = file:get_cwd(),
CWD.
out_dir(Dict) ->
case dict:find(out_dir, Dict) of
{ok, OutDir} ->
OutDir;
error ->
filename:join(fetch(app_dir,Dict), "priv")
end.
ebin_dir(Dict) ->
AppDir = fetch(app_dir, Dict),
filename:join(AppDir, "ebin").
sys_config(Dict) ->
case dict:find(sys_config, Dict) of
{ok, Filename} ->
Filename;
error ->
filename:join(fetch(out_dir,Dict), "sys.config")
end.
get_app(Dict) ->
AppDir = fetch(app_dir, Dict),
%% mremond: The program was crashing when the directory where not
%% containing the application version
%% Now we assume that version number is "BLDR" by default.
case string:tokens(hd(lists:reverse(string:tokens(AppDir, "/"))), "-") of
[App, Vsn] -> {AppDir, [App, Vsn]};
[App] ->
case dict:find(app_vsn, Dict) of
{ok, Vsn} ->
{AppDir, [App, Vsn]};
error ->
{AppDir, [App, "BLDR"]}
end
end.
get_app_filename(App, Dict) ->
AppDir = fetch(app_dir, Dict),
filename:join([AppDir, "src", App++".app.src"]).
get_rel_filename(Dict) ->
App = atom_to_list(dict:fetch(app_name, Dict)),
AppDir = fetch(app_dir, Dict),
filename:join([AppDir, "src", App++".rel.src"]).
modules(Dict) ->
Ebin = ebin_dir(Dict),
Ext = code:objfile_extension(),
L = recursive_list_dir(Ebin),
?report(debug, "L = ~p~n", [L]),
Skip = fetch(skip, Dict),
?report(debug, "Skip = ~p~n", [Skip]),
Modules = lists:foldr(
fun(F, Acc) ->
case make_mod(F) of
{ok, M} ->
[M|Acc];
error ->
Acc
end
end, [], [F || F <- L,
check_f(F, Ext)]),
{modules, Modules -- Skip}.
recursive_list_dir(Dir) ->
{ok, Fs} = file:list_dir(Dir),
lists:concat([f_expand(F,Dir) || F <- Fs]).
f_expand(Name,Dir) ->
Fname = filename:join(Dir,Name),
case file:read_file_info(Fname) of
{ok, #file_info{type=regular}} ->
[Fname];
{ok, #file_info{type=directory}} ->
?report(debug, "~p is a directory under ~p~n", [Fname, Dir]),
{ok, Fs} = file:list_dir(Fname),
lists:concat([f_expand(F,Fname) || F <- Fs]);
{ok, Finfo} ->
?report(debug, "~p not a directory or a regular file. Ignoring~n",
[Fname]),
[];
{error, Reason} ->
?report(error, "*** read_file_info(~p) -> {error, ~p}~n",
[Fname, Reason]),
[]
end.
make_mod(F) ->
case beam_lib:version(F) of
{ok, {Module, Vsn}} ->
{ok, Module};
_ ->
error
end.
% {ok,Fd} = file:open(F, [read]),
% parse_for_mod(Fd, F).
% {ok,Bin} = file:read_file(F),
% Text = binary_to_list(Bin),
% {match,Start,Len} =
% regexp:first_match(Text,
% "^[ ]*-[ ]*module[ ]*[(][ ]*.*[ ]*[)][ ]*\."),
% Name = case string:substr(Text, Start+8,Len-10) of
% "'" ++ Quoted ->
% "'" ++ Rest = lists:reverse(Quoted),
% list_to_atom(lists:reverse(Rest));
% Str ->
% list_to_atom(Str)
% end.
parse_for_mod(Fd, F) ->
parse_for_mod(Fd, io:parse_erl_exprs(Fd,''), F).
parse_for_mod(Fd, {ok,[{op,_,'-',{call,_,{atom,_,module},[Mod]}}],_}, F) ->
{ok,parse_mod_expr(Mod)};
% parse_for_mod(Fd, {ok,[{op,_,'-',
% {call,_,{atom,_,module},[{atom,_,Mod}]}}],_}, F) ->
% {ok,Mod};
parse_for_mod(Fd, {ok,_,_}, F) ->
parse_for_mod(Fd, io:parse_erl_exprs(Fd,''), F);
parse_for_mod(Fd, {error,_,_}=Error, F) ->
io:format("*** Error parsing ~s: ~p~n", [F, Error]),
error;
parse_for_mod(Fd, {eof,_}, F) ->
io:format("*** No module attribute found in ~s~n", [F]),
error.
parse_mod_expr(Expr) ->
list_to_atom(parse_mod_expr1(Expr)).
parse_mod_expr1({atom,_,A}) ->
atom_to_list(A);
parse_mod_expr1({record_field,_,R,{atom,_,A}}) ->
parse_mod_expr1(R) ++ "." ++ atom_to_list(A).
check_f(F, Ext) ->
case regexp:match(F, ".*"++Ext++"\$") of
{match, _, _} -> true;
_ -> false
end.
find_apps(Dict) ->
App = fetch(app, Dict),
{value, {_, NeededApps}} = lists:keysearch(applications, 1, App),
AppInfo = case dict:find(apps, Dict) of
{ok, As} ->
app_info(As, Dict);
error ->
[]
end,
RelApps = case dict:find(rel_src, Dict) of
{ok, {release, _, RA}} ->
RA;
error ->
[]
end,
AppName = dict:fetch(app_name, Dict),
Apps = merge_apps(RelApps, NeededApps, AppInfo) -- [AppName],
?report(debug, "Apps = ~p~n", [Apps]),
KnownApps = [{A,V,D} || {A,V,D} <- AppInfo,
V =/= unknown,
D =/= unknown],
search_apps(Apps, Dict, AppInfo, KnownApps).
merge_apps(RelApps, AppApps, AppInfo) ->
?report(debug, "merge_apps(~p, ~p, ~p)~n", [RelApps, AppApps, AppInfo]),
Acc0 = lists:map(fun({App,Vsn,Incl}) ->
App;
({App,Vsn}) ->
App;
(App) when atom(App) ->
App
end, RelApps),
with(Acc0,
[fun(Acc) ->
Acc ++ [A || {A,_,_} <- AppInfo,
not(lists:member(A, Acc))]
end,
fun(Acc) ->
Acc ++ [A || A <- AppApps,
not(lists:member(A, Acc))]
end,
fun(Acc) ->
InclApps = lists:foldl(
fun({_,_,Incls}, AccX) ->
AccX ++ Incls;
(_, AccX) ->
AccX
end, [], RelApps),
Acc ++ [A || A <- InclApps,
not(lists:member(A, Acc))]
end]).
app_info(Apps, Dict) ->
MyName = dict:fetch(app_name, Dict),
MyVsn = dict:fetch(app_vsn, Dict),
MyEbin = ebin_dir(Dict),
Info = lists:map(
fun(A) when atom(A) -> {A,unknown,unknown};
({A,V}) -> {A,V,unknown};
({A,V,D}) -> {A,V,D}
end, Apps),
AppName = dict:fetch(app_name, Dict),
case lists:keysearch(AppName, 1, Info) of
{value, {Ai,Vi,Di}} ->
[Version,Dir] = lists:foldr(
fun({unknown,Vx}, Acc) -> [Vx|Acc];
({Vx,Vx}, Acc) -> [Vx|Acc];
({V1,V2},_) ->
exit({conflicting_info_in_apps,
{{Ai,Vi,Di},
{MyName,MyVsn,MyEbin}}})
end, [], [{Vi,MyVsn},{Di,MyEbin}]),
lists:keyreplace(AppName, 1, Info,
{AppName, Version, Dir});
false ->
Info ++ [{AppName, dict:fetch(app_vsn, Dict), ebin_dir(Dict)}]
end.
search_apps(Apps, Dict, AppInfo, InitAcc) ->
?report(debug,"search_apps(~p,Dict,~p,~p)~n", [Apps,AppInfo,InitAcc]),
Path = fetch(path, Dict),
Ebin = ebin_dir(Dict),
Path1 = Path ++ [Ebin],
MyAppName = dict:fetch(app_name, Dict),
MyApp = {MyAppName, dict:fetch(app_vsn,Dict), ebin_dir(Dict)},
?report(debug, "Search Path = ~p~n", [Path1]),
Found = case expand_path(Path ++ [Ebin], Apps, AppInfo, InitAcc) of
{FoundInfo, []} ->
?report(debug, "LeftApps = [], FoundInfo = ~p~n", [FoundInfo]),
check_found(FoundInfo, Dict);
{FoundInfo, LeftApps} ->
%% LeftApps may still include applications for which we've
%% found versions (but didn't know if they were the right ones)
?report(debug, "LeftApps = ~p,~nFoundInfo = ~p~n",
[LeftApps, FoundInfo]),
case [A || A <- LeftApps,
not(lists:keymember(A, 1, FoundInfo))] of
[] ->
check_found(FoundInfo, Dict);
NotFound ->
exit({not_found, NotFound})
end
end,
replaceadd(MyAppName,1,Found,MyApp).
check_found(FoundInfo, Dict) ->
case lists:foldr(
fun({A,unknown,D}, {Found,Left}) ->
AppF = filename:join(D,atom_to_list(A) ++ ".app"),
case vsn_from_app_file(AppF) of
unknown ->
{Found,[{A,unknown,D}|Left]};
FoundVsn ->
{[{A,FoundVsn,D}|Found], Left}
end;
(GoodApp, {Found,Left}) ->
{[GoodApp|Found], Left}
end, {[],[]}, FoundInfo) of
{Found, []} ->
?report(debug, "Found apps:~n ~p~n", [FoundInfo]),
Found;
{Found, UnknownVsns} ->
%% These app directories didn't have a version
%% suffix. This is allowed, but we must then
%% extract the version some other way.... FFS
?report(debug, "Should find the versions of"
" these apps:~n ~p~n", [UnknownVsns]),
Found
end.
replaceadd(Key,Pos,[H|T],Obj) when element(Pos,H) == Key ->
[Obj|T];
replaceadd(Key,Pos,[H|T],Obj) ->
[H|replaceadd(Key,Pos,T,Obj)];
replaceadd(Key,Pos,[],Obj) ->
[Obj].
vsn_from_app_file(AppF) ->
case file:consult(AppF) of
{ok, [{application,_,Opts}]} ->
case lists:keysearch(vsn,1,Opts) of
{value,{_,Vsn}} ->
Vsn;
false ->
unknown
end;
Other ->
?report(debug, "file:consult(~p) -> ~p~n", [AppF,Other]),
unknown
end.
%%% expand_path/2 takes a list of path expressions, where each expression
%%% may contain wildcards (sh expressions)
expand_path([Dir|Ds], Apps, AppInfo, Acc) ->
?report(debug, "Dir = ~p~n", [Dir]),
SplitDir = filename:split(Dir),
case file:read_file_info(Dir) of
{ok, #file_info{type = directory}} ->
case lists:reverse(SplitDir) of
["ebin",AppDir|_] ->
case string:tokens(AppDir, "-") of
[App,Vsn] ->
?report(debug, "App = ~p, Vsn = ~p~n", [App,Vsn]),
AppA = list_to_atom(App),
{Acc1,Apps1} =
case lists:member(AppA, Apps) of
true ->
maybe_save_app(AppA, Vsn, Dir,
Apps, AppInfo, Acc);
false ->
{Acc, Apps}
end,
expand_path(Ds, Apps1, AppInfo, Acc1);
[App] ->
%% App dir without version suffix -- legal,
%% but we must extract the version somehow
AppA = list_to_atom(App),
AppF = filename:join(Dir, App ++ ".app"),
Vsn = vsn_from_app_file(AppF),
case lists:member(AppA, Apps) of
true ->
{Acc1,Apps1} =
maybe_save_app(AppA, Vsn, Dir,
Apps, AppInfo, Acc),
expand_path(Ds, Apps1, AppInfo, Acc1);
false ->
expand_path(Ds, Apps, AppInfo, Acc)
end;
Other ->
?report(debug, "*** strange app dir ~p~n",
[AppDir]),
expand_path(Ds, Apps, AppInfo, Acc)
end;
_ ->
expand_path(Ds, Apps, AppInfo, Acc)
end;
{error, enoent} ->
%% assume it is a regexp
{Acc1, RestApps} = expand_path(
d_expand(SplitDir), Apps, AppInfo, Acc),
expand_path(Ds, RestApps, AppInfo, Acc1);
_ ->
%% something else -- ignore
expand_path(Ds, Apps, AppInfo, Acc)
end;
expand_path([], Apps, AppInfo, Acc) ->
{Acc, Apps}.
maybe_save_app(AppA, Vsn, Dir, Apps, AppInfo, Acc) ->
?report(debug, ("maybe_save_app(~p,~p,~p,_,~n"
" ~p,~n"
" ~p)~n"),
[AppA,Vsn,Dir,AppInfo, Acc]),
case lists:keysearch(AppA, 1, Acc) of
{value, {_,unknown,unknown}} ->
{lists:keyreplace(
AppA,1,Acc,{AppA,Vsn,Dir}),
Apps};
{value, {_,Vsn,_}} ->
%% We already have this version
{Acc, Apps};
Result ->
case lists:keysearch(AppA, 1, AppInfo) of
{value, {_,unknown,_}} ->
case Result of
{value, {_,OtherVsn,_}} ->
case later_vsn(Vsn, OtherVsn) of
true ->
{lists:keyreplace(
AppA,1,Acc,{AppA,Vsn,Dir}),
Apps};
false ->
{Acc, Apps}
end;
false ->
{[{AppA,Vsn,Dir}|Acc], Apps}
end;
{value, {_, Vsn, unknown}} ->
%% This is the version we want. Look no further for
%% this app.
{[{AppA,Vsn,Dir}|Acc], Apps -- [AppA]};
{value, {_, OtherVsn, _}} ->
%% This is not the version we want
{Acc, Apps};
false ->
%% We have no prior knowledge of this app, other than
%% that it should be included
case Result of
{value, {_,OtherVsn,_}} ->
case later_vsn(Vsn, OtherVsn) of
true ->
{[{AppA,Vsn,Dir}|Acc], Apps};
false ->
{Acc, Apps}
end;
false ->
{[{AppA,Vsn,Dir}|Acc], Apps}
end
end
end.
%%% later_vsn(VsnA, VsnB) -> true | false.
%%% true if VsnA > VsnB, false otherwise
later_vsn(unknown,_) ->
false;
later_vsn(_,unknown) ->
true;
later_vsn(Vsn, Vsn) ->
false;
later_vsn(VsnA, VsnB) ->
case get_newest([{VsnB,[]},{VsnA,[]}]) of
{VsnB,_} ->
false;
{VsnA,[]} ->
true
end.
%%% get_newest([{Vsn,Info}]) -> {NewestVsn,RelatedInfo}.
%%%
get_newest([{Vsn,_}] =X) -> X;
get_newest([{Vsn1,_}=X1, {Vsn2,_}=X2 | Rest]) ->
V1 = split(Vsn1),
V2 = split(Vsn2),
Newest = get_newest1(V1, V2, X1, X2),
get_newest([Newest | Rest]).
get_newest1([H1|T1], [H2|T2], X1, X2) ->
if H1 > H2 -> X1;
H2 > H1 -> X2;
H1 == H2 ->
get_newest1(T1, T2, X1, X2)
end;
get_newest1([], [], X1, X2) -> X1;
get_newest1([H1|_], [], X1, X2) -> X1;
get_newest1([], [H2|_], X1, X2) -> X2.
split(VStr) ->
split(VStr, undefined, [], []).
split([], Mode, Acc1, Acc) ->
lists:reverse([subv(Mode, lists:reverse(Acc1)) | Acc]);
split("." ++ T, Mode, Acc1, Acc) ->
split(T, Mode, [], [subv(Mode, lists:reverse(Acc1)) | Acc]);
split([I|T], string, Acc1, Acc) when $0 =< I, I =< $9 ->
split(T, integer, [I], [subv(string, lists:reverse(Acc1)) | Acc]);
split([I|T], integer, Acc1, Acc) when $0 =< I, I =< $9 ->
split(T, integer, [I|Acc1], Acc);
split([C|T], integer, Acc1, Acc) ->
split(T, string, [C], [subv(integer, lists:reverse(Acc1)) | Acc]);
split([I|T], undefined, [], Acc) when $0 =< I, I =< $9 ->
split(T, integer, [I], Acc);
split([C|T], undefined, [], Acc) ->
split(T, string, [C], Acc).
subv(integer, SubV) ->
list_to_integer(SubV);
subv(string, SubV) ->
SubV.
%%% end later_vsn()
member_of(AppName, [AppName|As]) ->
{true, AppName};
member_of(AppName, [{AppName,Vsn}|As]) ->
{true, {AppName, Vsn}};
member_of(AppName, [_|As]) ->
member_of(AppName, As);
member_of(_, []) ->
false.
d_expand(["/"|Ds]) ->
d_expand(Ds, ["/"]);
d_expand(Ds) ->
{ok,Cwd} = file:get_cwd(),
d_expand(Ds, [Cwd]).
d_expand([D|Ds], Cur) ->
Pat = regexp:sh_to_awk(D),
Cur1 = lists:foldl(
fun(C, Acc) ->
case list_dir(C) of
[] ->
Acc;
Fs ->
Matches = [F || F <- Fs,
matches(F, Pat)],
Acc ++ [filename:join(C,F) || F <- Matches,
is_dir(C,F)]
end
end, [], Cur),
d_expand(Ds, Cur1);
d_expand([], Cur) ->
Cur.
matches(Str, Pat) ->
case regexp:match(Str,Pat) of
{match,_,_} ->
true;
nomatch ->
false
end.
is_dir(Parent, Dir) ->
case file:read_file_info(filename:join(Parent, Dir)) of
{ok, #file_info{type=directory}} ->
true;
_ ->
false
end.
list_dir(Dir) ->
case file:read_file_info(Dir) of
{ok, #file_info{type=directory}} ->
case file:list_dir(Dir) of
{ok, Fs} ->
Fs;
_ ->
[]
end;
_ ->
[]
end.
apps(Apps, Dict) ->
Path = fetch(path, Dict),
Pat = [filename:basename(filename:dirname(Dir))||Dir<-get_path(Dict)],
Vss = [string:tokens(D,"-") || D <- Pat, app(D)],
do_apps(Apps, Vss).
app(D) ->
case regexp:match(D,".*-[0-9].*") of
nomatch -> false;
_ -> true
end.
do_apps([], Vss) -> [];
do_apps([App|Apps], Vss) when list(App) ->
do_apps([list_to_atom(App)|Apps], Vss);
do_apps([App|Apps], Vss) when atom(App) ->
[{App, get_vsn(atom_to_list(App), Vss)}|do_apps(Apps, Vss)].
get_vsn(App, [[App, Vsn]|_]) -> Vsn;
get_vsn(App, [_|T]) -> get_vsn(App, T).
get_erts_vsn() ->
{ok, [[Root]]} = init:get_argument(root),
{ok, Fs} = file:list_dir(Root),
get_erts_vsn(Fs).
get_erts_vsn([[$e,$r,$t,$s,$-|Vsn]|_]) -> Vsn;
get_erts_vsn([_|T]) -> get_erts_vsn(T).
out(Dir, App, Ext, Term) ->
FN = filename:join(Dir, App++Ext),
out(FN, Term).
out(FN, Term) ->
?report(debug, "out(~p,~p)~n", [FN,Term]),
case file:open(FN, [write]) of
{ok, FD} ->
io:fwrite(FD, "~p.~n", [Term]),
file:close(FD);
{error, R} -> exit({bad_filename, {FN, R}})
end.
read_app_file(Dict) ->
AppName = fetch(app_name, Dict),
AppNameS = atom_to_list(AppName),
AppF = get_app_filename(AppNameS, Dict),
App = case file:consult(AppF) of
{ok, [{application, _Name, Options}]} ->
app_options(Options);
{ok, [Options]} when list(Options) ->
app_options(Options);
{error, enoent} ->
RealAppF = filename:join(ebin_dir(Dict),
AppNameS ++ ".app"),
case file:consult(RealAppF) of
{ok, [{application, _Name, Options}]} ->
app_options(Options);
{error, enoent} ->
?report(debug,
"no app file (~p) -- must generate one.~n",
[AppName]),
Options =
app_options(
[{description,
"auto-generated app file for " ++
AppNameS},
{applications, [kernel,stdlib]}])
end;
Other ->
exit({error_reading_app_file, {AppF, Other}})
end,
?report(debug, "App = ~p~n", [App]),
App.
app_options(Opts) ->
[opt(description,Opts,""), opt(id,Opts,""),
opt(registered,Opts,[]), opt(applications,Opts,[kernel,stdlib]),
opt(env,Opts,[]), opt(mod,Opts,[]),
opt(start_phases,Opts,undefined)].
opt(Key,List,Default) ->
case lists:keysearch(Key,1,List) of
{value, {_, Value}} ->
{Key, Value};
false ->
{Key, Default}
end.
get_path(Dict) ->
AppPath = ebin_dir(Dict),
Path = code:get_path(),
case lists:member(AppPath, Path) of
true ->
Path;
false ->
Path ++ [AppPath]
end.
% otp_path(Dict) ->
% case dict:find(otp_path, Dict) of
% {ok, Path} ->
% Path;
% error ->
% {ok,[[Root]]} = init:get_argument(root),
% filename:join([Root,"lib","*","ebin"])
% end.
script(File) ->
script(File, erl_eval:new_bindings()).
script(File, Bs) ->
case file:open(File, [read]) of
{ok, Fd} ->
Bs1 = erl_eval:add_binding('ScriptName',File,Bs),
R = case eval_stream(Fd, Bs1) of
{ok, Result} ->
{ok, Result};
Error ->
Error
end,
file:close(Fd),
R;
Error ->
Error
end.
path_script(Path, File) ->
path_script(Path, File, erl_eval:new_bindings()).
path_script(Path, File, Bs) ->
case file:path_open(Path, File, [read]) of
{ok,Fd,Full} ->
Bs1 = erl_eval:add_binding('ScriptName',Full,Bs),
case eval_stream(Fd, Bs1) of
{error,E} ->
file:close(Fd),
{error, {E, Full}};
{ok, R} ->
file:close(Fd),
{ok,R,Full}
end;
Error ->
Error
end.
eval_stream(Fd, Bs) ->
eval_stream(Fd, undefined, Bs).
eval_stream(Fd, Last, Bs) ->
eval_stream(io:parse_erl_exprs(Fd, ''), Fd, Last, Bs).
eval_stream({ok,Form,EndLine}, Fd, Last, Bs0) ->
case catch erl_eval:exprs(Form, Bs0) of
{value,V,Bs} ->
eval_stream(Fd, {V}, Bs);
{'EXIT',Reason} ->
{error, Reason}
end;
eval_stream({error,What,EndLine}, Fd, L, Bs) ->
{error, {parse_error, {What,EndLine}}};
eval_stream({eof,EndLine}, Fd, Last, Bs) ->
case Last of
{Val} ->
{ok, Val};
undefined ->
%% empty script
{error, empty_script}
end.
do_report(error) ->
%% always report errors
true;
do_report(Level) ->
LevelN = rpt_level(Level),
case get(builder_debug) of
true ->
true;
false ->
false;
undefined ->
case get(builder_report_level) of
undefined ->
false;
[{AtLevel,_}|_] when AtLevel >= LevelN ->
true;
_ ->
false
end
end.
rpt_level(none) -> 0;
rpt_level(progress) -> 1;
rpt_level(verbose) -> 2;
rpt_level(debug) -> 3;
rpt_level(N) when integer(N), N >= 0 ->
N.
push_report_level(Level, Dict) ->
Ref = fetch('#ref#', Dict),
N = rpt_level(Level),
case get(builder_report_level) of
undefined ->
put(builder_report_level, [{N,Ref}]),
io:format("pushed 1st report level ~p(~p)~n",[Level,N]);
[{Prev,Ref}|Tail] ->
put(builder_report_level, [{N,Ref}|Tail]),
io:format("redefined report level ~p(~p)~n",[Level,N]);
[_|_] = Levels ->
put(builder_report_level, [{N,Ref}|Levels]),
io:format("pushed next report level ~p(~p)~n",[Level,N])
end.
pop_report_level() ->
Level = get(builder_report_level),
case Level of
[_] ->
erase(builder_report_level);
[_|Tail] ->
put(builder_report_level, Tail);
undefined ->
ok
end.
verify_dir(Dir) ->
case file:read_file_info(Dir) of
{ok, FI} when FI#file_info.type == directory,
FI#file_info.access == read_write ->
ok;
{ok, FI} when FI#file_info.type == directory ->
exit({access, Dir});
{error, enoent} ->
try_create(Dir)
end.
try_create("/") ->
exit({create, "/"});
try_create(".") ->
exit({create, "/"});
try_create(Dir) ->
case file:make_dir(Dir) of
{error, Reason} ->
try_create(filename:dirname(Dir)),
try_create(Dir);
ok ->
ok
end.