diff options
-rw-r--r-- | src/r3.erl | 7 | ||||
-rw-r--r-- | src/rebar3.erl | 76 | ||||
-rw-r--r-- | src/rebar_agent.erl | 59 | ||||
-rw-r--r-- | src/rebar_api.erl | 38 | ||||
-rw-r--r-- | src/rebar_app_discover.erl | 83 | ||||
-rw-r--r-- | src/rebar_app_info.erl | 131 | ||||
-rw-r--r-- | src/rebar_app_utils.erl | 79 | ||||
-rw-r--r-- | src/rebar_base_compiler.erl | 108 | ||||
-rw-r--r-- | src/rebar_config.erl | 86 | ||||
-rw-r--r-- | src/rebar_core.erl | 34 | ||||
-rw-r--r-- | src/rebar_digraph.erl | 22 | ||||
-rw-r--r-- | src/rebar_dir.erl | 111 | ||||
-rw-r--r-- | src/rebar_dist_utils.erl | 11 | ||||
-rw-r--r-- | src/rebar_file_utils.erl | 6 | ||||
-rw-r--r-- | src/rebar_pkg_resource.erl | 6 | ||||
-rw-r--r-- | src/rebar_prv_install_deps.erl | 9 | ||||
-rw-r--r-- | src/rebar_utils.erl | 23 |
17 files changed, 792 insertions, 97 deletions
@@ -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 1059904..56bf3e8 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() -> rebar_utils:set_httpc_options(), @@ -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" @@ -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), @@ -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 == "" -> @@ -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 -> @@ -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} @@ -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}) -> @@ -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 @@ -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; @@ -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), 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..fd55960 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,10 @@ -include("rebar.hrl"). -include_lib("providers/include/providers.hrl"). +%% @doc from the base directory, find all the applications +%% at the top level and their dependencies based on the configuration +%% and profile information. +-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 +61,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 +77,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 +113,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 +133,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 +149,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. +%% @private 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 +172,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 +206,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 +272,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 +324,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 +348,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 9aa7419..dcb1975 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..97e27ab 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 -%% line in terms of pkg_hash, so we disturb source diffing as little -%% as possible. +%% @private Because attributes for packages are fairly large, there is the need +%% for a special formatting to ensure there's only one entry per lock file +%% line and that diffs are generally stable. +-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_digraph.erl b/src/rebar_digraph.erl index 363253a..a827735 100644 --- a/src/rebar_digraph.erl +++ b/src/rebar_digraph.erl @@ -1,3 +1,5 @@ +%%% @doc build a digraph of applications in order to figure out dependency +%%% and compile order. -module(rebar_digraph). -export([compile_order/1 @@ -7,7 +9,9 @@ -include("rebar.hrl"). -%% Sort apps with topological sort to get proper build order +%% @doc Sort apps with topological sort to get proper build order +-spec compile_order([rebar_app_info:t()]) -> + {ok, [rebar_app_info:t()]} | {error, no_sort | {cycles, [[binary(),...]]}}. compile_order(Apps) -> Graph = digraph:new(), lists:foreach(fun(App) -> @@ -33,6 +37,11 @@ compile_order(Apps) -> true = digraph:delete(Graph), Order. +%% @private Add a package and its dependencies to an existing digraph +-spec add(digraph:graph(), {PkgName, [Dep]}) -> ok when + PkgName :: binary(), + Dep :: {Name, term()} | Name, + Name :: atom() | iodata(). add(Graph, {PkgName, Deps}) -> case digraph:vertex(Graph, PkgName) of false -> @@ -57,6 +66,8 @@ add(Graph, {PkgName, Deps}) -> digraph:add_edge(Graph, V, V3) end, Deps). +%% @doc based on a list of vertices and edges, build a digraph. +-spec restore_graph({[digraph:vertex()], [digraph:edge()]}) -> digraph:graph(). restore_graph({Vs, Es}) -> Graph = digraph:new(), lists:foreach(fun({V, LastUpdated}) -> @@ -67,6 +78,8 @@ restore_graph({Vs, Es}) -> end, Es), Graph. +%% @doc convert a given exception's payload into an io description. +-spec format_error(any()) -> iolist(). format_error(no_solution) -> io_lib:format("No solution for packages found.", []). @@ -74,22 +87,27 @@ format_error(no_solution) -> %% Internal Functions %%==================================================================== +%% @doc alias for `digraph_utils:subgraph/2'. subgraph(Graph, Vertices) -> digraph_utils:subgraph(Graph, Vertices). +%% @private from a list of app names, fetch the proper app info records +%% for them. -spec names_to_apps([atom()], [rebar_app_info:t()]) -> [rebar_app_info:t()]. names_to_apps(Names, Apps) -> [element(2, App) || App <- [find_app_by_name(Name, Apps) || Name <- Names], App =/= error]. +%% @private fetch the proper app info record for a given app name. -spec find_app_by_name(atom(), [rebar_app_info:t()]) -> {ok, rebar_app_info:t()} | error. find_app_by_name(Name, Apps) -> ec_lists:find(fun(App) -> rebar_app_info:name(App) =:= Name end, Apps). -%% The union of all entries in the applications list for an app and +%% @private The union of all entries in the applications list for an app and %% the deps listed in its rebar.config is all deps that may be needed %% for building the app. +-spec all_apps_deps(rebar_app_info:t()) -> [binary()]. all_apps_deps(App) -> Applications = lists:usort([atom_to_binary(X, utf8) || X <- rebar_app_info:applications(App)]), Deps = lists:usort(lists:map(fun({Name, _}) -> Name; (Name) -> Name end, rebar_app_info:deps(App))), diff --git a/src/rebar_dir.erl b/src/rebar_dir.erl index 79a1c7f..069d8fd 100644 --- a/src/rebar_dir.erl +++ b/src/rebar_dir.erl @@ -1,3 +1,4 @@ +%%% @doc utility functions for directory and path handling of all kind. -module(rebar_dir). -export([base_dir/1, @@ -29,10 +30,14 @@ -include("rebar.hrl"). +%% @doc returns the directory root for build artifacts +%% for the current profile, such as `_build/default/'. -spec base_dir(rebar_state:t()) -> file:filename_all(). base_dir(State) -> profile_dir(rebar_state:opts(State), rebar_state:current_profiles(State)). +%% @doc returns the directory root for build artifacts for a given set +%% of profiles. -spec profile_dir(rebar_dict(), [atom()]) -> file:filename_all(). profile_dir(Opts, Profiles) -> {BaseDir, ProfilesStrings} = case [ec_cnv:to_list(P) || P <- Profiles] of @@ -46,25 +51,36 @@ profile_dir(Opts, Profiles) -> ProfilesDir = string:join(ProfilesStrings, "+"), filename:join(BaseDir, ProfilesDir). +%% @doc returns the directory where dependencies should be placed +%% given the current profile. -spec deps_dir(rebar_state:t()) -> file:filename_all(). deps_dir(State) -> filename:join(base_dir(State), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)). +%% @doc returns the directory where a dependency should be placed +%% given the current profile, based on its app name. Expects to be passed +%% the result of `deps_dir/1' as a first argument. -spec deps_dir(file:filename_all(), file:filename_all()) -> file:filename_all(). deps_dir(DepsDir, App) -> filename:join(DepsDir, App). +%% @doc returns the absolute path for the project root (by default, +%% the current working directory for the currently running escript). root_dir(State) -> filename:absname(rebar_state:get(State, root_dir, ?DEFAULT_ROOT_DIR)). +%% @doc returns the expected location of the `_checkouts' directory. -spec checkouts_dir(rebar_state:t()) -> file:filename_all(). checkouts_dir(State) -> filename:join(root_dir(State), rebar_state:get(State, checkouts_dir, ?DEFAULT_CHECKOUTS_DIR)). +%% @doc returns the expected location of a given app in the checkouts +%% directory for the project. -spec checkouts_dir(rebar_state:t(), file:filename_all()) -> file:filename_all(). checkouts_dir(State, App) -> filename:join(checkouts_dir(State), App). +%% @doc Returns the directory where plugins are located. -spec plugins_dir(rebar_state:t()) -> file:filename_all(). plugins_dir(State) -> case lists:member(global, rebar_state:current_profiles(State)) of @@ -74,33 +90,50 @@ plugins_dir(State) -> filename:join(base_dir(State), rebar_state:get(State, plugins_dir, ?DEFAULT_PLUGINS_DIR)) end. +%% @doc returns the list of relative path where the project applications can +%% be located. -spec lib_dirs(rebar_state:t()) -> file:filename_all(). lib_dirs(State) -> rebar_state:get(State, project_app_dirs, ?DEFAULT_PROJECT_APP_DIRS). +%% @doc returns the user's home directory. +-spec home_dir() -> file:filename_all(). home_dir() -> {ok, [[Home]]} = init:get_argument(home), Home. +%% @doc returns the directory where the global configuration files for rebar3 +%% may be stored. +-spec global_config_dir(rebar_state:t()) -> file:filename_all(). global_config_dir(State) -> Home = home_dir(), rebar_state:get(State, global_rebar_dir, filename:join([Home, ".config", "rebar3"])). +%% @doc returns the path of the global rebar.config file +-spec global_config(rebar_state:t()) -> file:filename_all(). global_config(State) -> filename:join(global_config_dir(State), "rebar.config"). +%% @doc returns the default path of the global rebar.config file +-spec global_config() -> file:filename_all(). global_config() -> Home = home_dir(), filename:join([Home, ".config", "rebar3", "rebar.config"]). +%% @doc returns the location for the global cache directory -spec global_cache_dir(rebar_dict()) -> file:filename_all(). global_cache_dir(Opts) -> Home = home_dir(), rebar_opts:get(Opts, global_rebar_dir, filename:join([Home, ".cache", "rebar3"])). +%% @doc appends the cache directory to the path passed to this function. +-spec local_cache_dir(file:filename_all()) -> file:filename_all(). local_cache_dir(Dir) -> filename:join(Dir, ".rebar3"). +%% @doc returns the current working directory, with some specific +%% conversions and handling done to be cross-platform compatible. +-spec get_cwd() -> file:filename_all(). get_cwd() -> {ok, Dir} = file:get_cwd(), %% On windows cwd may return capital letter for drive, @@ -109,20 +142,33 @@ get_cwd() -> %% cwd as soon as it possible. filename:join([Dir]). +%% @doc returns the file location for the global template +%% configuration variables file. +-spec template_globals(rebar_state:t()) -> file:filename_all(). template_globals(State) -> filename:join([global_config_dir(State), "templates", "globals"]). +%% @doc returns the location for the global template directory +-spec template_dir(rebar_state:t()) -> file:filename_all(). 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). +%% @doc make a path absolute +-spec make_absolute_path(file:filename()) -> file:filename(). make_absolute_path(Path) -> case filename:pathtype(Path) of absolute -> @@ -136,11 +182,16 @@ make_absolute_path(Path) -> filename:join([Dir, Path]) end. +%% @doc normalizing a path removes all of the `..' and the +%% `.' segments it may contain. +-spec make_normalized_path(file:filename()) -> file:filename(). make_normalized_path(Path) -> AbsPath = make_absolute_path(Path), Components = filename:split(AbsPath), make_normalized_path(Components, []). +%% @private drops path fragments for normalization +-spec make_normalized_path([string()], [string()]) -> file:filename(). make_normalized_path([], NormalizedPath) -> filename:join(lists:reverse(NormalizedPath)); make_normalized_path([H|T], NormalizedPath) -> @@ -150,48 +201,67 @@ 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) -> Base = lists:duplicate(max(length(Target) - 1, 0), ".."), filename:join(Base ++ Source). -%%%----------------------------------------------------------------- -%%% 'src_dirs' and 'extra_src_dirs' can be configured with options +%%% @doc +%%% `src_dirs' and `extra_src_dirs' can be configured with options %%% like this: -%%% +%%% ``` %%% {src_dirs,[{"foo",[{recursive,false}]}]} %%% {extra_src_dirs,[{"bar",[recursive]}]} (equivalent to {recursive,true}) -%%% -%%% src_dirs/1,2 and extra_src_dirs/1,2 return only the list of -%%% directories for the 'src_dirs' and 'extra_src_dirs' options -%%% respectively, while src_dirs_opts/2 return the options list for -%%% the given directory, no matter if it is configured as 'src_dirs' or -%%% 'extra_src_dirs'. -%%% +%%% ''' +%%% `src_dirs/1,2' and `extra_src_dirs/1,2' return only the list of +%%% directories for the `src_dirs' and `extra_src_dirs' options +%%% respectively, while `src_dirs_opts/2' returns the options list for +%%% the given directory, no matter if it is configured as `src_dirs' or +%%% `extra_src_dirs'. -spec src_dirs(rebar_dict()) -> list(file:filename_all()). src_dirs(Opts) -> src_dirs(Opts, []). +%% @doc same as `src_dirs/1', but allows to pass in a list of default options. -spec src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()). src_dirs(Opts, Default) -> src_dirs(src_dirs, Opts, Default). +%% @doc same as `src_dirs/1', but for the `extra_src_dirs' options -spec extra_src_dirs(rebar_dict()) -> list(file:filename_all()). extra_src_dirs(Opts) -> extra_src_dirs(Opts, []). +%% @doc same as `src_dirs/2', but for the `extra_src_dirs' options -spec extra_src_dirs(rebar_dict(), list(file:filename_all())) -> list(file:filename_all()). extra_src_dirs(Opts, Default) -> src_dirs(extra_src_dirs, Opts, Default). +%% @private agnostic version of src_dirs and extra_src_dirs. src_dirs(Type, Opts, Default) -> lists:usort([case D0 of {D,_} -> D; _ -> D0 end || D0 <- raw_src_dirs(Type,Opts,Default)]). +%% @private extracts the un-formatted src_dirs or extra_src_dirs +%% options as configured. raw_src_dirs(Type, Opts, Default) -> ErlOpts = rebar_opts:erl_opts(Opts), Vs = proplists:get_all_values(Type, ErlOpts), @@ -200,19 +270,23 @@ raw_src_dirs(Type, Opts, Default) -> Dirs -> Dirs end. +%% @doc returns all the source directories (`src_dirs' and +%% `extra_src_dirs'). -spec all_src_dirs(rebar_dict()) -> list(file:filename_all()). all_src_dirs(Opts) -> all_src_dirs(Opts, [], []). +%% @doc returns all the source directories (`src_dirs' and +%% `extra_src_dirs') while being able to configure defaults for both. -spec all_src_dirs(rebar_dict(), list(file:filename_all()), list(file:filename_all())) -> list(file:filename_all()). all_src_dirs(Opts, SrcDefault, ExtraDefault) -> lists:usort(src_dirs(Opts, SrcDefault) ++ extra_src_dirs(Opts, ExtraDefault)). -%%%----------------------------------------------------------------- +%%% @doc %%% Return the list of options for the given src directory %%% If the same option is given multiple times for a directory in the -%%% config, the priority order is: first occurence of 'src_dirs' -%%% followed by first occurence of 'extra_src_dirs'. +%%% config, the priority order is: first occurence of `src_dirs' +%%% followed by first occurence of `extra_src_dirs'. -spec src_dir_opts(rebar_dict(), file:filename_all()) -> [{atom(),term()}]. src_dir_opts(Opts, Dir) -> RawSrcDirs = raw_src_dirs(src_dirs, Opts, []), @@ -221,7 +295,7 @@ src_dir_opts(Opts, Dir) -> D==Dir], lists:ukeysort(1,proplists:unfold(lists:append(AllOpts))). -%%%----------------------------------------------------------------- +%%% @doc %%% Return the value of the 'recursive' option for the given directory. %%% If not given, the value of 'recursive' in the 'erlc_compiler' %%% options is used, and finally the default is 'true'. @@ -234,16 +308,17 @@ recursive(Opts, Dir) -> R = proplists:get_value(recursive, DirOpts, Default), R. -%% given a path if that path is an ancestor of an app dir return the path relative to that -%% apps outdir. if the path is not an ancestor to any app dirs but is an ancestor of the -%% project root return the path relative to the project base_dir. if it is not an ancestor +%% @doc given a path if that path is an ancestor of an app dir, return the path relative to that +%% apps outdir. If the path is not an ancestor to any app dirs but is an ancestor of the +%% project root, return the path relative to the project base_dir. If it is not an ancestor %% of either return it unmodified -spec retarget_path(rebar_state:t(), string()) -> string(). - retarget_path(State, Path) -> ProjectApps = rebar_state:project_apps(State), retarget_path(State, Path, ProjectApps). +%% @private worker for retarget_path/2 +%% @end %% not relative to any apps in project, check to see it's relative to %% project root retarget_path(State, Path, []) -> diff --git a/src/rebar_dist_utils.erl b/src/rebar_dist_utils.erl index f462826..93edf9d 100644 --- a/src/rebar_dist_utils.erl +++ b/src/rebar_dist_utils.erl @@ -7,6 +7,9 @@ %%%%%%%%%%%%%%%%%% %%% PUBLIC API %%% %%%%%%%%%%%%%%%%%% + +%% @doc allows to pick whether to use a short or long name, and +%% starts the distributed mode for it. -spec either(Name::atom(), SName::atom(), Opts::[{setcookie,term()}]) -> atom(). either(undefined, undefined, _) -> 'nonode@nohost'; @@ -19,13 +22,19 @@ either(undefined, SName, Opts) -> either(_, _, _) -> ?ABORT("Cannot have both short and long node names defined", []). +%% @doc starts a node with a short name. +-spec short(SName::atom(), Opts::[{setcookie,term()}]) -> term(). short(Name, Opts) -> start(Name, shortnames, Opts). +%% @doc starts a node with a long name. +-spec long(Name::atom(), Opts::[{setcookie,term()}]) -> term(). long(Name, Opts) -> start(Name, longnames, Opts). --spec find_options(rebar_state:state()) -> {Long, Short, Opts} when +%% @doc utility function to extract all distribution options +%% from a rebar3 state tuple. +-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 8e1713d..88419bd 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -137,6 +137,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 -> @@ -145,6 +148,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 84a0563..c9fe0ad 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 a0a44d1..c357e94 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -97,6 +97,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 -> @@ -116,11 +122,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 -> @@ -504,11 +515,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 -> @@ -750,6 +760,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, |