Skip to content

Commit

Permalink
Merge pull request #1391 from ferd/doc-and-types
Browse files Browse the repository at this point in the history
Type specifications and edocs improvements
  • Loading branch information
ferd authored Dec 23, 2016
2 parents a1a8387 + 8ae17c4 commit 768a7c5
Show file tree
Hide file tree
Showing 17 changed files with 792 additions and 97 deletions.
7 changes: 6 additions & 1 deletion src/r3.erl
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
%%% external alias for rebar_agent
%%% @doc external alias for `rebar_agent' for more convenient
%%% calls from a shell.
-module(r3).
-export([do/1, do/2]).

%% @doc alias for `rebar_agent:do/1'
-spec do(atom()) -> ok | {error, term()}.
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).
76 changes: 61 additions & 15 deletions src/rebar3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% -------------------------------------------------------------------
%%
%% @doc Main module for rebar3. Supports two interfaces; one for escripts,
%% and one for usage as a library (although rebar3 makes a lot of
%% assumptions about its environment, making it a bit tricky to use as
%% a lib).
%%
%% This module's job is mostly to set up the root environment for rebar3
%% and handle global options (mostly all from the ENV) and make them
%% accessible to the rest of the run.
%% @end
-module(rebar3).

-export([main/0,
Expand All @@ -43,14 +53,14 @@
%% Public API
%% ====================================================================

%% For running with:
%% @doc For running with:
%% erl +sbtu +A0 -noinput -mode minimal -boot start_clean -s rebar3 main -extra "$@"
-spec main() -> no_return().
main() ->
List = init:get_plain_arguments(),
main(List).

%% escript Entry point
%% @doc escript Entry point
-spec main(list()) -> no_return().
main(Args) ->
try run(Args) of
Expand All @@ -63,7 +73,8 @@ main(Args) ->
handle_error(Error)
end.

%% Erlang-API entry point
%% @doc Erlang-API entry point
-spec run(rebar_state:t(), [string()]) -> {ok, rebar_state:t()} | {error, term()}.
run(BaseState, Commands) ->
start_and_load_apps(api),
BaseState1 = rebar_state:set(BaseState, task, Commands),
Expand All @@ -78,6 +89,10 @@ run(BaseState, Commands) ->
%% Internal functions
%% ====================================================================

%% @private sets up the rebar3 environment based on the command line
%% arguments passed, if they have any relevance; used to translate
%% from the escript call-site into a common one with the library
%% usage.
run(RawArgs) ->
start_and_load_apps(command_line),

Expand All @@ -95,7 +110,13 @@ run(RawArgs) ->
{BaseState2, _Args1} = set_options(BaseState1, {[], []}),
run_aux(BaseState2, RawArgs).

%% @private Junction point between the CLI and library entry points.
%% From here on the module's role is a shared path here to finish
%% up setting the environment for the run.
-spec run_aux(rebar_state:t(), [string()]) ->
{ok, rebar_state:t()} | {error, term()}.
run_aux(State, RawArgs) ->
%% Profile override; can only support one profile
State1 = case os:getenv("REBAR_PROFILE") of
false ->
State;
Expand All @@ -108,6 +129,7 @@ run_aux(State, RawArgs) ->
rebar_utils:check_min_otp_version(rebar_state:get(State1, minimum_otp_vsn, undefined)),
rebar_utils:check_blacklisted_otp_versions(rebar_state:get(State1, blacklisted_otp_vsns, undefined)),

%% Change the default hex CDN
State2 = case os:getenv("HEX_CDN") of
false ->
State1;
Expand Down Expand Up @@ -144,6 +166,9 @@ run_aux(State, RawArgs) ->

rebar_core:init_command(rebar_state:command_args(State10, Args), Task).

%% @doc set up base configuration having to do with verbosity, where
%% to find config files, and so on, and return an internal rebar3 state term.
-spec init_config() -> rebar_state:t().
init_config() ->
rebar_utils:set_httpc_options(),

Expand Down Expand Up @@ -190,6 +215,17 @@ init_config() ->
%% Initialize vsn cache
rebar_state:set(State1, vsn_cache, dict:new()).

%% @doc Parse basic rebar3 arguments to find the top-level task
%% to be run; this parsing is only partial from the point of view that
%% runs done with arguments like `as $PROFILE do $TASK' will just
%% return `as', which is then in charge of doing a more dynamic
%% dispatch.
%% If no arguments are given, the `help' task is returned.
%% If special arguments like `-h' or `-v' are translated to `help'
%% and `version' tasks.
%% The unparsed parts of arguments are returned in:
%% `{Task, Rest}'.
-spec parse_args([string()]) -> {atom(), [string()]}.
parse_args([]) ->
parse_args(["help"]);
parse_args([H | Rest]) when H =:= "-h"
Expand All @@ -201,6 +237,7 @@ parse_args([H | Rest]) when H =:= "-v"
parse_args([Task | RawRest]) ->
{list_to_atom(Task), RawRest}.

%% @private actually not too sure what this does anymore.
set_options(State, {Options, NonOptArgs}) ->
GlobalDefines = proplists:get_all_values(defines, Options),

Expand All @@ -213,9 +250,8 @@ set_options(State, {Options, NonOptArgs}) ->

{rebar_state:set(State2, task, Task), NonOptArgs}.

%%
%% get log level based on getopt option
%%
%% @doc get log level based on getopt options and ENV
-spec log_level() -> integer().
log_level() ->
case os:getenv("QUIET") of
Q when Q == false; Q == "" ->
Expand All @@ -230,18 +266,16 @@ log_level() ->
rebar_log:error_level()
end.

%%
%% show version information and halt
%%
%% @doc show version information
-spec version() -> ok.
version() ->
{ok, Vsn} = application:get_key(rebar, vsn),
?CONSOLE("rebar ~s on Erlang/OTP ~s Erts ~s",
[Vsn, erlang:system_info(otp_release), erlang:system_info(version)]).

%% @private set global flag based on getopt option boolean value
%% TODO: Actually make it 'global'
%%
%% set global flag based on getopt option boolean value
%%
-spec set_global_flag(rebar_state:t(), list(), term()) -> rebar_state:t().
set_global_flag(State, Options, Flag) ->
Value = case proplists:get_bool(Flag, Options) of
true ->
Expand All @@ -251,9 +285,9 @@ set_global_flag(State, Options, Flag) ->
end,
rebar_state:set(State, Flag, Value).

%%
%% options accepted via getopt
%%

%% @doc options accepted via getopt
-spec global_option_spec_list() -> [{atom(), char(), string(), atom(), string()}, ...].
global_option_spec_list() ->
[
%% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
Expand All @@ -262,6 +296,9 @@ global_option_spec_list() ->
{task, undefined, undefined, string, "Task to run."}
].

%% @private translate unhandled errors and internal return codes into proper
%% erroneous program exits.
-spec handle_error(term()) -> no_return().
handle_error(rebar_abort) ->
erlang:halt(1);
handle_error({error, rebar_abort}) ->
Expand Down Expand Up @@ -295,6 +332,11 @@ handle_error(Error) ->
?INFO("When submitting a bug report, please include the output of `rebar3 report \"your command\"`", []),
erlang:halt(1).

%% @private Boot Erlang dependencies; problem is that escripts don't auto-boot
%% stuff the way releases do and we have to do it by hand.
%% This also lets us detect and show nicer errors when a critical lib is
%% not supported
-spec start_and_load_apps(command_line|api) -> term().
start_and_load_apps(Caller) ->
_ = application:load(rebar),
%% Make sure crypto is running
Expand All @@ -305,6 +347,9 @@ start_and_load_apps(Caller) ->
inets:start(),
inets:start(httpc, [{profile, rebar}]).

%% @doc Make sure a required app is running, or display an error message
%% and abort if there's a problem.
-spec ensure_running(atom(), command_line|api) -> ok | no_return().
ensure_running(App, Caller) ->
case application:start(App) of
ok -> ok;
Expand All @@ -321,6 +366,7 @@ ensure_running(App, Caller) ->
throw(rebar_abort)
end.

-spec state_from_global_config([term()], file:filename()) -> rebar_state:t().
state_from_global_config(Config, GlobalConfigFile) ->
GlobalConfigTerms = rebar_config:consult_file(GlobalConfigFile),
GlobalConfig = rebar_state:new(GlobalConfigTerms),
Expand Down
59 changes: 48 additions & 11 deletions src/rebar_agent.erl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
%%% @doc Runs a process that holds a rebar3 state and can be used
%%% to statefully maintain loaded project state into a running VM.
-module(rebar_agent).
-export([start_link/1, do/1, do/2]).
-export([init/1,
Expand All @@ -10,19 +12,34 @@
cwd,
show_warning=true}).

%% @doc boots an agent server; requires a full rebar3 state already.
%% By default (within rebar3), this isn't called; `rebar_prv_shell'
%% enters and transforms into this module
-spec start_link(rebar_state:t()) -> {ok, pid()}.
start_link(State) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, 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).

%% @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).

%%%%%%%%%%%%%%%%%
%%% CALLBACKS %%%
%%%%%%%%%%%%%%%%%

%% @private
init(State) ->
Cwd = rebar_dir:get_cwd(),
{ok, #state{state=State, cwd=Cwd}}.

%% @private
handle_call({cmd, Command}, _From, State=#state{state=RState, cwd=Cwd}) ->
MidState = maybe_show_warning(State),
{Res, NewRState} = run(default, Command, RState, Cwd),
Expand All @@ -34,18 +51,29 @@ handle_call({cmd, Namespace, Command}, _From, State = #state{state=RState, cwd=C
handle_call(_Call, _From, State) ->
{noreply, State}.

%% @private
handle_cast(_Cast, State) ->
{noreply, State}.

%% @private
handle_info(_Info, State) ->
{noreply, State}.

%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%% @private
terminate(_Reason, _State) ->
ok.

%%%%%%%%%%%%%%%
%%% PRIVATE %%%
%%%%%%%%%%%%%%%

%% @private runs the actual command and maintains the state changes
-spec run(atom(), atom(), rebar_state:t(), file:filename()) ->
{ok, rebar_state:t()} | {{error, term()}, rebar_state:t()}.
run(Namespace, Command, RState, Cwd) ->
try
case rebar_dir:get_cwd() of
Expand Down Expand Up @@ -74,12 +102,17 @@ run(Namespace, Command, RState, Cwd) ->
{{error, {Type, Reason}}, RState}
end.

%% @private function to display a warning for the feature only once
-spec maybe_show_warning(#state{}) -> #state{}.
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.

%% @private based on a rebar3 state term, reload paths in a way
%% that makes sense.
-spec refresh_paths(rebar_state:t()) -> ok.
refresh_paths(RState) ->
ToRefresh = (rebar_state:code_paths(RState, all_deps)
++ [filename:join([rebar_app_info:out_dir(App), "test"])
Expand Down Expand Up @@ -116,33 +149,38 @@ refresh_paths(RState) ->
end
end, ToRefresh).

%% @private from a disk config, reload and reapply with the current
%% profiles; used to find changes in the config from a prior run.
-spec refresh_state(rebar_state:t(), file:filename()) -> rebar_state:t().
refresh_state(RState, _Dir) ->
lists:foldl(
fun(F, State) -> F(State) end,
rebar3:init_config(),
[fun(S) -> rebar_state:apply_profiles(S, rebar_state:current_profiles(RState)) end]
).

%% @private takes a list of modules and reloads them
-spec reload_modules([module()]) -> term().
reload_modules([]) -> noop;
reload_modules(Modules) ->
reload_modules(Modules) ->
reload_modules(Modules, erlang:function_exported(code, prepare_loading, 1)).

%% OTP 19 and later -- use atomic loading and ignore unloadable mods
%% @private reloading modules, when there are modules to actually reload
reload_modules(Modules, true) ->
%% OTP 19 and later -- use atomic loading and ignore unloadable mods
case code:prepare_loading(Modules) of
{ok, Prepared} ->
[code:purge(M) || M <- Modules],
code:finish_loading(Prepared);

{error, ModRsns} ->
Blacklist =
Blacklist =
lists:foldr(fun({ModError, Error}, Acc) ->
case Error of
%perhaps cover other cases of failure?
% perhaps cover other cases of failure?
on_load_not_allowed ->
reload_modules([ModError], false),
[ModError|Acc];
_ ->
_ ->
?DEBUG("Module ~p failed to atomic load because ~p", [ModError, Error]),
[ModError|Acc]
end
Expand All @@ -151,16 +189,15 @@ reload_modules(Modules, true) ->
),
reload_modules(Modules -- Blacklist, true)
end;

%% Older versions, use a more ad-hoc mechanism.
reload_modules(Modules, false) ->
%% Older versions, use a more ad-hoc mechanism.
lists:foreach(fun(M) ->
code:delete(M),
code:purge(M),
code:delete(M),
code:purge(M),
case code:load_file(M) of
{module, M} -> ok;
{error, Error} ->
?DEBUG("Module ~p failed to load because ~p", [M, Error])
end
end, Modules
).
).
Loading

0 comments on commit 768a7c5

Please sign in to comment.