diff options
author | Tristan Sloughter <tristan.sloughter@gmail.com> | 2015-05-30 10:37:18 -0500 |
---|---|---|
committer | Tristan Sloughter <tristan.sloughter@gmail.com> | 2015-05-30 10:37:18 -0500 |
commit | b51a0e314b2fb5a26c99625d025d70bf5c571b23 (patch) | |
tree | 4169aafe29ed425f9e1ec7c7d90a927b55da211c /src | |
parent | a11c009fcfe02fb7741bb7ce0c8ebaf2141ffd8d (diff) | |
parent | c87f2a2c73ccdff29c0b320070c5599716b47fca (diff) |
Merge pull request #473 from ferd/improve-shell
improve the rebar3 shell
Diffstat (limited to 'src')
-rw-r--r-- | src/r3.erl | 7 | ||||
-rw-r--r-- | src/rebar_agent.erl | 112 | ||||
-rw-r--r-- | src/rebar_prv_do.erl | 4 | ||||
-rw-r--r-- | src/rebar_prv_shell.erl | 151 |
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. |