Преглед на файлове

Abuse error_handler to get free metacalls in r3

This uses the very risky '$handle_undefined_function'/2 export from the
r3 and rebar_agent modules to allow meta-calls that can support plugins
and all other rebar3 extensions.

This is nasty but very tempting. Currently we only support:

- r3:do(Command)
- r3:do(Namespace, Command)

There is currently no way to pass arguments to the function such that we
can, for example, run cover analysis or tests on a subset of suites.

With the new abuse of '$handle_undefined_function'/2, we can detect the
unused commands (since they are not exported) and re-route them:

- r3:Command()
- r3:Command("--args=as a string")
- r3:Command(Namespace, "--args=as a string")

Of course, in doing so, we make it impossible to use the 'do' provider
(as in 'rebar3 do ct -c, cover') since the 'do' function is already
required for things to work.

Since the previous function had very strict guards, we can, without
conflict, add manual overrides that simulate the meta-calls fine.

Sample run:
https://gist.github.com/ferd/2c06d59c7083c146d25e4ee301de0073
pull/1534/head
Fred Hebert преди 8 години
родител
ревизия
03425c788c
променени са 2 файла, в които са добавени 29 реда и са изтрити 7 реда
  1. +5
    -0
      src/r3.erl
  2. +24
    -7
      src/rebar_agent.erl

+ 5
- 0
src/r3.erl Целия файл

@ -2,6 +2,7 @@
%%% calls from a shell.
-module(r3).
-export([do/1, do/2]).
-export(['$handle_undefined_function'/2]).
%% @doc alias for `rebar_agent:do/1'
-spec do(atom()) -> ok | {error, term()}.
@ -10,3 +11,7 @@ do(Command) -> rebar_agent:do(Command).
%% @doc alias for `rebar_agent:do/2'
-spec do(atom(), atom()) -> ok | {error, term()}.
do(Namespace, Command) -> rebar_agent:do(Namespace, Command).
%% @private defer to rebar_agent
'$handle_undefined_function'(Cmd, Args) ->
rebar_agent:'$handle_undefined_function'(Cmd, Args).

+ 24
- 7
src/rebar_agent.erl Целия файл

@ -2,6 +2,7 @@
%%% to statefully maintain loaded project state into a running VM.
-module(rebar_agent).
-export([start_link/1, do/1, do/2]).
-export(['$handle_undefined_function'/2]).
-export([init/1,
handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
@ -22,13 +23,24 @@ start_link(State) ->
%% @doc runs a given command in the agent's context.
-spec do(atom()) -> ok | {error, term()}.
do(Command) when is_atom(Command) ->
gen_server:call(?MODULE, {cmd, Command}, infinity).
gen_server:call(?MODULE, {cmd, Command}, infinity);
do(Args) when is_list(Args) ->
gen_server:call(?MODULE, {cmd, default, do, Args}, infinity).
%% @doc runs a given command in the agent's context, under a given
%% namespace.
-spec do(atom(), atom()) -> ok | {error, term()}.
do(Namespace, Command) when is_atom(Namespace), is_atom(Command) ->
gen_server:call(?MODULE, {cmd, Namespace, Command}, infinity).
gen_server:call(?MODULE, {cmd, Namespace, Command}, infinity);
do(Namespace, Args) when is_atom(Namespace), is_list(Args) ->
gen_server:call(?MODULE, {cmd, Namespace, do, Args}, infinity).
'$handle_undefined_function'(Cmd, [Namespace, Args]) ->
gen_server:call(?MODULE, {cmd, Namespace, Cmd, Args}, infinity);
'$handle_undefined_function'(Cmd, [Args]) ->
gen_server:call(?MODULE, {cmd, default, Cmd, Args}, infinity);
'$handle_undefined_function'(Cmd, []) ->
gen_server:call(?MODULE, {cmd, default, Cmd}, infinity).
%%%%%%%%%%%%%%%%%
%%% CALLBACKS %%%
@ -42,11 +54,15 @@ init(State) ->
%% @private
handle_call({cmd, Command}, _From, State=#state{state=RState, cwd=Cwd}) ->
MidState = maybe_show_warning(State),
{Res, NewRState} = run(default, Command, RState, Cwd),
{Res, NewRState} = run(default, Command, "", RState, Cwd),
{reply, Res, MidState#state{state=NewRState}, hibernate};
handle_call({cmd, Namespace, Command}, _From, State = #state{state=RState, cwd=Cwd}) ->
MidState = maybe_show_warning(State),
{Res, NewRState} = run(Namespace, Command, RState, Cwd),
{Res, NewRState} = run(Namespace, Command, "", RState, Cwd),
{reply, Res, MidState#state{state=NewRState}, hibernate};
handle_call({cmd, Namespace, Command, Args}, _From, State = #state{state=RState, cwd=Cwd}) ->
MidState = maybe_show_warning(State),
{Res, NewRState} = run(Namespace, Command, Args, RState, Cwd),
{reply, Res, MidState#state{state=NewRState}, hibernate};
handle_call(_Call, _From, State) ->
{noreply, State}.
@ -72,13 +88,14 @@ terminate(_Reason, _State) ->
%%%%%%%%%%%%%%%
%% @private runs the actual command and maintains the state changes
-spec run(atom(), atom(), rebar_state:t(), file:filename()) ->
-spec run(atom(), atom(), string(), rebar_state:t(), file:filename()) ->
{ok, rebar_state:t()} | {{error, term()}, rebar_state:t()}.
run(Namespace, Command, RState, Cwd) ->
run(Namespace, Command, StrArgs, RState, Cwd) ->
try
case rebar_dir:get_cwd() of
Cwd ->
Args = [atom_to_list(Namespace), atom_to_list(Command)],
PArgs = getopt:tokenize(StrArgs),
Args = [atom_to_list(Namespace), atom_to_list(Command)] ++ PArgs,
CmdState0 = refresh_state(RState, Cwd),
CmdState1 = rebar_state:set(CmdState0, task, atom_to_list(Command)),
CmdState = rebar_state:set(CmdState1, caller, api),

Зареждане…
Отказ
Запис