From 774f1998ed6fdb643286c9296929486ce8c6a962 Mon Sep 17 00:00:00 2001 From: Eric B Merritt Date: Tue, 30 Apr 2013 16:02:36 -0700 Subject: support the full range of possible arguments in the programmatic api --- src/rcl_cmd_args.erl | 94 +++++++++++++++++++++++++++++++++++++-------- src/rcl_state.erl | 1 + src/relcool.erl | 86 ++++++++++++++++++++++++++++++++--------- test/rclt_command_SUITE.erl | 12 ++++-- 4 files changed, 155 insertions(+), 38 deletions(-) diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl index bfb63b7..f5ebecd 100644 --- a/src/rcl_cmd_args.erl +++ b/src/rcl_cmd_args.erl @@ -21,7 +21,7 @@ %%% @doc Trivial utility file to help handle common tasks -module(rcl_cmd_args). --export([args2state/1, +-export([args2state/2, format_error/1]). -include_lib("relcool/include/relcool.hrl"). @@ -29,12 +29,10 @@ %%============================================================================ %% API %%============================================================================ --spec args2state({error, Reason::term()} | {[getopt:option()], [string()]}) -> +-spec args2state([getopt:option()], [string()]) -> {ok, {rcl_state:t(), [string()]}} | relcool:error(). -args2state({error, Detail}) -> - ?RCL_ERROR({opt_parse, Detail}); -args2state({ok, {Opts, Target}}) +args2state(Opts, Target) when erlang:length(Target) == 0; erlang:length(Target) == 1 -> RelName = proplists:get_value(relname, Opts, undefined), RelVsn = proplists:get_value(relvsn, Opts, undefined), @@ -50,16 +48,12 @@ args2state({ok, {Opts, Target}}) Error -> Error end; -args2state({ok, {_Opts, Targets}}) -> +args2state(_Opts, Targets) -> ?RCL_ERROR({invalid_targets, Targets}). -spec format_error(Reason::term()) -> iolist(). format_error({invalid_targets, Targets}) -> io_lib:format("One config must be specified! not ~p~n", [Targets]); -format_error({opt_parse, {invalid_option, Opt}}) -> - io_lib:format("invalid option ~s~n", [Opt]); -format_error({opt_parse, Arg}) -> - io_lib:format("~p~n", [Arg]); format_error({invalid_option_arg, Arg}) -> case Arg of {goals, Goal} -> @@ -77,8 +71,12 @@ format_error({invalid_option_arg, Arg}) -> end; format_error({invalid_config_file, Config}) -> io_lib:format("Invalid configuration file specified: ~s", [Config]); +format_error({invalid_caller, Caller}) -> + io_lib:format("Invalid caller specified: ~s", [Caller]); format_error({failed_to_parse, Spec}) -> io_lib:format("Unable to parse spec ~s", [Spec]); +format_error({failed_to_parse_override, QA}) -> + io_lib:format("Failed to parse app override ~s", [QA]); format_error({not_directory, Dir}) -> io_lib:format("Library directory does not exist: ~s", [Dir]); format_error({invalid_log_level, LogLevel}) -> @@ -87,7 +85,6 @@ format_error({invalid_log_level, LogLevel}) -> format_error({invalid_target, Target}) -> io_lib:format("Invalid action specified: ~s", [Target]). - %%%=================================================================== %%% Internal Functions %%%=================================================================== @@ -140,13 +137,46 @@ create_log(Opts, Acc) -> -spec create_goals([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_goals(Opts, Acc) -> - case convert_goals(proplists:get_all_values(goals, Opts), []) of + Goals = proplists:get_value(goals, Opts, []) ++ + proplists:get_all_values(goal, Opts), + case convert_goals(Goals, []) of Error={error, _} -> Error; {ok, Specs} -> - create_output_dir(Opts, [{goals, Specs} | Acc]) + create_overrides(Opts, [{goals, Specs} | Acc]) + end. + +-spec create_overrides([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_overrides(Opts, Acc) -> + Overrides = proplists:get_all_values(override, Opts) ++ + proplists:get_value(overrides, Opts, []), + case convert_overrides(Overrides, []) of + {ok, Overrides} -> + create_output_dir(Opts, [{overrides, Overrides} | Acc]); + Error -> + Error end. +-spec convert_overrides([{atom(), string() | binary()} | + string() | binary()], [{atom(), string() | binary()}]) -> + {ok, [string() | binary()]} | relcool:error(). +convert_overrides([], Acc) -> + {ok, Acc}; +convert_overrides([QA = {OverrideApp, _} | Rest], Acc) + when erlang:is_atom(OverrideApp) -> + convert_overrides(Rest, [QA | Acc]); +convert_overrides([Override | Rest], Acc) + when erlang:is_list(Override); erlang:is_binary(Override) -> + case re:split(Override, ":") of + [AppName, AppDir] -> + convert_overrides(Rest, [{erlang:iolist_to_binary(AppName), AppDir} | Acc]); + _ -> + ?RCL_ERROR({failed_to_parse_override, Override}) + end; +convert_overrides([QA | _], _) -> + ?RCL_ERROR({failed_to_parse_override, QA}). + -spec convert_goals([string()], [rcl_depsolver:constraint()]) -> {ok,[rcl_depsolver:constraint()]} | relcool:error(). @@ -154,12 +184,26 @@ convert_goals([], Specs) -> %% Reverse the specs because order matters to rcl_depsolver {ok, lists:reverse(Specs)}; convert_goals([RawSpec | Rest], Acc) -> + parse_goal(RawSpec, Rest, Acc). + +-spec parse_goal(string() | binary() | rcl_depsolver:constraint(), + [string() | binary() | rcl_depsolver:constraint()], + rcl_depsolver:constraints()) -> + {ok, rcl_depsolver:constraints()} | relcool:error(). +parse_goal(Spec, Rest, Acc) + when erlang:is_atom(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(Spec, Rest, Acc) + when erlang:is_tuple(Spec) -> + convert_goals(Rest, [Spec | Acc]); +parse_goal(RawSpec, Rest, Acc) -> case rcl_goal:parse(RawSpec) of {ok, Spec} -> convert_goals(Rest, [Spec | Acc]); {fail, _} -> ?RCL_ERROR({failed_to_parse, RawSpec}) end. + -spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_output_dir(Opts, Acc) -> @@ -169,7 +213,8 @@ create_output_dir(Opts, Acc) -> -spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_lib_dirs(Opts, Acc) -> - Dirs = proplists:get_all_values(lib_dir, Opts), + Dirs = proplists:get_all_values(lib_dir, Opts) ++ + proplists:get_value(lib_dirs, Opts, []), case check_lib_dirs(Dirs) of Error = {error, _} -> Error; @@ -193,8 +238,27 @@ create_root_dir(Opts, Acc) -> {ok, rcl_state:cmd_args()} | relcool:error(). create_disable_default_libs(Opts, Acc) -> Def = proplists:get_value(disable_default_libs, Opts, false), - {ok, [{disable_default_libs, Def} | Acc]}. + create_caller(Opts, [{disable_default_libs, Def} | Acc]). +-spec create_caller([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | relcool:error(). +create_caller(Opts, Acc) -> + case proplists:get_value(caller, Opts, api) of + "command_line" -> + {ok, [{caller, command_line} | Acc]}; + "commandline" -> + {ok, [{caller, command_line} | Acc]}; + "api" -> + {ok, [{caller, api} | Acc]}; + api -> + {ok, [{caller, api} | Acc]}; + commandline -> + {ok, [{caller, command_line} | Acc]}; + command_line -> + {ok, [{caller, command_line} | Acc]}; + Caller -> + ?RCL_ERROR({invalid_caller, Caller}) + end. -spec check_lib_dirs([string()]) -> ok | relcool:error(). check_lib_dirs([]) -> ok; diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 4b21bbe..a4f091c 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -109,6 +109,7 @@ new(PropList, Target) lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])], config_file=proplists:get_value(config, PropList, undefined), action = Target, + caller = proplists:get_value(caller, PropList, api), goals=proplists:get_value(goals, PropList, []), providers = [], releases=ec_dictionary:new(ec_dict), diff --git a/src/relcool.erl b/src/relcool.erl index 7750efa..0079529 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -44,13 +44,12 @@ -spec main([string()]) -> ok | error() | {ok, rcl_state:t()}. main(Args) -> OptSpecList = opt_spec_list(), - Result = - case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of - {ok, State} -> - run_relcool_process(rcl_state:caller(State, command_line)); - Error={error, _} -> - Error - end, + Result = case getopt:parse(OptSpecList, Args) of + {ok, {Options, NonOptions}} -> + do([{caller, command_line} | Options], NonOptions); + {error, Detail} -> + ?RCL_ERROR({opt_parse, Detail}) + end, case Result of {error, _} -> report_error(rcl_state:caller(rcl_state:new([], undefined), @@ -107,25 +106,68 @@ do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) -> rcl_log:log_level(), [file:name()], [{atom(), file:name()}], file:name() | undefined) -> ok | error() | {ok, rcl_state:t()}. do(RootDir, RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Config) -> - State = rcl_state:new([{relname, RelName}, - {relvsn, RelVsn}, - {goals, Goals}, - {overrides, Overrides}, - {output_dir, OutputDir}, - {lib_dirs, LibDirs}, - {root_dir, RootDir}, - {log, rcl_log:new(LogLevel)}, - {config, Config}], - release), - run_relcool_process(rcl_state:caller(State, api)). + do([{relname, RelName}, + {relvsn, RelVsn}, + {goals, Goals}, + {overrides, Overrides}, + {output_dir, OutputDir}, + {lib_dirs, LibDirs}, + {root_dir, RootDir}, + {log_level, LogLevel}, + {config, Config}], + ["release"]). +%% @doc provides an API to run the Relcool process from erlang applications +%% +%% @param Opts - A proplist of options. There are good defaults for each of +%% these entries, so any or all may be omitted. Individual options may be: +%% +%%
+%%
{relname, RelName}
+%%
The release name to build
+%%
{relvsn, RelVsn}
+%%
The release version to build
+%%
{goals, Goals}
+%%
The release goals for the system in depsolver or Relcool goal +%% format (@see goals())
+%%
{lib_dirs, LibDirs}
+%%
A list of library dirs that should be used for the system
+%%
{lib_dir, LibDir}
+%%
A single lib dir that should be used for the system, may appear any +%% number of times and may be used in conjunction with lib_dirs
+%%
{output_dir, OutputDir}
+%%
The directory where the release should be built to
+%%
{root_dir, RootDir}
+%%
The base directory for this run of relcool.
+%%
{config, Config}
+%%
The path to a relcool config file
+%%
{log_level, LogLevel}
+%%
Defines the verbosity of output. Maybe a number between 0 and 2, with +%% with higher values being more verbose
+%%
{overrides, Overrides}
+%%
A list of app overrides for the system in the form of [{AppName:atom(), +%% Dir:string() | binary()} | string() | binary()] where the string or binary +%% is in the form "AppName:AppDir"
+%%
{override, Override}
+%%
A single of app override for the system in the same form as entries for +%% Overrides
+%%
+-spec do(proplists:proplist(), [string()]) -> + ok | error() | {ok, rcl_state:t()}. +do(Opts, NonOpts) -> + case rcl_cmd_args:args2state(Opts, NonOpts) of + {ok, State} -> + run_relcool_process(State); + Error={error, _} -> + Error + end. -spec opt_spec_list() -> [getopt:option_spec()]. opt_spec_list() -> [{relname, $n, "relname", string, "Specify the name for the release that will be generated"}, {relvsn, $v, "relvsn", string, "Specify the version for the release"}, - {goals, $g, "goal", string, + {goal, $g, "goal", string, "Specify a target constraint on the system. These are usually the OTP"}, {output_dir, $o, "output-dir", string, "The output directory for the release. This is `./` by default."}, @@ -136,6 +178,8 @@ opt_spec_list() -> "Disable the default system added lib dirs (means you must add them all manually"}, {log_level, $V, "verbose", {integer, 1}, "Verbosity level, maybe between 0 and 2"}, + {override_app, $a, "override_app", string, + "Provide an app name and a directory to override in the form :"}, {config, $c, "config", {string, ""}, "The path to a config file"}, {root_dir, $r, "root", string, "The project root directory"}]. @@ -143,6 +187,10 @@ opt_spec_list() -> format_error({invalid_return_value, Provider, Value}) -> [rcl_provider:format(Provider), " returned an invalid value ", io_lib:format("~p", [Value])]; +format_error({opt_parse, {invalid_option, Opt}}) -> + io_lib:format("invalid option ~s~n", [Opt]); +format_error({opt_parse, Arg}) -> + io_lib:format("~p~n", [Arg]); format_error({error, {Module, Reason}}) -> io_lib:format("~s~n", [Module:format_error(Reason)]). diff --git a/test/rclt_command_SUITE.erl b/test/rclt_command_SUITE.erl index 9c96005..05da548 100644 --- a/test/rclt_command_SUITE.erl +++ b/test/rclt_command_SUITE.erl @@ -58,7 +58,8 @@ normal_passing_case(Config) -> RelVsn = "33.222", CmdLine = ["-V", LogLevel, "-g",Goal1,"-g",Goal2, "-l", Lib1, "-l", Lib2, "-n", RelName, "-v", RelVsn, "-o", Outdir], - {ok, State} = rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine)), + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), + {ok, State} = rcl_cmd_args:args2state(Opts, Targets), ?assertMatch([Lib1, Lib2], rcl_state:lib_dirs(State)), ?assertMatch(Outdir, rcl_state:output_dir(State)), @@ -77,17 +78,20 @@ lib_fail_case(Config) -> ok = rcl_util:mkdir_p(Lib1), CmdLine = ["-l", Lib1, "-l", Lib2], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {not_directory, Lib2}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). spec_parse_fail_case(_Config) -> Spec = "aaeu:3333:33.22a44", CmdLine = ["-g", Spec], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {failed_to_parse, _Spec}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). config_fail_case(_Config) -> ConfigFile = "does-not-exist", CmdLine = ["-c", ConfigFile], + {ok, {Opts, Targets}} = getopt:parse(relcool:opt_spec_list(), CmdLine), ?assertMatch({error, {_, {invalid_config_file, ConfigFile}}}, - rcl_cmd_args:args2state(getopt:parse(relcool:opt_spec_list(), CmdLine))). + rcl_cmd_args:args2state(Opts, Targets)). -- cgit v1.2.3