summaryrefslogtreecommitdiff
path: root/src/rebar_prv_shell.erl
diff options
context:
space:
mode:
authorGeoff Cant <nem@erlang.geek.nz>2015-11-13 13:18:09 -0800
committerGeoff Cant <nem@erlang.geek.nz>2015-11-13 14:15:58 -0800
commit748142838cd11ce5cd532dd598d220cab8424f75 (patch)
treed57ef25ce2b0aa658be7564366a86834712c2f29 /src/rebar_prv_shell.erl
parent0b01c9fcf7443d36c6c3d67c74a70bad789ede5f (diff)
Feature: rebar shell [--script <FILE>]
Adds the ability to run an escript before starting the apps and interactive shell for a project. This is intended to improve the local development experience for projects by providing an easy way to run companion services (mock rest APIs, databases etc) that the project relies on. This patch also adds {shell, Defaults} to the rebar config file so that a project can supply default values for many of the new or improved 'rebar3 shell' options: * {apps, OTPApps} * {script_file, EscriptFileName} * {config, ConfigFileName} The order of option precedence is command line, rebar.config, relx.
Diffstat (limited to 'src/rebar_prv_shell.erl')
-rw-r--r--src/rebar_prv_shell.erl160
1 files changed, 131 insertions, 29 deletions
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index 4cf1e04..d93a21f 100644
--- a/src/rebar_prv_shell.erl
+++ b/src/rebar_prv_shell.erl
@@ -56,12 +56,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 +97,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)
@@ -134,6 +146,51 @@ setup_paths(State) ->
%% 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 +229,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 ->
@@ -261,31 +343,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.
--spec find_config_option(rebar_state:t()) -> [tuple()] | no_config.
+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()) -> 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_relx(rebar_state:t()) -> [tuple()] | no_config.
+-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_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) ->