diff options
-rw-r--r-- | include/relcool.hrl | 7 | ||||
-rw-r--r-- | src/rcl_app_info.erl | 26 | ||||
-rw-r--r-- | src/rcl_cmd_args.erl | 69 | ||||
-rw-r--r-- | src/rcl_provider.erl | 33 | ||||
-rw-r--r-- | src/rcl_prv_config.erl | 38 | ||||
-rw-r--r-- | src/rcl_prv_discover.erl | 18 | ||||
-rw-r--r-- | src/rcl_prv_release.erl | 31 | ||||
-rw-r--r-- | src/rcl_release.erl | 40 | ||||
-rw-r--r-- | src/rcl_topo.erl | 19 | ||||
-rw-r--r-- | src/relcool.erl | 52 | ||||
-rw-r--r-- | test/rclt_command_SUITE.erl | 8 | ||||
-rw-r--r-- | test/rclt_discover_SUITE.erl | 4 |
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)). |