aboutsummaryrefslogtreecommitdiffstats
path: root/src/rlx_prv_assembler.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rlx_prv_assembler.erl')
-rw-r--r--src/rlx_prv_assembler.erl263
1 files changed, 216 insertions, 47 deletions
diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl
index e942b64..2088de6 100644
--- a/src/rlx_prv_assembler.erl
+++ b/src/rlx_prv_assembler.erl
@@ -121,7 +121,10 @@ format_error({start_clean_script_generation_error, Module, Errors}) ->
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)]).
+ [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
@@ -162,7 +165,7 @@ copy_app_directories_to_output(State, Release, OutputDir) ->
false
end,
lists:flatten(ec_plists:map(fun(App) ->
- copy_app(LibDir, App, IncludeSrc, IncludeErts)
+ copy_app(State, LibDir, App, IncludeSrc, IncludeErts)
end, Apps))),
case Result of
[E | _] ->
@@ -179,7 +182,7 @@ prepare_applications(State, Apps) ->
Apps
end.
-copy_app(LibDir, App, IncludeSrc, IncludeErts) ->
+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),
@@ -196,57 +199,75 @@ copy_app(LibDir, App, IncludeSrc, IncludeErts) ->
true ->
[];
false ->
- copy_app_(App, AppDir, TargetDir, IncludeSrc)
+ copy_app_(State, App, AppDir, TargetDir, IncludeSrc)
end;
_ ->
- copy_app_(App, AppDir, TargetDir, IncludeSrc)
+ 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_(App, AppDir, TargetDir, IncludeSrc) ->
+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(App, AppDir);
+ rewrite_app_file(State, App, AppDir);
false ->
- copy_directory(AppDir, TargetDir, IncludeSrc),
- rewrite_app_file(App, TargetDir)
+ 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(App, TargetDir) ->
+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, AppData}]} = file:consult(AppFile),
- OldActiveDeps = proplists:get_value(applications, AppData, []),
- OldIncludedDeps = proplists:get_value(included_applications, AppData, []),
-
- case {OldActiveDeps, OldIncludedDeps} of
- {ActiveDeps, IncludedDeps} ->
- ok;
- _ ->
- AppData1 = lists:keyreplace(applications
- ,1
- ,AppData
- ,{applications, ActiveDeps}),
- AppData2 = lists:keyreplace(included_applications
- ,1
- ,AppData1
- ,{included_applications, IncludedDeps}),
- Spec = io_lib:format("~p.\n", [{application, AppName, AppData2}]),
- write_file_if_contents_differ(AppFile, Spec)
+ {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, Bytes) ->
- ToWrite = iolist_to_binary(Bytes),
- case file:read_file(Filename) of
- {ok, ToWrite} ->
+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);
@@ -275,8 +296,8 @@ link_directory(AppDir, TargetDir) ->
ok
end.
-copy_directory(AppDir, TargetDir, IncludeSrc) ->
- [copy_dir(AppDir, TargetDir, SubDir)
+copy_directory(State, App, AppDir, TargetDir, IncludeSrc) ->
+ [copy_dir(State, App, AppDir, TargetDir, SubDir)
|| SubDir <- ["ebin",
"include",
"priv",
@@ -289,13 +310,20 @@ copy_directory(AppDir, TargetDir, IncludeSrc) ->
[]
end]].
-copy_dir(AppDir, TargetDir, SubDir) ->
+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),
- case ec_file:copy(SubSource, SubTarget, [recursive]) of
+ %% 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 ->
@@ -305,6 +333,22 @@ copy_dir(AppDir, TargetDir, SubDir) ->
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),
@@ -349,13 +393,17 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
rlx_release:erts(Release),
ErlOpts);
true ->
- case rlx_state:get(State, extended_start_script, false) of
- true ->
- include_nodetool(BinDir);
- false ->
- ok
- end,
- extended_bin_file_contents(OsFamily, RelName, RelVsn, rlx_release:erts(Release), ErlOpts)
+ %% 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),
+ extended_bin_file_contents(OsFamily, RelName, RelVsn,
+ rlx_release:erts(Release), ErlOpts,
+ Hooks)
end,
%% We generate the start script by default, unless the user
%% tells us not too
@@ -386,6 +434,98 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
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) -> string:join(["hooks/builtin/pid",
+ "/var/run/$REL_NAME.pid"], "|");
+hook_invocation({pid, PidFile}) -> 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
+ 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(),
@@ -456,7 +596,7 @@ 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 ++ ".orig");
+ ok = rlx_util:symlink_or_copy(ConfigPath, RelConfPath);
_ ->
ok = ec_file:copy(ConfigPath, RelConfPath)
end.
@@ -502,6 +642,18 @@ include_erts(State, Release, OutputDir, RelDir) ->
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 ->
@@ -559,7 +711,7 @@ make_boot_script_variables(State) ->
% (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
+ % 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
@@ -635,13 +787,30 @@ bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
-extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
+extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks) ->
Template = case OsFamily of
unix -> extended_bin;
win32 -> extended_bin_windows
end,
+ %% turn all the hook lists into space separated strings
+ PreStartHooks = string:join(proplists:get_value(pre_start, Hooks, []), " "),
+ PostStartHooks = string:join(proplists:get_value(post_start, Hooks, []), " "),
+ PreStopHooks = string:join(proplists:get_value(pre_stop, Hooks, []), " "),
+ PostStopHooks = string:join(proplists:get_value(post_stop, Hooks, []), " "),
+ PreInstallUpgradeHooks = string:join(proplists:get_value(pre_install_upgrade,
+ Hooks, []), " "),
+ PostInstallUpgradeHooks = string:join(proplists:get_value(post_install_upgrade,
+ Hooks, []), " "),
+ StatusHook = string:join(proplists:get_value(status, Hooks, []), " "),
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
- {erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
+ {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}]).
erl_ini(OutputDir, ErtsVsn) ->
ErtsDirName = string:concat("erts-", ErtsVsn),