summaryrefslogtreecommitdiff
path: root/src/rebar_prv_shell.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rebar_prv_shell.erl')
-rw-r--r--src/rebar_prv_shell.erl229
1 files changed, 190 insertions, 39 deletions
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index 4cf1e04..ea759fc 100644
--- a/src/rebar_prv_shell.erl
+++ b/src/rebar_prv_shell.erl
@@ -27,6 +27,7 @@
-module(rebar_prv_shell).
-author("Kresten Krab Thorup <krab@trifork.com>").
+-author("Fred Hebert <mononcqc@ferd.ca>").
-behaviour(provider).
@@ -56,12 +57,23 @@ init(State) ->
{short_desc, "Run shell with project apps and deps in path."},
{desc, info()},
{opts, [{config, undefined, "config", string,
- "Path to the config file to use. Defaults to the "
- "sys_config defined for relx, if present."},
+ "Path to the config file to use. Defaults to "
+ "{shell, [{config, File}]} and then the relx "
+ "sys.config file if not specified."},
{name, undefined, "name", atom,
"Gives a long name to the node."},
{sname, undefined, "sname", atom,
- "Gives a short name to the node."}]}
+ "Gives a short name to the node."},
+ {script_file, undefined, "script", string,
+ "Path to an escript file to run before "
+ "starting the project apps. Defaults to "
+ "rebar.config {shell, [{script_file, File}]} "
+ "if not specified."},
+ {apps, undefined, "apps", string,
+ "A list of apps to boot before starting the "
+ "shell. (E.g. --apps app1,app2,app3) Defaults "
+ "to rebar.config {shell, [{apps, Apps}]} or "
+ "relx apps if not specified."}]}
])
),
{ok, State1}.
@@ -86,6 +98,7 @@ shell(State) ->
setup_name(State),
setup_paths(State),
setup_shell(),
+ maybe_run_script(State),
%% apps must be started after the change in shell because otherwise
%% their application masters never gets the new group leader (held in
%% their internal state)
@@ -99,22 +112,64 @@ info() ->
"Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n".
setup_shell() ->
- %% scan all processes for any with references to the old user and save them to
- %% update later
- NeedsUpdate = [Pid || Pid <- erlang:processes(),
- proplists:get_value(group_leader, erlang:process_info(Pid)) == whereis(user)
- ],
- %% terminate the current user
+ OldUser = kill_old_user(),
+ %% Test for support here
+ NewUser = try erlang:open_port({spawn,'tty_sl -c -e'}, []) of
+ Port when is_port(Port) ->
+ true = port_close(Port),
+ setup_new_shell()
+ catch
+ error:_ ->
+ setup_old_shell()
+ end,
+ rewrite_leaders(OldUser, NewUser).
+
+kill_old_user() ->
+ OldUser = whereis(user),
+ %% terminate the current user's port, in a way that makes it shut down,
+ %% but without taking down the supervision tree so that the escript doesn't
+ %% fully die
+ [P] = [P || P <- element(2,process_info(whereis(user), links)), is_port(P)],
+ user ! {'EXIT', P, normal}, % pretend the port died, then the port can die!
+ OldUser.
+
+setup_new_shell() ->
+ %% terminate the current user supervision structure
ok = supervisor:terminate_child(kernel_sup, user),
%% start a new shell (this also starts a new user under the correct group)
_ = user_drv:start(),
%% wait until user_drv and user have been registered (max 3 seconds)
ok = wait_until_user_started(3000),
+ whereis(user).
+
+setup_old_shell() ->
+ %% scan all processes for any with references to the old user and save them to
+ %% update later
+ NewUser = rebar_user:start(), % hikack IO stuff with fake user
+ NewUser = whereis(user),
+ NewUser.
+
+rewrite_leaders(OldUser, NewUser) ->
%% set any process that had a reference to the old user's group leader to the
%% new user process. Catch the race condition when the Pid exited after the
%% liveness check.
- _ = [catch erlang:group_leader(whereis(user), Pid) || Pid <- NeedsUpdate,
- is_process_alive(Pid)],
+ _ = [catch erlang:group_leader(NewUser, Pid)
+ || Pid <- erlang:processes(),
+ proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
+ is_process_alive(Pid)],
+ %% Application masters have the same problem, but they hold the old group
+ %% leader in their state and hold on to it. Re-point the processes whose
+ %% leaders are application masters. This can mess up a few things around
+ %% shutdown time, but is nicer than the current lock-up.
+ OldMasters = [Pid
+ || Pid <- erlang:processes(),
+ Pid < NewUser, % only change old masters
+ {_,Dict} <- [erlang:process_info(Pid, dictionary)],
+ {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
+ _ = [catch erlang:group_leader(NewUser, Pid)
+ || Pid <- erlang:processes(),
+ lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
+ OldMasters)],
try
%% enable error_logger's tty output
error_logger:swap_handler(tty),
@@ -128,12 +183,58 @@ setup_shell() ->
hope_for_best
end.
+
setup_paths(State) ->
%% Add deps to path
code:add_pathsa(rebar_state:code_paths(State, all_deps)),
%% add project app test paths
ok = add_test_paths(State).
+maybe_run_script(State) ->
+ case first_value([fun find_script_option/1,
+ fun find_script_rebar/1], State) of
+ no_value ->
+ ?DEBUG("No script_file specified.", []),
+ ok;
+ "none" ->
+ ?DEBUG("Shell script execution skipped (--script none).", []),
+ ok;
+ RelFile ->
+ File = filename:absname(RelFile),
+ try run_script_file(File)
+ catch
+ C:E ->
+ ?ABORT("Couldn't run shell escript ~p - ~p:~p~nStack: ~p",
+ [File, C, E, erlang:get_stacktrace()])
+ end
+ end.
+
+-spec find_script_option(rebar_state:t()) -> no_value | list().
+find_script_option(State) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ debug_get_value(script_file, Opts, no_value,
+ "Found script file from command line option.").
+
+-spec find_script_rebar(rebar_state:t()) -> no_value | list().
+find_script_rebar(State) ->
+ Config = rebar_state:get(State, shell, []),
+ %% Either a string, or undefined
+ debug_get_value(script_file, Config, no_value,
+ "Found script file from rebar config file.").
+
+run_script_file(File) ->
+ ?DEBUG("Extracting escript from ~p", [File]),
+ {ok, Script} = escript:extract(File, [compile_source]),
+ Beam = proplists:get_value(source, Script),
+ Mod = proplists:get_value(module, beam_lib:info(Beam)),
+ ?DEBUG("Compiled escript as ~p", [Mod]),
+ FakeFile = "/fake_path/" ++ atom_to_list(Mod),
+ {module, Mod} = code:load_binary(Mod, FakeFile, Beam),
+ ?DEBUG("Evaling ~p:main([]).", [Mod]),
+ Result = Mod:main([]),
+ ?DEBUG("Result: ~p", [Result]),
+ Result.
+
maybe_boot_apps(State) ->
case find_apps_to_boot(State) of
undefined ->
@@ -172,17 +273,42 @@ check_epmd(_) ->
find_apps_to_boot(State) ->
%% Try the shell_apps option
- case rebar_state:get(State, shell_apps, undefined) of
- undefined ->
- %% Get to the relx tuple instead
- case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
- {_, _, Apps} -> Apps;
- false -> undefined
- end;
+ case first_value([fun find_apps_option/1,
+ fun find_apps_rebar/1,
+ fun find_apps_relx/1], State) of
+ no_value ->
+ undefined;
Apps ->
Apps
end.
+-spec find_apps_option(rebar_state:t()) -> no_value | [atom()].
+find_apps_option(State) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ case debug_get_value(apps, Opts, no_value,
+ "Found shell apps from command line option.") of
+ no_value -> no_value;
+ AppsStr ->
+ [ list_to_atom(AppStr)
+ || AppStr <- string:tokens(AppsStr, " ,:") ]
+ end.
+
+-spec find_apps_rebar(rebar_state:t()) -> no_value | list().
+find_apps_rebar(State) ->
+ ShellOpts = rebar_state:get(State, shell, []),
+ debug_get_value(apps, ShellOpts, no_value,
+ "Found shell opts from command line option.").
+
+-spec find_apps_relx(rebar_state:t()) -> no_value | list().
+find_apps_relx(State) ->
+ case lists:keyfind(release, 1, rebar_state:get(State, relx, [])) of
+ {_, _, Apps} ->
+ ?DEBUG("Found shell apps from relx.", []),
+ Apps;
+ false ->
+ no_value
+ end.
+
load_apps(Apps) ->
[case application:load(App) of
ok ->
@@ -199,9 +325,14 @@ reread_config(State) ->
no_config ->
ok;
ConfigList ->
- _ = [application:set_env(Application, Key, Val)
+ try
+ [application:set_env(Application, Key, Val)
|| {Application, Items} <- ConfigList,
- {Key, Val} <- Items],
+ {Key, Val} <- Items]
+ catch _:_ ->
+ ?ERROR("The configuration file submitted could not be read "
+ "and will be ignored.", [])
+ end,
ok
end.
@@ -261,31 +392,51 @@ add_test_paths(State) ->
% First try the --config flag, then try the relx sys_config
-spec find_config(rebar_state:t()) -> [tuple()] | no_config.
find_config(State) ->
- case find_config_option(State) of
- no_config ->
- find_config_relx(State);
- Result ->
- Result
+ case first_value([fun find_config_option/1,
+ fun find_config_rebar/1,
+ fun find_config_relx/1], State) of
+ no_value ->
+ no_config;
+ Filename when is_list(Filename) ->
+ consult_config(State, Filename)
+ end.
+
+-spec first_value([Fun], State) -> no_value | Value when
+ Value :: any(),
+ State :: rebar_state:t(),
+ Fun :: fun ((State) -> no_value | Value).
+first_value([], _) -> no_value;
+first_value([Fun | Rest], State) ->
+ case Fun(State) of
+ no_value ->
+ first_value(Rest, State);
+ Value ->
+ Value
+ end.
+
+debug_get_value(Key, List, Default, Description) ->
+ case proplists:get_value(Key, List, Default) of
+ Default -> Default;
+ Value ->
+ ?DEBUG(Description, []),
+ Value
end.
--spec find_config_option(rebar_state:t()) -> [tuple()] | no_config.
+-spec find_config_option(rebar_state:t()) -> Filename::list() | no_value.
find_config_option(State) ->
{Opts, _} = rebar_state:command_parsed_args(State),
- case proplists:get_value(config, Opts) of
- undefined ->
- no_config;
- Filename ->
- consult_config(State, Filename)
- end.
+ debug_get_value(config, Opts, no_value,
+ "Found config from command line option.").
+
+-spec find_config_rebar(rebar_state:t()) -> [tuple()] | no_value.
+find_config_rebar(State) ->
+ debug_get_value(config, rebar_state:get(State, shell, []), no_value,
+ "Found config from rebar config file.").
--spec find_config_relx(rebar_state:t()) -> [tuple()] | no_config.
+-spec find_config_relx(rebar_state:t()) -> [tuple()] | no_value.
find_config_relx(State) ->
- case proplists:get_value(sys_config, rebar_state:get(State, relx, [])) of
- undefined ->
- no_config;
- Filename ->
- consult_config(State, Filename)
- end.
+ debug_get_value(sys_config, rebar_state:get(State, relx, []), no_value,
+ "Found config from relx.").
-spec consult_config(rebar_state:t(), string()) -> [tuple()].
consult_config(State, Filename) ->