浏览代码

Merge pull request #473 from ferd/improve-shell

improve the rebar3 shell
pull/483/head
Tristan Sloughter 10 年前
父节点
当前提交
b51a0e314b
共有 4 个文件被更改,包括 238 次插入36 次删除
  1. +7
    -0
      src/r3.erl
  2. +112
    -0
      src/rebar_agent.erl
  3. +4
    -0
      src/rebar_prv_do.erl
  4. +115
    -36
      src/rebar_prv_shell.erl

+ 7
- 0
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).

+ 112
- 0
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]
).

+ 4
- 0
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} ->

+ 115
- 36
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.

正在加载...
取消
保存