summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/r3.erl7
-rw-r--r--src/rebar_agent.erl112
-rw-r--r--src/rebar_prv_do.erl4
-rw-r--r--src/rebar_prv_shell.erl151
4 files changed, 238 insertions, 36 deletions
diff --git a/src/r3.erl b/src/r3.erl
new file mode 100644
index 0000000..5e8b26d
--- /dev/null
+++ b/src/r3.erl
@@ -0,0 +1,7 @@
+%%% external alias for rebar_agent
+-module(r3).
+-export([do/1, do/2]).
+
+do(Command) -> rebar_agent:do(Command).
+
+do(Namespace, Command) -> rebar_agent:do(Namespace, Command).
diff --git a/src/rebar_agent.erl b/src/rebar_agent.erl
new file mode 100644
index 0000000..0432fb8
--- /dev/null
+++ b/src/rebar_agent.erl
@@ -0,0 +1,112 @@
+-module(rebar_agent).
+-export([start_link/1, do/1, do/2]).
+-export([init/1,
+ handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2]).
+
+-include("rebar.hrl").
+
+-record(state, {state,
+ cwd,
+ show_warning=true}).
+
+start_link(State) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, State, []).
+
+do(Command) when is_atom(Command) ->
+ gen_server:call(?MODULE, {cmd, Command}, infinity).
+
+do(Namespace, Command) when is_atom(Namespace), is_atom(Command) ->
+ gen_server:call(?MODULE, {cmd, Namespace, Command}, infinity).
+
+init(State) ->
+ {ok, Cwd} = file:get_cwd(),
+ {ok, #state{state=State, cwd=Cwd}}.
+
+handle_call({cmd, Command}, _From, State=#state{state=RState, cwd=Cwd}) ->
+ MidState = maybe_show_warning(State),
+ {Res, NewRState} = run(default, Command, RState, Cwd),
+ {reply, Res, MidState#state{state=NewRState}};
+handle_call({cmd, Namespace, Command}, _From, State = #state{state=RState, cwd=Cwd}) ->
+ MidState = maybe_show_warning(State),
+ {Res, NewRState} = run(Namespace, Command, RState, Cwd),
+ {reply, Res, MidState#state{state=NewRState}};
+handle_call(_Call, _From, State) ->
+ {noreply, State}.
+
+handle_cast(_Cast, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+run(Namespace, Command, RState, Cwd) ->
+ try
+ case file:get_cwd() of
+ {ok, Cwd} ->
+ Args = [atom_to_list(Namespace), atom_to_list(Command)],
+ CmdState0 = refresh_state(RState, Cwd),
+ CmdState1 = rebar_state:set(CmdState0, task, atom_to_list(Command)),
+ CmdState = rebar_state:set(CmdState1, caller, api),
+ case rebar3:run(CmdState, Args) of
+ {ok, TmpState} ->
+ refresh_paths(TmpState),
+ {ok, CmdState};
+ {error, Err} when is_list(Err) ->
+ refresh_paths(CmdState),
+ {{error, lists:flatten(Err)}, CmdState};
+ {error, Err} ->
+ refresh_paths(CmdState),
+ {{error, Err}, CmdState}
+ end;
+ _ ->
+ {{error, cwd_changed}, RState}
+ end
+ catch
+ Type:Reason ->
+ ?DEBUG("Agent Stacktrace: ~p", [erlang:get_stacktrace()]),
+ {{error, {Type, Reason}}, RState}
+ end.
+
+maybe_show_warning(S=#state{show_warning=true}) ->
+ ?WARN("This feature is experimental and may be modified or removed at any time.", []),
+ S#state{show_warning=false};
+maybe_show_warning(State) ->
+ State.
+
+refresh_paths(RState) ->
+ ToRefresh = (rebar_state:code_paths(RState, all_deps)
+ ++ [filename:join([rebar_app_info:out_dir(App), "test"])
+ || App <- rebar_state:project_apps(RState)]
+ %% make sure to never reload self; halt()s the VM
+ ) -- [filename:dirname(code:which(?MODULE))],
+ %% Similar to rebar_utils:update_code/1, but also forces a reload
+ %% of used modules.
+ lists:foreach(fun(Path) ->
+ Name = filename:basename(Path, "/ebin"),
+ App = list_to_atom(Name),
+ application:load(App),
+ case application:get_key(App, modules) of
+ undefined ->
+ code:add_patha(Path),
+ ok;
+ {ok, Modules} ->
+ ?DEBUG("reloading ~p from ~s", [Modules, Path]),
+ code:replace_path(Name, Path),
+ [begin code:purge(M), code:delete(M), code:load_file(M) end
+ || M <- Modules]
+ end
+ end, ToRefresh).
+
+refresh_state(RState, _Dir) ->
+ lists:foldl(
+ fun(F, State) -> F(State) end,
+ rebar3:init_config(),
+ [fun(S) -> rebar_state:current_profiles(S, rebar_state:current_profiles(RState)) end]
+ ).
diff --git a/src/rebar_prv_do.erl b/src/rebar_prv_do.erl
index aee3a27..29ef054 100644
--- a/src/rebar_prv_do.erl
+++ b/src/rebar_prv_do.erl
@@ -47,6 +47,8 @@ do_tasks([{TaskStr, Args}|Tail], State) ->
default ->
%% The first task we hit might be a namespace!
case maybe_namespace(State2, Task, Args) of
+ {ok, FinalState} when Tail =:= [] ->
+ {ok, FinalState};
{ok, _} ->
do_tasks(Tail, State);
{error, Reason} ->
@@ -56,6 +58,8 @@ do_tasks([{TaskStr, Args}|Tail], State) ->
%% We're already in a non-default namespace, check the
%% task directly.
case rebar_core:process_command(State2, Task) of
+ {ok, FinalState} when Tail =:= [] ->
+ {ok, FinalState};
{ok, _} ->
do_tasks(Tail, State);
{error, Reason} ->
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index 8c0b7ff..c80badf 100644
--- a/src/rebar_prv_shell.erl
+++ b/src/rebar_prv_shell.erl
@@ -45,14 +45,25 @@
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
- State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
- {module, ?MODULE},
- {bare, false},
- {deps, ?DEPS},
- {example, "rebar3 shell"},
- {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."}]}])),
+ State1 = rebar_state:add_provider(
+ State,
+ providers:create([
+ {name, ?PROVIDER},
+ {module, ?MODULE},
+ {bare, false},
+ {deps, ?DEPS},
+ {example, "rebar3 shell"},
+ {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."},
+ {name, undefined, "name", atom,
+ "Gives a long name to the node."},
+ {sname, undefined, "sname", atom,
+ "Gives a short name to the node."}]}
+ ])
+ ),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
@@ -72,6 +83,18 @@ format_error(Reason) ->
%% immediately kill the script. ctrl-g, however, works fine
shell(State) ->
+ setup_name(State),
+ setup_paths(State),
+ maybe_boot_apps(State),
+ setup_shell(),
+ rebar_agent:start_link(State),
+ %% this call never returns (until user quits shell)
+ timer:sleep(infinity).
+
+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(),
@@ -91,18 +114,85 @@ shell(State) ->
%% disable the simple error_logger (which may have been added multiple
%% times). removes at most the error_logger added by init and the
%% error_logger added by the tty handler
- ok = remove_error_handler(3),
+ ok = remove_error_handler(3).
+
+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),
- %% try to read in sys.config file
- ok = reread_config(State),
- %% this call never returns (until user quits shell)
- timer:sleep(infinity).
+ ok = add_test_paths(State).
-info() ->
- "Start a shell with project and deps preloaded similar to~n'erl -pa ebin -pa deps/*/ebin'.~n".
+maybe_boot_apps(State) ->
+ case find_apps_to_boot(State) of
+ undefined ->
+ %% try to read in sys.config file
+ ok = reread_config(State);
+ Apps ->
+ %% load apps, then check config, then boot them.
+ load_apps(Apps),
+ ok = reread_config(State),
+ boot_apps(Apps)
+ end.
+
+setup_name(State) ->
+ {Opts, _} = rebar_state:command_parsed_args(State),
+ case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of
+ {undefined, undefined} ->
+ ok;
+ {Name, undefined} ->
+ net_kernel:start([Name, longnames]);
+ {undefined, SName} ->
+ net_kernel:start([SName, shortnames]);
+ {_, _} ->
+ ?ABORT("Cannot have both short and long node names defined", [])
+ end.
+
+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;
+ Apps ->
+ Apps
+ end.
+
+load_apps(Apps) ->
+ [case application:load(App) of
+ ok ->
+ {ok, Ks} = application:get_all_key(App),
+ load_apps(proplists:get_value(applications, Ks));
+ _ ->
+ error % will be caught when starting the app
+ end || App <- Apps,
+ not lists:keymember(App, 1, application:loaded_applications())],
+ ok.
+
+reread_config(State) ->
+ case find_config(State) of
+ no_config ->
+ ok;
+ ConfigList ->
+ _ = [application:set_env(Application, Key, Val)
+ || {Application, Items} <- ConfigList,
+ {Key, Val} <- Items],
+ ok
+ end.
+
+boot_apps(Apps) ->
+ ?WARN("The rebar3 shell is a development tool; to deploy "
+ "applications in production, consider using releases "
+ "(http://www.rebar3.org/v3.0/docs/releases)", []),
+ Res = [application:ensure_all_started(App) || App <- Apps],
+ _ = [?INFO("Booted ~p", [App])
+ || {ok, Booted} <- Res,
+ App <- Booted],
+ _ = [?ERROR("Failed to boot ~p for reason ~p", [App, Reason])
+ || {error, {App, Reason}} <- Res],
+ ok.
remove_error_handler(0) ->
?WARN("Unable to remove simple error_logger handler", []);
@@ -124,28 +214,14 @@ wait_until_user_started(Timeout) ->
end.
add_test_paths(State) ->
- lists:foreach(fun(App) ->
- AppDir = rebar_app_info:out_dir(App),
- %% ignore errors resulting from non-existent directories
- _ = code:add_path(filename:join([AppDir, "test"]))
- end, rebar_state:project_apps(State)),
+ _ = [begin
+ AppDir = rebar_app_info:out_dir(App),
+ %% ignore errors resulting from non-existent directories
+ _ = code:add_path(filename:join([AppDir, "test"]))
+ end || App <- rebar_state:project_apps(State)],
_ = code:add_path(filename:join([rebar_dir:base_dir(State), "test"])),
ok.
-reread_config(State) ->
- case find_config(State) of
- no_config ->
- ok;
- ConfigList ->
- lists:foreach(fun ({Application, Items}) ->
- lists:foreach(fun ({Key, Val}) ->
- application:set_env(Application, Key, Val)
- end,
- Items)
- end,
- ConfigList)
- end.
-
% First try the --config flag, then try the relx sys_config
-spec find_config(rebar_state:t()) -> [tuple()] | no_config.
find_config(State) ->
@@ -179,4 +255,7 @@ find_config_relx(State) ->
consult_config(State, Filename) ->
Fullpath = filename:join(rebar_dir:root_dir(State), Filename),
?DEBUG("Loading configuration from ~p", [Fullpath]),
- rebar_file_utils:try_consult(Fullpath).
+ case rebar_file_utils:try_consult(Fullpath) of
+ [T] -> T;
+ [] -> []
+ end.