summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/r3.erl7
-rw-r--r--src/rebar3.erl76
-rw-r--r--src/rebar_agent.erl59
-rw-r--r--src/rebar_api.erl38
-rw-r--r--src/rebar_app_discover.erl81
-rw-r--r--src/rebar_app_info.erl131
-rw-r--r--src/rebar_app_utils.erl79
-rw-r--r--src/rebar_base_compiler.erl108
-rw-r--r--src/rebar_config.erl82
-rw-r--r--src/rebar_core.erl34
-rw-r--r--src/rebar_dir.erl20
-rw-r--r--src/rebar_dist_utils.erl2
-rw-r--r--src/rebar_file_utils.erl6
-rw-r--r--src/rebar_pkg_resource.erl6
-rw-r--r--src/rebar_prv_install_deps.erl9
-rw-r--r--src/rebar_utils.erl23
16 files changed, 686 insertions, 75 deletions
diff --git a/src/r3.erl b/src/r3.erl
index 5e8b26d..d0d6c47 100644
--- a/src/r3.erl
+++ b/src/r3.erl
@@ -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).
diff --git a/src/rebar3.erl b/src/rebar3.erl
index 4e7e284..fa26ab2 100644
--- a/src/rebar3.erl
+++ b/src/rebar3.erl
@@ -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,
@@ -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
@@ -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),
@@ -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),
@@ -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;
@@ -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;
@@ -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() ->
%% Initialize logging system
Verbosity = log_level(),
@@ -188,6 +213,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"
@@ -199,6 +235,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),
@@ -211,9 +248,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 == "" ->
@@ -228,18 +264,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 ->
@@ -249,9 +283,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}
@@ -260,6 +294,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}) ->
@@ -293,6 +330,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
@@ -303,6 +345,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;
@@ -319,6 +364,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) ->
rebar_utils:set_httpc_options(),
GlobalConfigTerms = rebar_config:consult_file(GlobalConfigFile),
diff --git a/src/rebar_agent.erl b/src/rebar_agent.erl
index 4b0fc5f..ed9e45d 100644
--- a/src/rebar_agent.erl
+++ b/src/rebar_agent.erl
@@ -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,
@@ -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),
@@ -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
@@ -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"])
@@ -116,6 +149,9 @@ 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,
@@ -123,26 +159,28 @@ refresh_state(RState, _Dir) ->
[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
@@ -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
- ). \ No newline at end of file
+ ).
diff --git a/src/rebar_api.erl b/src/rebar_api.erl
index 6ebc500..9d9071e 100644
--- a/src/rebar_api.erl
+++ b/src/rebar_api.erl
@@ -1,4 +1,4 @@
-%%% Packages rebar.hrl features and macros into a more generic API
+%%% @doc Packages rebar.hrl features and macros into a more generic API
%%% that can be used by plugin builders.
-module(rebar_api).
-include("rebar.hrl").
@@ -30,42 +30,62 @@ abort() -> ?FAIL.
abort(Str, Args) -> ?ABORT(Str, Args).
%% @doc Prints to the console, including a newline
+-spec console(string(), list()) -> ok.
console(Str, Args) -> ?CONSOLE(Str, Args).
%% @doc logs with severity `debug'
+-spec debug(string(), list()) -> ok.
debug(Str, Args) -> ?DEBUG(Str, Args).
+
%% @doc logs with severity `info'
+-spec info(string(), list()) -> ok.
info(Str, Args) -> ?INFO(Str, Args).
+
%% @doc logs with severity `warn'
+-spec warn(string(), list()) -> ok.
warn(Str, Args) -> ?WARN(Str, Args).
+
%% @doc logs with severity `error'
+-spec error(string(), list()) -> ok.
error(Str, Args) -> ?ERROR(Str, Args).
-%%
-%% Given env. variable FOO we want to expand all references to
-%% it in InStr. References can have two forms: $FOO and ${FOO}
-%% The end of form $FOO is delimited with whitespace or eol
-%%
+%% @doc Given env. variable `FOO' we want to expand all references to
+%% it in `InStr'. References can have two forms: `$FOO' and `${FOO}'
+%% The end of form `$FOO' is delimited with whitespace or EOL
+-spec expand_env_variable(string(), string(), term()) -> string().
expand_env_variable(InStr, VarName, RawVarValue) ->
rebar_utils:expand_env_variable(InStr, VarName, RawVarValue).
+%% @doc returns the sytem architecture, in strings like
+%% `"19.0.4-x86_64-unknown-linux-gnu-64"'.
+-spec get_arch() -> string().
get_arch() ->
rebar_utils:get_arch().
+%% @doc returns the size of a word on the system, as a string
+-spec wordsize() -> string().
wordsize() ->
rebar_utils:wordsize().
-
-%% Add deps to the code path
+%% @doc Add deps to the code path
+-spec add_deps_to_path(rebar_state:t()) -> ok.
add_deps_to_path(State) ->
code:add_pathsa(rebar_state:code_paths(State, all_deps)).
-%% Revert to only having the beams necessary for running rebar3 and plugins in the path
+%% @doc Revert to only having the beams necessary for running rebar3 and
+%% plugins in the path
+-spec restore_code_path(rebar_state:t()) -> true | {error, term()}.
restore_code_path(State) ->
rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)).
+%% @doc checks if the current working directory is the base directory
+%% for the project.
+-spec processing_base_dir(rebar_state:t()) -> boolean().
processing_base_dir(State) ->
rebar_dir:processing_base_dir(State).
+%% @doc returns the SSL options adequate for the project based on
+%% its configuration, including for validation of certs.
+-spec ssl_opts(string()) -> [term()].
ssl_opts(Url) ->
rebar_pkg_resource:ssl_opts(Url).
diff --git a/src/rebar_app_discover.erl b/src/rebar_app_discover.erl
index 67acf54..acefdb4 100644
--- a/src/rebar_app_discover.erl
+++ b/src/rebar_app_discover.erl
@@ -1,3 +1,5 @@
+%%% @doc utility functions to do the basic discovery of apps
+%%% and layout for the project.
-module(rebar_app_discover).
-export([do/2,
@@ -11,6 +13,8 @@
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
+%% @doc from the base directory,
+-spec do(rebar_state:t(), [file:filename()]) -> rebar_state:t().
do(State, LibDirs) ->
BaseDir = rebar_state:dir(State),
Dirs = [filename:join(BaseDir, LibDir) || LibDir <- LibDirs],
@@ -55,6 +59,10 @@ do(State, LibDirs) ->
end
end, State1, SortedApps).
+%% @doc checks whether there is an app at the top level (and returns its
+%% name) or the 'root' atom in case we're in an umbrella project.
+-spec define_root_app([rebar_app_info:t()], rebar_state:t()) ->
+ root | binary().
define_root_app(Apps, State) ->
RootDir = rebar_dir:root_dir(State),
case ec_lists:find(fun(X) ->
@@ -67,11 +75,17 @@ define_root_app(Apps, State) ->
root
end.
+%% @doc formatting errors from the module.
+-spec format_error(term()) -> iodata().
format_error({module_list, File}) ->
io_lib:format("Error reading module list from ~p~n", [File]);
format_error({missing_module, Module}) ->
io_lib:format("Module defined in app file missing: ~p~n", [Module]).
+%% @doc handles the merging and application of profiles and overrides
+%% for a given application, within its own context.
+-spec merge_deps(rebar_app_info:t(), rebar_state:t()) ->
+ {rebar_app_info:t(), rebar_state:t()}.
merge_deps(AppInfo, State) ->
%% These steps make sure that hooks and artifacts are run in the context of
%% the application they are defined at. If an umbrella structure is used and
@@ -97,6 +111,10 @@ merge_deps(AppInfo, State) ->
{AppInfo2, State2}.
+%% @doc Applies a given profile for an app, ensuring the deps
+%% match the context it will require.
+-spec handle_profile(atom(), binary(), rebar_app_info:t(), rebar_state:t()) ->
+ rebar_state:t().
handle_profile(Profile, Name, AppInfo, State) ->
TopParsedDeps = rebar_state:get(State, {parsed_deps, Profile}, {[], []}),
TopLevelProfileDeps = rebar_state:get(State, {deps, Profile}, []),
@@ -113,6 +131,12 @@ handle_profile(Profile, Name, AppInfo, State) ->
State2 = rebar_state:set(State1, {deps, Profile}, ProfileDeps2),
rebar_state:set(State2, {parsed_deps, Profile}, TopParsedDeps++ParsedDeps).
+%% @doc parses all the known dependencies for a given profile
+-spec parse_profile_deps(Profile, Name, Deps, Opts, rebar_state:t()) -> [rebar_app_info:t()] when
+ Profile :: atom(),
+ Name :: binary(),
+ Deps :: [term()], % TODO: refine types
+ Opts :: term(). % TODO: refine types
parse_profile_deps(Profile, Name, Deps, Opts, State) ->
DepsDir = rebar_prv_install_deps:profile_dep_dir(State, Profile),
Locks = rebar_state:get(State, {locks, Profile}, []),
@@ -123,14 +147,21 @@ parse_profile_deps(Profile, Name, Deps, Opts, State) ->
,Locks
,1).
+%% @doc Find the app-level config and return the state updated
+%% with the relevant app-level data.
+-spec project_app_config(rebar_app_info:t(), rebar_state:t()) ->
+ {Config, rebar_state:t()} when
+ Config :: [any()].
project_app_config(AppInfo, State) ->
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
Dir = rebar_app_info:dir(AppInfo),
Opts = maybe_reset_hooks(Dir, rebar_state:opts(State), State),
{C, rebar_state:opts(State, Opts)}.
-%% Here we check if the app is at the root of the project.
+%% @doc Here we check if the app is at the root of the project.
%% If it is, then drop the hooks from the config so they aren't run twice
+-spec maybe_reset_hooks(file:filename(), Opts, rebar_state:t()) -> Opts when
+ Opts :: rebar_dict().
maybe_reset_hooks(Dir, Opts, State) ->
case ec_file:real_dir_path(rebar_dir:root_dir(State)) of
Dir ->
@@ -139,17 +170,22 @@ maybe_reset_hooks(Dir, Opts, State) ->
Opts
end.
+%% @doc make the hooks empty for a given set of options
+-spec reset_hooks(Opts) -> Opts when Opts :: rebar_dict().
reset_hooks(Opts) ->
lists:foldl(fun(Key, OptsAcc) ->
rebar_opts:set(OptsAcc, Key, [])
end, Opts, [post_hooks, pre_hooks, provider_hooks, artifacts]).
--spec all_app_dirs(list(file:name())) -> list(file:name()).
+%% @doc find the directories for all apps
+-spec all_app_dirs([file:name()]) -> [file:name()].
all_app_dirs(LibDirs) ->
lists:flatmap(fun(LibDir) ->
app_dirs(LibDir)
end, LibDirs).
+%% @doc find the directories based on the library directories
+-spec app_dirs([file:name()]) -> [file:name()].
app_dirs(LibDir) ->
Path1 = filename:join([LibDir,
"src",
@@ -168,32 +204,51 @@ app_dirs(LibDir) ->
[app_dir(File) || File <- Files] ++ Acc
end, [], [Path1, Path2, Path3])).
+%% @doc find all apps that haven't been built in a list of directories
+-spec find_unbuilt_apps([file:filename_all()]) -> [rebar_app_info:t()].
find_unbuilt_apps(LibDirs) ->
find_apps(LibDirs, invalid).
+%% @doc for each directory passed, find all apps that are valid.
+%% Returns all the related app info records.
-spec find_apps([file:filename_all()]) -> [rebar_app_info:t()].
find_apps(LibDirs) ->
find_apps(LibDirs, valid).
+%% @doc for each directory passed, find all apps according
+%% to the validity rule passed in. Returns all the related
+%% app info records.
-spec find_apps([file:filename_all()], valid | invalid | all) -> [rebar_app_info:t()].
find_apps(LibDirs, Validate) ->
rebar_utils:filtermap(fun(AppDir) ->
find_app(AppDir, Validate)
end, all_app_dirs(LibDirs)).
+%% @doc check that a given app in a directory is there, and whether it's
+%% valid or not based on the second argument. Returns the related
+%% app info record.
-spec find_app(file:filename_all(), valid | invalid | all) -> {true, rebar_app_info:t()} | false.
find_app(AppDir, Validate) ->
find_app(rebar_app_info:new(), AppDir, Validate).
+%% @doc check that a given app in a directory is there, and whether it's
+%% valid or not based on the second argument. Returns the related
+%% app info record.
+-spec find_app(rebar_app_info:t(), file:filename_all(), valid | invalid | all) ->
+ {true, rebar_app_info:t()} | false.
find_app(AppInfo, AppDir, Validate) ->
AppFile = filelib:wildcard(filename:join([AppDir, "ebin", "*.app"])),
AppSrcFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src"])),
AppSrcScriptFile = filelib:wildcard(filename:join([AppDir, "src", "*.app.src.script"])),
try_handle_app_file(AppInfo, AppFile, AppDir, AppSrcFile, AppSrcScriptFile, Validate).
+%% @doc find the directory that an appfile has
+-spec app_dir(file:filename()) -> file:filename().
app_dir(AppFile) ->
filename:join(rebar_utils:droplast(filename:split(filename:dirname(AppFile)))).
+%% @doc populates an app info record based on an app directory and its
+%% app file.
-spec create_app_info(rebar_app_info:t(), file:name(), file:name()) -> rebar_app_info:t().
create_app_info(AppInfo, AppDir, AppFile) ->
[{application, AppName, AppDetails}] = rebar_config:consult_app_file(AppFile),
@@ -215,8 +270,15 @@ create_app_info(AppInfo, AppDir, AppFile) ->
end,
rebar_app_info:dir(rebar_app_info:valid(AppInfo2, Valid), AppDir).
-%% Read in and parse the .app file if it is availabe. Do the same for
+%% @doc Read in and parse the .app file if it is availabe. Do the same for
%% the .app.src file if it exists.
+-spec try_handle_app_file(AppInfo, AppFile, AppDir, AppSrcFile, AppSrcScriptFile, valid | invalid | all) ->
+ {true, AppInfo} | false when
+ AppInfo :: rebar_app_info:t(),
+ AppFile :: file:filename(),
+ AppDir :: file:filename(),
+ AppSrcFile :: file:filename(),
+ AppSrcScriptFile :: file:filename().
try_handle_app_file(AppInfo, [], AppDir, [], AppSrcScriptFile, Validate) ->
try_handle_app_src_file(AppInfo, [], AppDir, AppSrcScriptFile, Validate);
try_handle_app_file(AppInfo, [], AppDir, AppSrcFile, _, Validate) ->
@@ -260,7 +322,14 @@ try_handle_app_file(AppInfo0, [File], AppDir, AppSrcFile, _, Validate) ->
try_handle_app_file(_AppInfo, Other, _AppDir, _AppSrcFile, _, _Validate) ->
throw({error, {multiple_app_files, Other}}).
-%% Read in the .app.src file if we aren't looking for a valid (already built) app
+%% @doc Read in the .app.src file if we aren't looking for a valid (already
+%% built) app.
+-spec try_handle_app_src_file(AppInfo, AppFile, AppDir, AppSrcFile, valid | invalid | all) ->
+ {true, AppInfo} | false when
+ AppInfo :: rebar_app_info:t(),
+ AppFile :: file:filename(),
+ AppDir :: file:filename(),
+ AppSrcFile :: file:filename().
try_handle_app_src_file(_AppInfo, _, _AppDir, [], _Validate) ->
false;
try_handle_app_src_file(_AppInfo, _, _AppDir, _AppSrcFile, valid) ->
@@ -277,9 +346,13 @@ try_handle_app_src_file(AppInfo, _, AppDir, [File], Validate) when Validate =:=
try_handle_app_src_file(_AppInfo, _, _AppDir, Other, _Validate) ->
throw({error, {multiple_app_files, Other}}).
+%% @doc checks whether the given app is not blacklisted in the config.
+-spec enable(rebar_state:t(), rebar_app_info:t()) -> boolean().
enable(State, AppInfo) ->
not lists:member(to_atom(rebar_app_info:name(AppInfo)),
rebar_state:get(State, excluded_apps, [])).
+%% @private convert a binary to an atom.
+-spec to_atom(binary()) -> atom().
to_atom(Bin) ->
list_to_atom(binary_to_list(Bin)).
diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl
index 4f19d77..fdaadb8 100644
--- a/src/rebar_app_info.erl
+++ b/src/rebar_app_info.erl
@@ -103,11 +103,13 @@
new() ->
#app_info_t{}.
+%% @doc Build a new app info value with only the app name set.
-spec new(atom() | binary() | string()) ->
{ok, t()}.
new(AppName) ->
{ok, #app_info_t{name=ec_cnv:to_binary(AppName)}}.
+%% @doc Build a new app info value with only the name and version set.
-spec new(atom() | binary() | string(), binary() | string()) ->
{ok, t()}.
new(AppName, Vsn) ->
@@ -144,6 +146,9 @@ new(Parent, AppName, Vsn, Dir, Deps) ->
out_dir=ec_cnv:to_list(Dir),
deps=Deps}}.
+%% @doc update the opts based on the contents of a config
+%% file for the app
+-spec update_opts(t(), rebar_dict(), [any()]) -> t().
update_opts(AppInfo, Opts, Config) ->
LockDeps = case resource_type(AppInfo) of
pkg ->
@@ -163,6 +168,8 @@ update_opts(AppInfo, Opts, Config) ->
AppInfo#app_info_t{opts=NewOpts
,default=NewOpts}.
+%% @private extract the deps for an app in `Dir' based on its config file data
+-spec deps_from_config(file:filename(), [any()]) -> [{tuple(), any()}, ...].
deps_from_config(Dir, Config) ->
case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
[] ->
@@ -184,30 +191,48 @@ discover(Dir) ->
not_found
end.
+%% @doc get the name of the app.
-spec name(t()) -> binary().
name(#app_info_t{name=Name}) ->
Name.
+%% @doc set the name of the app.
-spec name(t(), atom() | binary() | string()) -> t().
name(AppInfo=#app_info_t{}, AppName) ->
AppInfo#app_info_t{name=ec_cnv:to_binary(AppName)}.
+%% @doc get the dictionary of options for the app.
+-spec opts(t()) -> rebar_dict().
opts(#app_info_t{opts=Opts}) ->
Opts.
+%% @doc set the dictionary of options for the app.
+-spec opts(t(), rebar_dict()) -> t().
opts(AppInfo, Opts) ->
AppInfo#app_info_t{opts=Opts}.
+%% @doc get the dictionary of options under the default profile.
+%% Represents a root set prior to applying other profiles.
+-spec default(t()) -> rebar_dict().
default(#app_info_t{default=Default}) ->
Default.
+%% @doc set the dictionary of options under the default profile.
+%% Useful when re-applying profile.
+-spec default(t(), rebar_dict()) -> t().
default(AppInfo, Default) ->
AppInfo#app_info_t{default=Default}.
+%% @doc look up a value in the dictionary of options; fails if
+%% the key for it does not exist.
+-spec get(t(), term()) -> term().
get(AppInfo, Key) ->
{ok, Value} = dict:find(Key, AppInfo#app_info_t.opts),
Value.
+%% @doc look up a value in the dictionary of options; returns
+%% a `Default' value otherwise.
+-spec get(t(), term(), term()) -> term().
get(AppInfo, Key, Default) ->
case dict:find(Key, AppInfo#app_info_t.opts) of
{ok, Value} ->
@@ -216,10 +241,12 @@ get(AppInfo, Key, Default) ->
Default
end.
+%% @doc sets a given value in the dictionary of options for the app.
-spec set(t(), any(), any()) -> t().
set(AppInfo=#app_info_t{opts=Opts}, Key, Value) ->
AppInfo#app_info_t{opts = dict:store(Key, Value, Opts)}.
+%% @doc finds the .app.src file for an app, if any.
-spec app_file_src(t()) -> file:filename_all() | undefined.
app_file_src(#app_info_t{app_file_src=undefined, dir=Dir, name=Name}) ->
AppFileSrc = filename:join([ec_cnv:to_list(Dir), "src", ec_cnv:to_list(Name)++".app.src"]),
@@ -232,12 +259,15 @@ app_file_src(#app_info_t{app_file_src=undefined, dir=Dir, name=Name}) ->
app_file_src(#app_info_t{app_file_src=AppFileSrc}) ->
ec_cnv:to_list(AppFileSrc).
+%% @doc sets the .app.src file for an app. An app without such a file
+%% can explicitly be set with `undefined'.
-spec app_file_src(t(), file:filename_all() | undefined) -> t().
app_file_src(AppInfo=#app_info_t{}, undefined) ->
AppInfo#app_info_t{app_file_src=undefined};
app_file_src(AppInfo=#app_info_t{}, AppFileSrc) ->
AppInfo#app_info_t{app_file_src=ec_cnv:to_list(AppFileSrc)}.
+%% @doc finds the .app.src.script file for an app, if any.
-spec app_file_src_script(t()) -> file:filename_all() | undefined.
app_file_src_script(#app_info_t{app_file_src_script=undefined, dir=Dir, name=Name}) ->
AppFileSrcScript = filename:join([ec_cnv:to_list(Dir), "src", ec_cnv:to_list(Name)++".app.src.script"]),
@@ -250,12 +280,15 @@ app_file_src_script(#app_info_t{app_file_src_script=undefined, dir=Dir, name=Nam
app_file_src_script(#app_info_t{app_file_src_script=AppFileSrcScript}) ->
ec_cnv:to_list(AppFileSrcScript).
+%% @doc sets the .app.src.script file for an app. An app without such a file
+%% can explicitly be set with `undefined'.
-spec app_file_src_script(t(), file:filename_all()) -> t().
app_file_src_script(AppInfo=#app_info_t{}, undefined) ->
AppInfo#app_info_t{app_file_src_script=undefined};
app_file_src_script(AppInfo=#app_info_t{}, AppFileSrcScript) ->
AppInfo#app_info_t{app_file_src_script=ec_cnv:to_list(AppFileSrcScript)}.
+%% @doc finds the .app file for an app, if any.
-spec app_file(t()) -> file:filename_all() | undefined.
app_file(#app_info_t{app_file=undefined, out_dir=Dir, name=Name}) ->
AppFile = filename:join([ec_cnv:to_list(Dir), "ebin", ec_cnv:to_list(Name)++".app"]),
@@ -268,10 +301,13 @@ app_file(#app_info_t{app_file=undefined, out_dir=Dir, name=Name}) ->
app_file(#app_info_t{app_file=AppFile}) ->
AppFile.
+%% @doc sets the .app file for an app.
-spec app_file(t(), file:filename_all()) -> t().
app_file(AppInfo=#app_info_t{}, AppFile) ->
AppInfo#app_info_t{app_file=AppFile}.
+%% @doc returns the information stored in the app's app file,
+%% or if none, from the .app.src file.
-spec app_details(t()) -> list().
app_details(AppInfo=#app_info_t{app_details=[]}) ->
case app_file(AppInfo) of
@@ -290,59 +326,83 @@ app_details(AppInfo=#app_info_t{app_details=[]}) ->
app_details(#app_info_t{app_details=AppDetails}) ->
AppDetails.
+%% @doc stores the information that would be returned from the
+%% app file, when reading from `app_details/1'.
-spec app_details(t(), list()) -> t().
app_details(AppInfo=#app_info_t{}, AppDetails) ->
AppInfo#app_info_t{app_details=AppDetails}.
+%% @doc returns the app's parent in the dep tree.
+-spec parent(t()) -> root | binary().
parent(#app_info_t{parent=Parent}) ->
Parent.
+%% @doc sets the app's parent.
-spec parent(t(), binary() | root) -> t().
parent(AppInfo=#app_info_t{}, Parent) ->
AppInfo#app_info_t{parent=Parent}.
+%% @doc returns the original version of the app (unevaluated if
+%% asking for a semver)
-spec original_vsn(t()) -> string().
original_vsn(#app_info_t{original_vsn=Vsn}) ->
Vsn.
+%% @doc stores the original version of the app (unevaluated if
+%% asking for a semver)
-spec original_vsn(t(), string()) -> t().
original_vsn(AppInfo=#app_info_t{}, Vsn) ->
AppInfo#app_info_t{original_vsn=Vsn}.
+%% @doc returns the list of applications the app depends on.
-spec applications(t()) -> list().
applications(#app_info_t{applications=Applications}) ->
Applications.
+%% @doc sets the list of applications the app depends on.
+%% Should be obtained from the app file.
-spec applications(t(), list()) -> t().
applications(AppInfo=#app_info_t{}, Applications) ->
AppInfo#app_info_t{applications=Applications}.
+%% @doc returns the list of active profiles
-spec profiles(t()) -> list().
profiles(#app_info_t{profiles=Profiles}) ->
Profiles.
+%% @doc sets the list of active profiles
-spec profiles(t(), list()) -> t().
profiles(AppInfo=#app_info_t{}, Profiles) ->
AppInfo#app_info_t{profiles=Profiles}.
+%% @doc returns the list of dependencies
-spec deps(t()) -> list().
deps(#app_info_t{deps=Deps}) ->
Deps.
+%% @doc sets the list of dependencies.
-spec deps(t(), list()) -> t().
deps(AppInfo=#app_info_t{}, Deps) ->
AppInfo#app_info_t{deps=Deps}.
-dep_level(AppInfo=#app_info_t{}, Level) ->
- AppInfo#app_info_t{dep_level=Level}.
-
+%% @doc returns the level the app has in the lock files or in the
+%% dep tree.
+-spec dep_level(t()) -> non_neg_integer().
dep_level(#app_info_t{dep_level=Level}) ->
Level.
+%% @doc sets the level the app has in the lock files or in the
+%% dep tree.
+-spec dep_level(t(), non_neg_integer()) -> t().
+dep_level(AppInfo=#app_info_t{}, Level) ->
+ AppInfo#app_info_t{dep_level=Level}.
+
+%% @doc returns the directory that contains the app.
-spec dir(t()) -> file:name().
dir(#app_info_t{dir=Dir}) ->
Dir.
+%% @doc sets the directory that contains the app.
-spec dir(t(), file:name()) -> t().
dir(AppInfo=#app_info_t{out_dir=undefined}, Dir) ->
AppInfo#app_info_t{dir=ec_cnv:to_list(Dir),
@@ -350,54 +410,69 @@ dir(AppInfo=#app_info_t{out_dir=undefined}, Dir) ->
dir(AppInfo=#app_info_t{}, Dir) ->
AppInfo#app_info_t{dir=ec_cnv:to_list(Dir)}.
+%% @doc returns the directory where build artifacts for the app
+%% should go
-spec out_dir(t()) -> file:name().
out_dir(#app_info_t{out_dir=OutDir}) ->
OutDir.
+%% @doc sets the directory where build artifacts for the app
+%% should go
-spec out_dir(t(), file:name()) -> t().
out_dir(AppInfo=#app_info_t{}, OutDir) ->
AppInfo#app_info_t{out_dir=ec_cnv:to_list(OutDir)}.
+%% @doc gets the directory where ebin files for the app should go
-spec ebin_dir(t()) -> file:name().
ebin_dir(#app_info_t{out_dir=OutDir}) ->
ec_cnv:to_list(filename:join(OutDir, "ebin")).
+%% @doc gets the directory where private files for the app should go
-spec priv_dir(t()) -> file:name().
priv_dir(#app_info_t{out_dir=OutDir}) ->
ec_cnv:to_list(filename:join(OutDir, "priv")).
--spec resource_type(t(), pkg | src) -> t().
-resource_type(AppInfo=#app_info_t{}, Type) ->
- AppInfo#app_info_t{resource_type=Type}.
-
+%% @doc returns whether the app is source app or a package app.
-spec resource_type(t()) -> pkg | src.
resource_type(#app_info_t{resource_type=ResourceType}) ->
ResourceType.
--spec source(t(), string() | tuple() | checkout) -> t().
-source(AppInfo=#app_info_t{}, Source) ->
- AppInfo#app_info_t{source=Source}.
+%% @doc sets whether the app is source app or a package app.
+-spec resource_type(t(), pkg | src) -> t().
+resource_type(AppInfo=#app_info_t{}, Type) ->
+ AppInfo#app_info_t{resource_type=Type}.
+%% @doc finds the source specification for the app
-spec source(t()) -> string() | tuple().
source(#app_info_t{source=Source}) ->
Source.
--spec is_lock(t(), boolean()) -> t().
-is_lock(AppInfo=#app_info_t{}, IsLock) ->
- AppInfo#app_info_t{is_lock=IsLock}.
+%% @doc sets the source specification for the app
+-spec source(t(), string() | tuple() | checkout) -> t().
+source(AppInfo=#app_info_t{}, Source) ->
+ AppInfo#app_info_t{source=Source}.
+%% @doc returns the lock status for the app
-spec is_lock(t()) -> boolean().
is_lock(#app_info_t{is_lock=IsLock}) ->
IsLock.
--spec is_checkout(t(), boolean()) -> t().
-is_checkout(AppInfo=#app_info_t{}, IsCheckout) ->
- AppInfo#app_info_t{is_checkout=IsCheckout}.
+%% @doc sets the lock status for the app
+-spec is_lock(t(), boolean()) -> t().
+is_lock(AppInfo=#app_info_t{}, IsLock) ->
+ AppInfo#app_info_t{is_lock=IsLock}.
+%% @doc returns whether the app is a checkout app or not
-spec is_checkout(t()) -> boolean().
is_checkout(#app_info_t{is_checkout=IsCheckout}) ->
IsCheckout.
+%% @doc sets whether the app is a checkout app or not
+-spec is_checkout(t(), boolean()) -> t().
+is_checkout(AppInfo=#app_info_t{}, IsCheckout) ->
+ AppInfo#app_info_t{is_checkout=IsCheckout}.
+
+%% @doc returns whether the app is valid (built) or not
-spec valid(t()) -> boolean().
valid(AppInfo=#app_info_t{valid=undefined}) ->
case rebar_app_utils:validate_application_info(AppInfo) =:= true
@@ -410,14 +485,22 @@ valid(AppInfo=#app_info_t{valid=undefined}) ->
valid(#app_info_t{valid=Valid}) ->
Valid.
+%% @doc sets whether the app is valid (built) or not. If left unset,
+%% rebar3 will do the detection of the status itself.
-spec valid(t(), boolean()) -> t().
valid(AppInfo=#app_info_t{}, Valid) ->
AppInfo#app_info_t{valid=Valid}.
+%% @doc checks whether the app can be built with the current
+%% Erlang/OTP version. If the check fails, the function raises
+%% an exception and displays an error.
+-spec verify_otp_vsn(t()) -> ok | no_return().
verify_otp_vsn(AppInfo) ->
rebar_utils:check_min_otp_version(rebar_app_info:get(AppInfo, minimum_otp_vsn, undefined)),
rebar_utils:check_blacklisted_otp_versions(rebar_app_info:get(AppInfo, blacklisted_otp_vsns, [])).
+%% @doc checks whether all the build artifacts for an app to be considered
+%% valid are present.
-spec has_all_artifacts(#app_info_t{}) -> true | {false, file:filename()}.
has_all_artifacts(AppInfo) ->
Artifacts = rebar_app_info:get(AppInfo, artifacts, []),
@@ -427,6 +510,10 @@ has_all_artifacts(AppInfo) ->
,{out_dir, OutDir}],
all(OutDir, Context, Artifacts).
+%% @private checks that all files/artifacts in the directory are found.
+%% Template evaluation must happen and a bbmustache context needs to
+%% be provided.
+-spec all(file:filename(), term(), [string()]) -> true | {false, string()}.
all(_, _, []) ->
true;
all(Dir, Context, [File|Artifacts]) ->
@@ -441,15 +528,23 @@ all(Dir, Context, [File|Artifacts]) ->
%%%%%
+%% @doc given a set of override rules, modify the app info accordingly
+-spec apply_overrides(list(), t()) -> t().
apply_overrides(Overrides, AppInfo) ->
Name = binary_to_atom(rebar_app_info:name(AppInfo), utf8),
Opts = rebar_opts:apply_overrides(opts(AppInfo), Name, Overrides),
AppInfo#app_info_t{default=Opts, opts=Opts}.
+%% @doc adds a new profile with its own config to the app data
+-spec add_to_profile(t(), atom(), [{_,_}]) -> t().
add_to_profile(AppInfo, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
Opts = rebar_opts:add_to_profile(opts(AppInfo), Profile, KVs),
AppInfo#app_info_t{opts=Opts}.
+%% @doc applies and merges the profile configuration in the specified order
+%% of profiles (or for a single profile) and returns an app info record
+%% with the resulting configuration
+-spec apply_profiles(t(), atom() | [atom(),...]) -> t().
apply_profiles(AppInfo, Profile) when not is_list(Profile) ->
apply_profiles(AppInfo, [Profile]);
apply_profiles(AppInfo, [default]) ->
@@ -481,9 +576,13 @@ apply_profiles(AppInfo=#app_info_t{default = Defaults, profiles=CurrentProfiles}
end, Defaults, AppliedProfiles),
AppInfo#app_info_t{profiles = AppliedProfiles, opts=NewOpts}.
+%% @private drops duplicated profile definitions
+-spec deduplicate(list()) -> list().
deduplicate(Profiles) ->
do_deduplicate(lists:reverse(Profiles), []).
+%% @private drops duplicated profile definitions
+-spec do_deduplicate(list(), list()) -> list().
do_deduplicate([], Acc) ->
Acc;
do_deduplicate([Head | Rest], Acc) ->
diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl
index 847b0a5..7154999 100644
--- a/src/rebar_app_utils.erl
+++ b/src/rebar_app_utils.erl
@@ -44,10 +44,14 @@
%% Public API
%% ===================================================================
+%% @doc finds the proper app info record for a given app name in a list of
+%% such records.
-spec find(binary(), [rebar_app_info:t()]) -> {ok, rebar_app_info:t()} | error.
find(Name, Apps) ->
ec_lists:find(fun(App) -> rebar_app_info:name(App) =:= Name end, Apps).
+%% @doc finds the proper app info record for a given app name at a given version
+%% in a list of such records.
-spec find(binary(), binary(), [rebar_app_info:t()]) -> {ok, rebar_app_info:t()} | error.
find(Name, Vsn, Apps) ->
ec_lists:find(fun(App) ->
@@ -55,11 +59,18 @@ find(Name, Vsn, Apps) ->
andalso rebar_app_info:original_vsn(App) =:= Vsn
end, Apps).
+%% @doc checks if a given file is .app.src file
is_app_src(Filename) ->
%% If removing the extension .app.src yields a shorter name,
%% this is an .app.src file.
Filename =/= filename:rootname(Filename, ".app.src").
+%% @doc translates the name of the .app.src[.script] file to where
+%% its .app counterpart should be stored.
+-spec app_src_to_app(OutDir, SrcFilename) -> OutFilename when
+ OutDir :: file:filename(),
+ SrcFilename :: file:filename(),
+ OutFilename :: file:filename().
app_src_to_app(OutDir, Filename) ->
AppFile =
case lists:suffix(".app.src", Filename) of
@@ -72,10 +83,16 @@ app_src_to_app(OutDir, Filename) ->
filelib:ensure_dir(AppFile),
AppFile.
+%% @doc checks whether the .app file has all the required data to be valid,
+%% and cross-references it with compiled modules on disk
-spec validate_application_info(rebar_app_info:t()) -> boolean().
validate_application_info(AppInfo) ->
validate_application_info(AppInfo, rebar_app_info:app_details(AppInfo)).
+%% @doc checks whether the .app file has all the required data to be valid
+%% and cross-references it with compiled modules on disk.
+%% The app info is passed explicitly as a second argument.
+-spec validate_application_info(rebar_app_info:t(), list()) -> boolean().
validate_application_info(AppInfo, AppDetail) ->
EbinDir = rebar_app_info:ebin_dir(AppInfo),
case rebar_app_info:app_file(AppInfo) of
@@ -90,13 +107,37 @@ validate_application_info(AppInfo, AppDetail) ->
end
end.
--spec parse_deps(binary(), list(), rebar_state:t(), list(), integer()) -> [rebar_app_info:t()].
+%% @doc parses all dependencies from the root of the project
+-spec parse_deps(Dir, Deps, State, Locks, Level) -> [rebar_app_info:t()] when
+ Dir :: file:filename(),
+ Deps :: [tuple() | atom() | binary()], % TODO: meta to source() | lock()
+ State :: rebar_state:t(),
+ Locks :: [tuple()], % TODO: meta to [lock()]
+ Level :: non_neg_integer().
parse_deps(DepsDir, Deps, State, Locks, Level) ->
parse_deps(root, DepsDir, Deps, State, Locks, Level).
+%% @doc runs `parse_dep/6' for a set of dependencies.
+-spec parse_deps(Parent, Dir, Deps, State, Locks, Level) -> [rebar_app_info:t()] when
+ Parent :: root | binary(),
+ Dir :: file:filename(),
+ Deps :: [tuple() | atom() | binary()], % TODO: meta to source() | lock()
+ State :: rebar_state:t(),
+ Locks :: [tuple()], % TODO: meta to [lock()]
+ Level :: non_neg_integer().
parse_deps(Parent, DepsDir, Deps, State, Locks, Level) ->
[parse_dep(Dep, Parent, DepsDir, State, Locks, Level) || Dep <- Deps].
+%% @doc for a given dep, return its app info record. The function
+%% also has to choose whether to define the dep from its immediate spec
+%% (if it is a newer thing) or from the locks specified in the lockfile.
+-spec parse_dep(Dep, Parent, Dir, State, Locks, Level) -> rebar_app_info:t() when
+ Dep :: tuple() | atom() | binary(), % TODO: meta to source() | lock()
+ Parent :: root | binary(),
+ Dir :: file:filename(),
+ State :: rebar_state:t(),
+ Locks :: [tuple()], % TODO: meta to [lock()]
+ Level :: non_neg_integer().
parse_dep(Dep, Parent, DepsDir, State, Locks, Level) ->
Name = case Dep of
Dep when is_tuple(Dep) ->
@@ -117,6 +158,14 @@ parse_dep(Dep, Parent, DepsDir, State, Locks, Level) ->
end
end.
+%% @doc converts a dependency definition and a location for it on disk
+%% into an app info tuple representing it.
+-spec parse_dep(Parent, Dep, Dir, IsLock, State) -> rebar_app_info:t() when
+ Parent :: root | binary(),
+ Dep :: tuple() | atom() | binary(), % TODO: meta to source() | lock()
+ Dir :: file:filename(),
+ IsLock :: boolean(),
+ State :: rebar_state:t().
parse_dep(Parent, {Name, Vsn, {pkg, PkgName}}, DepsDir, IsLock, State) ->
{PkgName1, PkgVsn} = {ec_cnv:to_binary(PkgName), ec_cnv:to_binary(Vsn)},
dep_to_app(Parent, DepsDir, Name, PkgVsn, {pkg, PkgName1, PkgVsn, undefined}, IsLock, State);
@@ -152,6 +201,16 @@ parse_dep(Parent, {Name, Source, Level}, DepsDir, IsLock, State) when is_tuple(S
parse_dep(_, Dep, _, _, _) ->
throw(?PRV_ERROR({parse_dep, Dep})).
+%% @doc convert a dependency that has just been fetched into
+%% an app info record related to it
+-spec dep_to_app(Parent, Dir, Name, Vsn, Source, IsLock, State) -> rebar_app_info:t() when
+ Parent :: root | binary(),
+ Dir :: file:filename(),
+ Name :: binary(),
+ Vsn :: iodata() | undefined,
+ Source :: tuple(),
+ IsLock :: boolean(),
+ State :: rebar_state:t().
dep_to_app(Parent, DepsDir, Name, Vsn, Source, IsLock, State) ->
CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),
AppInfo = case rebar_app_info:discover(CheckoutsDir) of
@@ -166,7 +225,7 @@ dep_to_app(Parent, DepsDir, Name, Vsn, Source, IsLock, State) ->
not_found ->
rebar_app_info:new(Parent, Name, Vsn, Dir, [])
end,
- update_source(AppInfo0, Source, State)
+ update_source(AppInfo0, Source, State)
end,
C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
AppInfo1 = rebar_app_info:update_opts(AppInfo, rebar_app_info:opts(AppInfo), C),
@@ -177,6 +236,11 @@ dep_to_app(Parent, DepsDir, Name, Vsn, Source, IsLock, State) ->
AppInfo5 = rebar_app_info:profiles(AppInfo4, [default]),
rebar_app_info:is_lock(AppInfo5, IsLock).
+%% @doc sets the source for a given dependency or app along with metadata
+%% around version if required.
+-spec update_source(rebar_app_info:t(), Source, rebar_state:t()) ->
+ rebar_app_info:t() when
+ Source :: tuple() | atom() | binary(). % TODO: meta to source()
update_source(AppInfo, {pkg, PkgName, PkgVsn, Hash}, State) ->
{PkgName1, PkgVsn1} = case PkgVsn of
undefined ->
@@ -203,6 +267,9 @@ update_source(AppInfo, {pkg, PkgName, PkgVsn, Hash}, State) ->
update_source(AppInfo, Source, _State) ->
rebar_app_info:source(AppInfo, Source).
+%% @doc grab the checksum for a given package
+-spec fetch_checksum(atom(), string(), iodata() | undefined, rebar_state:t()) ->
+ iodata() | no_return().
fetch_checksum(PkgName, PkgVsn, Hash, State) ->
try
rebar_packages:registry_checksum({pkg, PkgName, PkgVsn, Hash}, State)
@@ -213,6 +280,8 @@ fetch_checksum(PkgName, PkgVsn, Hash, State) ->
rebar_packages:registry_checksum({pkg, PkgName, PkgVsn, Hash}, State)
end.
+%% @doc convert a given exception's payload into an io description.
+-spec format_error(any()) -> iolist().
format_error({missing_package, Package}) ->
io_lib:format("Package not found in registry: ~s", [Package]);
format_error({parse_dep, Dep}) ->
@@ -224,6 +293,10 @@ format_error(Error) ->
%% Internal functions
%% ===================================================================
+%% @private find the correct version of a package based on the version
+%% and name passed in.
+-spec get_package(binary(), binary() | string(), rebar_state:t()) ->
+ term() | no_return().
get_package(Dep, Vsn, State) ->
case rebar_packages:find_highest_matching(Dep, Vsn, ?PACKAGE_TABLE, State) of
{ok, HighestDepVsn} ->
@@ -232,6 +305,8 @@ get_package(Dep, Vsn, State) ->
throw(?PRV_ERROR({missing_package, ec_cnv:to_binary(Dep)}))
end.
+%% @private checks that all the beam files have been properly
+%% created.
-spec has_all_beams(file:filename_all(), [module()]) ->
true | ?PRV_ERROR({missing_module, module()}).
has_all_beams(EbinDir, [Module | ModuleList]) ->
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index 5d54057..02d567c 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -36,21 +36,65 @@
format_error_source/2]).
-define(DEFAULT_COMPILER_SOURCE_FORMAT, relative).
+-type desc() :: term().
+-type loc() :: {line(), col()} | line().
+-type line() :: integer().
+-type col() :: integer().
+-type err_or_warn() :: {module(), desc()} | {loc(), module(), desc()}.
+
+-type compile_fn_ret() :: ok | {ok, [string()]} | skipped | term().
+-type compile_fn() :: fun((file:filename(), [{_,_}] | rebar_dict()) -> compile_fn_ret()).
+-type compile_fn3() :: fun((file:filename(), file:filename(), [{_,_}] | rebar_dict())
+ -> compile_fn_ret()).
+-type error_tuple() :: {error, [string()], [string()]}.
+-export_type([compile_fn/0, compile_fn_ret/0, error_tuple/0]).
%% ===================================================================
%% Public API
%% ===================================================================
+%% @doc Runs a compile job, applying `compile_fn()' to all files,
+%% starting with `First' files, and then `RestFiles'.
+-spec run(rebar_dict() | [{_,_}] , [First], [Next], compile_fn()) ->
+ compile_fn_ret() when
+ First :: file:filename(),
+ Next :: file:filename().
run(Config, FirstFiles, RestFiles, CompileFn) ->
%% Compile the first files in sequence
compile_each(FirstFiles++RestFiles, Config, CompileFn).
+%% @doc Runs a compile job, applying `compile_fn3()' to all files,
+%% starting with `First' files, and then the other content of `SourceDir'.
+%% Files looked for are those ending in `SourceExt'. Results of the
+%% compilation are put in `TargetDir' with the base file names
+%% postfixed with `SourceExt'.
+-spec run(rebar_dict() | [{_,_}] , [First], SourceDir, SourceExt,
+ TargetDir, TargetExt, compile_fn3()) -> compile_fn_ret() when
+ First :: file:filename(),
+ SourceDir :: file:filename(),
+ TargetDir :: file:filename(),
+ SourceExt :: string(),
+ TargetExt :: string().
run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
Compile3Fn) ->
run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
Compile3Fn, [check_last_mod]).
+%% @doc Runs a compile job, applying `compile_fn3()' to all files,
+%% starting with `First' files, and then the other content of `SourceDir'.
+%% Files looked for are those ending in `SourceExt'. Results of the
+%% compilation are put in `TargetDir' with the base file names
+%% postfixed with `SourceExt'.
+%% Additional compile options can be passed in the last argument as
+%% a proplist.
+-spec run(rebar_dict() | [{_,_}] , [First], SourceDir, SourceExt,
+ TargetDir, TargetExt, compile_fn3(), [term()]) -> compile_fn_ret() when
+ First :: file:filename(),
+ SourceDir :: file:filename(),
+ TargetDir :: file:filename(),
+ SourceExt :: string(),
+ TargetExt :: string().
run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
Compile3Fn, Opts) ->
%% Convert simple extension to proper regex
@@ -73,13 +117,26 @@ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
simple_compile_wrapper(S, Target, Compile3Fn, C, CheckLastMod)
end).
+%% @doc Format good compiler results with warnings to work with
+%% module internals. Assumes that warnings are not treated as errors.
+-spec ok_tuple(file:filename(), [string()]) -> {ok, [string()]}.
ok_tuple(Source, Ws) ->
{ok, format_warnings(Source, Ws)}.
+%% @doc format error and warning strings for a given source file
+%% according to user preferences.
+-spec error_tuple(file:filename(), [Err], [Warn], rebar_dict() | [{_,_}]) ->
+ error_tuple() when
+ Err :: string(),
+ Warn :: string().
error_tuple(Source, Es, Ws, Opts) ->
{error, format_errors(Source, Es),
format_warnings(Source, Ws, Opts)}.
+%% @doc from a given path, and based on the user-provided options,
+%% format the file path according to the preferences.
+-spec format_error_source(file:filename(), rebar_dict() | [{_,_}]) ->
+ file:filename().
format_error_source(Path, Opts) ->
Type = case rebar_opts:get(Opts, compiler_source_format,
?DEFAULT_COMPILER_SOURCE_FORMAT) of
@@ -98,6 +155,8 @@ format_error_source(Path, Opts) ->
rebar_dir:make_relative_path(resolve_linked_source(Path), Cwd)
end.
+%% @private takes a filename and canonicalizes its path if it is a link.
+-spec resolve_linked_source(file:filename()) -> file:filename().
resolve_linked_source(Src) ->
{Dir, Base} = rebar_file_utils:split_dirname(Src),
filename:join(rebar_file_utils:resolve_link(Dir), Base).
@@ -106,6 +165,11 @@ resolve_linked_source(Src) ->
%% Internal functions
%% ===================================================================
+%% @private if a check for last modifications is required, do the verification
+%% and possibly skip the compile job.
+-spec simple_compile_wrapper(Source, Target, compile_fn3(), [{_,_}] | rebar_dict(), boolean()) -> compile_fn_ret() when
+ Source :: file:filename(),
+ Target :: file:filename().
simple_compile_wrapper(Source, Target, Compile3Fn, Config, false) ->
Compile3Fn(Source, Target, Config);
simple_compile_wrapper(Source, Target, Compile3Fn, Config, true) ->
@@ -116,18 +180,42 @@ simple_compile_wrapper(Source, Target, Compile3Fn, Config, true) ->
skipped
end.
+%% @private take a basic source set of file fragments and a target location,
+%% create a file path and name for a compile artifact.
+-spec target_file(SourceFile, SourceDir, SourceExt, TargetDir, TargetExt) -> File when
+ SourceFile :: file:filename(),
+ SourceDir :: file:filename(),
+ TargetDir :: file:filename(),
+ SourceExt :: string(),
+ TargetExt :: string(),
+ File :: file:filename().
target_file(SourceFile, SourceDir, SourceExt, TargetDir, TargetExt) ->
BaseFile = remove_common_path(SourceFile, SourceDir),
filename:join([TargetDir, filename:basename(BaseFile, SourceExt) ++ TargetExt]).
+%% @private removes the common prefix between two file paths.
+%% The remainder of the first file path passed will have its ending returned
+%% when either path starts diverging.
+-spec remove_common_path(file:filename(), file:filename()) -> file:filename().
remove_common_path(Fname, Path) ->
remove_common_path1(filename:split(Fname), filename:split(Path)).
+%% @private given two lists of file fragments, discard the identical
+%% prefixed sections, and return the final bit of the first operand
+%% as a filename.
+-spec remove_common_path1([string()], [string()]) -> file:filename().
remove_common_path1([Part | RestFilename], [Part | RestPath]) ->
remove_common_path1(RestFilename, RestPath);
remove_common_path1(FilenameParts, _) ->
filename:join(FilenameParts).
+%% @private runs the compile function `CompileFn' on every file
+%% passed internally, along with the related project configuration.
+%% If any errors are encountered, they're reported to stdout.
+-spec compile_each([file:filename()], Config, CompileFn) -> Ret | no_return() when
+ Config :: [{_,_}] | rebar_dict(),
+ CompileFn :: compile_fn(),
+ Ret :: compile_fn_ret().
compile_each([], _Config, _CompileFn) ->
ok;
compile_each([Source | Rest], Config, CompileFn) ->
@@ -148,12 +236,19 @@ compile_each([Source | Rest], Config, CompileFn) ->
end,
compile_each(Rest, Config, CompileFn).
+%% @private Formats and returns errors ready to be output.
+-spec format_errors(string(), [err_or_warn()]) -> [string()].
format_errors(Source, Errors) ->
format_errors(Source, "", Errors).
+%% @private Formats and returns warning strings ready to be output.
+-spec format_warnings(string(), [err_or_warn()]) -> [string()].
format_warnings(Source, Warnings) ->
format_warnings(Source, Warnings, []).
+%% @private Formats and returns warnings; chooses the distinct format they
+%% may have based on whether `warnings_as_errors' option is on.
+-spec format_warnings(string(), [err_or_warn()], rebar_dict() | [{_,_}]) -> [string()].
format_warnings(Source, Warnings, Opts) ->
%% `Opts' can be passed in both as a list or a dictionary depending
%% on whether the first call to rebar_erlc_compiler was done with
@@ -167,6 +262,11 @@ format_warnings(Source, Warnings, Opts) ->
end,
format_errors(Source, Prefix, Warnings).
+%% @private output compiler errors if they're judged to be reportable.
+-spec maybe_report(Reportable | term()) -> ok when
+ Reportable :: {{error, error_tuple()}, Source} | error_tuple() | ErrProps,
+ ErrProps :: [{error, string()} | Source, ...],
+ Source :: {source, string()}.
maybe_report({{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}}) ->
maybe_report(ErrorsAndWarnings);
maybe_report([{error, E}, {source, S}]) ->
@@ -177,15 +277,23 @@ maybe_report({error, Es, Ws}) ->
maybe_report(_) ->
ok.
+%% @private Outputs a bunch of strings, including a newline
+-spec report([string()]) -> ok.
report(Messages) ->
lists:foreach(fun(Msg) -> io:format("~s~n", [Msg]) end, Messages).
+%% private format compiler errors into proper outputtable strings
+-spec format_errors(_, Extra, [err_or_warn()]) -> [string()] when
+ Extra :: string().
format_errors(_MainSource, Extra, Errors) ->
[begin
[format_error(Source, Extra, Desc) || Desc <- Descs]
end
|| {Source, Descs} <- Errors].
+%% @private format compiler errors into proper outputtable strings
+-spec format_error(file:filename(), Extra, err_or_warn()) -> string() when
+ Extra :: string().
format_error(Source, Extra, {{Line, Column}, Mod, Desc}) ->
ErrorDesc = Mod:format_error(Desc),
?FMT("~s:~w:~w: ~s~s~n", [Source, Line, Column, Extra, ErrorDesc]);
diff --git a/src/rebar_config.erl b/src/rebar_config.erl
index 5a35b87..84f40d4 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -46,17 +46,25 @@
%% Public API
%% ===================================================================
+%% @doc reads the default config file.
-spec consult() -> [any()].
consult() ->
consult_file(config_file()).
+%% @doc reads the default config file in a given directory.
-spec consult(file:name()) -> [any()].
consult(Dir) ->
consult_file(filename:join(Dir, config_file())).
+%% @doc reads a given app file, including the `.script' variations,
+%% if any can be found.
+-spec consult_app_file(file:filename()) -> [any()].
consult_app_file(File) ->
consult_file_(File).
+%% @doc reads the lock file for the project, and re-formats its
+%% content to match the internals for rebar3.
+-spec consult_lock_file(file:filename()) -> [any()]. % TODO: refine lock()
consult_lock_file(File) ->
Terms = consult_file_(File),
case Terms of
@@ -80,6 +88,11 @@ consult_lock_file(File) ->
read_attrs(Vsn, Locks, Attrs)
end.
+%% @private outputs a warning for a newer lockfile format than supported
+%% at most once.
+%% The warning can also be cancelled by configuring the `warn_config_vsn'
+%% OTP env variable.
+-spec warn_vsn_once() -> ok.
warn_vsn_once() ->
Warn = application:get_env(rebar, warn_config_vsn) =/= {ok, false},
application:set_env(rebar, warn_config_vsn, false),
@@ -93,6 +106,11 @@ warn_vsn_once() ->
end.
+%% @doc Converts the internal format for locks into the multi-version
+%% compatible one used within rebar3 lock files.
+%% @end
+%% TODO: refine type for lock()
+-spec write_lock_file(file:filename(), [any()]) -> ok | {error, term()}.
write_lock_file(LockFile, Locks) ->
{NewLocks, Attrs} = write_attrs(Locks),
%% Write locks in the beta format, at least until it's been long
@@ -107,27 +125,41 @@ write_lock_file(LockFile, Locks) ->
format_attrs(Attrs)]))
end.
-%% Attributes have a special formatting to ensure there's only one per
+%% @private Attributes have a special formatting to ensure there's only one per
%% line in terms of pkg_hash, so we disturb source diffing as little
%% as possible.
+-spec format_attrs([term()]) -> iodata().
format_attrs([]) -> [];
format_attrs([{pkg_hash, Vals}|T]) ->
[io_lib:format("{pkg_hash,[~n",[]), format_hashes(Vals), "]}",
maybe_comma(T) | format_attrs(T)].
+%% @private format hashing in order to disturb source diffing as little
+%% as possible
+-spec format_hashes([term()]) -> iodata().
format_hashes([]) -> [];
format_hashes([{Pkg,Hash}|T]) ->
[" {", io_lib:format("~p",[Pkg]), ", ", io_lib:format("~p", [Hash]), "}",
maybe_comma(T) | format_hashes(T)].
+%% @private add a comma if we're not done with the full list of terms
+%% to convert.
+-spec maybe_comma([term()]) -> iodata().
maybe_comma([]) -> "";
maybe_comma([_|_]) -> io_lib:format(",~n", []).
+%% @private extract attributes from the lock file and integrate them
+%% into the full-blow internal lock format
+%% @end
+%% TODO: refine typings for lock()
+-spec read_attrs(_, [any()], [any()]) -> [any()].
read_attrs(_Vsn, Locks, Attrs) ->
%% Beta copy does not know how to expand attributes, but
%% is ready to support it.
expand_locks(Locks, extract_pkg_hashes(Attrs)).
+%% @private extract the package hashes from lockfile attributes, if any.
+-spec extract_pkg_hashes(list()) -> [binary()].
extract_pkg_hashes(Attrs) ->
Props = case Attrs of
[First|_] -> First;
@@ -135,6 +167,11 @@ extract_pkg_hashes(Attrs) ->
end,
proplists:get_value(pkg_hash, Props, []).
+%% @private extract attributes from the lock file and integrate them
+%% into the full-blow internal lock format
+%% @end
+%% TODO: refine typings for lock()
+-spec expand_locks(list(), list()) -> list().
expand_locks([], _Hashes) ->
[];
expand_locks([{Name, {pkg,PkgName,Vsn}, Lvl} | Locks], Hashes) ->
@@ -143,6 +180,9 @@ expand_locks([{Name, {pkg,PkgName,Vsn}, Lvl} | Locks], Hashes) ->
expand_locks([Lock|Locks], Hashes) ->
[Lock | expand_locks(Locks, Hashes)].
+%% @private split up extra attributes for locks out of the internal lock
+%% structure for backwards compatibility reasons
+-spec write_attrs(list()) -> {list(), list()}.
write_attrs(Locks) ->
%% No attribute known that needs to be taken out of the structure,
%% just return terms as is.
@@ -152,6 +192,9 @@ write_attrs(Locks) ->
_ -> {NewLocks, [{pkg_hash, lists:sort(Hashes)}]}
end.
+%% @private split up extra attributes for locks out of the internal lock
+%% structure for backwards compatibility reasons
+-spec split_locks(list(), list(), [{_,binary()}]) -> {list(), list()}.
split_locks([], Locks, Hashes) ->
{lists:reverse(Locks), Hashes};
split_locks([{Name, {pkg,PkgName,Vsn,undefined}, Lvl} | Locks], LAcc, HAcc) ->
@@ -161,11 +204,17 @@ split_locks([{Name, {pkg,PkgName,Vsn,Hash}, Lvl} | Locks], LAcc, HAcc) ->
split_locks([Lock|Locks], LAcc, HAcc) ->
split_locks(Locks, [Lock|LAcc], HAcc).
+%% @doc reads a given config file, including the `.script' variations,
+%% if any can be found, and asserts that the config format is in
+%% a key-value format.
+-spec consult_file(file:filename()) -> [{_,_}].
consult_file(File) ->
Terms = consult_file_(File),
true = verify_config_format(Terms),
Terms.
+%% @private reads a given file; if the file has a `.script'-postfixed
+%% counterpart, it is evaluated along with the original file.
-spec consult_file_(file:name()) -> [any()].
consult_file_(File) when is_binary(File) ->
consult_file_(binary_to_list(File));
@@ -185,6 +234,9 @@ consult_file_(File) ->
end
end.
+%% @private checks that a list is in a key-value format.
+%% Raises an exception in any other case.
+-spec verify_config_format([{_,_}]) -> true.
verify_config_format([]) ->
true;
verify_config_format([{_Key, _Value} | T]) ->
@@ -192,11 +244,14 @@ verify_config_format([{_Key, _Value} | T]) ->
verify_config_format([Term | _]) ->
throw(?PRV_ERROR({bad_config_format, Term})).
-%% no lockfile
+%% @doc takes an existing configuration and the content of a lockfile
+%% and merges the locks into the config.
+-spec merge_locks([{_,_}], list()) -> [{_,_}].
merge_locks(Config, []) ->
+%% no lockfile
Config;
-%% lockfile with entries
merge_locks(Config, Locks) ->
+ %% lockfile with entries
ConfigDeps = proplists:get_value(deps, Config, []),
%% We want the top level deps only from the lock file.
%% This ensures deterministic overrides for configs.
@@ -206,6 +261,8 @@ merge_locks(Config, Locks) ->
NewDeps = find_newly_added(ConfigDeps, Locks),
[{{locks, default}, Locks}, {{deps, default}, NewDeps++Deps} | Config].
+%% @doc convert a given exception's payload into an io description.
+-spec format_error(any()) -> iolist().
format_error({bad_config_format, Term}) ->
io_lib:format("Unable to parse config. Term is not in {Key, Value} format:~n~p", [Term]);
format_error({bad_dep_name, Dep}) ->
@@ -215,6 +272,8 @@ format_error({bad_dep_name, Dep}) ->
%% Internal functions
%% ===================================================================
+%% @private consults a consult file, then executes its related script file
+%% with the data returned from the consult.
-spec consult_and_eval(File::file:name_all(), Script::file:name_all()) ->
{ok, Terms::[term()]} |
{error, Reason::term()}.
@@ -234,18 +293,26 @@ consult_and_eval(File, Script) ->
Error
end.
+%% @private drops the .script extension from a filename.
+-spec remove_script_ext(file:filename()) -> file:filename().
remove_script_ext(F) ->
filename:rootname(F, ".script").
+%% @private sets up bindings for evaluations from a KV list.
+-spec bs([{_,_}]) -> erl_eval:binding_struct().
bs(Vars) ->
lists:foldl(fun({K,V}, Bs) ->
erl_eval:add_binding(K, V, Bs)
end, erl_eval:new_bindings(), Vars).
-%% Find deps that have been added to the config after the lock was created
+%% @private Find deps that have been added to the config after the lock was created
+-spec find_newly_added(list(), list()) -> list().
find_newly_added(ConfigDeps, LockedDeps) ->
[D || {true, D} <- [check_newly_added(Dep, LockedDeps) || Dep <- ConfigDeps]].
+%% @private checks if a given dependency is not within the lock file.
+%% TODO: refine types for dependencies
+-spec check_newly_added(term(), list()) -> false | {true, term()}.
check_newly_added({_, _}=Dep, LockedDeps) ->
check_newly_added_(Dep, LockedDeps);
check_newly_added({_, _, {pkg, _}}=Dep, LockedDeps) ->
@@ -255,6 +322,10 @@ check_newly_added({Name, _, Source}, LockedDeps) ->
check_newly_added(Dep, LockedDeps) ->
check_newly_added_(Dep, LockedDeps).
+%% @private checks if a given dependency is not within the lock file.
+%% TODO: refine types for dependencies
+%% @end
+-spec check_newly_added_(term(), list()) -> false | {true, term()}.
%% get [raw] deps out of the way
check_newly_added_({Name, Source, Opts}, LockedDeps) when is_tuple(Source),
is_list(Opts) ->
@@ -306,6 +377,9 @@ check_newly_added_(Dep, LockedDeps) when is_atom(Dep) ->
check_newly_added_(Dep, _) ->
throw(?PRV_ERROR({bad_dep_name, Dep})).
+%% @private returns the name/path of the default config file, or its
+%% override from the OS ENV var `REBAR_CONFIG'.
+-spec config_file() -> file:filename().
config_file() ->
case os:getenv("REBAR_CONFIG") of
false ->
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index da8c3e6..14c4906 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -24,6 +24,8 @@
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% -------------------------------------------------------------------
+%% @doc Module providing core functionality about command dispatch, namespacing,
+%% and chaining for rebar3.
-module(rebar_core).
-export([init_command/2, process_namespace/2, process_command/2, do/2, format_error/1]).
@@ -31,6 +33,12 @@
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
+%% @doc initial command set up; based on the first fragment of the
+%% command, dispatch to special environments. The keywords for
+%% `do' and `as' are implicitly reserved here, barring them from
+%% being used as other commands or namespaces.
+-spec init_command(rebar_state:t(), atom()) ->
+ {ok, rebar_state:t()} | {error, term()}.
init_command(State, do) ->
process_command(rebar_state:namespace(State, default), do);
init_command(State, as) ->
@@ -43,6 +51,14 @@ init_command(State, Command) ->
{error, Reason}
end.
+%% @doc parse the commands starting at the namespace level;
+%% a namespace is found if the first keyword to match is not
+%% belonging to an existing provider, and iff the keyword also
+%% matches a registered namespace.
+%% The command to run is returned last; for namespaces, some
+%% magic is done implicitly calling `do' as an indirect dispatcher.
+-spec process_namespace(rebar_state:t(), atom()) ->
+ {error, term()} | {ok, rebar_state:t(), atom()}.
process_namespace(_State, as) ->
{error, "Namespace 'as' is forbidden"};
process_namespace(State, Command) ->
@@ -61,7 +77,15 @@ process_namespace(State, Command) ->
{ok, rebar_state:namespace(State, default), Command}
end.
--spec process_command(rebar_state:t(), atom()) -> {ok, rebar_state:t()} | {error, string()} | {error, {module(), any()}}.
+%% @doc Dispatches a given command based on the current state.
+%% This requires mapping a command name to a specific provider.
+%% `as' and `do' are still treated as special providers here.
+%% Basic profile application may also be run.
+%%
+%% The function also takes care of expanding a provider to its
+%% dependencies in the proper order.
+-spec process_command(rebar_state:t(), atom()) ->
+ {ok, rebar_state:t()} | {error, string()} | {error, {module(), any()}}.
process_command(State, Command) ->
%% ? rebar_prv_install_deps:setup_env(State),
Providers = rebar_state:providers(State),
@@ -104,7 +128,11 @@ process_command(State, Command) ->
end
end.
--spec do([{atom(), atom()}], rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()} | {error, {module(), any()}}.
+%% @doc execute the selected providers. If a chain of providers
+%% has been returned, run them one after the other, while piping
+%% the state from the first into the next one.
+-spec do([{atom(), atom()}], rebar_state:t()) ->
+ {ok, rebar_state:t()} | {error, string()} | {error, {module(), any()}}.
do([], State) ->
{ok, State};
do([ProviderName | Rest], State) ->
@@ -142,6 +170,8 @@ do([ProviderName | Rest], State) ->
{error, ProviderName}
end.
+%% @doc convert a given exception's payload into an io description.
+-spec format_error(any()) -> iolist().
format_error({bad_provider_namespace, {Namespace, Name}}) ->
io_lib:format("Undefined command ~s in namespace ~s", [Name, Namespace]);
format_error({bad_provider_namespace, Name}) ->
diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl
index 79a1c7f..32cb264 100644
--- a/src/rebar_dir.erl
+++ b/src/rebar_dir.erl
@@ -115,10 +115,16 @@ template_globals(State) ->
template_dir(State) ->
filename:join([global_config_dir(State), "templates"]).
+%% @doc checks if the current working directory is the base directory
+%% for the project.
+-spec processing_base_dir(rebar_state:t()) -> boolean().
processing_base_dir(State) ->
Cwd = get_cwd(),
processing_base_dir(State, Cwd).
+%% @doc checks if the passed in directory is the base directory for
+%% the project.
+-spec processing_base_dir(rebar_state:t(), file:filename()) -> boolean().
processing_base_dir(State, Dir) ->
AbsDir = filename:absname(Dir),
AbsDir =:= rebar_state:get(State, base_dir).
@@ -150,11 +156,25 @@ make_normalized_path([H|T], NormalizedPath) ->
_ -> make_normalized_path(T, [H|NormalizedPath])
end.
+%% @doc take a source and a target path, and relativize the target path
+%% onto the source.
+%%
+%% Example:
+%% ```
+%% 1> rebar_dir:make_relative_path("a/b/c/d/file", "a/b/file").
+%% "c/d/file"
+%% 2> rebar_dir:make_relative_path("a/b/file", "a/b/c/d/file").
+%% "../../file"
+%% '''
+-spec make_relative_path(file:filename(), file:filename()) -> file:filename().
make_relative_path(Source, Target) ->
AbsSource = make_normalized_path(Source),
AbsTarget = make_normalized_path(Target),
do_make_relative_path(filename:split(AbsSource), filename:split(AbsTarget)).
+%% @private based on fragments of paths, replace the number of common
+%% segments by `../' bits, and add the rest of the source alone after it
+-spec do_make_relative_path([string()], [string()]) -> file:filename().
do_make_relative_path([H|T1], [H|T2]) ->
do_make_relative_path(T1, T2);
do_make_relative_path(Source, Target) ->
diff --git a/src/rebar_dist_utils.erl b/src/rebar_dist_utils.erl
index f462826..03f4392 100644
--- a/src/rebar_dist_utils.erl
+++ b/src/rebar_dist_utils.erl
@@ -25,7 +25,7 @@ short(Name, Opts) ->
long(Name, Opts) ->
start(Name, longnames, Opts).
--spec find_options(rebar_state:state()) -> {Long, Short, Opts} when
+-spec find_options(rebar_state:t()) -> {Long, Short, Opts} when
Long :: atom(),
Short :: atom(),
Opts :: [{setcookie,term()}].
diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl
index f2467c5..8645641 100644
--- a/src/rebar_file_utils.erl
+++ b/src/rebar_file_utils.erl
@@ -300,9 +300,8 @@ canonical_path([_|Acc], [".."|Rest]) -> canonical_path(Acc, Rest);
canonical_path([], [".."|Rest]) -> canonical_path([], Rest);
canonical_path(Acc, [Component|Rest]) -> canonical_path([Component|Acc], Rest).
-%% returns canonical target of path if path is a link, otherwise returns path
+%% @doc returns canonical target of path if path is a link, otherwise returns path
-spec resolve_link(string()) -> string().
-
resolve_link(Path) ->
case file:read_link(Path) of
{ok, Target} ->
@@ -310,9 +309,8 @@ resolve_link(Path) ->
{error, _} -> Path
end.
-%% splits a path into dirname and basename
+%% @doc splits a path into dirname and basename
-spec split_dirname(string()) -> {string(), string()}.
-
split_dirname(Path) ->
{filename:dirname(Path), filename:basename(Path)}.
diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl
index 5817817..8a84ce3 100644
--- a/src/rebar_pkg_resource.erl
+++ b/src/rebar_pkg_resource.erl
@@ -135,6 +135,9 @@ etag(Path) ->
false
end.
+%% @doc returns the SSL options adequate for the project based on
+%% its configuration, including for validation of certs.
+-spec ssl_opts(string()) -> [term()].
ssl_opts(Url) ->
case get_ssl_config() of
ssl_verify_enabled ->
@@ -143,6 +146,9 @@ ssl_opts(Url) ->
[{verify, verify_none}]
end.
+%% @doc returns the SSL options adequate for the project based on
+%% its configuration, including for validation of certs.
+-spec ssl_opts(atom(), string()) -> [term()].
ssl_opts(ssl_verify_enabled, Url) ->
case check_ssl_version() of
true ->
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index a8a7ea0..9c5e8ac 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -101,6 +101,7 @@ do_(State) ->
{error, Reason}
end.
+%% @doc convert a given exception's payload into an io description.
-spec format_error(any()) -> iolist().
format_error({dep_app_not_found, AppDir, AppName}) ->
io_lib:format("Dependency failure: Application ~s not found at the top level of directory ~s", [AppName, AppDir]);
@@ -125,8 +126,14 @@ format_error({cycles, Cycles}) ->
format_error(Reason) ->
io_lib:format("~p", [Reason]).
-%% Allows other providers to install deps in a given profile
+%% @doc Allows other providers to install deps in a given profile
%% manually, outside of what is provided by rebar3's deps tuple.
+-spec handle_deps_as_profile(Profile, State, Deps, Upgrade) -> {Apps, State} when
+ Profile :: atom(),
+ State :: rebar_state:t(),
+ Deps :: [tuple() | atom() | binary()], % TODO: meta to source() | lock()
+ Upgrade :: boolean(),
+ Apps :: [rebar_app_info:t()].
handle_deps_as_profile(Profile, State, Deps, Upgrade) ->
Locks = [],
Level = 0,
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index f55f40f..d92f119 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -95,6 +95,12 @@ sort_deps(Deps) ->
droplast(L) ->
lists:reverse(tl(lists:reverse(L))).
+%% @doc filtermap takes in a function that is either or both
+%% a predicate and a map, and returns the matching and valid elements.
+-spec filtermap(F, [In]) -> [Out] when
+ F :: fun((In) -> boolean() | {true, Out}),
+ In :: term(),
+ Out :: term().
filtermap(F, [Hd|Tail]) ->
case F(Hd) of
true ->
@@ -114,11 +120,16 @@ is_arch(ArchRegex) ->
false
end.
+%% @doc returns the sytem architecture, in strings like
+%% `"19.0.4-x86_64-unknown-linux-gnu-64"'.
+-spec get_arch() -> string().
get_arch() ->
Words = wordsize(),
otp_release() ++ "-"
++ erlang:system_info(system_architecture) ++ "-" ++ Words.
+%% @doc returns the size of a word on the system, as a string
+-spec wordsize() -> string().
wordsize() ->
try erlang:system_info({wordsize, external}) of
Val ->
@@ -502,11 +513,10 @@ patch_on_windows(Cmd, Env) ->
Cmd
end.
-%%
-%% Given env. variable FOO we want to expand all references to
-%% it in InStr. References can have two forms: $FOO and ${FOO}
-%% The end of form $FOO is delimited with whitespace or eol
-%%
+%% @doc Given env. variable `FOO' we want to expand all references to
+%% it in `InStr'. References can have two forms: `$FOO' and `${FOO}'
+%% The end of form `$FOO' is delimited with whitespace or EOL
+-spec expand_env_variable(string(), string(), term()) -> string().
expand_env_variable(InStr, VarName, RawVarValue) ->
case string:chr(InStr, $$) of
0 ->
@@ -748,6 +758,9 @@ remove_from_code_path(Paths) ->
code:del_path(Path)
end, Paths).
+%% @doc Revert to only having the beams necessary for running rebar3 and
+%% plugins in the path
+-spec cleanup_code_path([string()]) -> true | {error, term()}.
cleanup_code_path(OrigPath) ->
CurrentPath = code:get_path(),
AddedPaths = CurrentPath -- OrigPath,