aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/relcool.hrl7
-rw-r--r--src/rcl_app_info.erl26
-rw-r--r--src/rcl_cmd_args.erl69
-rw-r--r--src/rcl_provider.erl33
-rw-r--r--src/rcl_prv_config.erl38
-rw-r--r--src/rcl_prv_discover.erl18
-rw-r--r--src/rcl_prv_release.erl31
-rw-r--r--src/rcl_release.erl40
-rw-r--r--src/rcl_topo.erl19
-rw-r--r--src/relcool.erl52
-rw-r--r--test/rclt_command_SUITE.erl8
-rw-r--r--test/rclt_discover_SUITE.erl4
12 files changed, 190 insertions, 155 deletions
diff --git a/include/relcool.hrl b/include/relcool.hrl
index 732e5d3..11a0dec 100644
--- a/include/relcool.hrl
+++ b/include/relcool.hrl
@@ -18,3 +18,10 @@
-define(RCL_ERROR, 0).
-define(RCL_INFO, 1).
-define(RCL_DEBUG, 2).
+
+%% This is the default form of error messages for the Relcool
+%% system. It is expected that everything that returns an error use
+%% this and that they all expose a format_error/1 message that returns
+%% an iolist.
+-define(RCL_ERROR(Reason),
+ {error, {?MODULE, Reason}}).
diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl
index 4d91961..db32a8f 100644
--- a/src/rcl_app_info.erl
+++ b/src/rcl_app_info.erl
@@ -42,6 +42,7 @@
name/2,
vsn/1,
vsn/2,
+ vsn_as_string/1,
dir/1,
dir/2,
active_deps/1,
@@ -54,6 +55,8 @@
-export_type([t/0]).
+-include_lib("relcool/include/relcool.hrl").
+
-record(app_info_t, {name :: atom(),
vsn :: ec_semver:semver(),
dir :: file:name(),
@@ -76,7 +79,7 @@ new() ->
%% @doc build a complete version of the app info with all fields set.
-spec new(atom(), string(), file:name(), [atom()], [atom()]) ->
- {ok, t()} | {error, Reason::term()}.
+ {ok, t()} | relcool:error().
new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps)
when erlang:is_atom(AppName),
erlang:is_list(Dir),
@@ -84,7 +87,7 @@ new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps)
erlang:is_list(LibraryDeps) ->
case parse_version(Vsn) of
{fail, _} ->
- {error, {vsn_parse, AppName}};
+ ?RCL_ERROR({vsn_parse, AppName});
ParsedVsn ->
{ok, #app_info_t{name=AppName, vsn=ParsedVsn, dir=Dir,
active_deps=ActiveDeps,
@@ -104,17 +107,20 @@ name(AppInfo=#app_info_t{}, AppName)
vsn(#app_info_t{vsn=Vsn}) ->
Vsn.
--spec vsn(t(), string()) -> {ok, t()} | {error, Reason::term()}.
+-spec vsn_as_string(t()) -> string().
+vsn_as_string(#app_info_t{vsn=Vsn}) ->
+ erlang:binary_to_list(erlang:iolist_to_binary(ec_semver:format(Vsn))).
+
+-spec vsn(t(), string()) -> {ok, t()} | relcool:error().
vsn(AppInfo=#app_info_t{name=AppName}, AppVsn)
when erlang:is_list(AppVsn) ->
case parse_version(AppVsn) of
{fail, _} ->
- {error, {vsn_parse, AppName}};
+ ?RCL_ERROR({vsn_parse, AppName});
ParsedVsn ->
{ok, AppInfo#app_info_t{vsn=ParsedVsn}}
end.
-
-spec dir(t()) -> file:name().
dir(#app_info_t{dir=Dir}) ->
Dir.
@@ -139,10 +145,10 @@ library_deps(AppInfo=#app_info_t{}, LibraryDeps)
when erlang:is_list(LibraryDeps) ->
AppInfo#app_info_t{library_deps=LibraryDeps}.
--spec format_error({error, Reason::term()}) -> iolist().
-format_error({error, {vsn_parse, AppName, AppDir}}) ->
- io_lib:format("Error parsing version for ~p at ~s",
- [AppName, AppDir]).
+-spec format_error(Reason::term()) -> iolist().
+format_error({vsn_parse, AppName}) ->
+ io_lib:format("Error parsing version for ~p",
+ [AppName]).
-spec format(t()) -> iolist().
format(AppInfo) ->
@@ -158,8 +164,6 @@ format(Indent, #app_info_t{name=Name, vsn=Vsn, dir=Dir,
rcl_util:indent(Indent + 1), "Library Dependencies:\n",
[[rcl_util:indent(Indent + 2), erlang:atom_to_list(LibDep), ",\n"] || LibDep <- LibDeps]].
-
-
%%%===================================================================
%%% Internal Functions
%%%===================================================================
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl
index f8918c2..201a7af 100644
--- a/src/rcl_cmd_args.erl
+++ b/src/rcl_cmd_args.erl
@@ -21,19 +21,17 @@
%%% @doc Trivial utility file to help handle common tasks
-module(rcl_cmd_args).
--export([args2state/1]).
-
-%%============================================================================
-%% types
-%%============================================================================
+-export([args2state/1,
+ format_error/1]).
+-include_lib("relcool/include/relcool.hrl").
%%============================================================================
%% API
%%============================================================================
-spec args2state({error, Reason::term()} | {[getopt:option()], [string()]}) ->
- {error, Reason::term()} |
- {ok, {rcl_state:t(), [string()]}}.
+ {ok, {rcl_state:t(), [string()]}} |
+ relcool:error().
args2state(Error={error, _}) ->
Error;
args2state({ok, {Opts, Targets}}) ->
@@ -53,11 +51,40 @@ args2state({ok, {Opts, Targets}}) ->
end
end.
+-spec format_error(Reason::term()) -> iolist().
+format_error({invalid_option_arg, Arg}) ->
+ case Arg of
+ {goals, Goal} ->
+ io_lib:format("Invalid Goal argument -g ~p~n", [Goal]);
+ {relname, RelName} ->
+ io_lib:format("Invalid Release Name argument -n ~p~n", [RelName]);
+ {relvsn, RelVsn} ->
+ io_lib:format("Invalid Release Version argument -n ~p~n", [RelVsn]);
+ {output_dir, Outdir} ->
+ io_lib:format("Invalid Output Directory argument -n ~p~n", [Outdir]);
+ {lib_dir, LibDir} ->
+ io_lib:format("Invalid Library Directory argument -n ~p~n", [LibDir]);
+ {log_level, LogLevel} ->
+ io_lib:format("Invalid Library Directory argument -n ~p~n", [LogLevel])
+ end;
+format_error({invalid_config_file, Config}) ->
+ io_lib:format("Invalid configuration file specified: ~s", [Config]);
+format_error({failed_to_parse, Spec}) ->
+ io_lib:format("Unable to parse spec ~s", [Spec]);
+format_error({unable_to_create_output_dir, OutputDir}) ->
+ io_lib:format("Unable to create output directory (possible permissions issue): ~s",
+ [OutputDir]);
+format_error({not_directory, Dir}) ->
+ io_lib:format("Library directory does not exist: ~s", [Dir]);
+format_error({invalid_log_level, LogLevel}) ->
+ io_lib:format("Invalid log level specified -V ~p, log level must be in the"
+ " range 0..2", [LogLevel]).
+
%%%===================================================================
%%% Internal Functions
%%%===================================================================
-spec validate_configs([file:filename()]) ->
- {ok, [file:filename()]} | {error, Reason::term()}.
+ {ok, [file:filename()]} | relcool:error().
validate_configs(Configs) ->
Result =
lists:foldl(fun(_Config, Err = {error, _}) ->
@@ -67,7 +94,7 @@ validate_configs(Configs) ->
true ->
[filename:absname(Config) | Acc];
false ->
- {error, {invalid_config_file, Config}}
+ ?RCL_ERROR({invalid_config_file, Config})
end
end, [], Configs),
case Result of
@@ -80,29 +107,29 @@ validate_configs(Configs) ->
end.
-spec create_log([getopt:option()], rcl_state:cmd_args()) ->
- {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+ {ok, rcl_state:cmd_args()} | relcool:error().
create_log(Opts, Acc) ->
LogLevel = proplists:get_value(log_level, Opts, 0),
if
LogLevel >= 0, LogLevel =< 2 ->
create_goals(Opts, [{log, rcl_log:new(LogLevel)} | Acc]);
true ->
- {error, {invalid_log_level, LogLevel}}
+ ?RCL_ERROR({invalid_log_level, LogLevel})
end.
-spec create_goals([getopt:option()], rcl_state:cmd_args()) ->
- {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+ {ok, rcl_state:cmd_args()} | relcool:error().
create_goals(Opts, Acc) ->
case convert_goals(proplists:get_all_values(goals, Opts), []) of
- Error={error, {failed_to_parse, _Spec}} ->
+ Error={error, _} ->
Error;
{ok, Specs} ->
create_output_dir(Opts, [{goals, Specs} | Acc])
end.
-spec convert_goals([string()], [depsolver:constraint()]) ->
- {error,{failed_to_parse, string()}} |
- {ok,[depsolver:constraint()]}.
+ {ok,[depsolver:constraint()]} |
+ relcool:error().
convert_goals([], Specs) ->
%% Reverse the specs because order matters to depsolver
{ok, lists:reverse(Specs)};
@@ -111,10 +138,10 @@ convert_goals([RawSpec | Rest], Acc) ->
{ok, Spec} ->
convert_goals(Rest, [Spec | Acc]);
{fail, _} ->
- {error, {failed_to_parse, RawSpec}}
+ ?RCL_ERROR({failed_to_parse, RawSpec})
end.
-spec create_output_dir([getopt:option()], rcl_state:cmd_args()) ->
- {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+ {ok, rcl_state:cmd_args()} | relcool:error().
create_output_dir(Opts, Acc) ->
OutputDir = proplists:get_value(output_dir, Opts, "./relcool_output"),
case filelib:is_dir(OutputDir) of
@@ -123,14 +150,14 @@ create_output_dir(Opts, Acc) ->
ok ->
create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]);
{error, _} ->
- {error, {unable_to_create_output_dir, OutputDir}}
+ ?RCL_ERROR({unable_to_create_output_dir, OutputDir})
end;
true ->
create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc])
end.
-spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) ->
- {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+ {ok, rcl_state:cmd_args()} | relcool:error().
create_lib_dirs(Opts, Acc) ->
Dirs = proplists:get_all_values(lib_dir, Opts),
case check_lib_dirs(Dirs) of
@@ -140,13 +167,13 @@ create_lib_dirs(Opts, Acc) ->
{ok, [{lib_dirs, [filename:absname(Dir) || Dir <- Dirs]} | Acc]}
end.
--spec check_lib_dirs([string()]) -> ok | {error, {Reason::atom(), Dir::string()}}.
+-spec check_lib_dirs([string()]) -> ok | relcool:error().
check_lib_dirs([]) ->
ok;
check_lib_dirs([Dir | Rest]) ->
case filelib:is_dir(Dir) of
false ->
- {error, {not_directory, Dir}};
+ ?RCL_ERROR({not_directory, Dir});
true ->
check_lib_dirs(Rest)
end.
diff --git a/src/rcl_provider.erl b/src/rcl_provider.erl
index 5aa10bd..9d1dd88 100644
--- a/src/rcl_provider.erl
+++ b/src/rcl_provider.erl
@@ -8,19 +8,25 @@
-module(rcl_provider).
%% API
--export([new/2, do/2, format_error/2, format/1]).
+-export([new/2,
+ do/2,
+ format_error/1,
+ format_error/2,
+ format/1]).
-export_type([t/0]).
+-include_lib("relcool/include/relcool.hrl").
+
%%%===================================================================
%%% Types
%%%===================================================================
-opaque t() :: {?MODULE, module()}.
--callback init(rcl_state:t()) -> {ok, rcl_state:t()} | {error, Reason::term()}.
--callback do(rcl_state:t()) -> {error, Reason::term()} | {ok, rcl_state:t()}.
--callback format_error({error, Reason::term()}) -> iolist().
+-callback init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error().
+-callback do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error().
+-callback format_error(Reason::term()) -> iolist().
%%%===================================================================
%%% API
@@ -32,32 +38,35 @@
%% @param ModuleName The module name.
%% @param State0 The current state of the system
-spec new(module(), rcl_state:t()) ->
- {t(), {ok, rcl_state:t()} | {error, Reason::term()}}.
+ {t(), {ok, rcl_state:t()}} | relcool:error().
new(ModuleName, State0) when is_atom(ModuleName) ->
State1 = ModuleName:init(State0),
case code:which(ModuleName) of
non_existing ->
- erlang:error({non_existing, ModuleName});
+ ?RCL_ERROR({non_existing, ModuleName});
_ ->
- ok
- end,
- {{?MODULE, ModuleName}, State1}.
+ {{?MODULE, ModuleName}, State1}
+ end.
%% @doc Manipulate the state of the system, that new state
%%
%% @param Provider the provider object
%% @param State the current state of the system
-spec do(Provider::t(), rcl_state:t()) ->
- {error, Reason::term()} | {ok, rcl_state:t()}.
+ {ok, rcl_state:t()} | relcool:error().
do({?MODULE, Mod}, State) ->
Mod:do(State).
%% @doc format an error produced from a provider.
--spec format_error(t(), {error, Reason::term()}) -> iolist().
+-spec format_error(Reason::term()) -> iolist().
+format_error({non_existing, ModuleName}) ->
+ io_lib:format("~p does not exist in the system", [ModuleName]).
+
+%% @doc format an error produced from a provider.
+-spec format_error(t(), Reason::term()) -> iolist().
format_error({?MODULE, Mod}, Error) ->
Mod:format_error(Error).
-
%% @doc print the provider module name
%%
%% @param T - The provider
diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl
index 3b92a3b..5d97ce5 100644
--- a/src/rcl_prv_config.erl
+++ b/src/rcl_prv_config.erl
@@ -14,32 +14,35 @@
do/1,
format_error/1]).
+-include_lib("relcool/include/relcool.hrl").
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Required by the system, but not used in this provider
--spec init(rcl_state:t()) -> {ok, rcl_state:t()} | {error, Reason::term()}.
+-spec init(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error().
init(State) ->
{ok, State}.
%% @doc parse all the configs currently specified in the state,
%% populating the state as a result.
--spec do(rcl_state:t()) ->{ok, rcl_state:t()} | {error, Reason::term()}.
+-spec do(rcl_state:t()) ->{ok, rcl_state:t()} | relcool:error().
do(State) ->
ConfigFiles = rcl_state:config_files(State),
lists:foldl(fun load_config/2, {ok, State}, ConfigFiles).
--spec format_error({error, Reason::term()}) -> iolist().
-format_error({error, {consult, Reason}}) ->
+-spec format_error(Reason::term()) -> iolist().
+format_error({consult, Reason}) ->
file:format_error(Reason);
-format_error({error, {invalid_term, Term}}) ->
+format_error({invalid_term, Term}) ->
io_lib:format("Invalid term in config file: ~p", [Term]).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
+-spec load_config(file:filename(), {ok, rcl_state:t()} | relcool:error()) ->
+ {ok, rcl_state:t()} | relcool:error().
load_config(_, Err = {error, _}) ->
Err;
load_config(ConfigFile, {ok, State}) ->
@@ -47,13 +50,15 @@ load_config(ConfigFile, {ok, State}) ->
ok = file:set_cwd(filename:dirname(ConfigFile)),
Result = case file:consult(ConfigFile) of
{error, Reason} ->
- {error, {consult, Reason}};
+ ?RCL_ERROR({consult, Reason});
{ok, Terms} ->
lists:foldl(fun load_terms/2, {ok, State}, Terms)
end,
ok = file:set_cwd(CurrentCwd),
Result.
+-spec load_terms(term(), {ok, rcl_state:t()} | relcool:error()) ->
+ {ok, rcl_state:t()} | relcool:error().
load_terms({default_release, RelName, RelVsn}, {ok, State}) ->
{ok, rcl_state:default_release(State, RelName, RelVsn)};
load_terms({paths, Paths}, {ok, State}) ->
@@ -62,16 +67,16 @@ load_terms({paths, Paths}, {ok, State}) ->
load_terms({providers, Providers0}, {ok, State0}) ->
Providers1 = gen_providers(Providers0, State0),
case Providers1 of
- {error, _} ->
- Providers1;
+ {_, E={error, _}} ->
+ E;
{Providers3, {ok, State3}} ->
{ok, rcl_state:providers(State3, Providers3)}
end;
load_terms({add_providers, Providers0}, {ok, State0}) ->
Providers1 = gen_providers(Providers0, State0),
case Providers1 of
- {error, _} ->
- Providers1;
+ {_, E={error, _}} ->
+ E;
{Providers3, {ok, State1}} ->
ExistingProviders = rcl_state:providers(State1),
{ok, rcl_state:providers(State1, ExistingProviders ++ Providers3)}
@@ -80,7 +85,7 @@ load_terms({add_providers, Providers0}, {ok, State0}) ->
load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) ->
Release0 = rcl_release:new(RelName, Vsn),
case rcl_release:goals(Release0, Applications) of
- E = {error, _} ->
+ {_, E={error, _}} ->
E;
{ok, Release1} ->
{ok, rcl_state:add_release(State0, Release1)}
@@ -89,7 +94,7 @@ load_terms({release, {RelName, Vsn}, {erts, ErtsVsn},
Applications}, {ok, State}) ->
Release0 = rcl_release:erts(rcl_release:new(RelName, Vsn), ErtsVsn),
case rcl_release:goals(Release0, Applications) of
- E = {error, _} ->
+ {_, E={error, _}} ->
E;
{ok, Release1} ->
{ok, rcl_state:add_release(State, Release1)}
@@ -97,14 +102,17 @@ load_terms({release, {RelName, Vsn}, {erts, ErtsVsn},
load_terms({Name, Value}, {ok, State})
when erlang:is_atom(Name) ->
{ok, rcl_state:put(State, Name, Value)};
+load_terms(_, Error={error, _}) ->
+ Error;
load_terms(InvalidTerm, _) ->
- {error, {invalid_term, InvalidTerm}}.
-
+ ?RCL_ERROR({invalid_term, InvalidTerm}).
+-spec gen_providers([module()], rcl_state:t()) ->
+ {[rcl_provider:t()], {ok, rcl_state:t()} | relcool:error()}.
gen_providers(Providers, State) ->
lists:foldl(fun(ProviderName, {Providers1, {ok, State1}}) ->
{Provider, State2} = rcl_provider:new(ProviderName, State1),
{[Provider | Providers1], State2};
- (_, E={error, _}) ->
+ (_, E={_, {error, _}}) ->
E
end, {[], {ok, State}}, Providers).
diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl
index 4ac21a6..dcee041 100644
--- a/src/rcl_prv_discover.erl
+++ b/src/rcl_prv_discover.erl
@@ -29,6 +29,8 @@
do/1,
format_error/1]).
+-include_lib("relcool/include/relcool.hrl").
+
%%============================================================================
%% API
%%============================================================================
@@ -38,7 +40,7 @@ init(State) ->
%% @doc recursively dig down into the library directories specified in the state
%% looking for OTP Applications
--spec do(rcl_state:t()) -> {error, Reason::term()} | {ok, rcl_state:t()}.
+-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error().
do(State) ->
LibDirs = get_lib_dirs(State),
rcl_log:info(rcl_state:log(State),
@@ -73,11 +75,12 @@ do(State) ->
end),
{ok, rcl_state:available_apps(State, AppMeta1)};
_ ->
- {error, Errors}
+ ?RCL_ERROR(Errors)
end.
--spec format_error({error, [ErrorDetail::term()]}) -> iolist().
-format_error({error, ErrorDetails}) ->
+-spec format_error([ErrorDetail::term()]) -> iolist().
+format_error(ErrorDetails)
+ when erlang:is_list(ErrorDetails) ->
[[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails].
%%%===================================================================
@@ -126,9 +129,9 @@ add_system_lib_dir(State, LibDirs) ->
end.
-spec format_detail(ErrorDetail::term()) -> iolist().
-format_detail({accessing, File, eaccess}) ->
+format_detail({error, {accessing, File, eaccess}}) ->
io_lib:format("permission denied accessing file ~s", [File]);
-format_detail({accessing, File, Type}) ->
+format_detail({error, {accessing, File, Type}}) ->
io_lib:format("error (~p) accessing file ~s", [Type, File]);
format_detail({error, {no_beam_files, EbinDir}}) ->
io_lib:format("no beam files found in directory ~s", [EbinDir]);
@@ -143,8 +146,7 @@ format_detail({error, {unversioned_app, AppDir, _AppName}}) ->
io_lib:format("Application metadata exists but version is not available: ~s",
[AppDir]);
format_detail({error, {app_info_error, Detail}}) ->
- rcl_app_info:format_error({error, Detail}).
-
+ rcl_app_info:format_error(Detail).
-spec discover_dir(file:name()) ->
[rcl_app_info:t() | {error, Reason::term()}] |
diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl
index 7429e47..0d692da 100644
--- a/src/rcl_prv_release.erl
+++ b/src/rcl_prv_release.erl
@@ -30,6 +30,8 @@
do/1,
format_error/1]).
+-include_lib("relcool/include/relcool.hrl").
+
%%============================================================================
%% API
%%============================================================================
@@ -39,23 +41,23 @@ init(State) ->
%% @doc recursively dig down into the library directories specified in the state
%% looking for OTP Applications
--spec do(rcl_state:t()) -> {error, Reason::term()} | {ok, rcl_state:t()}.
+-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error().
do(State) ->
DepGraph = create_dep_graph(State),
find_default_release(State, DepGraph).
--spec format_error({error, ErrorDetail::term()}) -> iolist().
-format_error({error, {no_release_name, Vsn}}) ->
+-spec format_error(ErrorDetail::term()) -> iolist().
+format_error({no_release_name, Vsn}) ->
io_lib:format("A target release version was specified (~s) but no name", [Vsn]);
-format_error({error, {invalid_release_info, Info}}) ->
+format_error({invalid_release_info, Info}) ->
io_lib:format("Target release information is in an invalid format ~p", [Info]);
-format_error({error, {multiple_release_names, RelA, RelB}}) ->
+format_error({multiple_release_names, RelA, RelB}) ->
io_lib:format("No default release name was specified and there are multiple "
"releases in the config: ~s, ~s",
[RelA, RelB]);
-format_error({error, no_releases_in_system}) ->
+format_error(no_releases_in_system) ->
"No releases have been specified in the system!";
-format_error({error, {no_releases_for, RelName}}) ->
+format_error({no_releases_for, RelName}) ->
io_lib:format("No releases exist in the system for ~s!", [RelName]);
format_error({release_not_found, {RelName, RelVsn}}) ->
io_lib:format("No releases exist in the system for ~p:~s!", [RelName, RelVsn]);
@@ -83,7 +85,7 @@ create_dep_graph(State) ->
-spec find_default_release(rcl_state:t(), depsolver:t()) ->
- {ok, rcl_state:t()} | {error, Reason::term()}.
+ {ok, rcl_state:t()} | relcool:error().
find_default_release(State, DepGraph) ->
case rcl_state:default_release(State) of
{undefined, undefined} ->
@@ -91,7 +93,7 @@ find_default_release(State, DepGraph) ->
{RelName, undefined} ->
resolve_default_version(State, DepGraph, RelName);
{undefined, Vsn} ->
- {error, {no_release_name, Vsn}};
+ ?RCL_ERROR({no_release_name, Vsn});
{RelName, RelVsn} ->
solve_release(State, DepGraph, RelName, RelVsn)
end.
@@ -112,6 +114,7 @@ resolve_default_release(State0, DepGraph) ->
?RCL_ERROR(no_releases_in_system)
end.
+
resolve_default_version(State0, DepGraph, RelName) ->
%% Here we will just get the lastest version and run that.
AllReleases = ec_dictionary:to_list(rcl_state:releases(State0)),
@@ -130,7 +133,6 @@ resolve_default_version(State0, DepGraph, RelName) ->
?RCL_ERROR({no_releases_for, RelName})
end.
-
-spec release_sort({{rcl_release:name(),rcl_release:vsn()}, term()},
{{rcl_release:name(),rcl_release:vsn()}, term()}) ->
boolean().
@@ -145,8 +147,6 @@ release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) ->
erlang:atom_to_list(RelNameA) =< erlang:atom_to_list(RelNameB) andalso
ec_semver:lte(RelVsnA, RelVsnB).
-
-
solve_release(State0, DepGraph, RelName, RelVsn) ->
try
Release = rcl_state:get_release(State0, RelName, RelVsn),
@@ -155,11 +155,11 @@ solve_release(State0, DepGraph, RelName, RelVsn) ->
{ok, Pkgs} ->
set_resolved(State0, Release, Pkgs);
{error, Error} ->
- {error, {failed_solve, Error}}
+ ?RCL_ERROR({failed_solve, Error})
end
catch
throw:not_found ->
- {error, {release_not_found, RelName, RelVsn}}
+ ?RCL_ERROR({release_not_found, RelName, RelVsn})
end.
set_resolved(State, Release0, Pkgs) ->
@@ -175,10 +175,9 @@ set_resolved(State, Release0, Pkgs) ->
end),
{ok, rcl_state:update_release(State, Release1)};
{error, E} ->
- {error, {release_error, E}}
+ ?RCL_ERROR({release_error, E})
end.
-
%%%===================================================================
%%% Test Functions
%%%===================================================================
diff --git a/src/rcl_release.erl b/src/rcl_release.erl
index 4d3b768..c8e52fc 100644
--- a/src/rcl_release.erl
+++ b/src/rcl_release.erl
@@ -45,6 +45,8 @@
application_spec/0,
application_goal/0]).
+-include_lib("relcool/include/relcool.hrl").
+
-record(release_t, {name :: atom(),
vsn :: ec_semver:any_version(),
erts :: ec_semver:any_version(),
@@ -102,7 +104,7 @@ erts(Release, Vsn) ->
erts(#release_t{erts=Vsn}) ->
Vsn.
--spec goals(t(), [application_goal()]) -> {ok, t()} | {error, Reason::term()}.
+-spec goals(t(), [application_goal()]) -> {ok, t()} | relcool:error().
goals(Release, Goals0) ->
lists:foldl(fun parse_goal0/2,
{ok, Release}, Goals0).
@@ -112,14 +114,14 @@ goals(#release_t{goals=Goals}) ->
Goals.
-spec realize(t(), [{app_name(), app_vsn()}], [rcl_app_info:t()]) ->
- {ok, t()} | {error, Reason::term()}.
+ {ok, t()} | relcool:error().
realize(Rel, Pkgs0, World0) ->
World1 = subset_world(Pkgs0, World0),
case rcl_topo:sort_apps(World1) of
{ok, Pkgs1} ->
process_specs(realize_erts(Rel), Pkgs1);
- {error, E} ->
- {error, {topo_error, E}}
+ Error={error, _} ->
+ Error
end.
%% @doc this gives the application specs for the release. This can only be
@@ -165,8 +167,14 @@ format_goal({Constraint, AppType, AppInc}) ->
format_goal(Constraint) ->
depsolver:format_constraint(Constraint).
-format_error({error, {topo_error, E}}) ->
- rcl_topo:format_error({error, E}).
+-spec format_error(Reason::term()) -> iolist().
+format_error({topo_error, E}) ->
+ rcl_topo:format_error(E);
+format_error({failed_to_parse, Con}) ->
+ io_lib:format("Failed to parse constraint ~p", [Con]);
+format_error({invalid_constraint, Con}) ->
+ io_lib:format("Invalid constraint specified ~p", [Con]).
+
%%%===================================================================
%%% Internal Functions
%%%===================================================================
@@ -177,7 +185,7 @@ realize_erts(Rel) ->
Rel.
-spec process_specs(t(), [rcl_app_info:t()]) ->
- {ok, t()} | {error, Reason::term()}.
+ {ok, t()}.
process_specs(Rel=#release_t{annotations=Annots,
goals=Goals}, World) ->
ActiveApps = lists:flatten([rcl_app_info:active_deps(El) || El <- World] ++
@@ -245,8 +253,8 @@ get_app_info({PkgName, PkgVsn}, World) ->
end, World),
WorldEl.
--spec parse_goal0(application_goal(), {ok, t()} | {error, Reason::term()}) ->
- {ok, t()} | {error, Reason::term()}.
+-spec parse_goal0(application_goal(), {ok, t()} | relcool:error()) ->
+ {ok, t()} | relcool:error().
parse_goal0({Constraint0, Annots}, {ok, Release})
when erlang:is_atom(Annots) ->
parse_goal1(Release, Constraint0, {Annots, none});
@@ -261,8 +269,8 @@ parse_goal0(Constraint0, {ok, Release = #release_t{goals=Goals}})
parse_goal0(Constraint0, {ok, Release = #release_t{goals=Goals}})
when erlang:is_list(Constraint0) ->
case rcl_goal:parse(Constraint0) of
- E = {error, _} ->
- E;
+ {error, _Detail} ->
+ ?RCL_ERROR({failed_to_parse, Constraint0});
Constraint1 ->
{ok, Release#release_t{goals = [Constraint1 | Goals]}}
end;
@@ -271,12 +279,12 @@ parse_goal0(_, E = {error, _}) ->
-spec parse_goal1(t(), depsolver:constraint() | string(),
app_type() | incl_apps() | {app_type(), incl_apps() | none}) ->
- {ok, t()} | {error, Reason::term()}.
+ {ok, t()} | relcool:error().
parse_goal1(Release = #release_t{annotations=Annots, goals=Goals},
Constraint0, NewAnnots) ->
case rcl_goal:parse(Constraint0) of
- E0 = {error, _} ->
- E0;
+ {error, _} ->
+ ?RCL_ERROR({failed_to_parse, Constraint0});
Constraint1 ->
case get_app_name(Constraint1) of
E1 = {error, _} ->
@@ -289,7 +297,7 @@ parse_goal1(Release = #release_t{annotations=Annots, goals=Goals},
end.
-spec get_app_name(depsolver:constraint()) ->
- AppName::atom() | {error, {invalid_constraint, term()}}.
+ AppName::atom() | relcool:error().
get_app_name(AppName) when erlang:is_atom(AppName) ->
AppName;
get_app_name({AppName, _, _}) when erlang:is_atom(AppName) ->
@@ -297,4 +305,4 @@ get_app_name({AppName, _, _}) when erlang:is_atom(AppName) ->
get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) ->
AppName;
get_app_name(V) ->
- {error, {invalid_constraint, V}}.
+ ?RCL_ERROR({invalid_constraint, V}).
diff --git a/src/rcl_topo.erl b/src/rcl_topo.erl
index f6389a5..ec67b56 100644
--- a/src/rcl_topo.erl
+++ b/src/rcl_topo.erl
@@ -20,6 +20,8 @@
-export([sort_apps/1,
format_error/1]).
+-include_lib("relcool/include/relcool.hrl").
+
%%====================================================================
%% Types
%%====================================================================
@@ -38,7 +40,7 @@
%% sorted.
-spec sort_apps([rcl_app_info:t()]) ->
{ok, [rcl_app_info:t()]} |
- {error, Reason::term()}.
+ relcool:error().
sort_apps(Apps) ->
Pairs = apps_to_pairs(Apps),
case sort(Pairs) of
@@ -48,8 +50,8 @@ sort_apps(Apps) ->
E
end.
%% @doc nicely format the error from the sort.
--spec format_error({error, Reason::term()}) -> iolist().
-format_error({error, {cycle, Pairs}}) ->
+-spec format_error(Reason::term()) -> iolist().
+format_error({cycle, Pairs}) ->
["Cycle detected in dependency graph, this must be resolved "
"before we can continue:\n",
case Pairs of
@@ -62,12 +64,11 @@ format_error({error, {cycle, Pairs}}) ->
[]
end].
-
%%====================================================================
%% Internal Functions
%%====================================================================
%% @doc Do a topological sort on the list of pairs.
--spec sort([pair()]) -> {ok, [atom()]} | {error, {cycle, [pair()]}}.
+-spec sort([pair()]) -> {ok, [atom()]} | relcool:error().
sort(Pairs) ->
iterate(Pairs, [], all(Pairs)).
@@ -97,13 +98,13 @@ app_to_pairs(App) ->
%% @doc Iterate over the system. @private
-spec iterate([pair()], [name()], [name()]) ->
- {ok, [name()]} | {error, {cycle, [pair()]}}.
+ {ok, [name()]} | relcool:error().
iterate([], L, All) ->
{ok, remove_duplicates(L ++ subtract(All, L))};
iterate(Pairs, L, All) ->
case subtract(lhs(Pairs), rhs(Pairs)) of
[] ->
- {error, {cycle, Pairs}};
+ ?RCL_ERROR({cycle, Pairs});
Lhs ->
iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All)
end.
@@ -173,14 +174,14 @@ topo_2_test() ->
topo_pairs_cycle_test() ->
Pairs = [{app2, app1}, {app1, app2}, {stdlib, app1}],
- ?assertMatch({error, {cycle, [{app2, app1}, {app1, app2}]}},
+ ?assertMatch({error, {_, {cycle, [{app2, app1}, {app1, app2}]}}},
sort(Pairs)).
topo_apps_cycle_test() ->
{ok, App1} = rcl_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]),
{ok, App2} = rcl_app_info:new(app2, "0.1", "/no-dir", [app1], []),
Apps = [App1, App2],
- ?assertMatch({error, {cycle, [{app2,app1},{app1,app2}]}},
+ ?assertMatch({error, {_, {cycle, [{app2,app1},{app1,app2}]}}},
sort_apps(Apps)).
topo_apps_good_test() ->
diff --git a/src/relcool.erl b/src/relcool.erl
index ea3774b..806f473 100644
--- a/src/relcool.erl
+++ b/src/relcool.erl
@@ -24,14 +24,18 @@
do/7,
opt_spec_list/0]).
+-export_type([error/0]).
+
%%============================================================================
%% types
%%============================================================================
+-type error() :: {error, {Module::module(), Reason::term()}}.
+
%%============================================================================
%% API
%%============================================================================
--spec main([string()]) -> ok.
+-spec main([string()]) -> ok | error() | {ok, rcl_state:t()}.
main(Args) ->
OptSpecList = opt_spec_list(),
case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of
@@ -42,7 +46,6 @@ main(Args) ->
command_line), Error)
end.
-
%% @doc provides an API to run the Relcool process from erlang applications
%%
%% @param RelName - The release name to build (maybe `undefined`)
@@ -116,8 +119,8 @@ run_providers(ConfigProvider, Providers, State0) ->
lists:foldl(fun run_provider/2, {ok, State0}, Providers)
end.
--spec run_provider(rcl_provider:t(), {ok, rcl_state:t()} | {error, Reason::term()}) ->
- {ok, rcl_state:t()} | {error, Reason::term()}.
+-spec run_provider(rcl_provider:t(), {ok, rcl_state:t()} | error()) ->
+ {ok, rcl_state:t()} | error().
run_provider(_Provider, Error = {error, _}) ->
Error;
run_provider(Provider, {ok, State0}) ->
@@ -125,7 +128,7 @@ run_provider(Provider, {ok, State0}) ->
{ok, State1} ->
{ok, State1};
E={error, _} ->
- report_error(State0, rcl_provider:format_error(Provider, E))
+ report_error(State0, E)
end.
-spec usage() -> ok.
@@ -133,9 +136,9 @@ usage() ->
getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]").
--spec report_error(rcl_state:t(), term()) -> none().
-report_error(State, Error) ->
- io:format("~s~n", [to_error(Error)]),
+-spec report_error(rcl_state:t(), error()) -> none() | error().
+report_error(State, Error={error, {Module, Reason}}) ->
+ io:format("~s~n", [Module:format_error(Reason)]),
usage(),
case rcl_state:caller(State) of
command_line ->
@@ -143,36 +146,3 @@ report_error(State, Error) ->
api ->
Error
end.
-
--spec to_error(string() | {error, Reason::term()}) -> string().
-to_error(String) when erlang:is_list(String) ->
- String;
-to_error({error,{invalid_option_arg, Arg}}) ->
- case Arg of
- {goals, Goal} ->
- io_lib:format("Invalid Goal argument -g ~p~n", [Goal]);
- {relname, RelName} ->
- io_lib:format("Invalid Release Name argument -n ~p~n", [RelName]);
- {relvsn, RelVsn} ->
- io_lib:format("Invalid Release Version argument -n ~p~n", [RelVsn]);
- {output_dir, Outdir} ->
- io_lib:format("Invalid Output Directory argument -n ~p~n", [Outdir]);
- {lib_dir, LibDir} ->
- io_lib:format("Invalid Library Directory argument -n ~p~n", [LibDir]);
- {log_level, LogLevel} ->
- io_lib:format("Invalid Library Directory argument -n ~p~n", [LogLevel])
- end;
-to_error({error, {invalid_config_file, Config}}) ->
- io_lib:format("Invalid configuration file specified: ~s", [Config]);
-to_error({error, {failed_to_parse, Spec}}) ->
- io_lib:format("Unable to parse spec ~s", [Spec]);
-to_error({error, {unable_to_create_output_dir, OutputDir}}) ->
- io_lib:format("Unable to create output directory (possible permissions issue): ~s",
- [OutputDir]);
-to_error({error, {not_directory, Dir}}) ->
- io_lib:format("Library directory does not exist: ~s", [Dir]);
-to_error({error, {invalid_log_level, LogLevel}}) ->
- io_lib:format("Invalid log level specified -V ~p, log level must be in the"
- " range 0..2", [LogLevel]);
-to_error({error, Error}) ->
- io_lib:format("~p~n", [Error]).
diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl
index 5adf54a..fa4fc23 100644
--- a/test/rclt_command_SUITE.erl
+++ b/test/rclt_command_SUITE.erl
@@ -79,7 +79,7 @@ lib_fail_case(Config) ->
ok = rcl_util:mkdir_p(Lib1),
CmdLine = ["-l", Lib1, "-l", Lib2],
- ?assertMatch({error, {not_directory, Lib2}},
+ ?assertMatch({error, {_, {not_directory, Lib2}}},
rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))).
@@ -91,17 +91,17 @@ output_fail_case(Config) ->
CanNotCreate = filename:join([UnwritableDir, "out-dir-should-not-create"]),
CmdLine = ["-o", CanNotCreate],
- ?assertMatch({error, {unable_to_create_output_dir, CanNotCreate}},
+ ?assertMatch({error, {_, {unable_to_create_output_dir, CanNotCreate}}},
rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))).
spec_parse_fail_case(_Config) ->
Spec = "aaeu:3333:33.22a44",
CmdLine = ["-g", Spec],
- ?assertMatch({error, {failed_to_parse, _Spec}},
+ ?assertMatch({error, {_, {failed_to_parse, _Spec}}},
rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))).
config_fail_case(_Config) ->
ConfigFile = "does-not-exist",
CmdLine = [ConfigFile],
- ?assertMatch({error, {invalid_config_file, ConfigFile}},
+ ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}},
rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))).
diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl
index 0929e4a..63d7841 100644
--- a/test/rclt_discover_SUITE.erl
+++ b/test/rclt_discover_SUITE.erl
@@ -113,7 +113,7 @@ no_beam_case(Config) ->
State0 = proplists:get_value(state, Config),
{DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0),
EbinDir = filename:join([LibDir2, BadName, "ebin"]),
- ?assertMatch({error, [{no_beam_files, EbinDir}]},
+ ?assertMatch({error, {_, [{no_beam_files, EbinDir}]}},
rcl_provider:do(DiscoverProvider, State1)).
bad_ebin_case(Config) ->
@@ -144,7 +144,7 @@ bad_ebin_case(Config) ->
State0 = proplists:get_value(state, Config),
{DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0),
- ?assertMatch({error, [{invalid_app_file, Filename}]},
+ ?assertMatch({error, {_, [{invalid_app_file, Filename}]}},
rcl_provider:do(DiscoverProvider, State1)).