Packages
relx
3.25.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_prv_assembler.erl
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
%%%
%%% This file is provided to you 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.
%%%---------------------------------------------------------------------------
%%% @author Eric Merritt <ericbmerritt@gmail.com>
%%% @copyright (C) 2012 Erlware, LLC.
%%%
%%% @doc Given a complete built release this provider assembles that release
%%% into a release directory.
-module(rlx_prv_assembler).
-behaviour(provider).
-export([init/1,
do/1,
format_error/1]).
-include("relx.hrl").
-define(PROVIDER, release).
-define(DEPS, [resolve_release]).
%%============================================================================
%% API
%%============================================================================
-spec init(rlx_state:t()) -> {ok, rlx_state:t()}.
init(State) ->
State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER},
{module, ?MODULE},
{deps, ?DEPS},
{hooks, {[], [overlay]}}])),
{ok, State1}.
%% @doc recursively dig down into the library directories specified in the state
%% looking for OTP Applications
-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error().
do(State) ->
print_dev_mode(State),
{RelName, RelVsn} = rlx_state:default_configured_release(State),
Release = rlx_state:get_realized_release(State, RelName, RelVsn),
OutputDir = rlx_state:output_dir(State),
case create_output_dir(OutputDir) of
ok ->
case rlx_release:realized(Release) of
true ->
case copy_app_directories_to_output(State, Release, OutputDir) of
{ok, State1} ->
case rlx_state:debug_info(State1) =:= strip
andalso rlx_state:dev_mode(State1) =:= false of
true ->
case beam_lib:strip_release(OutputDir) of
{ok, _} ->
{ok, State1};
{error, _, Reason} ->
?RLX_ERROR({strip_release, Reason})
end;
false ->
{ok, State1}
end;
E ->
E
end;
false ->
?RLX_ERROR({unresolved_release, RelName, RelVsn})
end;
Error ->
Error
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({ec_file_error, AppDir, TargetDir, E}) ->
io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p",
[AppDir, TargetDir, E]);
format_error({vmargs_does_not_exist, Path}) ->
io_lib:format("The vm.args file specified for this release (~s) does not exist!",
[Path]);
format_error({vmargs_src_does_not_exist, Path}) ->
io_lib:format("The vm.args.src file specified for this release (~s) does not exist!",
[Path]);
format_error({config_does_not_exist, Path}) ->
io_lib:format("The sys.config file specified for this release (~s) does not exist!",
[Path]);
format_error({config_src_does_not_exist, Path}) ->
io_lib:format("The sys.config.src file specified for this release (~s) does not exist!",
[Path]);
format_error({sys_config_parse_error, ConfigPath, Reason}) ->
io_lib:format("The config file (~s) specified for this release could not be opened or parsed: ~s",
[ConfigPath, file:format_error(Reason)]);
format_error({specified_erts_does_not_exist, ErtsVersion}) ->
io_lib:format("Specified version of erts (~s) does not exist",
[ErtsVersion]);
format_error({release_script_generation_error, RelFile}) ->
io_lib:format("Unknown internal release error generating the release file to ~s",
[RelFile]);
format_error({release_script_generation_warning, Module, Warnings}) ->
["Warnings generating release \s",
rlx_util:indent(2), Module:format_warning(Warnings)];
format_error({unable_to_create_output_dir, OutputDir}) ->
io_lib:format("Unable to create output directory (possible permissions issue): ~s",
[OutputDir]);
format_error({release_script_generation_error, Module, Errors}) ->
["Errors generating release \n",
rlx_util:indent(2), Module:format_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(boot_script_generation_error) ->
"Unknown internal release error generating start_clean.boot";
format_error({boot_script_generation_warning, Module, Warnings}) ->
["Warnings generating start_clean.boot \s",
rlx_util:indent(2), Module:format_warning(Warnings)];
format_error({boot_script_generation_error, Module, Errors}) ->
["Errors generating start_clean.boot \n",
rlx_util:indent(2), Module:format_error(Errors)];
format_error({strip_release, Reason}) ->
io_lib:format("Stripping debug info from release beam files failed becuase ~s",
[beam_lib:format_error(Reason)]);
format_error({rewrite_app_file, AppFile, Error}) ->
io_lib:format("Unable to rewrite .app file ~s due to ~p",
[AppFile, Error]).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
print_dev_mode(State) ->
case rlx_state:dev_mode(State) of
true ->
ec_cmd_log:info(rlx_state:log(State),
"Dev mode enabled, release will be symlinked");
false ->
ok
end.
-spec create_output_dir(file:name()) ->
ok | {error, Reason::term()}.
create_output_dir(OutputDir) ->
case ec_file:is_dir(OutputDir) of
false ->
case rlx_util:mkdir_p(OutputDir) of
ok ->
ok;
{error, _} ->
?RLX_ERROR({unable_to_create_output_dir, OutputDir})
end;
true ->
ok
end.
copy_app_directories_to_output(State, Release, OutputDir) ->
LibDir = filename:join([OutputDir, "lib"]),
ok = ec_file:mkdir_p(LibDir),
IncludeSrc = rlx_state:include_src(State),
IncludeErts = rlx_state:get(State, include_erts, true),
Apps = prepare_applications(State, rlx_release:application_details(Release)),
Result = lists:filter(fun({error, _}) ->
true;
(_) ->
false
end,
lists:flatten(ec_plists:map(fun(App) ->
copy_app(State, LibDir, App, IncludeSrc, IncludeErts)
end, Apps))),
case Result of
[E | _] ->
E;
[] ->
create_release_info(State, Release, OutputDir)
end.
prepare_applications(State, Apps) ->
case rlx_state:dev_mode(State) of
true ->
[rlx_app_info:link(App, true) || App <- Apps];
false ->
Apps
end.
copy_app(State, LibDir, App, IncludeSrc, IncludeErts) ->
AppName = erlang:atom_to_list(rlx_app_info:name(App)),
AppVsn = rlx_app_info:original_vsn(App),
AppDir = rlx_app_info:dir(App),
TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]),
case AppDir == ec_cnv:to_binary(TargetDir) of
true ->
%% No need to do anything here, discover found something already in
%% a release dir
ok;
false ->
case IncludeErts of
false ->
case is_erts_lib(AppDir) of
true ->
[];
false ->
copy_app_(State, App, AppDir, TargetDir, IncludeSrc)
end;
_ ->
copy_app_(State, App, AppDir, TargetDir, IncludeSrc)
end
end.
is_erts_lib(Dir) ->
lists:prefix(filename:split(list_to_binary(code:lib_dir())), filename:split(Dir)).
copy_app_(State, App, AppDir, TargetDir, IncludeSrc) ->
remove_symlink_or_directory(TargetDir),
case rlx_app_info:link(App) of
true ->
link_directory(AppDir, TargetDir),
rewrite_app_file(State, App, AppDir);
false ->
copy_directory(State, App, AppDir, TargetDir, IncludeSrc),
rewrite_app_file(State, App, TargetDir)
end.
%% If excluded apps exist in this App's applications list we must write a new .app
rewrite_app_file(State, App, TargetDir) ->
Name = rlx_app_info:name(App),
ActiveDeps = rlx_app_info:active_deps(App),
IncludedDeps = rlx_app_info:library_deps(App),
AppFile = filename:join([TargetDir, "ebin", ec_cnv:to_list(Name) ++ ".app"]),
{ok, [{application, AppName, AppData0}]} = file:consult(AppFile),
OldActiveDeps = proplists:get_value(applications, AppData0, []),
OldIncludedDeps = proplists:get_value(included_applications, AppData0, []),
OldModules = proplists:get_value(modules, AppData0, []),
ExcludedModules = proplists:get_value(Name,
rlx_state:exclude_modules(State), []),
%% maybe replace excluded apps
AppData2 =
case {OldActiveDeps, OldIncludedDeps} of
{ActiveDeps, IncludedDeps} ->
AppData0;
_ ->
AppData1 = lists:keyreplace(applications
,1
,AppData0
,{applications, ActiveDeps}),
lists:keyreplace(included_applications
,1
,AppData1
,{included_applications, IncludedDeps})
end,
%% maybe replace excluded modules
AppData3 =
case ExcludedModules of
[] -> AppData2;
_ ->
lists:keyreplace(modules
,1
,AppData2
,{modules, OldModules -- ExcludedModules})
end,
Spec = [{application, AppName, AppData3}],
case write_file_if_contents_differ(AppFile, Spec) of
ok -> ok;
Error -> ?RLX_ERROR({rewrite_app_file, AppFile, Error})
end.
write_file_if_contents_differ(Filename, Spec) ->
ToWrite = io_lib:format("~p.\n", Spec),
case file:consult(Filename) of
{ok, Spec} ->
ok;
{ok, _} ->
file:write_file(Filename, ToWrite);
{error, _} ->
file:write_file(Filename, ToWrite)
end.
remove_symlink_or_directory(TargetDir) ->
case ec_file:is_symlink(TargetDir) of
true ->
ec_file:remove(TargetDir);
false ->
case ec_file:is_dir(TargetDir) of
true ->
ok = ec_file:remove(TargetDir, [recursive]);
false ->
ok
end
end.
link_directory(AppDir, TargetDir) ->
case rlx_util:symlink_or_copy(AppDir, TargetDir) of
{error, Reason} ->
?RLX_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason});
ok ->
ok
end.
copy_directory(State, App, AppDir, TargetDir, IncludeSrc) ->
[copy_dir(State, App, AppDir, TargetDir, SubDir)
|| SubDir <- ["ebin",
"include",
"priv",
"lib" |
case IncludeSrc of
true ->
["src",
"c_src"];
false ->
[]
end]].
copy_dir(State, App, AppDir, TargetDir, SubDir) ->
SubSource = filename:join(AppDir, SubDir),
SubTarget = filename:join(TargetDir, SubDir),
case ec_file:is_dir(SubSource) of
true ->
ok = rlx_util:mkdir_p(SubTarget),
%% get a list of the modules to be excluded from this app
AppName = rlx_app_info:name(App),
ExcludedModules = proplists:get_value(AppName, rlx_state:exclude_modules(State),
[]),
ExcludedFiles = [filename:join([binary_to_list(SubSource),
atom_to_list(M) ++ ".beam"]) ||
M <- ExcludedModules],
case copy_dir(SubSource, SubTarget, ExcludedFiles) of
{error, E} ->
?RLX_ERROR({ec_file_error, AppDir, SubTarget, E});
ok ->
ok
end;
false ->
ok
end.
%% no files are excluded, just copy the whole dir
copy_dir(SourceDir, TargetDir, []) ->
case ec_file:copy(SourceDir, TargetDir, [recursive]) of
{error, E} -> {error, E};
ok ->
ok
end;
copy_dir(SourceDir, TargetDir, ExcludeFiles) ->
SourceFiles = filelib:wildcard(
filename:join([binary_to_list(SourceDir), "*"])),
lists:foreach(fun(F) ->
ok = ec_file:copy(F,
filename:join([TargetDir,
filename:basename(F)]))
end, SourceFiles -- ExcludeFiles).
create_release_info(State0, Release0, OutputDir) ->
RelName = atom_to_list(rlx_release:name(Release0)),
ReleaseDir = rlx_util:release_output_dir(State0, Release0),
ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]),
StartCleanFile = filename:join([ReleaseDir, "start_clean.rel"]),
NoDotErlFile = filename:join([ReleaseDir, "no_dot_erlang.rel"]),
ok = ec_file:mkdir_p(ReleaseDir),
Release1 = rlx_release:relfile(Release0, ReleaseFile),
State1 = rlx_state:update_realized_release(State0, Release1),
case rlx_release:metadata(Release1) of
{ok, Meta} ->
case {rlx_release:start_clean_metadata(Release1),
rlx_release:no_dot_erlang_metadata(Release1)} of
{{ok, StartCleanMeta}, {ok, NoDotErlMeta}} ->
ok = ec_file:write_term(ReleaseFile, Meta),
ok = ec_file:write_term(StartCleanFile, StartCleanMeta),
ok = ec_file:write_term(NoDotErlFile, NoDotErlMeta),
write_bin_file(State1, Release1, OutputDir, ReleaseDir);
{{ok, _}, E} ->
E;
{_, E} ->
E
end;
E ->
E
end.
write_bin_file(State, Release, OutputDir, RelDir) ->
RelName = erlang:atom_to_list(rlx_release:name(Release)),
RelVsn = rlx_release:vsn(Release),
BinDir = filename:join([OutputDir, "bin"]),
ok = ec_file:mkdir_p(BinDir),
VsnRel = filename:join(BinDir, rlx_release:canonical_name(Release)),
BareRel = filename:join(BinDir, RelName),
ErlOpts = rlx_state:get(State, erl_opts, ""),
{OsFamily, _OsName} = rlx_util:os_type(State),
StartFile = case rlx_state:get(State, extended_start_script, false) of
false ->
case rlx_state:get(State, include_nodetool, false) of
true ->
include_nodetool(BinDir);
false ->
ok
end,
bin_file_contents(OsFamily, RelName, RelVsn,
rlx_release:erts(Release),
ErlOpts);
true ->
%% extended start script needs nodetool so it's
%% always included
include_nodetool(BinDir),
Hooks = expand_hooks(BinDir,
rlx_state:get(State,
extended_start_script_hooks,
[]),
State),
Extensions = rlx_state:get(State,
extended_start_script_extensions,
[]),
extended_bin_file_contents(OsFamily, RelName, RelVsn,
rlx_release:erts(Release), ErlOpts,
Hooks, Extensions)
end,
%% We generate the start script by default, unless the user
%% tells us not too
case rlx_state:get(State, generate_start_script, true) of
false ->
ok;
_ ->
VsnRelStartFile = case OsFamily of
unix -> VsnRel;
win32 -> rlx_string:concat(VsnRel, ".cmd")
end,
ok = file:write_file(VsnRelStartFile, StartFile),
ok = file:change_mode(VsnRelStartFile, 8#777),
BareRelStartFile = case OsFamily of
unix -> BareRel;
win32 -> rlx_string:concat(BareRel, ".cmd")
end,
ok = file:write_file(BareRelStartFile, StartFile),
ok = file:change_mode(BareRelStartFile, 8#777)
end,
ReleasesDir = filename:join(OutputDir, "releases"),
generate_start_erl_data_file(Release, ReleasesDir),
copy_or_generate_vmargs_file(State, Release, RelDir),
case copy_or_generate_sys_config_file(State, RelDir) of
ok ->
include_erts(State, Release, OutputDir, RelDir);
E ->
E
end.
expand_hooks(_Bindir, [], _State) -> [];
expand_hooks(BinDir, Hooks, _State) ->
expand_hooks(BinDir, Hooks, [], _State).
expand_hooks(_BinDir, [], Acc, _State) -> Acc;
expand_hooks(BinDir, [{Phase, Hooks0} | Rest], Acc, State) ->
%% filter and expand hooks to their respective shell scripts
Hooks =
lists:foldl(
fun(Hook, Acc0) ->
case validate_hook(Phase, Hook) of
true ->
%% all hooks are relative to the bin dir
HookScriptFilename = filename:join([BinDir,
hook_filename(Hook)]),
%% write the hook script file to it's proper location
ok = render_hook(hook_template(Hook), HookScriptFilename, State),
%% and return the invocation that's to be templated in the
%% extended script
Acc0 ++ [hook_invocation(Hook)];
false ->
ec_cmd_log:error(
rlx_state:log(State),
io_lib:format("~p hook is not allowed in the ~p phase, ignoring it", [Hook, Phase])
),
Acc0
end
end, [], Hooks0),
expand_hooks(BinDir, Rest, Acc ++ [{Phase, Hooks}], State).
%% the pid script hook is only allowed in the
%% post_start phase
%% with args
validate_hook(post_start, {pid, _}) -> true;
%% and without args
validate_hook(post_start, pid) -> true;
%% same for wait_for_vm_start, wait_for_process script
validate_hook(post_start, wait_for_vm_start) -> true;
validate_hook(post_start, {wait_for_process, _}) -> true;
%% custom hooks are allowed in all phases
validate_hook(_Phase, {custom, _}) -> true;
%% as well as status hooks
validate_hook(status, _) -> true;
%% deny all others
validate_hook(_, _) -> false.
hook_filename({custom, CustomScript}) -> CustomScript;
hook_filename(pid) -> "hooks/builtin/pid";
hook_filename({pid, _}) -> "hooks/builtin/pid";
hook_filename(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
hook_filename({wait_for_process, _}) -> "hooks/builtin/wait_for_process";
hook_filename(builtin_status) -> "hooks/builtin/status".
hook_invocation({custom, CustomScript}) -> CustomScript;
%% the pid builtin hook with no arguments writes to pid file
%% at /var/run/{{ rel_name }}.pid
hook_invocation(pid) -> rlx_string:join(["hooks/builtin/pid",
"/var/run/$REL_NAME.pid"], "|");
hook_invocation({pid, PidFile}) -> rlx_string:join(["hooks/builtin/pid",
PidFile], "|");
hook_invocation(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
hook_invocation({wait_for_process, Name}) ->
%% wait_for_process takes an atom as argument
%% which is the process name to wait for
rlx_string:join(["hooks/builtin/wait_for_process",
atom_to_list(Name)], "|");
hook_invocation(builtin_status) -> "hooks/builtin/status".
hook_template({custom, _}) -> custom;
hook_template(pid) -> builtin_hook_pid;
hook_template({pid, _}) -> builtin_hook_pid;
hook_template(wait_for_vm_start) -> builtin_hook_wait_for_vm_start;
hook_template({wait_for_process, _}) -> builtin_hook_wait_for_process;
hook_template(builtin_status) -> builtin_hook_status.
%% custom hooks are not rendered, they should
%% be copied by the release overlays
render_hook(custom, _, _) -> ok;
render_hook(TemplateName, Script, State) ->
ec_cmd_log:info(
rlx_state:log(State),
"rendering ~p hook to ~p~n",
[TemplateName, Script]
),
Template = render(TemplateName),
ok = filelib:ensure_dir(Script),
_ = ec_file:remove(Script),
ok = file:write_file(Script, Template),
ok = file:change_mode(Script, 8#755).
include_nodetool(BinDir) ->
NodeToolFile = nodetool_contents(),
InstallUpgradeFile = install_upgrade_escript_contents(),
NodeTool = filename:join([BinDir, "nodetool"]),
InstallUpgrade = filename:join([BinDir, "install_upgrade.escript"]),
ok = file:write_file(NodeTool, NodeToolFile),
ok = file:write_file(InstallUpgrade, InstallUpgradeFile).
%% @doc generate a start_erl.data file
-spec generate_start_erl_data_file(rlx_release:t(), file:name()) ->
ok | relx:error().
generate_start_erl_data_file(Release, ReleasesDir) ->
ErtsVersion = rlx_release:erts(Release),
ReleaseVersion = rlx_release:vsn(Release),
Data = ErtsVersion ++ " " ++ ReleaseVersion,
ok = file:write_file(filename:join(ReleasesDir, "start_erl.data"), Data).
%% @doc copy vm.args or generate one to releases/VSN/vm.args
-spec copy_or_generate_vmargs_file(rlx_state:t(), rlx_release:t(), file:name()) ->
{ok, rlx_state:t()} | relx:error().
copy_or_generate_vmargs_file(State, Release, RelDir) ->
RelVmargsPath = filename:join([RelDir, "vm.args"]),
RelVmargsSrcPath = filename:join([RelDir, "vm.args.src"]),
case rlx_state:vm_args_src(State) of
undefined ->
case rlx_state:vm_args(State) of
false ->
ok;
undefined ->
RelName = erlang:atom_to_list(rlx_release:name(Release)),
unless_exists_write_default(RelVmargsPath, vm_args_file(RelName));
ArgsPath ->
case filelib:is_regular(ArgsPath) of
false ->
?RLX_ERROR({vmargs_does_not_exist, ArgsPath});
true ->
copy_or_symlink_config_file(State, ArgsPath, RelVmargsPath)
end
end;
ArgsSrcPath ->
%% print a warning if vm_args is also set
case rlx_state:vm_args(State) of
undefined ->
ok;
_->
ec_cmd_log:warn(rlx_state:log(State),
"Both vm_args_src and vm_args are set, vm_args will be ignored~n", [])
end,
case filelib:is_regular(ArgsSrcPath) of
false ->
?RLX_ERROR({vmargs_src_does_not_exist, ArgsSrcPath});
true ->
copy_or_symlink_config_file(State, ArgsSrcPath, RelVmargsSrcPath)
end
end.
%% @doc copy config/sys.config or generate one to releases/VSN/sys.config
-spec copy_or_generate_sys_config_file(rlx_state:t(), file:name()) ->
{ok, rlx_state:t()} | relx:error().
copy_or_generate_sys_config_file(State, RelDir) ->
RelSysConfPath = filename:join([RelDir, "sys.config"]),
RelSysConfSrcPath = filename:join([RelDir, "sys.config.src"]),
case rlx_state:sys_config_src(State) of
undefined ->
case rlx_state:sys_config(State) of
false ->
ok;
undefined ->
unless_exists_write_default(RelSysConfPath, sys_config_file());
ConfigPath ->
case filelib:is_regular(ConfigPath) of
false ->
?RLX_ERROR({config_does_not_exist, ConfigPath});
true ->
%% validate sys.config is valid Erlang terms
case file:consult(ConfigPath) of
{ok, _} ->
copy_or_symlink_config_file(State, ConfigPath, RelSysConfPath);
{error, Reason} ->
?RLX_ERROR({sys_config_parse_error, ConfigPath, Reason})
end
end
end;
ConfigSrcPath ->
%% print a warning if sys_config is also set
case rlx_state:sys_config(State) of
P when P =:= false orelse P =:= undefined ->
ok;
_->
ec_cmd_log:warn(rlx_state:log(State),
"Both sys_config_src and sys_config are set, sys_config will be ignored~n", [])
end,
case filelib:is_regular(ConfigSrcPath) of
false ->
?RLX_ERROR({config_src_does_not_exist, ConfigSrcPath});
true ->
copy_or_symlink_config_file(State, ConfigSrcPath, RelSysConfSrcPath)
end
end.
%% @doc copy config/sys.config or generate one to releases/VSN/sys.config
-spec copy_or_symlink_config_file(rlx_state:t(), file:name(), file:name()) ->
ok.
copy_or_symlink_config_file(State, ConfigPath, RelConfPath) ->
ensure_not_exist(RelConfPath),
case rlx_state:dev_mode(State) of
true ->
ok = rlx_util:symlink_or_copy(ConfigPath, RelConfPath);
_ ->
ok = ec_file:copy(ConfigPath, RelConfPath)
end.
%% @doc Optionally add erts directory to release, if defined.
-spec include_erts(rlx_state:t(), rlx_release:t(), file:name(), file:name()) ->
{ok, rlx_state:t()} | relx:error().
include_erts(State, Release, OutputDir, RelDir) ->
Prefix = case rlx_state:get(State, include_erts, true) of
false ->
false;
true ->
code:root_dir();
P ->
filename:absname(P)
end,
case Prefix of
false ->
make_boot_script(State, Release, OutputDir, RelDir);
_ ->
ec_cmd_log:info(rlx_state:log(State),
"Including Erts from ~s~n", [Prefix]),
ErtsVersion = rlx_release:erts(Release),
ErtsDir = filename:join([Prefix, "erts-" ++ ErtsVersion]),
LocalErts = filename:join([OutputDir, "erts-" ++ ErtsVersion]),
{OsFamily, _OsName} = rlx_util:os_type(State),
case ec_file:is_dir(ErtsDir) of
false ->
?RLX_ERROR({specified_erts_does_not_exist, ErtsVersion});
true ->
ok = ec_file:mkdir_p(LocalErts),
ok = ec_file:copy(ErtsDir, LocalErts, [recursive]),
case OsFamily of
unix ->
Erl = filename:join([LocalErts, "bin", "erl"]),
ok = ec_file:remove(Erl),
ok = file:write_file(Erl, erl_script(ErtsVersion)),
ok = file:change_mode(Erl, 8#755);
win32 ->
ErlIni = filename:join([LocalErts, "bin", "erl.ini"]),
ok = ec_file:remove(ErlIni),
ok = file:write_file(ErlIni, erl_ini(OutputDir, ErtsVersion))
end,
%% delete erts src if the user requested it not be included
case rlx_state:include_src(State) of
true -> ok;
false ->
SrcDir = filename:join([LocalErts, "src"]),
%% ensure the src folder exists before deletion
case ec_file:exists(SrcDir) of
true -> ok = ec_file:remove(SrcDir, [recursive]);
false -> ok
end
end,
case rlx_state:get(State, extended_start_script, false) of
true ->
NodeToolFile = nodetool_contents(),
InstallUpgradeFile = install_upgrade_escript_contents(),
NodeTool = filename:join([LocalErts, "bin", "nodetool"]),
InstallUpgrade = filename:join([LocalErts, "bin", "install_upgrade.escript"]),
ok = file:write_file(NodeTool, NodeToolFile),
ok = file:write_file(InstallUpgrade, InstallUpgradeFile),
ok = file:change_mode(NodeTool, 8#755),
ok = file:change_mode(InstallUpgrade, 8#755);
false ->
ok
end,
make_boot_script(State, Release, OutputDir, RelDir)
end
end.
-spec make_boot_script(rlx_state:t(), rlx_release:t(), file:name(), file:name()) ->
{ok, rlx_state:t()} | relx:error().
make_boot_script(State, Release, OutputDir, RelDir) ->
Options = [{path, [RelDir | rlx_util:get_code_paths(Release, OutputDir)]},
{outdir, RelDir},
{variables, make_boot_script_variables(State)},
no_module_tests, silent],
Name = erlang:atom_to_list(rlx_release:name(Release)),
ReleaseFile = filename:join([RelDir, Name ++ ".rel"]),
case rlx_util:make_script(Options,
fun(CorrectedOptions) ->
systools:make_script(Name, CorrectedOptions)
end) of
ok ->
ec_cmd_log:info(rlx_state:log(State),
"release successfully created!"),
create_RELEASES(OutputDir, ReleaseFile),
create_no_dot_erlang(RelDir, OutputDir, Options, State),
create_start_clean(RelDir, OutputDir, Options, State);
error ->
?RLX_ERROR({release_script_generation_error, ReleaseFile});
{ok, _, []} ->
ec_cmd_log:info(rlx_state:log(State),
"release successfully created!"),
create_RELEASES(OutputDir, ReleaseFile),
create_no_dot_erlang(RelDir, OutputDir, Options, State),
create_start_clean(RelDir, OutputDir, Options, State);
{ok,Module,Warnings} ->
?RLX_ERROR({release_script_generation_warn, Module, Warnings});
{error,Module,Error} ->
?RLX_ERROR({release_script_generation_error, Module, Error})
end.
make_boot_script_variables(State) ->
% A boot variable is needed when {include_erts, false} and the application
% directories are split between the release/lib directory and the erts/lib
% directory.
% The built-in $ROOT variable points to the erts directory on Windows
% (dictated by erl.ini [erlang] Rootdir=) and so a boot variable is made
% pointing to the release directory
% On non-Windows, $ROOT is set by the ROOTDIR environment variable as the
% release directory, so a boot variable is made pointing to the erts
% directory.
% NOTE the boot variable can point to either the release/erts root directory
% or the release/erts lib directory, as long as the usage here matches the
% usage used in the start up scripts
case {os:type(), rlx_state:get(State, include_erts, true)} of
{{win32, _}, false} ->
[{"RELEASE_DIR", rlx_state:output_dir(State)}];
{{win32, _}, true} ->
[];
_ ->
[{"ERTS_LIB_DIR", code:lib_dir()}]
end.
create_no_dot_erlang(RelDir, OutputDir, Options, State) ->
create_boot_file(RelDir, OutputDir, Options, State, "no_dot_erlang").
create_start_clean(RelDir, OutputDir, Options, State) ->
create_boot_file(RelDir, OutputDir, Options, State, "start_clean").
create_boot_file(RelDir, OutputDir, Options, State, Name) ->
case rlx_util:make_script(Options,
fun(CorrectedOptions) ->
systools:make_script(Name, CorrectedOptions)
end) of
ok ->
ok = ec_file:copy(filename:join([RelDir, Name++".boot"]),
filename:join([OutputDir, "bin", Name++".boot"])),
ec_file:remove(filename:join([RelDir, Name++".rel"])),
ec_file:remove(filename:join([RelDir, Name++".script"])),
{ok, State};
error ->
?RLX_ERROR(boot_script_generation_error);
{ok, _, []} ->
ok = ec_file:copy(filename:join([RelDir, Name++".boot"]),
filename:join([OutputDir, "bin", Name++".boot"])),
ec_file:remove(filename:join([RelDir, Name++".rel"])),
ec_file:remove(filename:join([RelDir, Name++".script"])),
{ok, State};
{ok,Module,Warnings} ->
?RLX_ERROR({boot_script_generation_warn, Module, Warnings});
{error,Module,Error} ->
?RLX_ERROR({boot_script_generation_error, Module, Error})
end.
create_RELEASES(OutputDir, ReleaseFile) ->
{ok, OldCWD} = file:get_cwd(),
file:set_cwd(OutputDir),
release_handler:create_RELEASES("./",
"releases",
ReleaseFile,
[]),
file:set_cwd(OldCWD).
unless_exists_write_default(Path, File) ->
case ec_file:exists(Path) of
true ->
ok;
false ->
ok = file:write_file(Path, File)
end.
-spec ensure_not_exist(file:name()) -> ok.
ensure_not_exist(RelConfPath) ->
case ec_file:exists(RelConfPath) of
false ->
ok;
_ ->
ec_file:remove(RelConfPath)
end.
erl_script(ErtsVsn) ->
render(erl_script, [{erts_vsn, ErtsVsn}]).
bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
Template = case OsFamily of
unix -> bin;
win32 -> bin_windows
end,
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks, Extensions) ->
Template = case OsFamily of
unix -> extended_bin;
win32 -> extended_bin_windows
end,
%% turn all the hook lists into space separated strings
PreStartHooks = rlx_string:join(proplists:get_value(pre_start, Hooks, []), " "),
PostStartHooks = rlx_string:join(proplists:get_value(post_start, Hooks, []), " "),
PreStopHooks = rlx_string:join(proplists:get_value(pre_stop, Hooks, []), " "),
PostStopHooks = rlx_string:join(proplists:get_value(post_stop, Hooks, []), " "),
PreInstallUpgradeHooks = rlx_string:join(proplists:get_value(pre_install_upgrade,
Hooks, []), " "),
PostInstallUpgradeHooks = rlx_string:join(proplists:get_value(post_install_upgrade,
Hooks, []), " "),
StatusHook = rlx_string:join(proplists:get_value(status, Hooks, []), " "),
{ExtensionsList1, ExtensionDeclarations1} =
lists:foldl(fun({Name, Script},
{ExtensionsList0, ExtensionDeclarations0}) ->
ExtensionDeclaration = atom_to_list(Name) ++
"_extension=\"" ++
Script ++ "\"",
{ExtensionsList0 ++ [atom_to_list(Name)],
ExtensionDeclarations0 ++ [ExtensionDeclaration]}
end, {[], []}, Extensions),
% pipe separated string of extensions, to show on the start script usage
% (eg. foo|bar)
ExtensionsList = rlx_string:join(ExtensionsList1 ++ ["undefined"], "|"),
% command separated string of extension script declarations
% (eg. foo_extension="path/to/foo_script")
ExtensionDeclarations = rlx_string:join(ExtensionDeclarations1, ";"),
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts},
{pre_start_hooks, PreStartHooks},
{post_start_hooks, PostStartHooks},
{pre_stop_hooks, PreStopHooks},
{post_stop_hooks, PostStopHooks},
{pre_install_upgrade_hooks, PreInstallUpgradeHooks},
{post_install_upgrade_hooks, PostInstallUpgradeHooks},
{status_hook, StatusHook},
{extensions, ExtensionsList},
{extension_declarations, ExtensionDeclarations}]).
erl_ini(OutputDir, ErtsVsn) ->
ErtsDirName = rlx_string:concat("erts-", ErtsVsn),
BinDir = filename:join([OutputDir, ErtsDirName, bin]),
render(erl_ini, [{bin_dir, BinDir}, {output_dir, OutputDir}]).
install_upgrade_escript_contents() ->
render(install_upgrade_escript).
nodetool_contents() ->
render(nodetool).
sys_config_file() ->
render(sys_config).
vm_args_file(RelName) ->
render(vm_args, [{rel_name, RelName}]).
render(Template) ->
render(Template, []).
render(Template, Data) ->
Files = rlx_util:template_files(),
Tpl = rlx_util:load_file(Files, escript, atom_to_list(Template)),
{ok, Content} = rlx_util:render(Tpl, Data),
Content.