Packages
relx
4.8.0
4.10.0
4.9.0
4.8.0
4.7.0
4.6.0
4.5.0
4.4.0
4.3.0
4.2.0
4.1.0
4.0.2
4.0.1
4.0.0
4.0.0-rc2
3.33.0
3.32.1
3.32.0
3.31.0
3.30.0
3.29.0
3.28.0
3.27.0
3.26.0
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.23.1
3.23.0
3.22.4
3.22.3
retired
3.22.2
3.22.1
retired
3.22.0
3.21.1
3.21.0
3.20.0
3.19.0
3.18.0
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.0
3.8.0
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.2
3.3.1
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
Release assembler for Erlang/OTP Releases
Current section
Files
Jump to
Current section
Files
src/rlx_overlay.erl
-module(rlx_overlay).
-export([render/2,
format_error/1]).
-export([generate_overlay_vars/2,
render_string/2]).
-define(DIRECTORY_RE, ".*(\/|\\\\)$").
-include("relx.hrl").
-include("rlx_log.hrl").
render(Release, State) ->
case generate_overlay_vars(State, Release) of
{error, Reason} ->
erlang:error(?RLX_ERROR(Reason));
OverlayVars ->
Files = rlx_util:template_files(),
case do_overlay(State, Release, Files, OverlayVars) of
{error, _} = E -> erlang:error(E);
{ok, State1} -> {ok, State1}
end
end.
-spec format_error(ErrorDetail::term()) -> iolist().
format_error({unresolved_release, RelName, RelVsn}) ->
io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]);
format_error({unable_to_read_varsfile, FileName, Reason}) ->
io_lib:format("Unable to read vars file (~s) for overlay due to: ~p",
[FileName, Reason]);
format_error({read_template, FileName, Reason}) ->
io_lib:format("Unable to read template file (~s) for overlay due to: ~s",
[FileName, file:format_error(Reason)]);
format_error({overlay_failed, Errors}) ->
[[format_error(rlx_util:error_reason(Error)), "\n"] || Error <- Errors];
format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) ->
io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s",
[AppDir, TargetDir, rlx_util:indent(2),
file:format_error(Reason)]);
format_error({copy_failed, FromFile, ToFile, Err}) ->
io_lib:format("Unable to copy from ~s to ~s because of ~p",
[FromFile, ToFile, Err]);
format_error({unable_to_write, ToFile, Reason}) ->
io_lib:format("Unable to write to ~s because ~p",
[ToFile, Reason]);
format_error({unable_to_enclosing_dir, ToFile, Reason}) ->
io_lib:format("Unable to create enclosing directory for ~s because ~p",
[ToFile, Reason]);
format_error({unable_to_render_template, FromFile, Reason}) ->
io_lib:format("Unable to render template ~s because ~p",
[FromFile, Reason]);
format_error({unable_to_compile_template, FromFile, Reason}) ->
io_lib:format("Unable to compile template ~s because \n~s",
[FromFile, [format_errors(F, Es) || {F, Es} <- Reason]]);
format_error({unable_to_make_dir, Absolute, Error}) ->
io_lib:format("Unable to make directory ~s because ~p",
[Absolute, Error]);
format_error({unable_to_chmod, Mode, File, Err}) ->
io_lib:format("Unable to chmod ~p ~s because of ~p",
[Mode, File, Err]);
format_error({link_failed, FromFile, ToFile, Err}) ->
io_lib:format("Unable to symlink ~s to ~s because of ~p",
[FromFile, ToFile, Err]);
format_error({malformed_overlay, Invalid}) ->
io_lib:format("Overlay section malformed: ~p", [Invalid]).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
format_errors(File, [{none, Mod, E}|Es]) ->
[io_lib:format("~s~s: ~ts~n",
[rlx_util:indent(2), File,
Mod:format_error(E)])
|format_errors(File, Es)];
format_errors(File, [{{Line, Col}, Mod, E}|Es]) ->
[io_lib:format("~s~s:~w:~w: ~ts~n",
[rlx_util:indent(2), File, Line, Col,
Mod:format_error(E)])
|format_errors(File, Es)];
format_errors(File, [{Line, Mod, E}|Es]) ->
[io_lib:format("~s~s:~w: ~ts~n",
[rlx_util:indent(2), File, Line,
Mod:format_error(E)])
|format_errors(File, Es)];
format_errors(_, []) -> [].
-spec generate_overlay_vars(rlx_state:t(), rlx_release:t()) ->
proplists:proplist() | relx:error().
generate_overlay_vars(State, Release) ->
StateVars = generate_state_vars(Release, State),
ReleaseVars = generate_release_vars(Release),
get_overlay_vars_from_file(State, StateVars ++ ReleaseVars).
-spec get_overlay_vars_from_file(rlx_state:t(), proplists:proplist()) ->
proplists:proplist() | relx:error().
get_overlay_vars_from_file(State, OverlayVars) ->
case rlx_state:overlay_vars(State) of
[] ->
OverlayVars;
[H | _]=FileNames when is_list(H) ;
is_tuple(H) ->
read_overlay_vars(State, OverlayVars, FileNames);
FileName when is_list(FileName) ->
read_overlay_vars(State, OverlayVars, [FileName])
end.
-spec read_overlay_vars(rlx_state:t(), proplists:proplist(), [file:name()]) ->
proplists:proplist() | relx:error().
read_overlay_vars(State, OverlayVars, FileNames) ->
OverlayVarsValues = rlx_state:overlay_vars_values(State),
Terms = merge_overlay_vars(State, FileNames),
case render_overlay_vars(OverlayVars ++ OverlayVarsValues, Terms, []) of
{ok, NewTerms} ->
% We place `OverlayVarsValues' at the end on purpose; their
% definitions should be able to be overwritten by both internal
% and rendered vars, as not to change behaviour in
% setups preceding the support for overlays from the caller.
OverlayVars ++ NewTerms ++ OverlayVarsValues;
Error ->
Error
end.
-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist()) ->
proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, Terms) ->
check_overlay_inclusion(State, RelativeRoot, Terms, []).
-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist(), proplists:proplist()) ->
proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, [File|T], Terms) when is_list(File) ->
%% merge terms from the file into the current terms of the system
%% passing the Terms ensures duplicates delete the existing pair
%% while keeping the order of terms the same, as is done in `merge_overlay_vars'
IncludedTerms = merge_overlay_vars(State, Terms, [filename:join(RelativeRoot, File)]),
check_overlay_inclusion(State, RelativeRoot, T, IncludedTerms);
check_overlay_inclusion(State, RelativeRoot, [Tuple|T], Terms) ->
check_overlay_inclusion(State, RelativeRoot, T, Terms ++ [Tuple]);
check_overlay_inclusion(_State, _RelativeRoot, [], Terms) ->
Terms.
-spec merge_overlay_vars(rlx_state:t(), [file:name()]) ->
proplists:proplist().
merge_overlay_vars(State, FileNames) ->
merge_overlay_vars(State, [], FileNames).
merge_overlay_vars(State, Terms0, FileNames) ->
RelativeRoot = get_relative_root(State),
lists:foldl(fun(FileName, Acc) when is_list(FileName) ->
RelativePath = filename:join(RelativeRoot, iolist_to_binary(FileName)),
case file:consult(RelativePath) of
{ok, Terms} ->
%% the location of the included overlay files will be relative
%% to the current one being read
OverlayRelativeRoot = filename:dirname(FileName),
NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms),
%% Remove already defined variables from Acc,
%% append NewTerms, preserving order
lists:foldl(fun(NewTerm, A) ->
lists:keydelete(element(1, NewTerm), 1, A)
end, Acc, NewTerms) ++ NewTerms;
{error, Reason} ->
?log_warn(format_error({unable_to_read_varsfile, FileName, Reason})),
Acc
end;
(Var, Acc) ->
lists:keystore(element(1, Var), 1, Acc, Var)
end, Terms0, FileNames).
-spec render_overlay_vars(proplists:proplist(), proplists:proplist(),
proplists:proplist()) ->
{ok, proplists:proplist()} | relx:error().
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
when erlang:is_list(Value) ->
case io_lib:printable_list(Value) of
true ->
case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
{ok, Data} ->
%% Adding to the end sucks, but ordering needs to be retained
render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, Data}]);
Error ->
Error
end;
false ->
case render_overlay_vars(Acc ++ OverlayVars, Value, []) of
{ok, NewValue} ->
render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, NewValue}]);
Error ->
Error
end
end;
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
when erlang:is_binary(Value) ->
case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
{ok, Data} ->
render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, erlang:iolist_to_binary(Data)}]);
Error ->
Error
end;
render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) ->
render_overlay_vars(OverlayVars, Rest, Acc ++ [KeyValue]);
render_overlay_vars(_OverlayVars, [], Acc) ->
{ok, Acc}.
-spec generate_release_vars(rlx_release:t()) -> proplists:proplist().
generate_release_vars(Release) ->
[{erts_vsn, rlx_release:erts(Release)},
{erts_dir, code:root_dir()},
{release_erts_version, rlx_release:erts(Release)},
{release_name, rlx_release:name(Release)},
{release_version, rlx_release:vsn(Release)},
{release, rlx_release:canonical_name(Release)}].
-spec generate_state_vars(rlx_release:t(), rlx_state:t()) -> proplists:proplist().
generate_state_vars(Release, State) ->
[{output_dir, filename:join(rlx_state:base_output_dir(State),
rlx_release:name(Release))},
{target_dir, filename:join(rlx_state:base_output_dir(State),
rlx_release:name(Release))},
{overridden, [AppName || {AppName, _} <- rlx_state:overrides(State)]},
{overrides, rlx_state:overrides(State)},
{lib_dirs, rlx_state:lib_dirs(State)},
{vm_args, rlx_state:vm_args(State)},
{sys_config, rlx_state:sys_config(State)},
{root_dir, rlx_state:root_dir(State)}
%% include a var for each application. makes it easier to copy a file from an application
| [{rlx_app_info:name(AppInfo), rlx_app_info:dir(AppInfo)}
|| AppInfo <- rlx_release:applications(Release)]].
-spec do_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist()) ->
{ok, rlx_state:t()} | relx:error().
do_overlay(State, Release, Files, OverlayVars) ->
case rlx_state:overlay(State) of
[] ->
{ok, State};
Overlays ->
handle_errors(State,
lists:map(fun(Overlay) ->
do_individual_overlay(State,
Release,
Files,
OverlayVars,
Overlay)
end, Overlays))
end.
-spec handle_errors(rlx_state:t(), [ok | relx:error()]) ->
{ok, rlx_state:t()} | relx:error().
handle_errors(State, Result) ->
case [Error || Error <- Result,
rlx_util:is_error(Error)] of
Errors = [_|_] ->
?RLX_ERROR({overlay_failed, Errors});
[] ->
{ok, State}
end.
-spec do_individual_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist(),
OverlayDirective::term()) ->
{ok, rlx_state:t()} | relx:error().
do_individual_overlay(State, Release, _Files, OverlayVars, {chmod, Mode, Path}) ->
% mode can be specified directly as an integer value, or if it is
% not an integer we assume it's a template, which we render and convert
% blindly to an integer. So this will crash with an exception if for
% some reason something other than an integer is used
NewMode =
case is_integer(Mode) of
true ->
Mode;
false ->
erlang:binary_to_integer(render_string(OverlayVars, Mode))
end,
file_render_do(OverlayVars, Path,
fun(NewPath) ->
Absolute = absolute_path_to(State, Release, NewPath),
case file:change_mode(Absolute, NewMode) of
{error, Error} ->
?RLX_ERROR({unable_to_chmod, NewMode, NewPath, Error});
ok -> ok
end
end);
do_individual_overlay(State, Release, _Files, OverlayVars, {mkdir, Dir}) ->
file_render_do(OverlayVars, Dir,
fun(Dir0) ->
Absolute = absolute_path_to(State, Release, Dir0),
case rlx_file_utils:mkdir_p(Absolute) of
{error, Error} ->
?RLX_ERROR({unable_to_make_dir, Absolute, Error});
ok ->
ok
end
end);
do_individual_overlay(State, Release, _Files, OverlayVars, {copy, From, To}) ->
file_render_do(OverlayVars, From,
fun(FromFile) ->
file_render_do(OverlayVars, To,
fun(ToFile) ->
copy_to(State, Release, FromFile, ToFile)
end)
end);
do_individual_overlay(State, Release, Files, OverlayVars, {link, From, To}) ->
case rlx_state:mode(State) of
dev ->
file_render_do(OverlayVars, From,
fun(FromFile) ->
file_render_do(OverlayVars, To,
fun(ToFile) ->
link_to(State, Release, FromFile, ToFile)
end)
end);
_ ->
do_individual_overlay(State, Release, Files, OverlayVars, {copy, From, To})
end;
do_individual_overlay(State, Release, _Files, OverlayVars, {template, From, To}) ->
file_render_do(OverlayVars, From,
fun(FromFile) ->
file_render_do(OverlayVars, To,
fun(ToFile) ->
write_template(OverlayVars,
absolute_path_from(State, FromFile),
absolute_path_to(State, Release, ToFile))
end)
end);
do_individual_overlay(_State, _Release, _Files, _OverlayVars, Invalid) ->
?RLX_ERROR({malformed_overlay, Invalid}).
-spec wildcard_copy(rlx_state:t(), rlx_release:t(), file:filename_all(), file:filename_all(),
fun((file:filename_all(), file:filename_all()) -> ok | {error, term()}),
ErrorTag :: atom()) -> ok | relx:error().
wildcard_copy(State, Release, FromFile0, ToFile0, CopyFun, ErrorTag) ->
FromFile1 = absolute_path_from(State, FromFile0),
ToFile1 = absolute_path_to(State, Release, ToFile0),
Res = case is_directory(ToFile0) of
false ->
filelib:ensure_dir(ToFile1),
CopyFun(FromFile1, ToFile1);
true ->
Root = absolute_path_from(State, "."),
FromFiles = case is_list(FromFile0) of
true ->
filelib:wildcard(FromFile0, Root);
false ->
[FromFile1]
end,
rlx_file_utils:mkdir_p(ToFile1),
lists:foldl(fun(_, {error, _} = Error) -> Error;
(FromFile, ok) ->
CopyFun(filename:join(Root, FromFile),
filename:join(ToFile1, filename:basename(FromFile)))
end, ok, FromFiles)
end,
case Res of
ok ->
ok;
{error, Err} ->
?RLX_ERROR({ErrorTag,
FromFile1,
ToFile1, Err})
end.
-spec copy_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
copy_to(State, Release, FromFile0, ToFile0) ->
wildcard_copy(State, Release, FromFile0, ToFile0,
fun(FromPath, ToPath) ->
rlx_file_utils:copy(FromPath, ToPath, [recursive, {file_info, [mode, time]}]) end,
copy_failed).
-spec link_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
link_to(State, Release, FromFile0, ToFile0) ->
wildcard_copy(State, Release, FromFile0, ToFile0,
fun make_link/2,
link_failed).
make_link(FromFile, ToFile) ->
case rlx_file_utils:is_symlink(ToFile) of
true -> file:delete(ToFile);
false -> rlx_file_utils:remove(ToFile, [recursive])
end,
file:make_symlink(FromFile, ToFile).
get_relative_root(State) ->
rlx_state:root_dir(State).
absolute_path_from(State, Path) ->
absolutize(State, filename:join(get_relative_root(State), Path)).
absolute_path_to(State, Release, Path) ->
absolutize(State, filename:join([rlx_state:base_output_dir(State),
rlx_release:name(Release),
Path])).
-spec is_directory(file:name()) -> boolean().
is_directory(ToFile0) ->
case re:run(ToFile0, ?DIRECTORY_RE) of
nomatch ->
false;
_ ->
true
end.
-spec render_template(proplists:proplist(), binary()) ->
{ok, binary()} | relx:error().
render_template(OverlayVars, Data) ->
case rlx_util:render(Data, OverlayVars) of
{ok, IoData} ->
{ok, IoData};
{error, Reason} ->
?RLX_ERROR({unable_to_render_template, Data, Reason})
end.
-spec write_template(proplists:proplist(), file:name(), file:name()) ->
ok | relx:error().
write_template(OverlayVars, FromFile, ToFile) ->
case file:read_file(FromFile) of
{ok, File} ->
case render_template(OverlayVars, File) of
{ok, IoData} ->
case filelib:ensure_dir(ToFile) of
ok ->
%% we were asked to render a template
%% onto a symlink, this would cause an overwrite
%% of the original file, so we delete the symlink
%% and go ahead with the template render
case rlx_file_utils:is_symlink(ToFile) of
true -> rlx_file_utils:remove(ToFile);
false -> ok
end,
case file:write_file(ToFile, IoData) of
ok ->
ok = rlx_file_utils:copy_file_info(ToFile, FromFile, [mode, time]),
ok;
{error, Reason} ->
?RLX_ERROR({unable_to_write, ToFile, Reason})
end;
{error, Reason} ->
?RLX_ERROR({unable_to_enclosing_dir, ToFile, Reason})
end;
Error ->
Error
end;
{error, Error} ->
?RLX_ERROR({read_template, FromFile, Error})
end.
-spec render_string(proplists:proplist(), iolist()) ->
binary() | relx:error().
render_string(OverlayVars, Data) ->
case rlx_util:render(Data, OverlayVars) of
{ok, Bin} -> Bin;
{error, Error} ->
?RLX_ERROR({render_failed, Data, Error})
end.
-spec file_render_do(proplists:proplist(), iolist(),
fun((string() | binary()) -> {ok, rlx_state:t()} | relx:error())) ->
{ok, rlx_state:t()} | relx:error().
file_render_do(OverlayVars, File, NextAction) ->
case rlx_util:render(File, OverlayVars) of
{ok, Binary} when is_binary(File) ->
NextAction(Binary);
{ok, Binary} when is_list(File) ->
NextAction(binary_to_list(Binary));
{error, Error} ->
?RLX_ERROR({render_failed, File, Error})
end.
absolutize(State, FileName) ->
filename:absname(filename:join(rlx_state:root_dir(State), FileName)).