aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rcl_cmd_args.erl125
-rw-r--r--src/rcl_goal_utils.erl4
-rw-r--r--src/rcl_log.erl87
-rw-r--r--src/rcl_state.erl56
-rw-r--r--src/rcl_util.erl9
-rw-r--r--src/relcool.erl102
6 files changed, 340 insertions, 43 deletions
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl
new file mode 100644
index 0000000..454e762
--- /dev/null
+++ b/src/rcl_cmd_args.erl
@@ -0,0 +1,125 @@
+%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
+%%%
+%%% This file is provided to you under the Apache License,
+%%% Version 2.0 (the "License"); you may not use this file
+%%% except in compliance with the License. You may obtain
+%%% a copy of the License at
+%%%
+%%% http://www.apache.org/licenses/LICENSE-2.0
+%%%
+%%% Unless required by applicable law or agreed to in writing,
+%%% software distributed under the License is distributed on an
+%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%%% KIND, either express or implied. See the License for the
+%%% specific language governing permissions and limitations
+%%% under the License.
+%%%---------------------------------------------------------------------------
+%%% @author Eric Merritt <[email protected]>
+%%% @copyright (C) 2012 Erlware, LLC.
+%%%
+%%% @doc Trivial utility file to help handle common tasks
+-module(rcl_cmd_args).
+
+-export([args2state/1]).
+
+%%============================================================================
+%% types
+%%============================================================================
+
+
+%%============================================================================
+%% API
+%%============================================================================
+-spec args2state({error, Reason::term()} | {[getopt:option()], [string()]}) ->
+ {error, Reason::term()} |
+ {ok, {rcl_state:t(), [string()]}}.
+args2state(Error={error, _}) ->
+ Error;
+args2state({ok, {Opts, Targets}}) ->
+ RelName = proplists:get_value(relname, Opts, undefined),
+ RelVsn = proplists:get_value(relvsn, Opts, undefined),
+ case create_log(Opts,
+ [{relname, RelName},
+ {relvsn, RelVsn}]) of
+ Error = {error, _} ->
+ Error;
+ {ok, CommandLineConfig} ->
+ {ok, {rcl_state:new(CommandLineConfig), Targets}}
+ end.
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+-spec create_log([getopt:option()], rcl_state:cmd_args()) ->
+ {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+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}}
+ end.
+
+-spec create_goals([getopt:option()], rcl_state:cmd_args()) ->
+ {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+create_goals(Opts, Acc) ->
+ case convert_goals(proplists:get_all_values(goals, Opts), []) of
+ Error={error, {failed_to_parse, _Spec}} ->
+ 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()]}.
+convert_goals([], Specs) ->
+ %% Reverse the specs because order matters to depsolver
+ {ok, lists:reverse(Specs)};
+convert_goals([RawSpec | Rest], Acc) ->
+ case rcl_goal:parse(RawSpec) of
+ {ok, Spec} ->
+ convert_goals(Rest, [Spec | Acc]);
+ {fail, _} ->
+ {error, {failed_to_parse, RawSpec}}
+ end.
+-spec create_output_dir([getopt:option()], rcl_state:cmd_args()) ->
+ {ok, rcl_state:cmd_args()} | {error, Reason::term()}.
+create_output_dir(Opts, Acc) ->
+ OutputDir = proplists:get_value(output_dir, Opts, "./relcool_output"),
+ case filelib:is_dir(OutputDir) of
+ false ->
+ case rcl_util:mkdir_p(OutputDir) of
+ ok ->
+ create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]);
+ {error, _} ->
+ {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()}.
+create_lib_dirs(Opts, Acc) ->
+ Dirs = proplists:get_all_values(lib_dir, Opts),
+ case check_lib_dirs(Dirs) of
+ Error = {error, _} ->
+ Error;
+ ok ->
+ {ok, [{lib_dirs, Dirs} | Acc]}
+ end.
+
+-spec check_lib_dirs([string()]) -> ok | {error, {Reason::atom(), Dir::string()}}.
+check_lib_dirs([]) ->
+ ok;
+check_lib_dirs([Dir | Rest]) ->
+ case filelib:is_dir(Dir) of
+ false ->
+ {error, {not_directory, Dir}};
+ true ->
+ check_lib_dirs(Rest)
+ end.
diff --git a/src/rcl_goal_utils.erl b/src/rcl_goal_utils.erl
index 75fcda4..213a87d 100644
--- a/src/rcl_goal_utils.erl
+++ b/src/rcl_goal_utils.erl
@@ -3,7 +3,7 @@
%%%
%%% This file is provided to you under the Apache License,
%%% Version 2.0 (the "License"); you may not use this file
-%%% except in compliance with the License. You may obtain
+%%% except in compliance with the License. You may obtain
%%% a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
@@ -29,7 +29,6 @@
%% types
%%============================================================================
-
%%============================================================================
%% API
%%============================================================================
@@ -68,7 +67,6 @@ to_op(<<":btwn:">>) ->
to_op(<<":between:">>) ->
'between'.
-
to_vsn(Version) when erlang:is_list(Version) ->
to_vsn(erlang:iolist_to_binary(Version));
to_vsn(Vsn) ->
diff --git a/src/rcl_log.erl b/src/rcl_log.erl
index 63eee14..a5fb43a 100644
--- a/src/rcl_log.erl
+++ b/src/rcl_log.erl
@@ -32,11 +32,13 @@
error/2,
error/3,
log_level/1,
- atom_log_level/1]).
+ atom_log_level/1,
+ format/1]).
-export_type([int_log_level/0,
log_level/0,
- state/0]).
+ log_fun/0,
+ t/0]).
-include_lib("relcool/include/relcool.hrl").
@@ -48,13 +50,15 @@
%% Why no warn? because for our purposes there is no difference between error
%% and warn
-type log_level() :: error | info | debug.
--opaque state() :: {?MODULE, log_level()}.
+-opaque t() :: {?MODULE, int_log_level()}.
+
+-type log_fun() :: fun(() -> iolist()).
%%============================================================================
%% API
%%============================================================================
%% @doc Create a new 'log level' for the system
--spec new(int_log_level() | log_level()) -> state().
+-spec new(int_log_level() | log_level()) -> t().
new(LogLevel) when LogLevel >= 0, LogLevel =< 2 ->
{?MODULE, LogLevel};
new(AtomLogLevel)
@@ -68,44 +72,66 @@ new(AtomLogLevel)
end,
new(LogLevel).
-%% @doc log at the debug level given the current log state with a string
--spec debug(state(), string()) -> none().
+
+%% @doc log at the debug level given the current log state with a string or
+%% function that returns a string
+-spec debug(t(), string() | log_fun()) -> ok.
+debug(LogState, Fun)
+ when erlang:is_function(Fun) ->
+ log(LogState, ?RCL_DEBUG, Fun);
debug(LogState, String) ->
debug(LogState, "~s~n", [String]).
%% @doc log at the debug level given the current log state with a format string
%% and argements @see io:format/2
--spec debug(state(), string(), [any()]) -> none().
+-spec debug(t(), string(), [any()]) -> ok.
debug(LogState, FormatString, Args) ->
log(LogState, ?RCL_DEBUG, FormatString, Args).
-%% @doc log at the info level given the current log state with a string
--spec info(state(), string()) -> none().
+%% @doc log at the info level given the current log state with a string or
+%% function that returns a string
+-spec info(t(), string() | log_fun()) -> ok.
+info(LogState, Fun)
+ when erlang:is_function(Fun) ->
+ log(LogState, ?RCL_INFO, Fun);
info(LogState, String) ->
info(LogState, "~s~n", [String]).
%% @doc log at the info level given the current log state with a format string
%% and argements @see io:format/2
--spec info(state(), string(), [any()]) -> none().
+-spec info(t(), string(), [any()]) -> ok.
info(LogState, FormatString, Args) ->
log(LogState, ?RCL_INFO, FormatString, Args).
-%% @doc log at the error level given the current log state with a string
--spec error(state(), string()) -> none().
+%% @doc log at the error level given the current log state with a string or
+%% format string that returns a function
+-spec error(t(), string() | log_fun()) -> ok.
+error(LogState, Fun)
+ when erlang:is_function(Fun) ->
+ log(LogState, ?RCL_ERROR, Fun);
error(LogState, String) ->
error(LogState, "~s~n", [String]).
%% @doc log at the error level given the current log state with a format string
%% and argements @see io:format/2
--spec error(state(), string(), [any()]) -> none().
+-spec error(t(), string(), [any()]) -> ok.
error(LogState, FormatString, Args) ->
log(LogState, ?RCL_ERROR, FormatString, Args).
+%% @doc Execute the fun passed in if log level is as expected.
+-spec log(t(), int_log_level(), log_fun()) -> ok.
+log({?MODULE, DetailLogLevel}, LogLevel, Fun)
+ when DetailLogLevel >= LogLevel ->
+ io:format("~s~n", [Fun()]);
+log(_, _, _) ->
+ ok.
+
+
%% @doc when the module log level is less then or equal to the log level for the
%% call then write the log info out. When its not then ignore the call.
--spec log(state(), int_log_level(), string(), [any()]) -> ok.
+-spec log(t(), int_log_level(), string(), [any()]) -> ok.
log({?MODULE, DetailLogLevel}, LogLevel, FormatString, Args)
- when DetailLogLevel =< LogLevel,
+ when DetailLogLevel >= LogLevel,
erlang:is_list(Args) ->
io:format(FormatString, Args);
log(_, _, _, _) ->
@@ -113,7 +139,7 @@ log(_, _, _, _) ->
%% @doc return a boolean indicating if the system should log for the specified
%% levelg
--spec should(state(), int_log_level()) -> ok.
+-spec should(t(), int_log_level() | any()) -> boolean().
should({?MODULE, DetailLogLevel}, LogLevel)
when DetailLogLevel >= LogLevel ->
true;
@@ -121,12 +147,12 @@ should(_, _) ->
false.
%% @doc get the current log level as an integer
--spec log_level(state()) -> int_log_level().
+-spec log_level(t()) -> int_log_level().
log_level({?MODULE, DetailLogLevel}) ->
DetailLogLevel.
%% @doc get the current log level as an atom
--spec atom_log_level(state()) -> log_level().
+-spec atom_log_level(t()) -> log_level().
atom_log_level({?MODULE, ?RCL_ERROR}) ->
error;
atom_log_level({?MODULE, ?RCL_INFO}) ->
@@ -134,6 +160,13 @@ atom_log_level({?MODULE, ?RCL_INFO}) ->
atom_log_level({?MODULE, ?RCL_DEBUG}) ->
debug.
+-spec format(t()) -> iolist().
+format(Log) ->
+ [<<"(">>,
+ erlang:integer_to_list(log_level(Log)), <<":">>,
+ erlang:atom_to_list(atom_log_level(Log)),
+ <<")">>].
+
%%%===================================================================
%%% Test Functions
%%%===================================================================
@@ -143,23 +176,23 @@ atom_log_level({?MODULE, ?RCL_DEBUG}) ->
should_test() ->
ErrorLogState = new(error),
- ?assert(should(ErrorLogState, ?RCL_ERROR)),
- ?assert(not should(ErrorLogState, ?RCL_INFO)),
- ?assert(not should(ErrorLogState, ?RCL_DEBUG)),
+ ?assertMatch(true, should(ErrorLogState, ?RCL_ERROR)),
+ ?assertMatch(true, not should(ErrorLogState, ?RCL_INFO)),
+ ?assertMatch(true, not should(ErrorLogState, ?RCL_DEBUG)),
?assertEqual(?RCL_ERROR, log_level(ErrorLogState)),
?assertEqual(error, atom_log_level(ErrorLogState)),
InfoLogState = new(info),
- ?assert(should(InfoLogState, ?RCL_ERROR)),
- ?assert(should(InfoLogState, ?RCL_INFO)),
- ?assert(not should(InfoLogState, ?RCL_DEBUG)),
+ ?assertMatch(true, should(InfoLogState, ?RCL_ERROR)),
+ ?assertMatch(true, should(InfoLogState, ?RCL_INFO)),
+ ?assertMatch(true, not should(InfoLogState, ?RCL_DEBUG)),
?assertEqual(?RCL_INFO, log_level(InfoLogState)),
?assertEqual(info, atom_log_level(InfoLogState)),
DebugLogState = new(debug),
- ?assert(should(DebugLogState, ?RCL_ERROR)),
- ?assert(should(DebugLogState, ?RCL_INFO)),
- ?assert(should(DebugLogState, ?RCL_DEBUG)),
+ ?assertMatch(true, should(DebugLogState, ?RCL_ERROR)),
+ ?assertMatch(true, should(DebugLogState, ?RCL_INFO)),
+ ?assertMatch(true, should(DebugLogState, ?RCL_DEBUG)),
?assertEqual(?RCL_DEBUG, log_level(DebugLogState)),
?assertEqual(debug, atom_log_level(DebugLogState)).
diff --git a/src/rcl_state.erl b/src/rcl_state.erl
index 7175d1f..3a61e58 100644
--- a/src/rcl_state.erl
+++ b/src/rcl_state.erl
@@ -24,30 +24,72 @@
-module(rcl_state).
-export([new/1,
- log/1]).
+ log/1,
+ output_dir/1,
+ lib_dirs/1,
+ goals/1,
+ format/1,
+ format/2]).
--export_type([state/0]).
+-export_type([t/0,
+ cmd_args/0]).
--record(?MODULE, {log :: rcl_log:state()}).
+-record(?MODULE, {log :: rcl_log:t(),
+ output_dir :: file:name(),
+ lib_dirs=[] :: [file:name()],
+ goals=[] :: [depsolver:constraint()]}).
%%============================================================================
%% types
%%============================================================================
--opaque state() :: record(?MODULE).
+
+-type cmd_args() :: proplists:proplist().
+-opaque t() :: record(?MODULE).
%%============================================================================
%% API
%%============================================================================
%% @doc Create a new 'log level' for the system
--spec new(proplists:proplist()) -> state().
+-spec new(proplists:proplist()) -> t().
new(PropList) when erlang:is_list(PropList) ->
- #?MODULE{log = proplists:get_value(log, PropList, rcl_log:new(error))}.
+ #?MODULE{log = proplists:get_value(log, PropList, rcl_log:new(error)),
+ output_dir=proplists:get_value(output_dir, PropList, ""),
+ lib_dirs=proplists:get_value(lib_dirs, PropList, []),
+ goals=proplists:get_value(goals, PropList, [])}.
%% @doc get the current log state for the system
--spec log(state()) -> rc_log:state().
+-spec log(t()) -> rcl_log:t().
log(#?MODULE{log=LogState}) ->
LogState.
+-spec output_dir(t()) -> file:name().
+output_dir(#?MODULE{output_dir=OutDir}) ->
+ OutDir.
+
+-spec lib_dirs(t()) -> [file:name()].
+lib_dirs(#?MODULE{lib_dirs=LibDir}) ->
+ LibDir.
+
+-spec goals(t()) -> [depsolver:constraints()].
+goals(#?MODULE{goals=TS}) ->
+ TS.
+
+-spec format(t()) -> iolist().
+format(Mod) ->
+ format(Mod, 0).
+
+-spec format(t(), non_neg_integer()) -> iolist().
+format(#?MODULE{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, goals=Goals},
+ Indent) ->
+ [rcl_util:indent(Indent),
+ <<"state:\n">>,
+ rcl_util:indent(Indent + 1), <<"log: ">>, rcl_log:format(LogState), <<",\n">>,
+ rcl_util:indent(Indent + 1), "goals: \n",
+ [[rcl_util:indent(Indent + 2), depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals],
+ rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n",
+ rcl_util:indent(Indent + 1), "lib_dirs: \n",
+ [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs]].
+
%%%===================================================================
%%% Test Functions
%%%===================================================================
diff --git a/src/rcl_util.erl b/src/rcl_util.erl
index c0b5c5d..be20302 100644
--- a/src/rcl_util.erl
+++ b/src/rcl_util.erl
@@ -21,8 +21,10 @@
%%% @doc Trivial utility file to help handle common tasks
-module(rcl_util).
--export([mkdir_p/1]).
+-export([mkdir_p/1,
+ indent/1]).
+-define(ONE_LEVEL_INDENT, " ").
%%============================================================================
%% types
%%============================================================================
@@ -41,6 +43,11 @@ mkdir_p(Path) ->
DirName = filename:join([filename:absname(Path), "tmp"]),
filelib:ensure_dir(DirName).
+%% @doc ident to the level specified
+-spec indent(non_neg_integer()) -> iolist().
+indent(Amount) when erlang:is_integer(Amount) ->
+ [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)].
+
%%%===================================================================
%%% Test Functions
%%%===================================================================
diff --git a/src/relcool.erl b/src/relcool.erl
index 7702207..f8a9bbd 100644
--- a/src/relcool.erl
+++ b/src/relcool.erl
@@ -1,12 +1,104 @@
-%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
+%%%
+%%% This file is provided to you under the Apache License,
+%%% Version 2.0 (the "License"); you may not use this file
+%%% except in compliance with the License. You may obtain
+%%% a copy of the License at
+%%%
+%%% http://www.apache.org/licenses/LICENSE-2.0
+%%%
+%%% Unless required by applicable law or agreed to in writing,
+%%% software distributed under the License is distributed on an
+%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%%% KIND, either express or implied. See the License for the
+%%% specific language governing permissions and limitations
+%%% under the License.
%%%---------------------------------------------------------------------------
%%% @author Eric Merritt <[email protected]>
+%%% @copyright (C) 2012 Erlware, LLC.
%%% @doc
-%%% @copyright (C) 2007-2012 Erlware, Inc.
-%%%---------------------------------------------------------------------------
-module(relcool).
--export([main/1]).
+-export([main/1, opt_spec_list/0]).
+
+%%============================================================================
+%% types
+%%============================================================================
+
+%%============================================================================
+%% API
+%%============================================================================
+-spec main([string()]) -> ok.
+main(Args) ->
+ io:format("~p~n", [Args]),
+ OptSpecList = opt_spec_list(),
+ case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of
+ {ok, {State, _Target}} ->
+ run_relcool_process(State);
+ Error={error, _} ->
+ report_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, "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."},
+ {lib_dir, $l, "lib-dir", string, "Additional dirs that should be searched for OTP Apps"},
+ {log_level, $V, "verbose", {integer, 2}, "Verbosity level, maybe between 0 and 2"}
+ ].
-main(_Args) ->
+%%============================================================================
+%% internal api
+%%============================================================================
+run_relcool_process(State) ->
+ rcl_log:info(rcl_state:log(State), "Starting relcool build process ..."),
+ rcl_log:debug(rcl_state:log(State),
+ fun() ->
+ rcl_state:format(State)
+ end),
ok.
+
+-spec usage() -> ok.
+usage() ->
+ getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]").
+
+
+-spec report_error(term()) -> none().
+report_error(Error) ->
+ io:format("~s~n", [to_error(Error)]),
+ usage(),
+ erlang:halt(127).
+
+-spec to_error({error, Reason::term()}) -> 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, {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]).