summaryrefslogtreecommitdiff
path: root/src/rebar_config.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rebar_config.erl')
-rw-r--r--src/rebar_config.erl119
1 files changed, 104 insertions, 15 deletions
diff --git a/src/rebar_config.erl b/src/rebar_config.erl
index b50c030..2651ca1 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -26,7 +26,8 @@
%% -------------------------------------------------------------------
-module(rebar_config).
--export([consult/1
+-export([consult_root/0
+ ,consult/1
,consult_app_file/1
,consult_file/1
,consult_lock_file/1
@@ -39,17 +40,31 @@
-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").
+-define(DEFAULT_CONFIG_FILE, "rebar.config").
+
%% ===================================================================
%% Public API
%% ===================================================================
+%% @doc reads the default config file at the top of a full project
+-spec consult_root() -> [any()].
+consult_root() ->
+ 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, ?DEFAULT_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
@@ -59,7 +74,7 @@ consult_lock_file(File) ->
read_attrs(beta, Locks, []);
[{Vsn, Locks}|Attrs] when is_list(Locks) -> % versioned lock file
%% Because this is the first version of rebar3 to introduce a lock
- %% file, all versionned lock files with a different versions have
+ %% file, all versioned lock files with a different version have
%% to be newer.
case Vsn of
?CONFIG_VERSION ->
@@ -73,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),
@@ -86,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
@@ -95,34 +120,46 @@ write_lock_file(LockFile, Locks) ->
file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks]));
_ ->
file:write_file(LockFile,
- io_lib:format("{~p,~n~p}.~n[~n~s~n].~n",
+ io_lib:format("{~p,~n~p}.~n[~n~ts~n].~n",
[?CONFIG_VERSION, NewLocks,
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)];
-format_attrs([H|T]) ->
- [io_lib:format("~p~s", [H, maybe_comma(T)]) | format_attrs(T)].
+ 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;
@@ -130,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) ->
@@ -138,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.
@@ -147,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) ->
@@ -156,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));
@@ -180,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]) ->
@@ -187,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.
@@ -201,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}) ->
@@ -210,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()}.
@@ -226,21 +290,31 @@ consult_and_eval(File, Script) ->
{ok, Term} ->
{ok, [Term]};
Error ->
+ ?ERROR("Error evaluating configuration script at ~p:~n~p~n",
+ [Script, Error]),
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) ->
@@ -250,6 +324,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) ->
@@ -283,7 +361,7 @@ check_newly_added_({Name, Source}, LockedDeps) ->
false
end;
check_newly_added_(Dep, LockedDeps) when is_atom(Dep) ->
- Name = ec_cnv:to_binary(Dep),
+ Name = rebar_utils:to_binary(Dep),
case lists:keyfind(Name, 1, LockedDeps) of
false ->
{true, Name};
@@ -292,11 +370,22 @@ check_newly_added_(Dep, LockedDeps) when is_atom(Dep) ->
0 ->
{true, Name};
_ ->
- ?WARN("Newly added dep ~s is locked at a lower level. "
- "If you really want to unlock it, use 'rebar3 upgrade ~s'",
+ ?WARN("Newly added dep ~ts is locked at a lower level. "
+ "If you really want to unlock it, use 'rebar3 upgrade ~ts'",
[Name, Name]),
false
end
end;
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 ->
+ ?DEFAULT_CONFIG_FILE;
+ ConfigFile ->
+ ConfigFile
+ end.