diff options
Diffstat (limited to 'src/rlx_prv_assembler.erl')
-rw-r--r-- | src/rlx_prv_assembler.erl | 263 |
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), |